maint: fixed incorrect argument switch order in make-redirect.sh script
[barry/progweb.git] / src / tzwrapper.cc
blob84a76b2ef7f1f0298e2dd2836db0a19096fe6e78
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-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"
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 == 3 ) {
94 // only a date available, so force time to 00:00:00
95 result->tm_hour = 0;
96 result->tm_min = 0;
97 result->tm_sec = 0;
99 else if( found != 6 ) {
100 return 0;
103 utc = ts.find('Z', 15) != string::npos;
105 if( zone && zoneminutes ) {
106 *zone = false;
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;
114 it++;
115 string offset = ts.substr(it);
117 int hour, min;
118 found = sscanf(offset.c_str(), "%02d%02d",
119 &hour, &min);
120 if( offset.size() == 4 && found == 2 ) {
121 *zone = true;
122 *zoneminutes = hour * 60 + min;
123 if( neg != string::npos )
124 *zoneminutes *= -1;
129 return result;
132 std::string tm_to_iso(const struct tm *t, bool utc)
134 char tmp[128];
136 int cc = snprintf(tmp, sizeof(tmp), "%04d%02d%02dT%02d%02d%02d",
137 t->tm_year + 1900,
138 t->tm_mon + 1,
139 t->tm_mday,
140 t->tm_hour,
141 t->tm_min,
142 t->tm_sec
144 if( cc < 0 || (size_t)cc >= sizeof(tmp) )
145 return "";
147 if( utc ) {
148 if( (size_t)cc >= (sizeof(tmp) - 1) )
149 return ""; // not enough room for Z
150 tmp[cc++] = 'Z';
151 tmp[cc] = 0;
154 return tmp;
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.
174 char buf[128];
175 sprintf(buf, "XXX%c%02d:%02d",
176 (zoneminutes < 0 ? '+' : '-'),
177 abs(zoneminutes) / 60,
178 abs(zoneminutes) % 60
180 return Set(buf);
183 time_t TzWrapper::iso_mktime(const char *timestamp)
185 bool utc, zone;
186 struct tm t;
187 int zoneminutes;
188 if( !iso_to_tm(timestamp, &t, utc, &zone, &zoneminutes) )
189 return (time_t)-1;
191 TzWrapper tzw;
192 if( utc ) {
193 tzw.SetUTC();
195 else if( zone ) {
196 tzw.SetOffset(zoneminutes);
198 return tzw.mktime(&t);
201 }} // namespace Barry::Sync
204 #ifdef TZ_TEST_MODE
205 #include <iostream>
206 using namespace std;
207 using namespace Barry::Sync;
208 int main()
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);
236 if( t1 == t2 )
237 cout << "t1 == t2: passed" << endl;
238 else
239 cout << "t1 != t2: ERROR" << endl;
241 time_t t3 = TzWrapper::iso_mktime("2010-05-01T03:15:00.000Z");
242 cout << ctime(&t3);
243 if( t2 == t3 )
244 cout << "t2 == t3: passed" << endl;
245 else
246 cout << "t2 != t3: ERROR" << endl;
248 time_t t4 = TzWrapper::iso_mktime("2010-05-01T04:15:00.000+01:00");
249 cout << ctime(&t4);
250 if( t3 == t4 )
251 cout << "t3 == t4: passed" << endl;
252 else
253 cout << "t3 != t4: ERROR" << endl;
255 time_t t5 = TzWrapper::iso_mktime("2010-05-01T00:15:00.000-03:00");
256 cout << ctime(&t5);
257 if( t4 == t5 )
258 cout << "t4 == t5: passed" << endl;
259 else
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;
264 else
265 cout << "Date check: ERROR" << endl;
267 cout << "t1: " << tm_to_iso(gmtime(&t1), true) << endl;
269 bool utc, zone;
270 int zoneminutes;
271 struct tm zonetest;
272 if( !iso_to_tm("2010-05-01T04:15:00.000-", &zonetest, utc, &zone, &zoneminutes) )
273 cout << "iso_to_tm failed wrongly: ERROR" << endl;
274 if( zone )
275 cout << "zone true?: ERROR" << endl;
276 else
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;
281 if( zone )
282 cout << "zone true?: ERROR" << endl;
283 else
284 cout << "zone fail check2: passed" << endl;
286 #endif