Path: blob/main/contrib/llvm-project/libcxx/src/experimental/time_zone.cpp
35231 views
//===----------------------------------------------------------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78// For information see https://libcxx.llvm.org/DesignDocs/TimeZone.html910// TODO TZDB look at optimizations11//12// The current algorithm is correct but not efficient. For example, in a named13// rule based continuation finding the next rule does quite a bit of work,14// returns the next rule and "forgets" its state. This could be better.15//16// It would be possible to cache lookups. If a time for a zone is calculated its17// sys_info could be kept and the next lookup could test whether the time is in18// a "known" sys_info. The wording in the Standard hints at this slowness by19// "suggesting" this could be implemented on the user's side.2021// TODO TZDB look at removing quirks22//23// The code has some special rules to adjust the timing at the continuation24// switches. This works correctly, but some of the places feel odd. It would be25// good to investigate this further and see whether all quirks are needed or26// that there are better fixes.27//28// These quirks often use a 12h interval; this is the scan interval of zdump,29// which implies there are no sys_info objects with a duration of less than 12h.3031#include <algorithm>32#include <cctype>33#include <chrono>34#include <expected>35#include <map>36#include <numeric>37#include <ranges>3839#include "include/tzdb/time_zone_private.h"40#include "include/tzdb/tzdb_list_private.h"4142// TODO TZDB remove debug printing43#ifdef PRINT44# include <print>45#endif4647_LIBCPP_BEGIN_NAMESPACE_STD4849#ifdef PRINT50template <>51struct formatter<chrono::sys_info, char> {52template <class ParseContext>53constexpr typename ParseContext::iterator parse(ParseContext& ctx) {54return ctx.begin();55}5657template <class FormatContext>58typename FormatContext::iterator format(const chrono::sys_info& info, FormatContext& ctx) const {59return std::format_to(60ctx.out(), "[{}, {}) {:%Q%q} {:%Q%q} {}", info.begin, info.end, info.offset, info.save, info.abbrev);61}62};63#endif6465namespace chrono {6667//===----------------------------------------------------------------------===//68// Details69//===----------------------------------------------------------------------===//7071struct __sys_info {72sys_info __info;73bool __can_merge; // Can the returned sys_info object be merged with74};7576// Return type for helper function to get a sys_info.77// - The expected result returns the "best" sys_info object. This object can be78// before the requested time. Sometimes sys_info objects from different79// continuations share their offset, save, and abbrev and these objects are80// merged to one sys_info object. The __can_merge flag determines whether the81// current result can be merged with the next result.82// - The unexpected result means no sys_info object was found and the time is83// the time to be used for the next search iteration.84using __sys_info_result = expected<__sys_info, sys_seconds>;8586template <ranges::forward_range _Range,87class _Type,88class _Proj = identity,89indirect_strict_weak_order<const _Type*, projected<ranges::iterator_t<_Range>, _Proj>> _Comp = ranges::less>90[[nodiscard]] static ranges::borrowed_iterator_t<_Range>91__binary_find(_Range&& __r, const _Type& __value, _Comp __comp = {}, _Proj __proj = {}) {92auto __end = ranges::end(__r);93auto __ret = ranges::lower_bound(ranges::begin(__r), __end, __value, __comp, __proj);94if (__ret == __end)95return __end;9697// When the value does not match the predicate it's equal and a valid result98// was found.99return !std::invoke(__comp, __value, std::invoke(__proj, *__ret)) ? __ret : __end;100}101102// Format based on https://data.iana.org/time-zones/tz-how-to.html103//104// 1 a time zone abbreviation that is a string of three or more characters that105// are either ASCII alphanumerics, "+", or "-"106// 2 the string "%z", in which case the "%z" will be replaced by a numeric time107// zone abbreviation108// 3 a pair of time zone abbreviations separated by a slash ('/'), in which109// case the first string is the abbreviation for the standard time name and110// the second string is the abbreviation for the daylight saving time name111// 4 a string containing "%s", in which case the "%s" will be replaced by the112// text in the appropriate Rule's LETTER column, and the resulting string113// should be a time zone abbreviation114//115// Rule 1 is not strictly validated since America/Barbados uses a two letter116// abbreviation AT.117[[nodiscard]] static string118__format(const __tz::__continuation& __continuation, const string& __letters, seconds __save) {119bool __shift = false;120string __result;121for (char __c : __continuation.__format) {122if (__shift) {123switch (__c) {124case 's':125std::ranges::copy(__letters, std::back_inserter(__result));126break;127128case 'z': {129if (__continuation.__format.size() != 2)130std::__throw_runtime_error(131std::format("corrupt tzdb FORMAT field: %z should be the entire contents, instead contains '{}'",132__continuation.__format)133.c_str());134chrono::hh_mm_ss __offset{__continuation.__stdoff + __save};135if (__offset.is_negative()) {136__result += '-';137__offset = chrono::hh_mm_ss{-(__continuation.__stdoff + __save)};138} else139__result += '+';140141if (__offset.minutes() != 0min)142std::format_to(std::back_inserter(__result), "{:%H%M}", __offset);143else144std::format_to(std::back_inserter(__result), "{:%H}", __offset);145} break;146147default:148std::__throw_runtime_error(149std::format("corrupt tzdb FORMAT field: invalid sequence '%{}' found, expected %s or %z", __c).c_str());150}151__shift = false;152153} else if (__c == '/') {154if (__save != 0s)155__result.clear();156else157break;158159} else if (__c == '%') {160__shift = true;161} else if (__c == '+' || __c == '-' || std::isalnum(__c)) {162__result.push_back(__c);163} else {164std::__throw_runtime_error(165std::format(166"corrupt tzdb FORMAT field: invalid character '{}' found, expected +, -, or an alphanumeric value", __c)167.c_str());168}169}170171if (__shift)172std::__throw_runtime_error("corrupt tzdb FORMAT field: input ended with the start of the escape sequence '%'");173174if (__result.empty())175std::__throw_runtime_error("corrupt tzdb FORMAT field: result is empty");176177return __result;178}179180[[nodiscard]] static sys_seconds __to_sys_seconds(year_month_day __ymd, seconds __seconds) {181seconds __result = static_cast<sys_days>(__ymd).time_since_epoch() + __seconds;182return sys_seconds{__result};183}184185[[nodiscard]] static seconds __at_to_sys_seconds(const __tz::__continuation& __continuation) {186switch (__continuation.__at.__clock) {187case __tz::__clock::__local:188return __continuation.__at.__time - __continuation.__stdoff -189std::visit(190[](const auto& __value) {191using _Tp = decay_t<decltype(__value)>;192if constexpr (same_as<_Tp, monostate>)193return chrono::seconds{0};194else if constexpr (same_as<_Tp, __tz::__save>)195return chrono::duration_cast<seconds>(__value.__time);196else if constexpr (same_as<_Tp, std::string>)197// For a named rule based continuation the SAVE depends on the RULE198// active at the end. This should be determined separately.199return chrono::seconds{0};200else201static_assert(sizeof(_Tp) == 0); // TODO TZDB static_assert(false); after droping clang-16 support202203std::__libcpp_unreachable();204},205__continuation.__rules);206207case __tz::__clock::__universal:208return __continuation.__at.__time;209210case __tz::__clock::__standard:211return __continuation.__at.__time - __continuation.__stdoff;212}213std::__libcpp_unreachable();214}215216[[nodiscard]] static year_month_day __to_year_month_day(year __year, month __month, __tz::__on __on) {217return std::visit(218[&](const auto& __value) {219using _Tp = decay_t<decltype(__value)>;220if constexpr (same_as<_Tp, chrono::day>)221return year_month_day{__year, __month, __value};222else if constexpr (same_as<_Tp, weekday_last>)223return year_month_day{static_cast<sys_days>(year_month_weekday_last{__year, __month, __value})};224else if constexpr (same_as<_Tp, __tz::__constrained_weekday>)225return __value(__year, __month);226else227static_assert(sizeof(_Tp) == 0); // TODO TZDB static_assert(false); after droping clang-16 support228229std::__libcpp_unreachable();230},231__on);232}233234[[nodiscard]] static sys_seconds __until_to_sys_seconds(const __tz::__continuation& __continuation) {235// Does UNTIL contain the magic value for the last continuation?236if (__continuation.__year == chrono::year::min())237return sys_seconds::max();238239year_month_day __ymd = chrono::__to_year_month_day(__continuation.__year, __continuation.__in, __continuation.__on);240return chrono::__to_sys_seconds(__ymd, chrono::__at_to_sys_seconds(__continuation));241}242243// Holds the UNTIL time for a continuation with a named rule.244//245// Unlike continuations with an fixed SAVE named rules have a variable SAVE.246// This means when the UNTIL uses the local wall time the actual UNTIL value can247// only be determined when the SAVE is known. This class holds that abstraction.248class __named_rule_until {249public:250explicit __named_rule_until(const __tz::__continuation& __continuation)251: __until_{chrono::__until_to_sys_seconds(__continuation)},252__needs_adjustment_{253// The last continuation of a ZONE has no UNTIL which basically is254// until the end of _local_ time. This value is expressed by255// sys_seconds::max(). Subtracting the SAVE leaves large value.256// However SAVE can be negative, which would add a value to maximum257// leading to undefined behaviour. In practice this often results in258// an overflow to a very small value.259__until_ != sys_seconds::max() && __continuation.__at.__clock == __tz::__clock::__local} {}260261// Gives the unadjusted until value, this is useful when the SAVE is not known262// at all.263sys_seconds __until() const noexcept { return __until_; }264265bool __needs_adjustment() const noexcept { return __needs_adjustment_; }266267// Returns the UNTIL adjusted for SAVE.268sys_seconds operator()(seconds __save) const noexcept { return __until_ - __needs_adjustment_ * __save; }269270private:271sys_seconds __until_;272bool __needs_adjustment_;273};274275[[nodiscard]] static seconds __at_to_seconds(seconds __stdoff, const __tz::__rule& __rule) {276switch (__rule.__at.__clock) {277case __tz::__clock::__local:278// Local time and standard time behave the same. This is not279// correct. Local time needs to adjust for the current saved time.280// To know the saved time the rules need to be known and sorted.281// This needs a time so to avoid the chicken and egg adjust the282// saving of the local time later.283return __rule.__at.__time - __stdoff;284285case __tz::__clock::__universal:286return __rule.__at.__time;287288case __tz::__clock::__standard:289return __rule.__at.__time - __stdoff;290}291std::__libcpp_unreachable();292}293294[[nodiscard]] static sys_seconds __from_to_sys_seconds(seconds __stdoff, const __tz::__rule& __rule, year __year) {295year_month_day __ymd = chrono::__to_year_month_day(__year, __rule.__in, __rule.__on);296297seconds __at = chrono::__at_to_seconds(__stdoff, __rule);298return chrono::__to_sys_seconds(__ymd, __at);299}300301[[nodiscard]] static sys_seconds __from_to_sys_seconds(seconds __stdoff, const __tz::__rule& __rule) {302return chrono::__from_to_sys_seconds(__stdoff, __rule, __rule.__from);303}304305[[nodiscard]] static const vector<__tz::__rule>&306__get_rules(const __tz::__rules_storage_type& __rules_db, const string& __rule_name) {307auto __result = chrono::__binary_find(__rules_db, __rule_name, {}, [](const auto& __p) { return __p.first; });308if (__result == std::end(__rules_db))309std::__throw_runtime_error(("corrupt tzdb: rule '" + __rule_name + " 'does not exist").c_str());310311return __result->second;312}313314// Returns the letters field for a time before the first rule.315//316// Per https://data.iana.org/time-zones/tz-how-to.html317// One wrinkle, not fully explained in zic.8.txt, is what happens when switching318// to a named rule. To what values should the SAVE and LETTER data be319// initialized?320//321// 1 If at least one transition has happened, use the SAVE and LETTER data from322// the most recent.323// 2 If switching to a named rule before any transition has happened, assume324// standard time (SAVE zero), and use the LETTER data from the earliest325// transition with a SAVE of zero.326//327// This function implements case 2.328[[nodiscard]] static string __letters_before_first_rule(const vector<__tz::__rule>& __rules) {329auto __letters =330__rules //331| views::filter([](const __tz::__rule& __rule) { return __rule.__save.__time == 0s; }) //332| views::transform([](const __tz::__rule& __rule) { return __rule.__letters; }) //333| views::take(1);334335if (__letters.empty())336std::__throw_runtime_error("corrupt tzdb: rule has zero entries");337338return __letters.front();339}340341// Determines the information based on the continuation and the rules.342//343// There are several special cases to take into account344//345// === Entries before the first rule becomes active ===346// Asia/Hong_Kong347// 9 - JST 1945 N 18 2 // (1)348// 8 HK HK%sT // (2)349// R HK 1946 o - Ap 21 0 1 S // (3)350// There (1) is active until Novemer 18th 1945 at 02:00, after this time351// (2) becomes active. The first rule entry for HK (3) becomes active352// from April 21st 1945 at 01:00. In the period between (2) is active.353// This entry has an offset.354// This entry has no save, letters, or dst flag. So in the period355// after (1) and until (3) no rule entry is associated with the time.356357[[nodiscard]] static sys_info __get_sys_info_before_first_rule(358sys_seconds __begin,359sys_seconds __end,360const __tz::__continuation& __continuation,361const vector<__tz::__rule>& __rules) {362return sys_info{363__begin,364__end,365__continuation.__stdoff,366chrono::minutes(0),367chrono::__format(__continuation, __letters_before_first_rule(__rules), 0s)};368}369370// Returns the sys_info object for a time before the first rule.371// When this first rule has a SAVE of 0s the sys_info for the time before the372// first rule and for the first rule are identical and will be merged.373[[nodiscard]] static sys_info __get_sys_info_before_first_rule(374sys_seconds __begin,375sys_seconds __rule_end, // The end used when SAVE != 0s376sys_seconds __next_end, // The end used when SAVE == 0s the times are merged377const __tz::__continuation& __continuation,378const vector<__tz::__rule>& __rules,379vector<__tz::__rule>::const_iterator __rule) {380if (__rule->__save.__time != 0s)381return __get_sys_info_before_first_rule(__begin, __rule_end, __continuation, __rules);382383return sys_info{384__begin, __next_end, __continuation.__stdoff, 0min, chrono::__format(__continuation, __rule->__letters, 0s)};385}386387[[nodiscard]] static seconds __at_to_seconds(seconds __stdoff, seconds __save, const __tz::__rule& __rule) {388switch (__rule.__at.__clock) {389case __tz::__clock::__local:390return __rule.__at.__time - __stdoff - __save;391392case __tz::__clock::__universal:393return __rule.__at.__time;394395case __tz::__clock::__standard:396return __rule.__at.__time - __stdoff;397}398std::__libcpp_unreachable();399}400401[[nodiscard]] static sys_seconds402__rule_to_sys_seconds(seconds __stdoff, seconds __save, const __tz::__rule& __rule, year __year) {403year_month_day __ymd = chrono::__to_year_month_day(__year, __rule.__in, __rule.__on);404405seconds __at = chrono::__at_to_seconds(__stdoff, __save, __rule);406return chrono::__to_sys_seconds(__ymd, __at);407}408409// Returns the first rule after __time.410// Note that a rule can be "active" in multiple years, this may result in an411// infinite loop where the same rule is returned every time, use __current to412// guard against that.413//414// When no next rule exists the returned time will be sys_seconds::max(). This415// can happen in practice. For example,416//417// R So 1945 o - May 24 2 2 M418// R So 1945 o - S 24 3 1 S419// R So 1945 o - N 18 2s 0 -420//421// Has 3 rules that are all only active in 1945.422[[nodiscard]] static pair<sys_seconds, vector<__tz::__rule>::const_iterator>423__next_rule(sys_seconds __time,424seconds __stdoff,425seconds __save,426const vector<__tz::__rule>& __rules,427vector<__tz::__rule>::const_iterator __current) {428year __year = year_month_day{chrono::floor<days>(__time)}.year();429430// Note it would probably be better to store the pairs in a vector and then431// use min() to get the smallest element432map<sys_seconds, vector<__tz::__rule>::const_iterator> __candidates;433// Note this evaluates all rules which is a waste of effort; when the entries434// are beyond the current year's "next year" (where "next year" is not always435// year + 1) the algorithm should end.436for (auto __it = __rules.begin(); __it != __rules.end(); ++__it) {437for (year __y = __it->__from; __y <= __it->__to; ++__y) {438// Adding the current entry for the current year may lead to infinite439// loops due to the SAVE adjustment. Skip these entries.440if (__y == __year && __it == __current)441continue;442443sys_seconds __t = chrono::__rule_to_sys_seconds(__stdoff, __save, *__it, __y);444if (__t <= __time)445continue;446447_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(!__candidates.contains(__t), "duplicated rule");448__candidates[__t] = __it;449break;450}451}452453if (!__candidates.empty()) [[likely]] {454auto __it = __candidates.begin();455456// When no rule is selected the time before the first rule and the first rule457// should not be merged.458if (__time == sys_seconds::min())459return *__it;460461// There can be two constitutive rules that are the same. For example,462// Hong Kong463//464// R HK 1973 o - D 30 3:30 1 S (R1)465// R HK 1965 1976 - Ap Su>=16 3:30 1 S (R2)466//467// 1973-12-29 19:30:00 R1 becomes active.468// 1974-04-20 18:30:00 R2 becomes active.469// Both rules have a SAVE of 1 hour and LETTERS are S for both of them.470while (__it != __candidates.end()) {471if (__current->__save.__time != __it->second->__save.__time || __current->__letters != __it->second->__letters)472return *__it;473474++__it;475}476}477478return {sys_seconds::max(), __rules.end()};479}480481// Returns the first rule of a set of rules.482// This is not always the first of the listed rules. For example483// R Sa 2008 2009 - Mar Su>=8 0 0 -484// R Sa 2007 2008 - O Su>=8 0 1 -485// The transition in October 2007 happens before the transition in March 2008.486[[nodiscard]] static vector<__tz::__rule>::const_iterator487__first_rule(seconds __stdoff, const vector<__tz::__rule>& __rules) {488return chrono::__next_rule(sys_seconds::min(), __stdoff, 0s, __rules, __rules.end()).second;489}490491[[nodiscard]] static __sys_info_result __get_sys_info_rule(492sys_seconds __time,493sys_seconds __continuation_begin,494const __tz::__continuation& __continuation,495const vector<__tz::__rule>& __rules) {496auto __rule = chrono::__first_rule(__continuation.__stdoff, __rules);497_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(__rule != __rules.end(), "the set of rules has no first rule");498499// Avoid selecting a time before the start of the continuation500__time = std::max(__time, __continuation_begin);501502sys_seconds __rule_begin = chrono::__from_to_sys_seconds(__continuation.__stdoff, *__rule);503504// The time sought is very likely inside the current rule.505// When the continuation's UNTIL uses the local clock there are edge cases506// where this is not true.507//508// Start to walk the rules to find the proper one.509//510// For now we just walk all the rules TODO TZDB investigate whether a smarter511// algorithm would work.512auto __next = chrono::__next_rule(__rule_begin, __continuation.__stdoff, __rule->__save.__time, __rules, __rule);513514// Ignore small steps, this happens with America/Punta_Arenas for the515// transition516// -4:42:46 - SMT 1927 S517// -5 x -05/-04 1932 S518// ...519//520// R x 1927 1931 - S 1 0 1 -521// R x 1928 1932 - Ap 1 0 0 -522//523// America/Punta_Arenas Thu Sep 1 04:42:45 1927 UT = Thu Sep 1 00:42:45 1927 -04 isdst=1 gmtoff=-14400524// America/Punta_Arenas Sun Apr 1 03:59:59 1928 UT = Sat Mar 31 23:59:59 1928 -04 isdst=1 gmtoff=-14400525// America/Punta_Arenas Sun Apr 1 04:00:00 1928 UT = Sat Mar 31 23:00:00 1928 -05 isdst=0 gmtoff=-18000526//527// Without this there will be a transition528// [1927-09-01 04:42:45, 1927-09-01 05:00:00) -05:00:00 0min -05529530if (sys_seconds __begin = __rule->__save.__time != 0s ? __rule_begin : __next.first; __time < __begin) {531if (__continuation_begin == sys_seconds::min() || __begin - __continuation_begin > 12h)532return __sys_info{__get_sys_info_before_first_rule(533__continuation_begin, __rule_begin, __next.first, __continuation, __rules, __rule),534false};535536// Europe/Berlin537// 1 c CE%sT 1945 May 24 2 (C1)538// 1 So CE%sT 1946 (C2)539//540// R c 1944 1945 - Ap M>=1 2s 1 S (R1)541//542// R So 1945 o - May 24 2 2 M (R2)543//544// When C2 becomes active the time would be before the first rule R2,545// giving a 1 hour sys_info.546seconds __save = __rule->__save.__time;547__named_rule_until __continuation_end{__continuation};548sys_seconds __sys_info_end = std::min(__continuation_end(__save), __next.first);549550return __sys_info{551sys_info{__continuation_begin,552__sys_info_end,553__continuation.__stdoff + __save,554chrono::duration_cast<minutes>(__save),555chrono::__format(__continuation, __rule->__letters, __save)},556__sys_info_end == __continuation_end(__save)};557}558559// See above for America/Asuncion560if (__rule->__save.__time == 0s && __time < __next.first) {561return __sys_info{562sys_info{__continuation_begin,563__next.first,564__continuation.__stdoff,5650min,566chrono::__format(__continuation, __rule->__letters, 0s)},567false};568}569570if (__rule->__save.__time != 0s) {571// another fix for America/Punta_Arenas when not at the start of the572// sys_info object.573seconds __save = __rule->__save.__time;574if (__continuation_begin >= __rule_begin - __save && __time < __next.first) {575return __sys_info{576sys_info{__continuation_begin,577__next.first,578__continuation.__stdoff + __save,579chrono::duration_cast<minutes>(__save),580chrono::__format(__continuation, __rule->__letters, __save)},581false};582}583}584585__named_rule_until __continuation_end{__continuation};586while (__next.second != __rules.end()) {587#ifdef PRINT588std::print(589stderr,590"Rule for {}: [{}, {}) off={} save={} duration={}\n",591__time,592__rule_begin,593__next.first,594__continuation.__stdoff,595__rule->__save.__time,596__next.first - __rule_begin);597#endif598599sys_seconds __end = __continuation_end(__rule->__save.__time);600601sys_seconds __sys_info_begin = std::max(__continuation_begin, __rule_begin);602sys_seconds __sys_info_end = std::min(__end, __next.first);603seconds __diff = chrono::abs(__sys_info_end - __sys_info_begin);604605if (__diff < 12h) {606// Z America/Argentina/Buenos_Aires -3:53:48 - LMT 1894 O 31607// -4:16:48 - CMT 1920 May608// -4 - -04 1930 D609// -4 A -04/-03 1969 O 5610// -3 A -03/-02 1999 O 3611// -4 A -04/-03 2000 Mar 3612// ...613//614// ...615// R A 1989 1992 - O Su>=15 0 1 -616// R A 1999 o - O Su>=1 0 1 -617// R A 2000 o - Mar 3 0 0 -618// R A 2007 o - D 30 0 1 -619// ...620621// The 1999 switch uses the same rule, but with a different stdoff.622// R A 1999 o - O Su>=1 0 1 -623// stdoff -3 -> 1999-10-03 03:00:00624// stdoff -4 -> 1999-10-03 04:00:00625// This generates an invalid entry and this is evaluated as a transition.626// Looking at the zdump like output in libc++ this generates jumps in627// the UTC time.628629__rule = __next.second;630__next = __next_rule(__next.first, __continuation.__stdoff, __rule->__save.__time, __rules, __rule);631__end = __continuation_end(__rule->__save.__time);632__sys_info_end = std::min(__end, __next.first);633}634635if ((__time >= __rule_begin && __time < __next.first) || __next.first >= __end) {636__sys_info_begin = std::max(__continuation_begin, __rule_begin);637__sys_info_end = std::min(__end, __next.first);638639return __sys_info{640sys_info{__sys_info_begin,641__sys_info_end,642__continuation.__stdoff + __rule->__save.__time,643chrono::duration_cast<minutes>(__rule->__save.__time),644chrono::__format(__continuation, __rule->__letters, __rule->__save.__time)},645__sys_info_end == __end};646}647648__rule_begin = __next.first;649__rule = __next.second;650__next = __next_rule(__rule_begin, __continuation.__stdoff, __rule->__save.__time, __rules, __rule);651}652653return __sys_info{654sys_info{std::max(__continuation_begin, __rule_begin),655__continuation_end(__rule->__save.__time),656__continuation.__stdoff + __rule->__save.__time,657chrono::duration_cast<minutes>(__rule->__save.__time),658chrono::__format(__continuation, __rule->__letters, __rule->__save.__time)},659true};660}661662[[nodiscard]] static __sys_info_result __get_sys_info_basic(663sys_seconds __time, sys_seconds __continuation_begin, const __tz::__continuation& __continuation, seconds __save) {664sys_seconds __continuation_end = chrono::__until_to_sys_seconds(__continuation);665return __sys_info{666sys_info{__continuation_begin,667__continuation_end,668__continuation.__stdoff + __save,669chrono::duration_cast<minutes>(__save),670__continuation.__format},671true};672}673674[[nodiscard]] static __sys_info_result675__get_sys_info(sys_seconds __time,676sys_seconds __continuation_begin,677const __tz::__continuation& __continuation,678const __tz::__rules_storage_type& __rules_db) {679return std::visit(680[&](const auto& __value) {681using _Tp = decay_t<decltype(__value)>;682if constexpr (same_as<_Tp, std::string>)683return chrono::__get_sys_info_rule(684__time, __continuation_begin, __continuation, __get_rules(__rules_db, __value));685else if constexpr (same_as<_Tp, monostate>)686return chrono::__get_sys_info_basic(__time, __continuation_begin, __continuation, chrono::seconds(0));687else if constexpr (same_as<_Tp, __tz::__save>)688return chrono::__get_sys_info_basic(__time, __continuation_begin, __continuation, __value.__time);689else690static_assert(sizeof(_Tp) == 0); // TODO TZDB static_assert(false); after droping clang-16 support691692std::__libcpp_unreachable();693},694__continuation.__rules);695}696697// The transition from one continuation to the next continuation may result in698// two constitutive continuations with the same "offset" information.699// [time.zone.info.sys]/3700// The begin and end data members indicate that, for the associated time_zone701// and time_point, the offset and abbrev are in effect in the range702// [begin, end). This information can be used to efficiently iterate the703// transitions of a time_zone.704//705// Note that this does considers a change in the SAVE field not to be a706// different sys_info, zdump does consider this different.707// LWG XXXX The sys_info range should be affected by save708// matches the behaviour of the Standard and zdump.709//710// Iff the "offsets" are the same '__current.__end' is replaced with711// '__next.__end', which effectively merges the two objects in one object. The712// function returns true if a merge occurred.713[[nodiscard]] bool __merge_continuation(sys_info& __current, const sys_info& __next) {714if (__current.end != __next.begin)715return false;716717if (__current.offset != __next.offset || __current.abbrev != __next.abbrev || __current.save != __next.save)718return false;719720__current.end = __next.end;721return true;722}723724//===----------------------------------------------------------------------===//725// Public API726//===----------------------------------------------------------------------===//727728[[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI time_zone time_zone::__create(unique_ptr<time_zone::__impl>&& __p) {729_LIBCPP_ASSERT_NON_NULL(__p != nullptr, "initialized time_zone without a valid pimpl object");730time_zone result;731result.__impl_ = std::move(__p);732return result;733}734735_LIBCPP_EXPORTED_FROM_ABI time_zone::~time_zone() = default;736737[[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI string_view time_zone::__name() const noexcept { return __impl_->__name(); }738739[[nodiscard]] _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI sys_info740time_zone::__get_info(sys_seconds __time) const {741optional<sys_info> __result;742bool __valid_result = false; // true iff __result.has_value() is true and743// __result.begin <= __time < __result.end is true.744bool __can_merge = false;745sys_seconds __continuation_begin = sys_seconds::min();746// Iterates over the Zone entry and its continuations. Internally the Zone747// entry is split in a Zone information and the first continuation. The last748// continuation has no UNTIL field. This means the loop should always find a749// continuation.750//751// For more information on background of zone information please consult the752// following information753// [zic manual](https://www.man7.org/linux/man-pages/man8/zic.8.html)754// [tz source info](https://data.iana.org/time-zones/tz-how-to.html)755// On POSIX systems the zdump tool can be useful:756// zdump -v Asia/Hong_Kong757// Gives all transitions in the Hong Kong time zone.758//759// During iteration the result for the current continuation is returned. If760// no continuation is applicable it will return the end time as "error". When761// two continuations are contiguous and contain the "same" information these762// ranges are merged as one range.763// The merging requires keeping any result that occurs before __time,764// likewise when a valid result is found the algorithm needs to test the next765// continuation to see whether it can be merged. For example, Africa/Ceuta766// Continuations767// 0 s WE%sT 1929 (C1)768// 0 - WET 1967 (C2)769// 0 Sp WE%sT 1984 Mar 16 (C3)770//771// Rules772// R s 1926 1929 - O Sa>=1 24s 0 - (R1)773//774// R Sp 1967 o - Jun 3 12 1 S (R2)775//776// The rule R1 is the last rule used in C1. The rule R2 is the first rule in777// C3. Since R2 is the first rule this means when a continuation uses this778// rule its value prior to R2 will be SAVE 0 LETTERS of the first entry with a779// SAVE of 0, in this case WET.780// This gives the following changes in the information.781// 1928-10-07 00:00:00 C1 R1 becomes active: offset 0 save 0 abbrev WET782// 1929-01-01 00:00:00 C2 becomes active: offset 0 save 0 abbrev WET783// 1967-01-01 00:00:00 C3 becomes active: offset 0 save 0 abbrev WET784// 1967-06-03 12:00:00 C3 R2 becomes active: offset 0 save 1 abbrev WEST785//786// The first 3 entries are contiguous and contain the same information, this787// means the period [1928-10-07 00:00:00, 1967-06-03 12:00:00) should be788// returned in one sys_info object.789790const auto& __continuations = __impl_->__continuations();791const __tz::__rules_storage_type& __rules_db = __impl_->__rules_db();792for (auto __it = __continuations.begin(); __it != __continuations.end(); ++__it) {793const auto& __continuation = *__it;794__sys_info_result __sys_info = chrono::__get_sys_info(__time, __continuation_begin, __continuation, __rules_db);795796if (__sys_info) {797_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(798__sys_info->__info.begin < __sys_info->__info.end, "invalid sys_info range");799800// Filters out dummy entries801// Z America/Argentina/Buenos_Aires -3:53:48 - LMT 1894 O 31802// ...803// -4 A -04/-03 2000 Mar 3 (C1)804// -3 A -03/-02 (C2)805//806// ...807// R A 2000 o - Mar 3 0 0 -808// R A 2007 o - D 30 0 1 -809// ...810//811// This results in an entry812// [2000-03-03 03:00:00, 2000-03-03 04:00:00) -10800s 60min -03813// for [C1 & R1, C1, R2) which due to the end of the continuation is an814// one hour "sys_info". Instead the entry should be ignored and replaced815// by [C2 & R1, C2 & R2) which is the proper range816// "[2000-03-03 03:00:00, 2007-12-30 03:00:00) -02:00:00 60min -02817818if (std::holds_alternative<string>(__continuation.__rules) && __sys_info->__can_merge &&819__sys_info->__info.begin + 12h > __sys_info->__info.end) {820__continuation_begin = __sys_info->__info.begin;821continue;822}823824if (!__result) {825// First entry found, always keep it.826__result = __sys_info->__info;827828__valid_result = __time >= __result->begin && __time < __result->end;829__can_merge = __sys_info->__can_merge;830} else if (__can_merge && chrono::__merge_continuation(*__result, __sys_info->__info)) {831// The results are merged, update the result state. This may832// "overwrite" a valid sys_info object with another valid sys_info833// object.834__valid_result = __time >= __result->begin && __time < __result->end;835__can_merge = __sys_info->__can_merge;836} else {837// Here things get interesting:838// For example, America/Argentina/San_Luis839//840// -3 A -03/-02 2008 Ja 21 (C1)841// -4 Sa -04/-03 2009 O 11 (C2)842//843// R A 2007 o - D 30 0 1 - (R1)844//845// R Sa 2007 2008 - O Su>=8 0 1 - (R2)846//847// Based on C1 & R1 the end time of C1 is 2008-01-21 03:00:00848// Based on C2 & R2 the end time of C1 is 2008-01-21 02:00:00849// In this case the earlier time is the real time of the transition.850// However the algorithm used gives 2008-01-21 03:00:00.851//852// So we need to calculate the previous UNTIL in the current context and853// see whether it's earlier.854855// The results could not be merged.856// - When we have a valid result that result is the final result.857// - Otherwise the result we had is before __time and the result we got858// is at a later time (possibly valid). This result is always better859// than the previous result.860if (__valid_result) {861return *__result;862} else {863_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(864__it != __continuations.begin(), "the first rule should always seed the result");865const auto& __last = *(__it - 1);866if (std::holds_alternative<string>(__last.__rules)) {867// Europe/Berlin868// 1 c CE%sT 1945 May 24 2 (C1)869// 1 So CE%sT 1946 (C2)870//871// R c 1944 1945 - Ap M>=1 2s 1 S (R1)872//873// R So 1945 o - May 24 2 2 M (R2)874//875// When C2 becomes active the time would be before the first rule R2,876// giving a 1 hour sys_info. This is not valid and the results need877// merging.878879if (__result->end != __sys_info->__info.begin) {880// When the UTC gap between the rules is due to the change of881// offsets adjust the new time to remove the gap.882sys_seconds __end = __result->end - __result->offset;883sys_seconds __begin = __sys_info->__info.begin - __sys_info->__info.offset;884if (__end == __begin) {885__sys_info->__info.begin = __result->end;886}887}888}889890__result = __sys_info->__info;891__valid_result = __time >= __result->begin && __time < __result->end;892__can_merge = __sys_info->__can_merge;893}894}895__continuation_begin = __result->end;896} else {897__continuation_begin = __sys_info.error();898}899}900if (__valid_result)901return *__result;902903std::__throw_runtime_error("tzdb: corrupt db");904}905906// Is the "__local_time" present in "__first" and "__second". If so the907// local_info has an ambiguous result.908[[nodiscard]] static bool909__is_ambiguous(local_seconds __local_time, const sys_info& __first, const sys_info& __second) {910std::chrono::local_seconds __end_first{__first.end.time_since_epoch() + __first.offset};911std::chrono::local_seconds __begin_second{__second.begin.time_since_epoch() + __second.offset};912913return __local_time < __end_first && __local_time >= __begin_second;914}915916// Determines the result of the "__local_time". This expects the object917// "__first" to be earlier in time than "__second".918[[nodiscard]] static local_info919__get_info(local_seconds __local_time, const sys_info& __first, const sys_info& __second) {920std::chrono::local_seconds __end_first{__first.end.time_since_epoch() + __first.offset};921std::chrono::local_seconds __begin_second{__second.begin.time_since_epoch() + __second.offset};922923if (__local_time < __end_first) {924if (__local_time >= __begin_second)925// |--------|926// |------|927// ^928return {local_info::ambiguous, __first, __second};929930// |--------|931// |------|932// ^933return {local_info::unique, __first, sys_info{}};934}935936if (__local_time < __begin_second)937// |--------|938// |------|939// ^940return {local_info::nonexistent, __first, __second};941942// |--------|943// |------|944// ^945return {local_info::unique, __second, sys_info{}};946}947948[[nodiscard]] _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI local_info949time_zone::__get_info(local_seconds __local_time) const {950seconds __local_seconds = __local_time.time_since_epoch();951952/* An example of a typical year with a DST switch displayed in local time.953*954* At the first of April the time goes forward one hour. This means the955* time marked with ~~ is not a valid local time. This is represented by the956* nonexistent value in local_info.result.957*958* At the first of November the time goes backward one hour. This means the959* time marked with ^^ happens twice. This is represented by the ambiguous960* value in local_info.result.961*962* 2020.11.01 2021.04.01 2021.11.01963* offset +05 offset +05 offset +05964* save 0s save 1h save 0s965* |------------//----------|966* |---------//--------------|967* |-------------968* ~~ ^^969*970* These shifts can happen due to changes in the current time zone for a971* location. For example, Indian/Kerguelen switched only once. In 1950 from an972* offset of 0 hours to an offset of +05 hours.973*974* During all these shifts the UTC time will not have gaps.975*/976977// The code needs to determine the system time for the local time. There is no978// information available. Assume the offset between system time and local time979// is 0s. This gives an initial estimate.980sys_seconds __guess{__local_seconds};981sys_info __info = __get_info(__guess);982983// At this point the offset can be used to determine an estimate for the local984// time. Before doing that, determine the offset and validate whether the985// local time is the range [chrono::local_seconds::min(),986// chrono::local_seconds::max()).987if (__local_seconds < 0s && __info.offset > 0s)988if (__local_seconds - chrono::local_seconds::min().time_since_epoch() < __info.offset)989return {-1, __info, {}};990991if (__local_seconds > 0s && __info.offset < 0s)992if (chrono::local_seconds::max().time_since_epoch() - __local_seconds < -__info.offset)993return {-2, __info, {}};994995// Based on the information found in the sys_info, the local time can be996// converted to a system time. This resulting time can be in the following997// locations of the sys_info:998//999// |---------//--------------|1000// 1 2.1 2.2 2.3 31001//1002// 1. The estimate is before the returned sys_info object.1003// The result is either non-existent or unique in the previous sys_info.1004// 2. The estimate is in the sys_info object1005// - If the sys_info begin is not sys_seconds::min(), then it might be at1006// 2.1 and could be ambiguous with the previous or unique.1007// - If sys_info end is not sys_seconds::max(), then it might be at 2.31008// and could be ambiguous with the next or unique.1009// - Else it is at 2.2 and always unique. This case happens when a1010// time zone has no transitions. For example, UTC or GMT+1.1011// 3. The estimate is after the returned sys_info object.1012// The result is either non-existent or unique in the next sys_info.1013//1014// There is no specification where the "middle" starts. Similar issues can1015// happen when sys_info objects are "short", then "unique in the next" could1016// become "ambiguous in the next and the one following". Theoretically there1017// is the option of the following time-line1018//1019// |------------|1020// |----|1021// |-----------------|1022//1023// However the local_info object only has 2 sys_info objects, so this option1024// is not tested.10251026sys_seconds __sys_time{__local_seconds - __info.offset};1027if (__sys_time < __info.begin)1028// Case 1 before __info1029return chrono::__get_info(__local_time, __get_info(__info.begin - 1s), __info);10301031if (__sys_time >= __info.end)1032// Case 3 after __info1033return chrono::__get_info(__local_time, __info, __get_info(__info.end));10341035// Case 2 in __info1036if (__info.begin != sys_seconds::min()) {1037// Case 2.1 Not at the beginning, when not ambiguous the result should test1038// case 2.3.1039sys_info __prev = __get_info(__info.begin - 1s);1040if (__is_ambiguous(__local_time, __prev, __info))1041return {local_info::ambiguous, __prev, __info};1042}10431044if (__info.end == sys_seconds::max())1045// At the end so it's case 2.21046return {local_info::unique, __info, sys_info{}};10471048// This tests case 2.2 or case 2.3.1049return chrono::__get_info(__local_time, __info, __get_info(__info.end));1050}10511052} // namespace chrono10531054_LIBCPP_END_NAMESPACE_STD105510561057