PSTime/src/TimeFormat.cpp

Go to the documentation of this file.
00001 //--------------------------------------------------------------------------
00002 // File and Version Information:
00003 //      $Id: TimeFormat.cpp 7381 2013-12-16 22:43:48Z salnikov@SLAC.STANFORD.EDU $
00004 //
00005 // Description:
00006 //      Class TimeFormat...
00007 //
00008 // Author List:
00009 //      Andrei Salnikov
00010 //
00011 //------------------------------------------------------------------------
00012 
00013 //-----------------------
00014 // This Class's Header --
00015 //-----------------------
00016 #include "PSTime/TimeFormat.h"
00017 
00018 //-----------------
00019 // C/C++ Headers --
00020 //-----------------
00021 #include <stdio.h>
00022 #include <string.h>
00023 #include <boost/regex.hpp>
00024 #include <algorithm>
00025 #include <sstream>
00026 #include <iomanip>
00027 
00028 //-------------------------------
00029 // Collaborating Class Headers --
00030 //-------------------------------
00031 #include "PSTime/Exceptions.h"
00032 #include "PSTime/TimeUtils.h"
00033 
00034 //-----------------------------------------------------------------------
00035 // Local Macros, Typedefs, Structures, Unions and Forward Declarations --
00036 //-----------------------------------------------------------------------
00037 
00038 using namespace std;
00039 
00040 namespace {
00041 
00042   // regular expressions for parsing date and time
00043 #define DATE_RE "(\\d{4})(?:-?(\\d{2})(?:-?(\\d{2}))?)?"
00044 #define TIME_RE "(\\d{1,2})(?::?(\\d{2})(?::?(\\d{2})(?:[.](\\d{1,9}))?)?)?"
00045 #define TZ_RE "Z|(?:([-+])(\\d{2})(?::?(\\d{2}))?)"
00046   boost::regex dtre( "^" DATE_RE "(?:(?: +|T)(?:" TIME_RE ")?(" TZ_RE ")?)?$" ) ;
00047 
00048   // time specified as seconds.fractions
00049   boost::regex secre ( "^S(\\d{0,10})(?:[.](\\d{1,9}))?$" ) ;
00050 
00051 
00052   // Turn string into nanoseconds, strings is everything that
00053   // appears after decimal dot.
00054   // "1"    -> 100000000 ns
00055   // "123"  -> 123000000 ns
00056   // "123456789987654321" -> 123456789ns (truncation, no rounding)
00057   long getNsec ( const std::string& nsecStr )
00058   {
00059     char buf[10] ;
00060     unsigned ndig = nsecStr.length() ;
00061     if ( ndig > 9 ) ndig = 9 ;
00062 
00063     std::copy ( nsecStr.begin(), nsecStr.begin()+ndig, buf ) ;
00064     std::fill ( buf+ndig, buf+9, '0' ) ;
00065     buf[9] = '\0' ;
00066     return strtol ( buf, 0, 10 ) ;
00067   }
00068 
00069   // compare two tm structs
00070   bool cmp_tm ( struct tm* lhs, struct tm* rhs )
00071   {
00072     if ( lhs->tm_year != rhs->tm_year ) return false ;
00073     if ( lhs->tm_mon != rhs->tm_mon ) return false ;
00074     if ( lhs->tm_mday != rhs->tm_mday ) return false ;
00075     if ( lhs->tm_hour != rhs->tm_hour ) return false ;
00076     if ( lhs->tm_min != rhs->tm_min ) return false ;
00077     if ( lhs->tm_sec != rhs->tm_sec ) return false ;
00078     if ( lhs->tm_isdst >= 0 and rhs->tm_isdst >= 0 ) {
00079       if ( lhs->tm_isdst != rhs->tm_isdst ) return false ;
00080     }
00081     return true ;
00082   }
00083 
00084   
00085   // Format nanoseconds part as fractional seconds
00086   void formatNsec(uint32_t nsec, int prec, std::ostream& str)
00087   {
00088     // constrain precision
00089     if (prec < 1) prec = 1;
00090     if (prec > 9) prec = 9;
00091 
00092     // format it to temporary buffer
00093     char buf[16];
00094     snprintf(buf, sizeof buf, ".%09d", int(nsec));
00095     
00096     // print dot and then first 'prec' digits.
00097     buf[prec+1] = '\0';
00098     str << buf;
00099   }
00100 }
00101 
00102 //              ----------------------------------------
00103 //              -- Public Function Member Definitions --
00104 //              ----------------------------------------
00105 
00106 namespace PSTime {
00107 namespace TimeFormat {
00108 
00109 /*
00110  * Parse the time string and return time
00111  */
00112 Time
00113 parseTime( const std::string& timeStr )
00114 {
00115   time_t sec = 0 ;
00116   long nsec = 0 ;
00117 
00118   // match the string against the regular expression
00119   boost::smatch match ;
00120   if ( boost::regex_match( timeStr, match, secre ) ) {
00121 
00122     // time is given as S<sec>.<frac>
00123     sec = strtoul ( match.str(1).c_str(), 0, 10 ) ;
00124     if ( match[2].matched ) nsec = getNsec( match[2] ) ;
00125 
00126   } else if ( boost::regex_match( timeStr, match, dtre ) ) {
00127 
00128     // Close-to-ISO8601 time specification
00129 
00130     struct tm stm ;
00131     memset( &stm, 0, sizeof stm ) ;
00132 
00133     stm.tm_mday = 1 ;
00134 
00135     // parse the date
00136     stm.tm_year = strtoul ( match.str(1).c_str(), 0, 10 ) ;
00137     stm.tm_year -= 1900 ;
00138     if ( match[2].matched ) stm.tm_mon = strtoul ( match.str(2).c_str(), 0, 10 ) - 1 ;
00139     if ( stm.tm_mon < 0 or stm.tm_mon > 11 ) throw TimeParseException( ERR_LOC, "month out of range" ) ;
00140     if ( match[3].matched ) stm.tm_mday = strtoul ( match.str(3).c_str(), 0, 10 ) ;
00141     if ( stm.tm_mday < 1 or stm.tm_mday > 31 ) throw TimeParseException( ERR_LOC, "day out of range" ) ;
00142 
00143     // parse the time
00144     if ( match[4].matched ) stm.tm_hour = strtoul ( match.str(4).c_str(), 0, 10 ) ;
00145     if ( stm.tm_hour < 0 or stm.tm_hour > 23 ) throw TimeParseException( ERR_LOC, "hours out of range" ) ;
00146     if ( match[5].matched ) stm.tm_min = strtoul ( match.str(5).c_str(), 0, 10 ) ;
00147     if ( stm.tm_min < 0 or stm.tm_min > 59 ) throw TimeParseException( ERR_LOC, "minutes out of range" ) ;
00148     if ( match[6].matched ) stm.tm_sec = strtoul ( match.str(6).c_str(), 0, 10 ) ;
00149     if ( stm.tm_sec < 0 or stm.tm_sec > 60 ) throw TimeParseException( ERR_LOC, "seconds out of range" ) ;
00150     if ( match[7].matched ) nsec = getNsec( match[7] ) ;
00151 
00152     if ( match[8].matched ) {
00153 
00154       // timezone is specified
00155 
00156       int tzoffset_min = 0 ;
00157       if ( match[8] != "Z" ) {
00158 
00159         // we have proper offset, calculate offset in minutes, will adjust it later
00160         int tz_hour = strtol ( match.str(10).c_str(), 0, 10 ) ;
00161         int tz_min = 0 ;
00162         if ( match[11].matched ) tz_min = strtol ( match.str(11).c_str(), 0, 10 ) ;
00163         if ( tz_hour > 12 or tz_min > 59 ) throw TimeParseException( ERR_LOC, "timezone out of range" ) ;
00164 
00165         tzoffset_min = tz_hour * 60 ;
00166         tzoffset_min += tz_min ;
00167         if ( match[9] == "-" ) tzoffset_min = -tzoffset_min ;
00168 
00169       }
00170 
00171       struct tm vtm = stm ;
00172       sec = TimeUtils::timegm( &stm ) ;
00173       if ( time_t(-1) == sec ) throw TimeParseException( ERR_LOC, "timegm() failed" ) ;
00174 
00175       // to validate the input compare the structures, if nothing was changed then input is OK
00176       if ( not ::cmp_tm( &stm, &vtm ) ) throw TimeParseException( ERR_LOC, "input time validation failed" ) ;
00177 
00178       // adjust for timezone
00179       sec -= tzoffset_min * 60 ;
00180 
00181     } else {
00182 
00183       // No timezone specified, we should assume the time is in the local timezone.
00184       // Let it guess the daylight saving time status.
00185       stm.tm_isdst = -1 ;
00186       struct tm vtm = stm ;
00187       sec = mktime( &stm ) ;
00188       if ( time_t(-1) == sec ) throw TimeParseException( ERR_LOC, "mktime() failed" ) ;
00189 
00190       // to validate the input compare the structures, if nothing was changed then input is OK
00191       if ( not ::cmp_tm( &stm, &vtm ) ) throw TimeParseException( ERR_LOC, "input time validation failed" ) ;
00192 
00193     }
00194 
00195   } else {
00196 
00197     throw TimeParseException( ERR_LOC, "failed to parse the string: "+timeStr ) ;
00198 
00199   }
00200 
00201   return Time( sec, nsec ) ;
00202 }
00203 
00204 /*
00205  * Convert time to string according to format
00206  */
00207 std::string
00208 format ( const Time& time, const std::string& afmt, Time::Zone zone )
00209 {
00210   std::ostringstream str;
00211   format ( str, time, afmt, zone );
00212   return str.str();
00213 }
00214 
00215 void
00216 format ( std::ostream& str, const Time& time, const std::string& afmt, Time::Zone zone )
00217 {
00218 
00219   // broken down time
00220   struct tm stm = time.gettm(zone);
00221   
00222   char fill = str.fill('0'); 
00223   
00224   for (std::string::size_type p = 0; p != afmt.size(); ++p) {
00225     if (afmt[p] != '%') {
00226       // non-percent, copy and move forward
00227       str << afmt[p];
00228     } else if (p+1 == afmt.size()) {
00229       // percent, but it is the last char, just copy
00230       str << afmt[p];
00231     } else {
00232       
00233       // go to next char
00234       ++ p;
00235       
00236       switch (afmt[p]) {
00237       case '%' :
00238         str << afmt[p];
00239         break;
00240       case 'd' :
00241         // The day of the month as a decimal number (range 01 to 31).
00242         str << setw(2) << stm.tm_mday ;
00243         break;
00244       case 'F' :
00245         // Equivalent to %Y-%m-%d (the ISO 8601 date format).
00246         str << setw(4) << stm.tm_year+1900 << '-' << setw(2) << stm.tm_mon+1 
00247             << '-' << setw(2) << stm.tm_mday;
00248         break;
00249       case 'H' :
00250         // The hour as a decimal number using a 24-hour clock (range 00 to 23).
00251         str << setw(2) << stm.tm_hour;
00252         break;
00253       case 'm' :
00254         // The month as a decimal number (range 01 to 12).
00255         str << setw(2) << stm.tm_mon+1;
00256         break;
00257       case 'M' :
00258         // The minute as a decimal number (range 00 to 59).
00259         str << setw(2) << stm.tm_min;
00260         break;
00261       case 's' :
00262         // The number of seconds since the Epoch, i.e., since 1970-01-01 00:00:00 UTC.
00263         str << time.sec();
00264         break;
00265       case 'S' :
00266         // The second as a decimal number (range 00 to 60).
00267         str << setw(2) << stm.tm_sec;
00268         break;
00269       case 'T' :
00270         // The time in 24-hour notation (%H:%M:%S).
00271         str << setw(2) << stm.tm_hour << ':' << setw(2) << stm.tm_min 
00272             << ':' << setw(2) << stm.tm_sec;
00273         break;
00274       case 'Y' :
00275         // The year as a decimal number including the century.
00276         str << setw(4) << stm.tm_year+1900;
00277         break;
00278       case 'z' :
00279         // 'Z' if time is printed in UTC zone, or offset from UTC to local time.
00280         if (zone == Time::UTC) {
00281           str << 'Z';
00282         } else {
00283           // Non-portable code, tm_gmtoff is glibc extension
00284           char sign = stm.tm_gmtoff > 0 ? '+' : '-' ;
00285           int offmin = abs(stm.tm_gmtoff / 60);
00286           int offhour = offmin / 60;
00287           offmin %= 60;
00288           str << sign << setw(2) << offhour;
00289           if (offmin) str << setw(2) << offmin;
00290         }
00291         break;
00292       case '.' :
00293         // might be .<N>f
00294         if (p+2 < afmt.size() and isdigit(afmt[p+1]) and afmt[p+2] == 'f') {
00295           ::formatNsec(time.nsec(), afmt[p+1]-'0', str);
00296           p += 2;
00297         }
00298         break;
00299       case 'f' :
00300         // Equivalent to %.9f
00301         ::formatNsec(time.nsec(), 9, str);
00302         break;
00303       default :
00304         // unknown, copy both % and this guy
00305         str << '%' << afmt[p];
00306         break;
00307       }
00308     }
00309   }
00310   
00311   str.fill(fill); 
00312 }
00313 
00314 
00315 } // namespace TimeFormat
00316 } // namespace PSTime

Generated on 19 Dec 2016 for PSANAclasses by  doxygen 1.4.7