23 #include "gnc-timezone.hpp" 29 #include <boost/date_time/gregorian/gregorian.hpp> 32 #include <boost/locale/encoding_utf.hpp> 37 static const QofLogModule log_module =
"gnc-timezone";
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;
48 const unsigned int TimeZoneProvider::min_year = 1400;
49 const unsigned int TimeZoneProvider::max_year = 9999;
56 auto memp =
reinterpret_cast<unsigned char*
>(t);
57 std::reverse(memp, memp +
sizeof(T));
69 template<
typename T>
inline std::string to_string(T num);
73 to_string<unsigned int>(
unsigned int num)
75 constexpr
unsigned int numchars =
sizeof num * 3 + 1;
76 char buf [numchars] {};
77 snprintf (buf, numchars,
"%u", num);
78 return std::string(buf);
83 to_string<int>(
int num)
85 constexpr
unsigned int numchars =
sizeof num * 3 + 1;
87 snprintf (buf, numchars,
"%d", num);
88 return std::string(buf);
93 windows_default_tzname (
void)
96 "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation";
97 constexpr
size_t keysize {128};
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)
104 if (RegQueryValueExA (key,
"TimeZoneKeyName",
nullptr,
nullptr,
105 (LPBYTE)key_name, &tz_keysize) != ERROR_SUCCESS)
107 memset (key_name, 0, tz_keysize);
111 return std::string(key_name);
119 SYSTEMTIME StandardDate;
120 SYSTEMTIME DaylightDate;
123 static time_zone_names
124 windows_tz_names (HKEY key)
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.");
136 constexpr
auto d_size =
sizeof (((TIME_ZONE_INFORMATION*)0)->DaylightName);
137 char dlt_name[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.");
143 return time_zone_names (std_name, std_name, dlt_name, dlt_name);
146 #define make_week_num(x) static_cast<boost::date_time::nth_kday_of_month<boost::gregorian::date>::week_num>(x) 149 zone_from_regtzi (
const RegTZI& regtzi, time_zone_names names)
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;
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);
170 if (regtzi.StandardDate.wMonth != 0)
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));
180 catch (boost::gregorian::bad_month& err)
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);
188 return TZ_Ptr(
new time_zone(names, std_off, offsets, dates));
192 TimeZoneProvider::load_windows_dynamic_tz (HKEY key, time_zone_names names)
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.");
204 if (RegQueryValueExA (key,
"LastEntry", NULL, NULL,
205 (LPBYTE) &last, &size) != ERROR_SUCCESS)
206 throw std::invalid_argument (
"No last entry.");
209 for (
unsigned int year = first; year <= last; year++)
211 auto s = to_string(year);
212 auto ystr = s.c_str();
214 size =
sizeof regtzi;
215 auto err_val = RegQueryValueExA (key, ystr, NULL, NULL,
216 (LPBYTE) ®tzi, &size);
217 if (err_val != ERROR_SUCCESS)
221 tz = zone_from_regtzi (regtzi, names);
223 m_zone_vector.push_back (std::make_pair(0, tz));
224 m_zone_vector.push_back (std::make_pair(year, tz));
226 m_zone_vector.push_back (std::make_pair(max_year, tz));
228 catch (std::invalid_argument)
233 catch (std::bad_alloc)
242 TimeZoneProvider::load_windows_classic_tz (HKEY key, time_zone_names names)
245 unsigned long size =
sizeof regtzi;
248 if (RegQueryValueExA (key,
"TZI", NULL, NULL,
249 (LPBYTE) ®tzi, &size) == ERROR_SUCCESS)
251 m_zone_vector.push_back(
252 std::make_pair(max_year, zone_from_regtzi (regtzi, names)));
255 catch (std::bad_alloc)
264 TimeZoneProvider::load_windows_default_tz()
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)));
279 TimeZoneProvider::TimeZoneProvider (
const std::string& identifier) :
283 const std::string reg_key =
284 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\";
286 auto key_name = (identifier.empty() ? windows_default_tzname () :
289 if (key_name.empty())
291 load_windows_default_tz();
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);
299 time_zone_names names {windows_tz_names (key)};
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);
310 throw std::invalid_argument (
"No data for TZ " + key_name);
312 #elif PLATFORM(POSIX) 313 using std::to_string;
317 using boost::posix_time::ptime;
326 uint8_t reserved[15];
327 uint8_t ttisgmtcnt[4];
328 uint8_t ttisstdcnt[4];
356 static std::unique_ptr<char[]>
357 find_tz_file(
const std::string& name)
362 if (
auto tzenv = getenv(
"TZ"))
363 tzname = std::string(std::getenv(
"TZ"));
369 if (tzname[0] ==
':')
370 tzname.erase(tzname.begin());
371 if (tzname[0] ==
'/')
373 ifs.open(tzname, std::ios::in|std::ios::binary|std::ios::ate);
377 const char* tzdir_c = std::getenv(
"TZDIR");
378 std::string tzdir = tzdir_c ? tzdir_c :
"/usr/share/zoneinfo";
380 ifs.open(std::move(tzdir +
"/" + tzname),
381 std::ios::in|std::ios::binary|std::ios::ate);
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);
395 using TZInfoVec = std::vector<TZInfo>;
396 using TZInfoIter = TZInfoVec::iterator;
400 IANAParser(
const std::string& name) : IANAParser(find_tz_file(name)) {}
401 IANAParser(std::unique_ptr<
char[]>);
402 std::vector<Transition>transitions;
407 IANAParser::IANAParser(std::unique_ptr<
char[]>fileblock)
409 unsigned int fb_index = 0;
410 TZHead tzh = *
reinterpret_cast<TZHead*
>(&fileblock[fb_index]);
411 static constexpr
int ttinfo_size = 6;
413 int transition_size = 4;
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'))
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);
433 tzh = *
reinterpret_cast<TZHead*
>(&fileblock[fb_index]);
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)));
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)
445 fb_index = start_index + index * transition_size;
446 auto info_index = info_index_zero + index;
447 if (transition_size == 4)
449 int32_t transition_time;
451 memcpy(&transition_time,
452 endian_swap(reinterpret_cast<int32_t*>(&fileblock[fb_index])),
454 auto info =
static_cast<uint8_t
>(fileblock[info_index]);
455 transitions.push_back({transition_time, info});
459 int64_t transition_time;
461 memcpy(&transition_time,
462 endian_swap(reinterpret_cast<int64_t*>(&fileblock[fb_index])),
464 auto info =
static_cast<uint8_t
>(fileblock[info_index]);
465 transitions.push_back({transition_time, info});
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)
476 fb_index = start_index + index * ttinfo_size;
479 memcpy(&info, &fileblock[fb_index], ttinfo_size);
480 endian_swap(&info.gmtoff);
482 {info, &fileblock[abbrev + info.abbrind],
483 fileblock[std_dist + index] !=
'\0',
484 fileblock[gmt_dist + index] !=
'\0'});
492 using gregorian_date = boost::gregorian::date;
493 using IANAParser::TZInfoIter;
494 using ndate = boost::gregorian::nth_day_of_the_week_in_month;
496 boost::date_time::nth_kday_of_month<boost::gregorian::date>::week_num;
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;
504 boost::gregorian::greg_month month;
505 boost::gregorian::greg_weekday dow;
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))
515 Transition::operator==(
const Transition& rhs)
const noexcept
517 return (month == rhs.month && dow == rhs.dow && week == rhs.week);
523 return ndate(week, dow, month);
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;
535 duration to_std_time;
536 duration to_dst_time;
541 DSTRule::DSTRule() : to_std(), to_dst(), to_std_time {}, to_dst_time {},
542 std_info (), dst_info () {};
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)
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)
554 std::swap(to_std, to_dst);
555 std::swap(to_std_time, to_dst_time);
556 std::swap(std_info, dst_info);
581 to_dst_time += boost::posix_time::seconds(std_info->info.gmtoff);
583 to_std_time += boost::posix_time::seconds(std_info->info.gmtoff);
585 to_std_time += boost::posix_time::seconds(dst_info->info.gmtoff);
590 DSTRule::operator==(
const DSTRule& rhs)
const noexcept
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);
601 DSTRule::operator!=(
const DSTRule& rhs)
const noexcept
603 return ! operator==(rhs);
608 zone_no_dst(
int year, IANAParser::TZInfoIter std_info)
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);
619 zone_from_rule(
int year, DSTRule::DSTRule rule)
621 using boost::gregorian::partial_date;
622 using boost::local_time::partial_date_dst_rule;
624 boost::local_time::nth_day_of_the_week_in_month_dst_rule;
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);
638 TimeZoneProvider::parse_file(
const std::string& tzname)
640 IANAParser::IANAParser parser(tzname);
641 using boost::posix_time::hours;
642 const auto one_year = hours(366 * 24);
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)
654 auto this_info = parser.tzinfo.begin() + txi->index;
657 auto this_time = ptime(date(1970, 1, 1),
658 time_duration(txi->timestamp / 3600, 0,
659 txi->timestamp % 3600));
666 auto this_year = this_time.date().year();
668 if (last_time.is_not_a_date_time())
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));
674 else if (last_info->info.isdst == this_info->info.isdst)
676 m_zone_vector.push_back(zone_no_dst(this_year, this_info));
682 else if (this_time - last_time > one_year)
684 auto year = last_time.date().year();
685 if (m_zone_vector.back().first == year)
687 m_zone_vector.push_back(zone_no_dst(year, last_info));
693 else if (!this_info->info.isdst)
695 DSTRule::DSTRule new_rule(last_info, this_info,
696 last_time, this_time);
697 if (new_rule != last_rule)
699 last_rule = new_rule;
700 auto year = last_time.date().year();
701 m_zone_vector.push_back(zone_from_rule(year, new_rule));
705 catch(
const boost::gregorian::bad_year& err)
709 last_time = this_time;
710 last_info = this_info;
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));
722 TimeZoneProvider::construct(
const std::string& tzname)
728 catch(
const std::invalid_argument& err)
732 TZ_Ptr zone(
new PTZ(tzname));
733 m_zone_vector.push_back(std::make_pair(max_year, zone));
735 catch(std::exception& err)
743 TimeZoneProvider::TimeZoneProvider(
const std::string& tzname) : m_zone_vector {}
745 if(construct(tzname))
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))
751 DEBUG(
"No valid $TZ, resorting to /etc/localtime.\n");
754 parse_file(
"/etc/localtime");
756 catch(
const std::invalid_argument& env)
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));
767 TimeZoneProvider::get(
int year)
const noexcept
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;
779 TimeZoneProvider::dump() const noexcept
781 for (
auto zone : m_zone_vector)
782 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.