tzwrapper.cc

Go to the documentation of this file.
00001 ///
00002 /// \file       tzwrapper.cc
00003 ///             Timezone adjustment class, wrapping the TZ environment
00004 ///             variable to make struct tm -> time_t conversions easier.
00005 ///
00006 
00007 /*
00008     Copyright (C) 2010-2012, Chris Frey <cdfrey@foursquare.net>, To God be the glory
00009     Released to the public domain.
00010     Included in Barry and Barrified the namespace July 2010
00011 */
00012 
00013 #include "tzwrapper.h"
00014 #include <string.h>
00015 #include <stdio.h>
00016 #include <string>
00017 
00018 using namespace std;
00019 
00020 namespace Barry { namespace Sync {
00021 
00022 time_t utc_mktime(struct tm *utctime)
00023 {
00024         time_t result;
00025         struct tm tmp, check;
00026 
00027         // loop, converting "local time" to time_t and back to utc tm,
00028         // and adjusting until there are no differences... this
00029         // automatically takes care of DST issues.
00030 
00031         // do first conversion
00032         tmp = *utctime;
00033         tmp.tm_isdst = -1;
00034         result = mktime(&tmp);
00035         if( result == (time_t)-1 )
00036                 return (time_t)-1;
00037         if( gmtime_r(&result, &check) == NULL )
00038                 return (time_t)-1;
00039 
00040         // loop until match
00041         while(  check.tm_year != utctime->tm_year ||
00042                 check.tm_mon != utctime->tm_mon ||
00043                 check.tm_mday != utctime->tm_mday ||
00044                 check.tm_hour != utctime->tm_hour ||
00045                 check.tm_min != utctime->tm_min )
00046         {
00047                 tmp.tm_min  += utctime->tm_min - check.tm_min;
00048                 tmp.tm_hour += utctime->tm_hour - check.tm_hour;
00049                 tmp.tm_mday += utctime->tm_mday - check.tm_mday;
00050                 tmp.tm_year += utctime->tm_year - check.tm_year;
00051                 tmp.tm_isdst = -1;
00052 
00053                 result = mktime(&tmp);
00054                 if( result == (time_t)-1 )
00055                         return (time_t)-1;
00056                 gmtime_r(&result, &check);
00057                 if( gmtime_r(&result, &check) == NULL )
00058                         return (time_t)-1;
00059         }
00060 
00061         return result;
00062 }
00063 
00064 struct tm* iso_to_tm(const char *timestamp,
00065                         struct tm *result,
00066                         bool &utc,
00067                         bool *zone,
00068                         int *zoneminutes)
00069 {
00070         memset(result, 0, sizeof(struct tm));
00071 
00072         // handle YYYY-MM-DDTHH:MM:SS.uuu-HH:MM format
00073         // by stripping out the dashes and colons
00074         string ts = timestamp;
00075         string::iterator i = ts.begin();
00076         bool date = true;
00077         while( i != ts.end() ) {
00078                 if( *i == 'T' )
00079                         date = false;
00080                 if( (date && *i == '-') || *i == ':' )
00081                         ts.erase(i);
00082                 else
00083                         ++i;
00084         }
00085 
00086         int found = sscanf(ts.c_str(), "%04d%02d%02dT%02d%02d%02d",
00087                 &(result->tm_year), &(result->tm_mon), &(result->tm_mday),
00088                 &(result->tm_hour), &(result->tm_min), &(result->tm_sec));
00089 
00090         result->tm_year -= 1900;
00091         result->tm_mon -= 1;
00092         result->tm_isdst = -1;
00093         if( found == 3 ) {
00094                 // only a date available, so force time to 00:00:00
00095                 result->tm_hour = 0;
00096                 result->tm_min = 0;
00097                 result->tm_sec = 0;
00098         }
00099         else if( found != 6 ) {
00100                 return 0;
00101         }
00102 
00103         utc = ts.find('Z', 15) != string::npos;
00104 
00105         if( zone && zoneminutes ) {
00106                 *zone = false;
00107 
00108                 size_t neg = ts.find('-', 15);
00109                 size_t pos = ts.find('+', 15);
00110 
00111                 if( neg != string::npos || pos != string::npos ) {
00112                         // capture timezone offset
00113                         size_t it = neg != string::npos ? neg : pos;
00114                         it++;
00115                         string offset = ts.substr(it);
00116 
00117                         int hour, min;
00118                         found = sscanf(offset.c_str(), "%02d%02d",
00119                                 &hour, &min);
00120                         if( offset.size() == 4 && found == 2 ) {
00121                                 *zone = true;
00122                                 *zoneminutes = hour * 60 + min;
00123                                 if( neg != string::npos )
00124                                         *zoneminutes *= -1;
00125                         }
00126                 }
00127         }
00128 
00129         return result;
00130 }
00131 
00132 std::string tm_to_iso(const struct tm *t, bool utc)
00133 {
00134         char tmp[128];
00135 
00136         int cc = snprintf(tmp, sizeof(tmp), "%04d%02d%02dT%02d%02d%02d",
00137                 t->tm_year + 1900,
00138                 t->tm_mon + 1,
00139                 t->tm_mday,
00140                 t->tm_hour,
00141                 t->tm_min,
00142                 t->tm_sec
00143                 );
00144         if( cc < 0 || (size_t)cc >= sizeof(tmp) )
00145                 return "";
00146 
00147         if( utc ) {
00148                 if( (size_t)cc >= (sizeof(tmp) - 1) )
00149                         return "";              // not enough room for Z
00150                 tmp[cc++] = 'Z';
00151                 tmp[cc] = 0;
00152         }
00153 
00154         return tmp;
00155 }
00156 
00157 TzWrapper& TzWrapper::SetOffset(int zoneminutes)
00158 {
00159         //
00160         // Set a custom TZ with the offset in hours/minutes.
00161         //
00162         // Note that TZ sees negative offsets as *ahead* of
00163         // UTC and positive offsets as behind UTC.  Therefore,
00164         // Berlin, one hour ahead of UTC is -01:00 and
00165         // Canada/Eastern standard time is +05:00.
00166         //
00167         // This is exactly opposite to the ISO timestamp format
00168         // which would have +01:00 and -05:00 respectively,
00169         // and therefore exactly opposite to the sign of zoneminutes.
00170         //
00171         // We use a fake timezone name of XXX here, since it
00172         // doesn't matter, we are only interested in the offset.
00173 
00174         char buf[128];
00175         sprintf(buf, "XXX%c%02d:%02d",
00176                 (zoneminutes < 0 ? '+' : '-'),
00177                 abs(zoneminutes) / 60,
00178                 abs(zoneminutes) % 60
00179                 );
00180         return Set(buf);
00181 }
00182 
00183 time_t TzWrapper::iso_mktime(const char *timestamp)
00184 {
00185         bool utc, zone;
00186         struct tm t;
00187         int zoneminutes;
00188         if( !iso_to_tm(timestamp, &t, utc, &zone, &zoneminutes) )
00189                 return (time_t)-1;
00190 
00191         TzWrapper tzw;
00192         if( utc ) {
00193                 tzw.SetUTC();
00194         }
00195         else if( zone ) {
00196                 tzw.SetOffset(zoneminutes);
00197         }
00198         return tzw.mktime(&t);
00199 }
00200 
00201 }} // namespace Barry::Sync
00202 
00203 
00204 #ifdef TZ_TEST_MODE
00205 #include <iostream>
00206 using namespace std;
00207 using namespace Barry::Sync;
00208 int main()
00209 {
00210         time_t now = time(NULL);
00211 
00212         cout << "TZ:             " << TzWrapper().ctime(&now);
00213         cout << "UTC:            " << TzWrapper("").ctime(&now);
00214         cout << "UTC:            " << TzWrapper().SetUTC().ctime(&now);
00215         cout << "SysLocaltime:   " << TzWrapper().SetUTC().SetSysLocal().ctime(&now);
00216         cout << "TZ:             " << TzWrapper().SetUTC().SetDefault().ctime(&now);
00217         cout << "Canada/Eastern: " << TzWrapper("Canada/Eastern").ctime(&now);
00218         cout << "Canada/Pacific: " << TzWrapper("Canada/Pacific").ctime(&now);
00219 
00220         {
00221                 TzWrapper tzw("UTC");
00222                 cout << "UTC:            " << ctime(&now);
00223         }
00224 
00225         cout << "TZ:             " << ctime(&now);
00226 
00227         // test iso_mktime()... the test values assume a Canada/Eastern TZ
00228         cout << "Using Canada/Eastern:" << endl;
00229         TzWrapper tzw("Canada/Eastern");
00230         const char *iso1 = "20100430T231500";
00231         const char *iso2 = "20100501T031500Z";
00232         time_t t1 = TzWrapper::iso_mktime(iso1);
00233         time_t t2 = TzWrapper::iso_mktime(iso2);
00234         cout << " " << iso1 << ": (" << t1 << ") " << ctime(&t1);
00235         cout        << iso2 << ": (" << t2 << ") " << ctime(&t2);
00236         if( t1 == t2 )
00237                 cout << "t1 == t2: passed" << endl;
00238         else
00239                 cout << "t1 != t2: ERROR" << endl;
00240 
00241         time_t t3 = TzWrapper::iso_mktime("2010-05-01T03:15:00.000Z");
00242         cout << ctime(&t3);
00243         if( t2 == t3 )
00244                 cout << "t2 == t3: passed" << endl;
00245         else
00246                 cout << "t2 != t3: ERROR" << endl;
00247 
00248         time_t t4 = TzWrapper::iso_mktime("2010-05-01T04:15:00.000+01:00");
00249         cout << ctime(&t4);
00250         if( t3 == t4 )
00251                 cout << "t3 == t4: passed" << endl;
00252         else
00253                 cout << "t3 != t4: ERROR" << endl;
00254 
00255         time_t t5 = TzWrapper::iso_mktime("2010-05-01T00:15:00.000-03:00");
00256         cout << ctime(&t5);
00257         if( t4 == t5 )
00258                 cout << "t4 == t5: passed" << endl;
00259         else
00260                 cout << "t4 != t5: ERROR: t4: " << t4 << " t5: " << t5  << endl;
00261 
00262         if( TzWrapper::iso_mktime("20100430") != (time_t)-1 )
00263                 cout << "Date check: passed" << endl;
00264         else
00265                 cout << "Date check: ERROR" << endl;
00266 
00267         cout << "t1: " << tm_to_iso(gmtime(&t1), true) << endl;
00268 
00269         bool utc, zone;
00270         int zoneminutes;
00271         struct tm zonetest;
00272         if( !iso_to_tm("2010-05-01T04:15:00.000-", &zonetest, utc, &zone, &zoneminutes) )
00273                 cout << "iso_to_tm failed wrongly: ERROR" << endl;
00274         if( zone )
00275                 cout << "zone true?: ERROR" << endl;
00276         else
00277                 cout << "zone fail check: passed" << endl;
00278 
00279         if( !iso_to_tm("2010-05-01T04:15:00.000-010", &zonetest, utc, &zone, &zoneminutes) )
00280                 cout << "iso_to_tm failed wrongly: ERROR" << endl;
00281         if( zone )
00282                 cout << "zone true?: ERROR" << endl;
00283         else
00284                 cout << "zone fail check2: passed" << endl;
00285 }
00286 #endif
00287