# 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-2011, 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