doc: added link to Nicolas Vivien's development blog
[barry.git] / src / tzwrapper.cc
blob5fd673c6d311b0679e82a9c6323047d1474501d0
1 ///
2 /// \file tzwrapper.cc
3 /// Timezone adjustment class, wrapping the TZ environment
4 /// variable to make struct tm -> time_t conversions easier.
5 ///
7 /*
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"
14 #include <string.h>
15 #include <stdio.h>
16 #include <string>
18 using namespace std;
20 namespace Barry { namespace Sync {
22 time_t utc_mktime(struct tm *utctime)
24 time_t result;
25 struct tm tmp, check;
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
32 tmp = *utctime;
33 tmp.tm_isdst = -1;
34 result = mktime(&tmp);
35 if( result == (time_t)-1 )
36 return (time_t)-1;
37 if( gmtime_r(&result, &check) == NULL )
38 return (time_t)-1;
40 // loop until match
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;
51 tmp.tm_isdst = -1;
53 result = mktime(&tmp);
54 if( result == (time_t)-1 )
55 return (time_t)-1;
56 gmtime_r(&result, &check);
57 if( gmtime_r(&result, &check) == NULL )
58 return (time_t)-1;
61 return result;
64 struct tm* iso_to_tm(const char *timestamp,
65 struct tm *result,
66 bool &utc,
67 bool *zone,
68 int *zoneminutes)
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();
76 bool date = true;
77 while( i != ts.end() ) {
78 if( *i == 'T' )
79 date = false;
80 if( (date && *i == '-') || *i == ':' )
81 ts.erase(i);
82 else
83 ++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;
91 result->tm_mon -= 1;
92 result->tm_isdst = -1;
93 if( found != 6 ) {
94 return 0;
97 utc = ts.find('Z', 15) != string::npos;
99 if( zone && zoneminutes ) {
100 *zone = false;
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;
108 it++;
109 string offset = ts.substr(it);
111 int hour, min;
112 found = sscanf(offset.c_str(), "%02d%02d",
113 &hour, &min);
114 if( offset.size() == 4 && found == 2 ) {
115 *zone = true;
116 *zoneminutes = hour * 60 + min;
117 if( neg != string::npos )
118 *zoneminutes *= -1;
123 return result;
126 std::string tm_to_iso(const struct tm *t, bool utc)
128 char tmp[128];
130 int cc = snprintf(tmp, sizeof(tmp), "%04d%02d%02dT%02d%02d%02d",
131 t->tm_year + 1900,
132 t->tm_mon + 1,
133 t->tm_mday,
134 t->tm_hour,
135 t->tm_min,
136 t->tm_sec
138 if( cc < 0 || (size_t)cc >= sizeof(tmp) )
139 return "";
141 if( utc ) {
142 if( (size_t)cc >= (sizeof(tmp) - 1) )
143 return ""; // not enough room for Z
144 tmp[cc++] = 'Z';
145 tmp[cc] = 0;
148 return tmp;
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.
168 char buf[128];
169 sprintf(buf, "XXX%c%02d:%02d",
170 (zoneminutes < 0 ? '+' : '-'),
171 abs(zoneminutes) / 60,
172 abs(zoneminutes) % 60
174 return Set(buf);
177 time_t TzWrapper::iso_mktime(const char *timestamp)
179 bool utc, zone;
180 struct tm t;
181 int zoneminutes;
182 if( !iso_to_tm(timestamp, &t, utc, &zone, &zoneminutes) )
183 return (time_t)-1;
185 TzWrapper tzw;
186 if( utc ) {
187 tzw.SetUTC();
189 else if( zone ) {
190 tzw.SetOffset(zoneminutes);
192 return tzw.mktime(&t);
195 }} // namespace Barry::Sync
198 #ifdef TZ_TEST_MODE
199 #include <iostream>
200 using namespace std;
201 using namespace Barry::Sync;
202 int main()
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);
230 if( t1 == t2 )
231 cout << "t1 == t2: passed" << endl;
232 else
233 cout << "t1 != t2: ERROR" << endl;
235 time_t t3 = TzWrapper::iso_mktime("2010-05-01T03:15:00.000Z");
236 cout << ctime(&t3);
237 if( t2 == t3 )
238 cout << "t2 == t3: passed" << endl;
239 else
240 cout << "t2 != t3: ERROR" << endl;
242 time_t t4 = TzWrapper::iso_mktime("2010-05-01T04:15:00.000+01:00");
243 cout << ctime(&t4);
244 if( t3 == t4 )
245 cout << "t3 == t4: passed" << endl;
246 else
247 cout << "t3 != t4: ERROR" << endl;
249 time_t t5 = TzWrapper::iso_mktime("2010-05-01T00:15:00.000-03:00");
250 cout << ctime(&t5);
251 if( t4 == t5 )
252 cout << "t4 == t5: passed" << endl;
253 else
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;
258 else
259 cout << "Fail check: ERROR" << endl;
261 cout << "t1: " << tm_to_iso(gmtime(&t1), true) << endl;
263 bool utc, zone;
264 int zoneminutes;
265 struct tm zonetest;
266 if( !iso_to_tm("2010-05-01T04:15:00.000-", &zonetest, utc, &zone, &zoneminutes) )
267 cout << "iso_to_tm failed wrongly: ERROR" << endl;
268 if( zone )
269 cout << "zone true?: ERROR" << endl;
270 else
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;
275 if( zone )
276 cout << "zone true?: ERROR" << endl;
277 else
278 cout << "zone fail check2: passed" << endl;
280 #endif