GnuCash c935c2f+
Loading...
Searching...
No Matches
gnc-timezone.cpp
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"
35static const QofLogModule log_module = "gnc-timezone";
36
37using namespace gnc::date;
38
39using duration = boost::posix_time::time_duration;
40using time_zone = boost::local_time::custom_time_zone;
41using dst_offsets = boost::local_time::dst_adjustment_offsets;
42using calc_rule_ptr = boost::local_time::dst_calc_rule_ptr;
43using PTZ = boost::local_time::posix_time_zone;
44
45const unsigned int TimeZoneProvider::min_year = 1400;
46const unsigned int TimeZoneProvider::max_year = 9999;
47
48template<typename T>
49T*
50endian_swap(T* t)
51{
52#ifndef WORDS_BIGENDIAN
53 auto memp = reinterpret_cast<unsigned char*>(t);
54 std::reverse(memp, memp + sizeof(T));
55#endif
56 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)
64using std::to_string;
65#else
66template<typename T> inline std::string to_string(T num);
67
68template<>
69inline std::string
70to_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
78template<>
79inline std::string
80to_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
89static std::string
90windows_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
111typedef struct
112{
113 LONG Bias;
114 LONG StandardBias;
115 LONG DaylightBias;
116 SYSTEMTIME StandardDate;
117 SYSTEMTIME DaylightDate;
118} RegTZI;
119
120static time_zone_names
121windows_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
145static TZ_Ptr
146zone_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
188void
189TimeZoneProvider::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) &regtzi, &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 (const std::invalid_argument& err)
226 {
227 RegCloseKey (key);
228 throw;
229 }
230 catch (const std::bad_alloc& err)
231 {
232 RegCloseKey (key);
233 throw;
234 }
235 RegCloseKey (key);
236}
237
238void
239TimeZoneProvider::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) &regtzi, &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 (const std::bad_alloc& err)
253 {
254 RegCloseKey (key);
255 throw;
256 }
257 RegCloseKey (key);
258}
259
260void
261TimeZoneProvider::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
276TimeZoneProvider::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)
310using std::to_string;
311#include <istream>
312#include <cstdlib>
313
314using boost::posix_time::ptime;
315//To enable using Transition with different meanings for IANA files
316//and for DSTRules.
317namespace 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 find_tz_file(const std::string& name)
355 {
356 std::ifstream ifs;
357 auto tzname = name;
358 if (tzname.empty())
359 if (auto tzenv = std::getenv("TZ"))
360 tzname = std::string(tzenv);
361 //std::cout << "Testing tzname " << tzname << "\n";
362 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 if (tzname[0] == ':')
367 tzname.erase(tzname.begin());
368 if (tzname[0] == '/') //Absolute filename
369 {
370 ifs.open(tzname, std::ios::in|std::ios::binary|std::ios::ate);
371 }
372 else
373 {
374 const char* tzdir_c = std::getenv("TZDIR");
375 std::string tzdir = tzdir_c ? tzdir_c : "/usr/share/zoneinfo";
376//Note that we're not checking the filename.
377 ifs.open(std::move(tzdir + "/" + tzname),
378 std::ios::in|std::ios::binary|std::ios::ate);
379 }
380 }
381
382 if (! ifs.is_open())
383 throw std::invalid_argument("The timezone string failed to resolve to a valid filename");
384 std::streampos filesize = ifs.tellg();
385 std::unique_ptr<char[]>fileblock(new char[filesize]);
386 ifs.seekg(0, std::ios::beg);
387 ifs.read(fileblock.get(), filesize);
388 ifs.close();
389 return fileblock;
390 }
391
392 using TZInfoVec = std::vector<TZInfo>;
393 using TZInfoIter = TZInfoVec::iterator;
394
395 struct IANAParser
396 {
397 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 IANAParser::IANAParser(std::unique_ptr<char[]>fileblock)
405 {
406 unsigned int fb_index = 0;
407 TZHead tzh = *reinterpret_cast<TZHead*>(&fileblock[fb_index]);
408 static constexpr int ttinfo_size = 6; //struct TTInfo gets padded
409 last_year = 2037; //Constrained by 32-bit time_t.
410 int transition_size = 4; // length of a transition time in the file
411
412 auto time_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.timecnt)));
413 auto type_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.typecnt)));
414 auto char_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.charcnt)));
415 auto isgmt_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.ttisgmtcnt)));
416 auto isstd_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.ttisstdcnt)));
417 auto leap_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.leapcnt)));
418 if ((tzh.version == '2' || tzh.version == '3'))
419 {
420 fb_index = (sizeof(tzh) +
421 (sizeof(uint32_t) + sizeof(uint8_t)) * time_count +
422 ttinfo_size * type_count +
423 sizeof(char) * char_count +
424 sizeof(uint8_t) * isgmt_count +
425 sizeof(uint8_t) * isstd_count +
426 2 * sizeof(uint32_t) * leap_count);
427
428 //This might change at some point in the probably very
429 //distant future.
430 tzh = *reinterpret_cast<TZHead*>(&fileblock[fb_index]);
431 last_year = 2499;
432 time_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.timecnt)));
433 type_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.typecnt)));
434 char_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.charcnt)));
435 transition_size = 8;
436 }
437 fb_index += sizeof(tzh);
438 auto start_index = fb_index;
439 auto info_index_zero = start_index + time_count * transition_size;
440 for(uint32_t index = 0; index < time_count; ++index)
441 {
442 fb_index = start_index + index * transition_size;
443 auto info_index = info_index_zero + index;
444 if (transition_size == 4)
445 {
446 int32_t transition_time;
447 // Ensure correct alignment for ARM.
448 memcpy(&transition_time,
449 endian_swap(reinterpret_cast<int32_t*>(&fileblock[fb_index])),
450 sizeof(int32_t));
451 auto info = static_cast<uint8_t>(fileblock[info_index]);
452 transitions.push_back({transition_time, info});
453 }
454 else
455 {
456 int64_t transition_time;
457 // Ensure correct alignment for ARM.
458 memcpy(&transition_time, &fileblock[fb_index],
459 sizeof(int64_t));
460 transition_time = *(endian_swap(&transition_time));
461 auto info = static_cast<uint8_t>(fileblock[info_index]);
462 transitions.push_back({transition_time, info});
463 }
464 }
465
466 //Add in the tzinfo indexes consumed in the previous loop
467 start_index = info_index_zero + time_count;
468 auto abbrev = start_index + type_count * ttinfo_size;
469 auto std_dist = abbrev + char_count;
470 auto gmt_dist = std_dist + type_count;
471 for(uint32_t index = 0; index < type_count; ++index)
472 {
473 fb_index = start_index + index * ttinfo_size;
474 /* Use memcpy instead of static_cast to avoid memory alignment issues with chars */
475 TTInfo info{};
476 memcpy(&info, &fileblock[fb_index], ttinfo_size);
477 endian_swap(&info.gmtoff);
478 tzinfo.push_back(
479 {info, &fileblock[abbrev + info.abbrind],
480 (index < isstd_count ? fileblock[std_dist + index] != '\0' : true),
481 (index < isgmt_count ? fileblock[gmt_dist + index] != '\0' : false)});
482 }
483
484 }
485}
486
487namespace 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 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 Transition::Transition(gregorian_date date) :
507 month(date.month()), dow(date.day_of_week()),
508 week(static_cast<week_num>((6 + date.day() - date.day_of_week()) / 7))
509 {}
510
511 bool
512 Transition::operator==(const Transition& rhs) const noexcept
513 {
514 return (month == rhs.month && dow == rhs.dow && week == rhs.week);
515 }
516
517 ndate
518 Transition::get()
519 {
520 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 DSTRule::DSTRule() : to_std(), to_dst(), to_std_time {}, to_dst_time {},
539 std_info (), dst_info () {};
540
541 DSTRule::DSTRule (TZInfoIter info1, TZInfoIter info2,
542 ptime date1, ptime date2) :
543 to_std(date1.date()), to_dst(date2.date()),
544 to_std_time(date1.time_of_day()), to_dst_time(date2.time_of_day()),
545 std_info(info1), dst_info(info2)
546 {
547 if (info1->info.isdst == info2->info.isdst)
548 throw(std::invalid_argument("Both infos have the same dst value."));
549 if (info1->info.isdst && !info2->info.isdst)
550 {
551 std::swap(to_std, to_dst);
552 std::swap(to_std_time, to_dst_time);
553 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 to_dst_time += boost::posix_time::seconds(std_info->info.gmtoff);
579 if (std_info->isstd) //if isstd always use standard time
580 to_std_time += boost::posix_time::seconds(std_info->info.gmtoff);
581 else
582 to_std_time += boost::posix_time::seconds(dst_info->info.gmtoff);
583
584 }
585
586 bool
587 DSTRule::operator==(const DSTRule& rhs) const noexcept
588 {
589 return (to_std == rhs.to_std &&
590 to_dst == rhs.to_dst &&
591 to_std_time == rhs.to_std_time &&
592 to_dst_time == rhs.to_dst_time &&
593 std_info == rhs.std_info &&
594 dst_info == rhs.dst_info);
595 }
596
597 bool
598 DSTRule::operator!=(const DSTRule& rhs) const noexcept
599 {
600 return ! operator==(rhs);
601 }
602}
603
604static TZ_Entry
605zone_no_dst(int year, IANAParser::TZInfoIter std_info)
606{
607 time_zone_names names(std_info->name, std_info->name, "", "");
608 duration std_off(0, 0, std_info->info.gmtoff);
609 dst_offsets offsets({0, 0, 0}, {0, 0, 0}, {0, 0, 0});
610 boost::local_time::dst_calc_rule_ptr calc_rule(nullptr);
611 TZ_Ptr tz(new time_zone(names, std_off, offsets, calc_rule));
612 return std::make_pair(year, tz);
613}
614
615static TZ_Entry
616zone_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 time_zone_names names(rule.std_info->name, rule.std_info->name,
624 rule.dst_info->name, rule.dst_info->name);
625 duration std_off(0, 0, rule.std_info->info.gmtoff);
626 duration dlt_off(0, 0,
627 rule.dst_info->info.gmtoff - rule.std_info->info.gmtoff);
628 dst_offsets offsets(dlt_off, rule.to_dst_time, rule.to_std_time);
629 calc_rule_ptr dates(new nth_day_rule(rule.to_dst.get(), rule.to_std.get()));
630 TZ_Ptr tz(new time_zone(names, std_off, offsets, dates));
631 return std::make_pair(year, tz);
632}
633
634void
635TimeZoneProvider::parse_file(const std::string& tzname)
636{
637 IANAParser::IANAParser parser(tzname);
638 using boost::posix_time::hours;
639 const auto one_year = hours(366 * 24); //Might be a leap year.
640 auto last_info = std::find_if(parser.tzinfo.begin(), parser.tzinfo.end(),
641 [](IANAParser::TZInfo tz)
642 {return !tz.info.isdst;});
643 auto last_time = ptime();
644 DSTRule::DSTRule last_rule;
645 using boost::gregorian::date;
646 using boost::posix_time::ptime;
647 using boost::posix_time::time_duration;
648 for (auto txi = parser.transitions.begin();
649 txi != parser.transitions.end(); ++txi)
650 {
651 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 auto this_time = ptime(date(1970, 1, 1),
655 time_duration(txi->timestamp / 3600, 0,
656 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 auto this_year = this_time.date().year();
664 //Initial case
665 if (last_time.is_not_a_date_time())
666 {
667 m_zone_vector.push_back(zone_no_dst(this_year - 1, last_info));
668 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 else if (last_info->info.isdst == this_info->info.isdst)
672 {
673 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 else if (this_time - last_time > one_year)
680 {
681 auto year = last_time.date().year();
682 if (m_zone_vector.back().first == year)
683 year = year + 1; // no operator ++ or +=, sigh.
684 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 else if (!this_info->info.isdst)
691 {
692 DSTRule::DSTRule new_rule(last_info, this_info,
693 last_time, this_time);
694 if (new_rule != last_rule)
695 {
696 last_rule = new_rule;
697 auto year = last_time.date().year();
698 m_zone_vector.push_back(zone_from_rule(year, new_rule));
699 }
700 }
701 }
702 catch(const boost::gregorian::bad_year& err)
703 {
704 continue;
705 } catch (std::out_of_range &)
706 {
707 continue;
708 }
709 last_time = this_time;
710 last_info = this_info;
711 }
712/* if the transitions end before the end of the zoneinfo coverage
713 * period then the zone rescinded DST and we need a final no-dstzone.
714 */
715 if (last_time.is_not_a_date_time())
716 m_zone_vector.push_back(zone_no_dst(max_year, last_info));
717 else if (last_time.date().year() < parser.last_year)
718 m_zone_vector.push_back(zone_no_dst(last_time.date().year(), last_info));
719}
720
721bool
722TimeZoneProvider::construct(const std::string& tzname)
723{
724 try
725 {
726 parse_file(tzname);
727 }
728 catch(const std::invalid_argument& err)
729 {
730 try
731 {
732 TZ_Ptr zone(new PTZ(tzname));
733 m_zone_vector.push_back(std::make_pair(max_year, zone));
734 }
735 catch(std::exception& err)
736 {
737 return false;
738 }
739 }
740 return true;
741}
742
743TimeZoneProvider::TimeZoneProvider(const std::string& tzname) : m_zone_vector {}
744{
745 if(construct(tzname))
746 return;
747 DEBUG("%s invalid, trying TZ environment variable.\n", tzname.c_str());
748 const char* tz_env = getenv("TZ");
749 if(tz_env && construct(tz_env))
750 return;
751 DEBUG("No valid $TZ, resorting to /etc/localtime.\n");
752 try
753 {
754 parse_file("/etc/localtime");
755 }
756 catch(const std::invalid_argument& env)
757 {
758 DEBUG("/etc/localtime invalid, resorting to GMT.");
759 TZ_Ptr zone(new PTZ("UTC0"));
760 m_zone_vector.push_back(std::make_pair(max_year, zone));
761 }
762}
763#endif
764
765
766TZ_Ptr
767TimeZoneProvider::get(int year) const noexcept
768{
769 if (m_zone_vector.empty())
770 return TZ_Ptr(new PTZ("UTC0"));
771 auto iter = find_if(m_zone_vector.rbegin(), m_zone_vector.rend(),
772 [=](TZ_Entry e) { return e.first <= year; });
773 if (iter == m_zone_vector.rend())
774 return m_zone_vector.front().second;
775 return iter->second;
776}
777
778void
779TimeZoneProvider::dump() const noexcept
780{
781 for (const auto& zone : m_zone_vector)
782 std::cout << zone.first << ": " << zone.second->to_posix_string() << "\n";
783}
#define DEBUG(format, args...)
Print a debugging message.
Definition qoflog.h:264
#define PWARN(format, args...)
Log a warning.
Definition qoflog.h:250