3 /// Timezone adjustment class, wrapping the TZ environment
4 /// variable to make struct tm -> time_t conversions easier.
8 Copyright (C) 2010-2011, 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;
94 // only a date available, so force time to 00:00:00
99 else if( found
!= 6 ) {
103 utc
= ts
.find('Z', 15) != string::npos
;
105 if( zone
&& zoneminutes
) {
108 size_t neg
= ts
.find('-', 15);
109 size_t pos
= ts
.find('+', 15);
111 if( neg
!= string::npos
|| pos
!= string::npos
) {
112 // capture timezone offset
113 size_t it
= neg
!= string::npos
? neg
: pos
;
115 string offset
= ts
.substr(it
);
118 found
= sscanf(offset
.c_str(), "%02d%02d",
120 if( offset
.size() == 4 && found
== 2 ) {
122 *zoneminutes
= hour
* 60 + min
;
123 if( neg
!= string::npos
)
132 std::string
tm_to_iso(const struct tm
*t
, bool utc
)
136 int cc
= snprintf(tmp
, sizeof(tmp
), "%04d%02d%02dT%02d%02d%02d",
144 if( cc
< 0 || (size_t)cc
>= sizeof(tmp
) )
148 if( (size_t)cc
>= (sizeof(tmp
) - 1) )
149 return ""; // not enough room for Z
157 TzWrapper
& TzWrapper::SetOffset(int zoneminutes
)
160 // Set a custom TZ with the offset in hours/minutes.
162 // Note that TZ sees negative offsets as *ahead* of
163 // UTC and positive offsets as behind UTC. Therefore,
164 // Berlin, one hour ahead of UTC is -01:00 and
165 // Canada/Eastern standard time is +05:00.
167 // This is exactly opposite to the ISO timestamp format
168 // which would have +01:00 and -05:00 respectively,
169 // and therefore exactly opposite to the sign of zoneminutes.
171 // We use a fake timezone name of XXX here, since it
172 // doesn't matter, we are only interested in the offset.
175 sprintf(buf
, "XXX%c%02d:%02d",
176 (zoneminutes
< 0 ? '+' : '-'),
177 abs(zoneminutes
) / 60,
178 abs(zoneminutes
) % 60
183 time_t TzWrapper::iso_mktime(const char *timestamp
)
188 if( !iso_to_tm(timestamp
, &t
, utc
, &zone
, &zoneminutes
) )
196 tzw
.SetOffset(zoneminutes
);
198 return tzw
.mktime(&t
);
201 }} // namespace Barry::Sync
207 using namespace Barry::Sync
;
210 time_t now
= time(NULL
);
212 cout
<< "TZ: " << TzWrapper().ctime(&now
);
213 cout
<< "UTC: " << TzWrapper("").ctime(&now
);
214 cout
<< "UTC: " << TzWrapper().SetUTC().ctime(&now
);
215 cout
<< "SysLocaltime: " << TzWrapper().SetUTC().SetSysLocal().ctime(&now
);
216 cout
<< "TZ: " << TzWrapper().SetUTC().SetDefault().ctime(&now
);
217 cout
<< "Canada/Eastern: " << TzWrapper("Canada/Eastern").ctime(&now
);
218 cout
<< "Canada/Pacific: " << TzWrapper("Canada/Pacific").ctime(&now
);
221 TzWrapper
tzw("UTC");
222 cout
<< "UTC: " << ctime(&now
);
225 cout
<< "TZ: " << ctime(&now
);
227 // test iso_mktime()... the test values assume a Canada/Eastern TZ
228 cout
<< "Using Canada/Eastern:" << endl
;
229 TzWrapper
tzw("Canada/Eastern");
230 const char *iso1
= "20100430T231500";
231 const char *iso2
= "20100501T031500Z";
232 time_t t1
= TzWrapper::iso_mktime(iso1
);
233 time_t t2
= TzWrapper::iso_mktime(iso2
);
234 cout
<< " " << iso1
<< ": (" << t1
<< ") " << ctime(&t1
);
235 cout
<< iso2
<< ": (" << t2
<< ") " << ctime(&t2
);
237 cout
<< "t1 == t2: passed" << endl
;
239 cout
<< "t1 != t2: ERROR" << endl
;
241 time_t t3
= TzWrapper::iso_mktime("2010-05-01T03:15:00.000Z");
244 cout
<< "t2 == t3: passed" << endl
;
246 cout
<< "t2 != t3: ERROR" << endl
;
248 time_t t4
= TzWrapper::iso_mktime("2010-05-01T04:15:00.000+01:00");
251 cout
<< "t3 == t4: passed" << endl
;
253 cout
<< "t3 != t4: ERROR" << endl
;
255 time_t t5
= TzWrapper::iso_mktime("2010-05-01T00:15:00.000-03:00");
258 cout
<< "t4 == t5: passed" << endl
;
260 cout
<< "t4 != t5: ERROR: t4: " << t4
<< " t5: " << t5
<< endl
;
262 if( TzWrapper::iso_mktime("20100430") != (time_t)-1 )
263 cout
<< "Date check: passed" << endl
;
265 cout
<< "Date check: ERROR" << endl
;
267 cout
<< "t1: " << tm_to_iso(gmtime(&t1
), true) << endl
;
272 if( !iso_to_tm("2010-05-01T04:15:00.000-", &zonetest
, utc
, &zone
, &zoneminutes
) )
273 cout
<< "iso_to_tm failed wrongly: ERROR" << endl
;
275 cout
<< "zone true?: ERROR" << endl
;
277 cout
<< "zone fail check: passed" << endl
;
279 if( !iso_to_tm("2010-05-01T04:15:00.000-010", &zonetest
, utc
, &zone
, &zoneminutes
) )
280 cout
<< "iso_to_tm failed wrongly: ERROR" << endl
;
282 cout
<< "zone true?: ERROR" << endl
;
284 cout
<< "zone fail check2: passed" << endl
;