3 /// Timezone adjustment class, wrapping the TZ environment
4 /// variable to make struct tm -> time_t conversions easier.
8 Copyright (C) 2010, Chris Frey <cdfrey@foursquare.net>, To God be the glory
9 Released to the public domain.
10 Included in Barry and Barrified the namespace July 2010
13 #include "tzwrapper.h"
20 namespace Barry
{ namespace Sync
{
22 time_t utc_mktime(struct tm
*utctime
)
27 // loop, converting "local time" to time_t and back to utc tm,
28 // and adjusting until there are no differences... this
29 // automatically takes care of DST issues.
31 // do first conversion
34 result
= mktime(&tmp
);
35 if( result
== (time_t)-1 )
37 if( gmtime_r(&result
, &check
) == NULL
)
41 while( check
.tm_year
!= utctime
->tm_year
||
42 check
.tm_mon
!= utctime
->tm_mon
||
43 check
.tm_mday
!= utctime
->tm_mday
||
44 check
.tm_hour
!= utctime
->tm_hour
||
45 check
.tm_min
!= utctime
->tm_min
)
47 tmp
.tm_min
+= utctime
->tm_min
- check
.tm_min
;
48 tmp
.tm_hour
+= utctime
->tm_hour
- check
.tm_hour
;
49 tmp
.tm_mday
+= utctime
->tm_mday
- check
.tm_mday
;
50 tmp
.tm_year
+= utctime
->tm_year
- check
.tm_year
;
53 result
= mktime(&tmp
);
54 if( result
== (time_t)-1 )
56 gmtime_r(&result
, &check
);
57 if( gmtime_r(&result
, &check
) == NULL
)
64 struct tm
* iso_to_tm(const char *timestamp
,
70 memset(result
, 0, sizeof(struct tm
));
72 // handle YYYY-MM-DDTHH:MM:SS.uuu-HH:MM format
73 // by stripping out the dashes and colons
74 string ts
= timestamp
;
75 string::iterator i
= ts
.begin();
77 while( i
!= ts
.end() ) {
80 if( (date
&& *i
== '-') || *i
== ':' )
86 int found
= sscanf(ts
.c_str(), "%04d%02d%02dT%02d%02d%02d",
87 &(result
->tm_year
), &(result
->tm_mon
), &(result
->tm_mday
),
88 &(result
->tm_hour
), &(result
->tm_min
), &(result
->tm_sec
));
90 result
->tm_year
-= 1900;
92 result
->tm_isdst
= -1;
97 utc
= ts
.find('Z', 15) != string::npos
;
99 if( zone
&& zoneminutes
) {
102 size_t neg
= ts
.find('-', 15);
103 size_t pos
= ts
.find('+', 15);
105 if( neg
!= string::npos
|| pos
!= string::npos
) {
106 // capture timezone offset
107 size_t it
= neg
!= string::npos
? neg
: pos
;
109 string offset
= ts
.substr(it
);
112 found
= sscanf(offset
.c_str(), "%02d%02d",
114 if( offset
.size() == 4 && found
== 2 ) {
116 *zoneminutes
= hour
* 60 + min
;
117 if( neg
!= string::npos
)
126 std::string
tm_to_iso(const struct tm
*t
, bool utc
)
130 int cc
= snprintf(tmp
, sizeof(tmp
), "%04d%02d%02dT%02d%02d%02d",
138 if( cc
< 0 || (size_t)cc
>= sizeof(tmp
) )
142 if( (size_t)cc
>= (sizeof(tmp
) - 1) )
143 return ""; // not enough room for Z
151 TzWrapper
& TzWrapper::SetOffset(int zoneminutes
)
154 // Set a custom TZ with the offset in hours/minutes.
156 // Note that TZ sees negative offsets as *ahead* of
157 // UTC and positive offsets as behind UTC. Therefore,
158 // Berlin, one hour ahead of UTC is -01:00 and
159 // Canada/Eastern standard time is +05:00.
161 // This is exactly opposite to the ISO timestamp format
162 // which would have +01:00 and -05:00 respectively,
163 // and therefore exactly opposite to the sign of zoneminutes.
165 // We use a fake timezone name of XXX here, since it
166 // doesn't matter, we are only interested in the offset.
169 sprintf(buf
, "XXX%c%02d:%02d",
170 (zoneminutes
< 0 ? '+' : '-'),
171 abs(zoneminutes
) / 60,
172 abs(zoneminutes
) % 60
177 time_t TzWrapper::iso_mktime(const char *timestamp
)
182 if( !iso_to_tm(timestamp
, &t
, utc
, &zone
, &zoneminutes
) )
190 tzw
.SetOffset(zoneminutes
);
192 return tzw
.mktime(&t
);
195 }} // namespace Barry::Sync
201 using namespace Barry::Sync
;
204 time_t now
= time(NULL
);
206 cout
<< "TZ: " << TzWrapper().ctime(&now
);
207 cout
<< "UTC: " << TzWrapper("").ctime(&now
);
208 cout
<< "UTC: " << TzWrapper().SetUTC().ctime(&now
);
209 cout
<< "SysLocaltime: " << TzWrapper().SetUTC().SetSysLocal().ctime(&now
);
210 cout
<< "TZ: " << TzWrapper().SetUTC().SetDefault().ctime(&now
);
211 cout
<< "Canada/Eastern: " << TzWrapper("Canada/Eastern").ctime(&now
);
212 cout
<< "Canada/Pacific: " << TzWrapper("Canada/Pacific").ctime(&now
);
215 TzWrapper
tzw("UTC");
216 cout
<< "UTC: " << ctime(&now
);
219 cout
<< "TZ: " << ctime(&now
);
221 // test iso_mktime()... the test values assume a Canada/Eastern TZ
222 cout
<< "Using Canada/Eastern:" << endl
;
223 TzWrapper
tzw("Canada/Eastern");
224 const char *iso1
= "20100430T231500";
225 const char *iso2
= "20100501T031500Z";
226 time_t t1
= TzWrapper::iso_mktime(iso1
);
227 time_t t2
= TzWrapper::iso_mktime(iso2
);
228 cout
<< " " << iso1
<< ": (" << t1
<< ") " << ctime(&t1
);
229 cout
<< iso2
<< ": (" << t2
<< ") " << ctime(&t2
);
231 cout
<< "t1 == t2: passed" << endl
;
233 cout
<< "t1 != t2: ERROR" << endl
;
235 time_t t3
= TzWrapper::iso_mktime("2010-05-01T03:15:00.000Z");
238 cout
<< "t2 == t3: passed" << endl
;
240 cout
<< "t2 != t3: ERROR" << endl
;
242 time_t t4
= TzWrapper::iso_mktime("2010-05-01T04:15:00.000+01:00");
245 cout
<< "t3 == t4: passed" << endl
;
247 cout
<< "t3 != t4: ERROR" << endl
;
249 time_t t5
= TzWrapper::iso_mktime("2010-05-01T00:15:00.000-03:00");
252 cout
<< "t4 == t5: passed" << endl
;
254 cout
<< "t4 != t5: ERROR: t4: " << t4
<< " t5: " << t5
<< endl
;
256 if( TzWrapper::iso_mktime("20100430") == (time_t)-1 )
257 cout
<< "Fail check: passed" << endl
;
259 cout
<< "Fail check: ERROR" << endl
;
261 cout
<< "t1: " << tm_to_iso(gmtime(&t1
), true) << endl
;
266 if( !iso_to_tm("2010-05-01T04:15:00.000-", &zonetest
, utc
, &zone
, &zoneminutes
) )
267 cout
<< "iso_to_tm failed wrongly: ERROR" << endl
;
269 cout
<< "zone true?: ERROR" << endl
;
271 cout
<< "zone fail check: passed" << endl
;
273 if( !iso_to_tm("2010-05-01T04:15:00.000-010", &zonetest
, utc
, &zone
, &zoneminutes
) )
274 cout
<< "iso_to_tm failed wrongly: ERROR" << endl
;
276 cout
<< "zone true?: ERROR" << endl
;
278 cout
<< "zone fail check2: passed" << endl
;