23 #include "gnc-timezone.hpp" 29 #include <boost/date_time/gregorian/gregorian.hpp> 32 #include <boost/locale/encoding_utf.hpp> 35 static const QofLogModule log_module =
"gnc-timezone";
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;
45 const unsigned int TimeZoneProvider::min_year = 1400;
46 const unsigned int TimeZoneProvider::max_year = 9999;
53 auto memp =
reinterpret_cast<unsigned char*
>(t);
54 std::reverse(memp, memp +
sizeof(T));
66 template<
typename T>
inline std::string to_string(T num);
70 to_string<unsigned int>(
unsigned int num)
72 constexpr
unsigned int numchars =
sizeof num * 3 + 1;
73 char buf [numchars] {};
74 snprintf (buf, numchars,
"%u", num);
75 return std::string(buf);
80 to_string<int>(
int num)
82 constexpr
unsigned int numchars =
sizeof num * 3 + 1;
84 snprintf (buf, numchars,
"%d", num);
85 return std::string(buf);
90 windows_default_tzname (
void)
93 "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation";
94 constexpr
size_t keysize {128};
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)
101 if (RegQueryValueExA (key,
"TimeZoneKeyName",
nullptr,
nullptr,
102 (LPBYTE)key_name, &tz_keysize) != ERROR_SUCCESS)
104 memset (key_name, 0, tz_keysize);
108 return std::string(key_name);
116 SYSTEMTIME StandardDate;
117 SYSTEMTIME DaylightDate;
120 static time_zone_names
121 windows_tz_names (HKEY key)
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.");
133 constexpr
auto d_size =
sizeof (((TIME_ZONE_INFORMATION*)0)->DaylightName);
134 char dlt_name[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.");
140 return time_zone_names (std_name, std_name, dlt_name, dlt_name);
143 #define make_week_num(x) static_cast<boost::date_time::nth_kday_of_month<boost::gregorian::date>::week_num>(x) 146 zone_from_regtzi (
const RegTZI& regtzi, time_zone_names names)
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;
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);
167 if (regtzi.StandardDate.wMonth != 0)
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));
177 catch (boost::gregorian::bad_month& err)
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);
185 return TZ_Ptr(
new time_zone(names, std_off, offsets, dates));
189 TimeZoneProvider::load_windows_dynamic_tz (HKEY key, time_zone_names names)
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.");
201 if (RegQueryValueExA (key,
"LastEntry", NULL, NULL,
202 (LPBYTE) &last, &size) != ERROR_SUCCESS)
203 throw std::invalid_argument (
"No last entry.");
206 for (
unsigned int year = first; year <= last; year++)
208 auto s = to_string(year);
209 auto ystr = s.c_str();
211 size =
sizeof regtzi;
212 auto err_val = RegQueryValueExA (key, ystr, NULL, NULL,
213 (LPBYTE) ®tzi, &size);
214 if (err_val != ERROR_SUCCESS)
218 tz = zone_from_regtzi (regtzi, names);
220 m_zone_vector.push_back (std::make_pair(0, tz));
221 m_zone_vector.push_back (std::make_pair(year, tz));
223 m_zone_vector.push_back (std::make_pair(max_year, tz));
225 catch (std::invalid_argument)
230 catch (std::bad_alloc)
239 TimeZoneProvider::load_windows_classic_tz (HKEY key, time_zone_names names)
242 unsigned long size =
sizeof regtzi;
245 if (RegQueryValueExA (key,
"TZI", NULL, NULL,
246 (LPBYTE) ®tzi, &size) == ERROR_SUCCESS)
248 m_zone_vector.push_back(
249 std::make_pair(max_year, zone_from_regtzi (regtzi, names)));
252 catch (std::bad_alloc)
261 TimeZoneProvider::load_windows_default_tz()
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)));
276 TimeZoneProvider::TimeZoneProvider (
const std::string& identifier) :
280 const std::string reg_key =
281 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\";
283 auto key_name = (identifier.empty() ? windows_default_tzname () :
286 if (key_name.empty())
288 load_windows_default_tz();
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);
296 time_zone_names names {windows_tz_names (key)};
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);
307 throw std::invalid_argument (
"No data for TZ " + key_name);
309 #elif PLATFORM(POSIX) 310 using std::to_string;
314 using boost::posix_time::ptime;
323 uint8_t reserved[15];
324 uint8_t ttisgmtcnt[4];
325 uint8_t ttisstdcnt[4];
353 static std::unique_ptr<char[]>
354 find_tz_file(
const std::string& name)
359 if (
auto tzenv = std::getenv(
"TZ"))
360 tzname = std::string(tzenv);
366 if (tzname[0] ==
':')
367 tzname.erase(tzname.begin());
368 if (tzname[0] ==
'/')
370 ifs.open(tzname, std::ios::in|std::ios::binary|std::ios::ate);
374 const char* tzdir_c = std::getenv(
"TZDIR");
375 std::string tzdir = tzdir_c ? tzdir_c :
"/usr/share/zoneinfo";
377 ifs.open(std::move(tzdir +
"/" + tzname),
378 std::ios::in|std::ios::binary|std::ios::ate);
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);
392 using TZInfoVec = std::vector<TZInfo>;
393 using TZInfoIter = TZInfoVec::iterator;
397 IANAParser(
const std::string& name) : IANAParser(find_tz_file(name)) {}
398 IANAParser(std::unique_ptr<
char[]>);
399 std::vector<Transition>transitions;
404 IANAParser::IANAParser(std::unique_ptr<
char[]>fileblock)
406 unsigned int fb_index = 0;
407 TZHead tzh = *
reinterpret_cast<TZHead*
>(&fileblock[fb_index]);
408 static constexpr
int ttinfo_size = 6;
410 int transition_size = 4;
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'))
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);
430 tzh = *
reinterpret_cast<TZHead*
>(&fileblock[fb_index]);
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)));
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)
442 fb_index = start_index + index * transition_size;
443 auto info_index = info_index_zero + index;
444 if (transition_size == 4)
446 int32_t transition_time;
448 memcpy(&transition_time,
449 endian_swap(reinterpret_cast<int32_t*>(&fileblock[fb_index])),
451 auto info =
static_cast<uint8_t
>(fileblock[info_index]);
452 transitions.push_back({transition_time, info});
456 int64_t transition_time;
458 memcpy(&transition_time,
459 endian_swap(reinterpret_cast<int64_t*>(&fileblock[fb_index])),
461 auto info =
static_cast<uint8_t
>(fileblock[info_index]);
462 transitions.push_back({transition_time, info});
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)
473 fb_index = start_index + index * ttinfo_size;
476 memcpy(&info, &fileblock[fb_index], ttinfo_size);
477 endian_swap(&info.gmtoff);
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)});
489 using gregorian_date = boost::gregorian::date;
490 using IANAParser::TZInfoIter;
491 using ndate = boost::gregorian::nth_day_of_the_week_in_month;
493 boost::date_time::nth_kday_of_month<boost::gregorian::date>::week_num;
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;
501 boost::gregorian::greg_month month;
502 boost::gregorian::greg_weekday dow;
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))
512 Transition::operator==(
const Transition& rhs)
const noexcept
514 return (month == rhs.month && dow == rhs.dow && week == rhs.week);
520 return ndate(week, dow, month);
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;
532 duration to_std_time;
533 duration to_dst_time;
538 DSTRule::DSTRule() : to_std(), to_dst(), to_std_time {}, to_dst_time {},
539 std_info (), dst_info () {};
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)
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)
551 std::swap(to_std, to_dst);
552 std::swap(to_std_time, to_dst_time);
553 std::swap(std_info, dst_info);
578 to_dst_time += boost::posix_time::seconds(std_info->info.gmtoff);
580 to_std_time += boost::posix_time::seconds(std_info->info.gmtoff);
582 to_std_time += boost::posix_time::seconds(dst_info->info.gmtoff);
587 DSTRule::operator==(
const DSTRule& rhs)
const noexcept
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);
598 DSTRule::operator!=(
const DSTRule& rhs)
const noexcept
600 return ! operator==(rhs);
605 zone_no_dst(
int year, IANAParser::TZInfoIter std_info)
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);
616 zone_from_rule(
int year, DSTRule::DSTRule rule)
618 using boost::gregorian::partial_date;
619 using boost::local_time::partial_date_dst_rule;
621 boost::local_time::nth_day_of_the_week_in_month_dst_rule;
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);
635 TimeZoneProvider::parse_file(
const std::string& tzname)
637 IANAParser::IANAParser parser(tzname);
638 using boost::posix_time::hours;
639 const auto one_year = hours(366 * 24);
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)
651 auto this_info = parser.tzinfo.begin() + txi->index;
654 auto this_time = ptime(date(1970, 1, 1),
655 time_duration(txi->timestamp / 3600, 0,
656 txi->timestamp % 3600));
663 auto this_year = this_time.date().year();
665 if (last_time.is_not_a_date_time())
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));
671 else if (last_info->info.isdst == this_info->info.isdst)
673 m_zone_vector.push_back(zone_no_dst(this_year, this_info));
679 else if (this_time - last_time > one_year)
681 auto year = last_time.date().year();
682 if (m_zone_vector.back().first == year)
684 m_zone_vector.push_back(zone_no_dst(year, last_info));
690 else if (!this_info->info.isdst)
692 DSTRule::DSTRule new_rule(last_info, this_info,
693 last_time, this_time);
694 if (new_rule != last_rule)
696 last_rule = new_rule;
697 auto year = last_time.date().year();
698 m_zone_vector.push_back(zone_from_rule(year, new_rule));
702 catch(
const boost::gregorian::bad_year& err)
706 last_time = this_time;
707 last_info = this_info;
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));
719 TimeZoneProvider::construct(
const std::string& tzname)
725 catch(
const std::invalid_argument& err)
729 TZ_Ptr zone(
new PTZ(tzname));
730 m_zone_vector.push_back(std::make_pair(max_year, zone));
732 catch(std::exception& err)
740 TimeZoneProvider::TimeZoneProvider(
const std::string& tzname) : m_zone_vector {}
742 if(construct(tzname))
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))
748 DEBUG(
"No valid $TZ, resorting to /etc/localtime.\n");
751 parse_file(
"/etc/localtime");
753 catch(
const std::invalid_argument& env)
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));
764 TimeZoneProvider::get(
int year)
const noexcept
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;
776 TimeZoneProvider::dump() const noexcept
778 for (
const auto& zone : m_zone_vector)
779 std::cout << zone.first <<
": " << zone.second->to_posix_string() <<
"\n";
#define DEBUG(format, args...)
Print a debugging message.
#define PWARN(format, args...)
Log a warning.