Branch data Line data Source code
1 : : /********************************************************************\
2 : : * gnc-timezone.cpp - Retrieve timezone information from OS. *
3 : : * Copyright 2014 John Ralls <jralls@ceridwen.us> *
4 : : * Based on work done with Arnel Borja for GLib's gtimezone in 2012.*
5 : : * This program is free software; you can redistribute it and/or *
6 : : * modify it under the terms of the GNU General Public License as *
7 : : * published by the Free Software Foundation; either version 2 of *
8 : : * the License, or (at your option) any later version. *
9 : : * *
10 : : * This program is distributed in the hope that it will be useful, *
11 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 : : * GNU General Public License for more details. *
14 : : * *
15 : : * You should have received a copy of the GNU General Public License*
16 : : * along with this program; if not, contact: *
17 : : * *
18 : : * Free Software Foundation Voice: +1-617-542-5942 *
19 : : * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
20 : : * Boston, MA 02110-1301, USA gnu@gnu.org *
21 : : \********************************************************************/
22 : :
23 : : #include "gnc-timezone.hpp"
24 : :
25 : : #include <string>
26 : : #include <cstdint>
27 : : #include <iostream>
28 : : #include <algorithm>
29 : : #include <boost/date_time/gregorian/gregorian.hpp>
30 : : #if PLATFORM(WINDOWS)
31 : : //We'd prefer to use std::codecvt, but it's not supported by gcc until 5.0.
32 : : #include <boost/locale/encoding_utf.hpp>
33 : : #endif
34 : : #include "qoflog.h"
35 : : static const QofLogModule log_module = "gnc-timezone";
36 : :
37 : : using namespace gnc::date;
38 : :
39 : : using duration = boost::posix_time::time_duration;
40 : : using time_zone = boost::local_time::custom_time_zone;
41 : : using dst_offsets = boost::local_time::dst_adjustment_offsets;
42 : : using calc_rule_ptr = boost::local_time::dst_calc_rule_ptr;
43 : : using PTZ = boost::local_time::posix_time_zone;
44 : :
45 : : const unsigned int TimeZoneProvider::min_year = 1400;
46 : : const unsigned int TimeZoneProvider::max_year = 9999;
47 : :
48 : : template<typename T>
49 : : T*
50 : 22914 : endian_swap(T* t)
51 : : {
52 : : #if ! WORDS_BIGENDIAN
53 : 22914 : auto memp = reinterpret_cast<unsigned char*>(t);
54 : 22914 : std::reverse(memp, memp + sizeof(T));
55 : : #endif
56 : 22914 : return t;
57 : : }
58 : :
59 : : #if PLATFORM(WINDOWS)
60 : : /* libstdc++ to_string is broken on MinGW with no real interest in fixing it.
61 : : * See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52015
62 : : */
63 : : #if ! COMPILER(MINGW)
64 : : using std::to_string;
65 : : #else
66 : : template<typename T> inline std::string to_string(T num);
67 : :
68 : : template<>
69 : : inline std::string
70 : : to_string<unsigned int>(unsigned int num)
71 : : {
72 : : constexpr unsigned int numchars = sizeof num * 3 + 1;
73 : : char buf [numchars] {};
74 : : snprintf (buf, numchars, "%u", num);
75 : : return std::string(buf);
76 : : }
77 : :
78 : : template<>
79 : : inline std::string
80 : : to_string<int>(int num)
81 : : {
82 : : constexpr unsigned int numchars = sizeof num * 3 + 1;
83 : : char buf [numchars];
84 : : snprintf (buf, numchars, "%d", num);
85 : : return std::string(buf);
86 : : }
87 : : #endif
88 : :
89 : : static std::string
90 : : windows_default_tzname (void)
91 : : {
92 : : const char *subkey =
93 : : "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation";
94 : : constexpr size_t keysize {128};
95 : : HKEY key;
96 : : char key_name[keysize] {};
97 : : unsigned long tz_keysize = keysize;
98 : : if (RegOpenKeyExA (HKEY_LOCAL_MACHINE, subkey, 0,
99 : : KEY_QUERY_VALUE, &key) == ERROR_SUCCESS)
100 : : {
101 : : if (RegQueryValueExA (key, "TimeZoneKeyName", nullptr, nullptr,
102 : : (LPBYTE)key_name, &tz_keysize) != ERROR_SUCCESS)
103 : : {
104 : : memset (key_name, 0, tz_keysize);
105 : : }
106 : : RegCloseKey (key);
107 : : }
108 : : return std::string(key_name);
109 : : }
110 : :
111 : : typedef struct
112 : : {
113 : : LONG Bias;
114 : : LONG StandardBias;
115 : : LONG DaylightBias;
116 : : SYSTEMTIME StandardDate;
117 : : SYSTEMTIME DaylightDate;
118 : : } RegTZI;
119 : :
120 : : static time_zone_names
121 : : windows_tz_names (HKEY key)
122 : : {
123 : : /* The weird sizeof arg is because C++ won't find a type's
124 : : * element, just an object's.
125 : : */
126 : : constexpr auto s_size = sizeof (((TIME_ZONE_INFORMATION*)0)->StandardName);
127 : : char std_name[s_size];
128 : : unsigned long size = s_size;
129 : : if (RegQueryValueExA (key, "Std", NULL, NULL,
130 : : (LPBYTE)&(std_name), &size) != ERROR_SUCCESS)
131 : : throw std::invalid_argument ("Registry contains no standard name.");
132 : :
133 : : constexpr auto d_size = sizeof (((TIME_ZONE_INFORMATION*)0)->DaylightName);
134 : : char dlt_name[d_size];
135 : : size = d_size;
136 : : if (RegQueryValueExA (key, "Dlt", NULL, NULL,
137 : : (LPBYTE)&(dlt_name), &size) != ERROR_SUCCESS)
138 : : throw std::invalid_argument ("Registry contains no daylight name.");
139 : :
140 : : return time_zone_names (std_name, std_name, dlt_name, dlt_name);
141 : : }
142 : :
143 : : #define make_week_num(x) static_cast<boost::date_time::nth_kday_of_month<boost::gregorian::date>::week_num>(x)
144 : :
145 : : static TZ_Ptr
146 : : zone_from_regtzi (const RegTZI& regtzi, time_zone_names names)
147 : : {
148 : : using ndate = boost::gregorian::nth_day_of_the_week_in_month;
149 : : using nth_day_rule = boost::local_time::nth_day_of_the_week_in_month_dst_rule;
150 : : /* Note that Windows runs its biases backwards from POSIX and
151 : : * boost::date_time: It's the value added to the local time to get
152 : : * GMT rather than the value added to GMT to get local time; for
153 : : * the same reason the DaylightBias is negative as one generally
154 : : * adds an hour less to the local time to get GMT. Biases are in
155 : : * minutes.
156 : : */
157 : : duration std_off (0, regtzi.StandardBias - regtzi.Bias, 0);
158 : : duration dlt_off (0, -regtzi.DaylightBias, 0);
159 : : duration start_time (regtzi.StandardDate.wHour, regtzi.StandardDate.wMinute,
160 : : regtzi.StandardDate.wSecond);
161 : : duration end_time (regtzi.DaylightDate.wHour, regtzi.DaylightDate.wMinute,
162 : : regtzi.DaylightDate.wSecond);
163 : : dst_offsets offsets (dlt_off, start_time, end_time);
164 : : auto std_week_num = make_week_num(regtzi.StandardDate.wDay);
165 : : auto dlt_week_num = make_week_num(regtzi.DaylightDate.wDay);
166 : : calc_rule_ptr dates;
167 : : if (regtzi.StandardDate.wMonth != 0)
168 : : {
169 : : try
170 : : {
171 : : ndate start (dlt_week_num, regtzi.DaylightDate.wDayOfWeek,
172 : : regtzi.DaylightDate.wMonth);
173 : : ndate end(std_week_num, regtzi.StandardDate.wDayOfWeek,
174 : : regtzi.StandardDate.wMonth);
175 : : dates.reset(new nth_day_rule (start, end));
176 : : }
177 : : catch (boost::gregorian::bad_month& err)
178 : : {
179 : : PWARN("Caught Bad Month Exception. Daylight Bias: %ld "
180 : : "Standard Month : %d Daylight Month: %d",
181 : : regtzi.DaylightBias, regtzi.StandardDate.wMonth,
182 : : regtzi.DaylightDate.wMonth);
183 : : }
184 : : }
185 : : return TZ_Ptr(new time_zone(names, std_off, offsets, dates));
186 : : }
187 : :
188 : : void
189 : : TimeZoneProvider::load_windows_dynamic_tz (HKEY key, time_zone_names names)
190 : : {
191 : : DWORD first, last;
192 : :
193 : : try
194 : : {
195 : : unsigned long size = sizeof first;
196 : : if (RegQueryValueExA (key, "FirstEntry", NULL, NULL,
197 : : (LPBYTE) &first, &size) != ERROR_SUCCESS)
198 : : throw std::invalid_argument ("No first entry.");
199 : :
200 : : size = sizeof last;
201 : : if (RegQueryValueExA (key, "LastEntry", NULL, NULL,
202 : : (LPBYTE) &last, &size) != ERROR_SUCCESS)
203 : : throw std::invalid_argument ("No last entry.");
204 : :
205 : : TZ_Ptr tz {};
206 : : for (unsigned int year = first; year <= last; year++)
207 : : {
208 : : auto s = to_string(year);
209 : : auto ystr = s.c_str();
210 : : RegTZI regtzi {};
211 : : size = sizeof regtzi;
212 : : auto err_val = RegQueryValueExA (key, ystr, NULL, NULL,
213 : : (LPBYTE) ®tzi, &size);
214 : : if (err_val != ERROR_SUCCESS)
215 : : {
216 : : break;
217 : : }
218 : : tz = zone_from_regtzi (regtzi, names);
219 : : if (year == first)
220 : : m_zone_vector.push_back (std::make_pair(0, tz));
221 : : m_zone_vector.push_back (std::make_pair(year, tz));
222 : : }
223 : : m_zone_vector.push_back (std::make_pair(max_year, tz));
224 : : }
225 : : catch (std::invalid_argument)
226 : : {
227 : : RegCloseKey (key);
228 : : throw;
229 : : }
230 : : catch (std::bad_alloc)
231 : : {
232 : : RegCloseKey (key);
233 : : throw;
234 : : }
235 : : RegCloseKey (key);
236 : : }
237 : :
238 : : void
239 : : TimeZoneProvider::load_windows_classic_tz (HKEY key, time_zone_names names)
240 : : {
241 : : RegTZI regtzi {};
242 : : unsigned long size = sizeof regtzi;
243 : : try
244 : : {
245 : : if (RegQueryValueExA (key, "TZI", NULL, NULL,
246 : : (LPBYTE) ®tzi, &size) == ERROR_SUCCESS)
247 : : {
248 : : m_zone_vector.push_back(
249 : : std::make_pair(max_year, zone_from_regtzi (regtzi, names)));
250 : : }
251 : : }
252 : : catch (std::bad_alloc)
253 : : {
254 : : RegCloseKey (key);
255 : : throw;
256 : : }
257 : : RegCloseKey (key);
258 : : }
259 : :
260 : : void
261 : : TimeZoneProvider::load_windows_default_tz()
262 : : {
263 : : TIME_ZONE_INFORMATION tzi {};
264 : : GetTimeZoneInformation (&tzi);
265 : : RegTZI regtzi { tzi.Bias, tzi.StandardBias, tzi.DaylightBias,
266 : : tzi.StandardDate, tzi.DaylightDate };
267 : : using boost::locale::conv::utf_to_utf;
268 : : auto std_name = utf_to_utf<char>(tzi.StandardName,
269 : : tzi.StandardName + sizeof(tzi.StandardName));
270 : : auto dlt_name = utf_to_utf<char>(tzi.DaylightName,
271 : : tzi.DaylightName + sizeof(tzi.DaylightName));
272 : : time_zone_names names (std_name, std_name, dlt_name, dlt_name);
273 : : m_zone_vector.push_back(std::make_pair(max_year, zone_from_regtzi(regtzi, names)));
274 : : }
275 : :
276 : : TimeZoneProvider::TimeZoneProvider (const std::string& identifier) :
277 : : m_zone_vector ()
278 : : {
279 : : HKEY key;
280 : : const std::string reg_key =
281 : : "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\";
282 : :
283 : : auto key_name = (identifier.empty() ? windows_default_tzname () :
284 : : identifier);
285 : :
286 : : if (key_name.empty())
287 : : {
288 : : load_windows_default_tz();
289 : : return;
290 : : }
291 : : std::string subkey = reg_key + key_name;
292 : : if (RegOpenKeyExA (HKEY_LOCAL_MACHINE, subkey.c_str(), 0,
293 : : KEY_QUERY_VALUE, &key) != ERROR_SUCCESS)
294 : : throw std::invalid_argument ("No TZ in registry named " + key_name);
295 : :
296 : : time_zone_names names {windows_tz_names (key)};
297 : : RegCloseKey (key);
298 : :
299 : : std::string subkey_dynamic = subkey + "\\Dynamic DST";
300 : : if (RegOpenKeyExA (HKEY_LOCAL_MACHINE, subkey_dynamic.c_str(), 0,
301 : : KEY_QUERY_VALUE, &key) == ERROR_SUCCESS)
302 : : this->load_windows_dynamic_tz (key, names);
303 : : else if (RegOpenKeyExA (HKEY_LOCAL_MACHINE, subkey.c_str(), 0,
304 : : KEY_QUERY_VALUE, &key) == ERROR_SUCCESS)
305 : : this->load_windows_classic_tz (key, names);
306 : : else
307 : : throw std::invalid_argument ("No data for TZ " + key_name);
308 : : }
309 : : #elif PLATFORM(POSIX)
310 : : using std::to_string;
311 : : #include <istream>
312 : : #include <cstdlib>
313 : :
314 : : using boost::posix_time::ptime;
315 : : //To enable using Transition with different meanings for IANA files
316 : : //and for DSTRules.
317 : : namespace IANAParser
318 : : {
319 : : struct TZHead
320 : : {
321 : : char magic[4];
322 : : char version;
323 : : uint8_t reserved[15];
324 : : uint8_t ttisgmtcnt[4];
325 : : uint8_t ttisstdcnt[4];
326 : : uint8_t leapcnt[4];
327 : : uint8_t timecnt[4];
328 : : uint8_t typecnt[4];
329 : : uint8_t charcnt[4];
330 : : };
331 : :
332 : : struct TTInfo
333 : : {
334 : : int32_t gmtoff;
335 : : uint8_t isdst;
336 : : uint8_t abbrind;
337 : : };
338 : :
339 : : struct TZInfo
340 : : {
341 : : TTInfo info;
342 : : std::string name;
343 : : bool isstd;
344 : : bool isgmt;
345 : : };
346 : :
347 : : struct Transition
348 : : {
349 : : int64_t timestamp;
350 : : uint8_t index;
351 : : };
352 : :
353 : : static std::unique_ptr<char[]>
354 : 114 : find_tz_file(const std::string& name)
355 : : {
356 : 114 : std::ifstream ifs;
357 : 114 : auto tzname = name;
358 : 114 : if (tzname.empty())
359 : 114 : if (auto tzenv = std::getenv("TZ"))
360 : 228 : tzname = std::string(tzenv);
361 : : //std::cout << "Testing tzname " << tzname << "\n";
362 : 114 : if (!tzname.empty())
363 : : {
364 : : //POSIX specifies that that identifier should begin with ':', but we
365 : : //should be liberal. If it's there, it's not part of the filename.
366 : 114 : if (tzname[0] == ':')
367 : 0 : tzname.erase(tzname.begin());
368 : 114 : if (tzname[0] == '/') //Absolute filename
369 : : {
370 : 0 : ifs.open(tzname, std::ios::in|std::ios::binary|std::ios::ate);
371 : : }
372 : : else
373 : : {
374 : 114 : const char* tzdir_c = std::getenv("TZDIR");
375 : 114 : std::string tzdir = tzdir_c ? tzdir_c : "/usr/share/zoneinfo";
376 : : //Note that we're not checking the filename.
377 : 114 : ifs.open(std::move(tzdir + "/" + tzname),
378 : : std::ios::in|std::ios::binary|std::ios::ate);
379 : 114 : }
380 : : }
381 : :
382 : 114 : if (! ifs.is_open())
383 : 0 : throw std::invalid_argument("The timezone string failed to resolve to a valid filename");
384 : 114 : std::streampos filesize = ifs.tellg();
385 : 114 : std::unique_ptr<char[]>fileblock(new char[filesize]);
386 : 114 : ifs.seekg(0, std::ios::beg);
387 : 114 : ifs.read(fileblock.get(), filesize);
388 : 114 : ifs.close();
389 : 228 : return fileblock;
390 : 114 : }
391 : :
392 : : using TZInfoVec = std::vector<TZInfo>;
393 : : using TZInfoIter = TZInfoVec::iterator;
394 : :
395 : : struct IANAParser
396 : : {
397 : 114 : IANAParser(const std::string& name) : IANAParser(find_tz_file(name)) {}
398 : : IANAParser(std::unique_ptr<char[]>);
399 : : std::vector<Transition>transitions;
400 : : TZInfoVec tzinfo;
401 : : int last_year;
402 : : };
403 : :
404 : 114 : IANAParser::IANAParser(std::unique_ptr<char[]>fileblock)
405 : : {
406 : 114 : unsigned int fb_index = 0;
407 : 114 : TZHead tzh = *reinterpret_cast<TZHead*>(&fileblock[fb_index]);
408 : : static constexpr int ttinfo_size = 6; //struct TTInfo gets padded
409 : 114 : last_year = 2037; //Constrained by 32-bit time_t.
410 : 114 : int transition_size = 4; // length of a transition time in the file
411 : :
412 : 114 : auto time_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.timecnt)));
413 : 114 : auto type_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.typecnt)));
414 : 114 : auto char_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.charcnt)));
415 : 114 : auto isgmt_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.ttisgmtcnt)));
416 : 114 : auto isstd_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.ttisstdcnt)));
417 : 114 : auto leap_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.leapcnt)));
418 : 114 : if ((tzh.version == '2' || tzh.version == '3'))
419 : : {
420 : 114 : fb_index = (sizeof(tzh) +
421 : 114 : (sizeof(uint32_t) + sizeof(uint8_t)) * time_count +
422 : 114 : ttinfo_size * type_count +
423 : 114 : sizeof(char) * char_count +
424 : 114 : sizeof(uint8_t) * isgmt_count +
425 : 114 : sizeof(uint8_t) * isstd_count +
426 : 114 : 2 * sizeof(uint32_t) * leap_count);
427 : :
428 : : //This might change at some point in the probably very
429 : : //distant future.
430 : 114 : tzh = *reinterpret_cast<TZHead*>(&fileblock[fb_index]);
431 : 114 : last_year = 2499;
432 : 114 : time_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.timecnt)));
433 : 114 : type_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.typecnt)));
434 : 114 : char_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.charcnt)));
435 : 114 : transition_size = 8;
436 : : }
437 : 114 : fb_index += sizeof(tzh);
438 : 114 : auto start_index = fb_index;
439 : 114 : auto info_index_zero = start_index + time_count * transition_size;
440 : 21318 : for(uint32_t index = 0; index < time_count; ++index)
441 : : {
442 : 21204 : fb_index = start_index + index * transition_size;
443 : 21204 : auto info_index = info_index_zero + index;
444 : 21204 : if (transition_size == 4)
445 : : {
446 : : int32_t transition_time;
447 : : // Ensure correct alignment for ARM.
448 : 0 : memcpy(&transition_time,
449 : 0 : endian_swap(reinterpret_cast<int32_t*>(&fileblock[fb_index])),
450 : : sizeof(int32_t));
451 : 0 : auto info = static_cast<uint8_t>(fileblock[info_index]);
452 : 0 : transitions.push_back({transition_time, info});
453 : : }
454 : : else
455 : : {
456 : : int64_t transition_time;
457 : : // Ensure correct alignment for ARM.
458 : 21204 : memcpy(&transition_time,
459 : 21204 : endian_swap(reinterpret_cast<int64_t*>(&fileblock[fb_index])),
460 : : sizeof(int64_t));
461 : 21204 : auto info = static_cast<uint8_t>(fileblock[info_index]);
462 : 21204 : transitions.push_back({transition_time, info});
463 : : }
464 : : }
465 : :
466 : : //Add in the tzinfo indexes consumed in the previous loop
467 : 114 : start_index = info_index_zero + time_count;
468 : 114 : auto abbrev = start_index + type_count * ttinfo_size;
469 : 114 : auto std_dist = abbrev + char_count;
470 : 114 : auto gmt_dist = std_dist + type_count;
471 : 798 : for(uint32_t index = 0; index < type_count; ++index)
472 : : {
473 : 684 : fb_index = start_index + index * ttinfo_size;
474 : : /* Use memcpy instead of static_cast to avoid memory alignment issues with chars */
475 : 684 : TTInfo info{};
476 : 684 : memcpy(&info, &fileblock[fb_index], ttinfo_size);
477 : 684 : endian_swap(&info.gmtoff);
478 : 684 : tzinfo.push_back(
479 : 684 : {info, &fileblock[abbrev + info.abbrind],
480 : 684 : (index < isstd_count ? fileblock[std_dist + index] != '\0' : true),
481 : 684 : (index < isgmt_count ? fileblock[gmt_dist + index] != '\0' : false)});
482 : : }
483 : :
484 : 3534 : }
485 : : }
486 : :
487 : : namespace DSTRule
488 : : {
489 : : using gregorian_date = boost::gregorian::date;
490 : : using IANAParser::TZInfoIter;
491 : : using ndate = boost::gregorian::nth_day_of_the_week_in_month;
492 : : using week_num =
493 : : boost::date_time::nth_kday_of_month<boost::gregorian::date>::week_num;
494 : :
495 : : struct Transition
496 : : {
497 : 228 : Transition() : month(1), dow(0), week(static_cast<week_num>(0)) {}
498 : : Transition(gregorian_date date);
499 : : bool operator==(const Transition& rhs) const noexcept;
500 : : ndate get();
501 : : boost::gregorian::greg_month month;
502 : : boost::gregorian::greg_weekday dow;
503 : : week_num week;
504 : : };
505 : :
506 : 20976 : Transition::Transition(gregorian_date date) :
507 : 20976 : month(date.month()), dow(date.day_of_week()),
508 : 20976 : week(static_cast<week_num>((6 + date.day() - date.day_of_week()) / 7))
509 : 20976 : {}
510 : :
511 : : bool
512 : 18126 : Transition::operator==(const Transition& rhs) const noexcept
513 : : {
514 : 18126 : return (month == rhs.month && dow == rhs.dow && week == rhs.week);
515 : : }
516 : :
517 : : ndate
518 : 8208 : Transition::get()
519 : : {
520 : 8208 : return ndate(week, dow, month);
521 : : }
522 : :
523 : : struct DSTRule
524 : : {
525 : : DSTRule();
526 : : DSTRule(TZInfoIter info1, TZInfoIter info2,
527 : : ptime date1, ptime date2);
528 : : bool operator==(const DSTRule& rhs) const noexcept;
529 : : bool operator!=(const DSTRule& rhs) const noexcept;
530 : : Transition to_std;
531 : : Transition to_dst;
532 : : duration to_std_time;
533 : : duration to_dst_time;
534 : : TZInfoIter std_info;
535 : : TZInfoIter dst_info;
536 : : };
537 : :
538 : 114 : DSTRule::DSTRule() : to_std(), to_dst(), to_std_time {}, to_dst_time {},
539 : 114 : std_info (), dst_info () {};
540 : :
541 : 10488 : DSTRule::DSTRule (TZInfoIter info1, TZInfoIter info2,
542 : 10488 : ptime date1, ptime date2) :
543 : 10488 : to_std(date1.date()), to_dst(date2.date()),
544 : 10488 : to_std_time(date1.time_of_day()), to_dst_time(date2.time_of_day()),
545 : 10488 : std_info(info1), dst_info(info2)
546 : : {
547 : 10488 : if (info1->info.isdst == info2->info.isdst)
548 : 0 : throw(std::invalid_argument("Both infos have the same dst value."));
549 : 10488 : if (info1->info.isdst && !info2->info.isdst)
550 : : {
551 : 10488 : std::swap(to_std, to_dst);
552 : 10488 : std::swap(to_std_time, to_dst_time);
553 : 10488 : std::swap(std_info, dst_info);
554 : : }
555 : :
556 : : /* Documentation notwithstanding, the date-time rules are
557 : : * looking for local time (wall clock to use the RFC 8538
558 : : * definition) values.
559 : : *
560 : : * The TZ Info contains two fields, isstd and isgmt (renamed
561 : : * to isut in newer versions of tzinfo). In theory if both are
562 : : * 0 the transition times represent wall-clock times,
563 : : * i.e. time stamps in the respective time zone's local time
564 : : * at the moment of the transition. If isstd is 1 then the
565 : : * representation is always in standard time instead of
566 : : * daylight time; this is significant for dst->std
567 : : * transitions. If isgmt/isut is one then isstd must also be
568 : : * set and the transition time is in UTC.
569 : : *
570 : : * In practice it seems that the timestamps are always in UTC
571 : : * so the isgmt/isut flag isn't meaningful. The times always
572 : : * need to have the utc offset added to them to make the
573 : : * transition occur at the right time; the isstd flag
574 : : * determines whether that should be the standard offset or
575 : : * the daylight offset for the daylight->standard transition.
576 : : */
577 : :
578 : 10488 : to_dst_time += boost::posix_time::seconds(std_info->info.gmtoff);
579 : 10488 : if (std_info->isstd) //if isstd always use standard time
580 : 0 : to_std_time += boost::posix_time::seconds(std_info->info.gmtoff);
581 : : else
582 : 10488 : to_std_time += boost::posix_time::seconds(dst_info->info.gmtoff);
583 : :
584 : 10488 : }
585 : :
586 : : bool
587 : 10488 : DSTRule::operator==(const DSTRule& rhs) const noexcept
588 : : {
589 : 18126 : return (to_std == rhs.to_std &&
590 : 7638 : to_dst == rhs.to_dst &&
591 : 6384 : to_std_time == rhs.to_std_time &&
592 : 12768 : to_dst_time == rhs.to_dst_time &&
593 : 24510 : std_info == rhs.std_info &&
594 : 16872 : dst_info == rhs.dst_info);
595 : : }
596 : :
597 : : bool
598 : 10488 : DSTRule::operator!=(const DSTRule& rhs) const noexcept
599 : : {
600 : 10488 : return ! operator==(rhs);
601 : : }
602 : : }
603 : :
604 : : static TZ_Entry
605 : 912 : zone_no_dst(int year, IANAParser::TZInfoIter std_info)
606 : : {
607 : 2736 : time_zone_names names(std_info->name, std_info->name, "", "");
608 : 912 : duration std_off(0, 0, std_info->info.gmtoff);
609 : 912 : dst_offsets offsets({0, 0, 0}, {0, 0, 0}, {0, 0, 0});
610 : 912 : boost::local_time::dst_calc_rule_ptr calc_rule(nullptr);
611 : 912 : TZ_Ptr tz(new time_zone(names, std_off, offsets, calc_rule));
612 : 1824 : return std::make_pair(year, tz);
613 : 912 : }
614 : :
615 : : static TZ_Entry
616 : 4104 : zone_from_rule(int year, DSTRule::DSTRule rule)
617 : : {
618 : : using boost::gregorian::partial_date;
619 : : using boost::local_time::partial_date_dst_rule;
620 : : using nth_day_rule =
621 : : boost::local_time::nth_day_of_the_week_in_month_dst_rule;
622 : :
623 : 4104 : time_zone_names names(rule.std_info->name, rule.std_info->name,
624 : 8208 : rule.dst_info->name, rule.dst_info->name);
625 : 4104 : duration std_off(0, 0, rule.std_info->info.gmtoff);
626 : : duration dlt_off(0, 0,
627 : 4104 : rule.dst_info->info.gmtoff - rule.std_info->info.gmtoff);
628 : 4104 : dst_offsets offsets(dlt_off, rule.to_dst_time, rule.to_std_time);
629 : 4104 : calc_rule_ptr dates(new nth_day_rule(rule.to_dst.get(), rule.to_std.get()));
630 : 4104 : TZ_Ptr tz(new time_zone(names, std_off, offsets, dates));
631 : 8208 : return std::make_pair(year, tz);
632 : 4104 : }
633 : :
634 : : void
635 : 114 : TimeZoneProvider::parse_file(const std::string& tzname)
636 : : {
637 : 114 : IANAParser::IANAParser parser(tzname);
638 : : using boost::posix_time::hours;
639 : 114 : const auto one_year = hours(366 * 24); //Might be a leap year.
640 : 114 : auto last_info = std::find_if(parser.tzinfo.begin(), parser.tzinfo.end(),
641 : 114 : [](IANAParser::TZInfo tz)
642 : 114 : {return !tz.info.isdst;});
643 : 114 : auto last_time = ptime();
644 : 114 : DSTRule::DSTRule last_rule;
645 : : using boost::gregorian::date;
646 : : using boost::posix_time::ptime;
647 : : using boost::posix_time::time_duration;
648 : 114 : for (auto txi = parser.transitions.begin();
649 : 21318 : txi != parser.transitions.end(); ++txi)
650 : : {
651 : 21204 : auto this_info = parser.tzinfo.begin() + txi->index;
652 : : //Can't use boost::posix_date::from_time_t() constructor because it
653 : : //silently casts the time_t to an int32_t.
654 : 21204 : auto this_time = ptime(date(1970, 1, 1),
655 : 42408 : time_duration(txi->timestamp / 3600, 0,
656 : 42408 : txi->timestamp % 3600));
657 : : /* Note: The "get" function retrieves the last zone with a
658 : : * year *earlier* than the requested year: Zone periods run
659 : : * from the saved year to the beginning year of the next zone.
660 : : */
661 : : try
662 : : {
663 : 21204 : auto this_year = this_time.date().year();
664 : : //Initial case
665 : 21204 : if (last_time.is_not_a_date_time())
666 : : {
667 : 114 : m_zone_vector.push_back(zone_no_dst(this_year - 1, last_info));
668 : 114 : m_zone_vector.push_back(zone_no_dst(this_year, this_info));
669 : : }
670 : : // No change in is_dst means a permanent zone change.
671 : 21090 : else if (last_info->info.isdst == this_info->info.isdst)
672 : : {
673 : 114 : m_zone_vector.push_back(zone_no_dst(this_year, this_info));
674 : : }
675 : : /* If there have been no transitions in at least a year
676 : : * then we need to create a no-DST rule with last_info to
677 : : * reflect the frozen timezone.
678 : : */
679 : 20976 : else if (this_time - last_time > one_year)
680 : : {
681 : 456 : auto year = last_time.date().year();
682 : 456 : if (m_zone_vector.back().first == year)
683 : 228 : year = year + 1; // no operator ++ or +=, sigh.
684 : 456 : m_zone_vector.push_back(zone_no_dst(year, last_info));
685 : : }
686 : : /* It's been less than a year, so it's probably a DST
687 : : * cycle. This consumes two transitions so we want only
688 : : * the return-to-standard-time one to make a DST rule.
689 : : */
690 : 20520 : else if (!this_info->info.isdst)
691 : : {
692 : : DSTRule::DSTRule new_rule(last_info, this_info,
693 : 10488 : last_time, this_time);
694 : 10488 : if (new_rule != last_rule)
695 : : {
696 : 4104 : last_rule = new_rule;
697 : 4104 : auto year = last_time.date().year();
698 : 4104 : m_zone_vector.push_back(zone_from_rule(year, new_rule));
699 : : }
700 : : }
701 : : }
702 : 0 : catch(const boost::gregorian::bad_year& err)
703 : : {
704 : 0 : continue;
705 : 0 : }
706 : 21204 : last_time = this_time;
707 : 21204 : last_info = this_info;
708 : : }
709 : : /* if the transitions end before the end of the zoneinfo coverage
710 : : * period then the zone rescinded DST and we need a final no-dstzone.
711 : : */
712 : 114 : if (last_time.is_not_a_date_time())
713 : 0 : m_zone_vector.push_back(zone_no_dst(max_year, last_info));
714 : 114 : else if (last_time.date().year() < parser.last_year)
715 : 114 : m_zone_vector.push_back(zone_no_dst(last_time.date().year(), last_info));
716 : 114 : }
717 : :
718 : : bool
719 : 114 : TimeZoneProvider::construct(const std::string& tzname)
720 : : {
721 : : try
722 : : {
723 : 114 : parse_file(tzname);
724 : : }
725 : 0 : catch(const std::invalid_argument& err)
726 : : {
727 : : try
728 : : {
729 : 0 : TZ_Ptr zone(new PTZ(tzname));
730 : 0 : m_zone_vector.push_back(std::make_pair(max_year, zone));
731 : 0 : }
732 : 0 : catch(std::exception& err)
733 : : {
734 : 0 : return false;
735 : 0 : }
736 : 0 : }
737 : 114 : return true;
738 : : }
739 : :
740 : 114 : TimeZoneProvider::TimeZoneProvider(const std::string& tzname) : m_zone_vector {}
741 : : {
742 : 114 : if(construct(tzname))
743 : 114 : return;
744 : 0 : DEBUG("%s invalid, trying TZ environment variable.\n", tzname.c_str());
745 : 0 : const char* tz_env = getenv("TZ");
746 : 0 : if(tz_env && construct(tz_env))
747 : 0 : return;
748 : 0 : DEBUG("No valid $TZ, resorting to /etc/localtime.\n");
749 : : try
750 : : {
751 : 0 : parse_file("/etc/localtime");
752 : : }
753 : 0 : catch(const std::invalid_argument& env)
754 : : {
755 : 0 : DEBUG("/etc/localtime invalid, resorting to GMT.");
756 : 0 : TZ_Ptr zone(new PTZ("UTC0"));
757 : 0 : m_zone_vector.push_back(std::make_pair(max_year, zone));
758 : 0 : }
759 : 0 : }
760 : : #endif
761 : :
762 : :
763 : : TZ_Ptr
764 : 222204 : TimeZoneProvider::get(int year) const noexcept
765 : : {
766 : 222204 : if (m_zone_vector.empty())
767 : 0 : return TZ_Ptr(new PTZ("UTC0"));
768 : 222204 : auto iter = find_if(m_zone_vector.rbegin(), m_zone_vector.rend(),
769 : 4166438 : [=](TZ_Entry e) { return e.first <= year; });
770 : 222204 : if (iter == m_zone_vector.rend())
771 : 3 : return m_zone_vector.front().second;
772 : 222201 : return iter->second;
773 : : }
774 : :
775 : : void
776 : 0 : TimeZoneProvider::dump() const noexcept
777 : : {
778 : 0 : for (const auto& zone : m_zone_vector)
779 : 0 : std::cout << zone.first << ": " << zone.second->to_posix_string() << "\n";
780 : 0 : }
|