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