GnuCash  5.6-150-g038405b370+
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"
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 endian_swap(T* t)
51 {
52 #if ! 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)
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) &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 (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) &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 (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  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,
459  endian_swap(reinterpret_cast<int64_t*>(&fileblock[fb_index])),
460  sizeof(int64_t));
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 
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  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 
604 static TZ_Entry
605 zone_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 
615 static TZ_Entry
616 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  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 
634 void
635 TimeZoneProvider::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  }
706  last_time = this_time;
707  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  if (last_time.is_not_a_date_time())
713  m_zone_vector.push_back(zone_no_dst(max_year, last_info));
714  else if (last_time.date().year() < parser.last_year)
715  m_zone_vector.push_back(zone_no_dst(last_time.date().year(), last_info));
716 }
717 
718 bool
719 TimeZoneProvider::construct(const std::string& tzname)
720 {
721  try
722  {
723  parse_file(tzname);
724  }
725  catch(const std::invalid_argument& err)
726  {
727  try
728  {
729  TZ_Ptr zone(new PTZ(tzname));
730  m_zone_vector.push_back(std::make_pair(max_year, zone));
731  }
732  catch(std::exception& err)
733  {
734  return false;
735  }
736  }
737  return true;
738 }
739 
740 TimeZoneProvider::TimeZoneProvider(const std::string& tzname) : m_zone_vector {}
741 {
742  if(construct(tzname))
743  return;
744  DEBUG("%s invalid, trying TZ environment variable.\n", tzname.c_str());
745  const char* tz_env = getenv("TZ");
746  if(tz_env && construct(tz_env))
747  return;
748  DEBUG("No valid $TZ, resorting to /etc/localtime.\n");
749  try
750  {
751  parse_file("/etc/localtime");
752  }
753  catch(const std::invalid_argument& env)
754  {
755  DEBUG("/etc/localtime invalid, resorting to GMT.");
756  TZ_Ptr zone(new PTZ("UTC0"));
757  m_zone_vector.push_back(std::make_pair(max_year, zone));
758  }
759 }
760 #endif
761 
762 
763 TZ_Ptr
764 TimeZoneProvider::get(int year) const noexcept
765 {
766  if (m_zone_vector.empty())
767  return TZ_Ptr(new PTZ("UTC0"));
768  auto iter = find_if(m_zone_vector.rbegin(), m_zone_vector.rend(),
769  [=](TZ_Entry e) { return e.first <= year; });
770  if (iter == m_zone_vector.rend())
771  return m_zone_vector.front().second;
772  return iter->second;
773 }
774 
775 void
776 TimeZoneProvider::dump() const noexcept
777 {
778  for (const auto& zone : m_zone_vector)
779  std::cout << zone.first << ": " << zone.second->to_posix_string() << "\n";
780 }
#define DEBUG(format, args...)
Print a debugging message.
Definition: qoflog.h:264
#define PWARN(format, args...)
Log a warning.
Definition: qoflog.h:250