diff options
author | Mark de Wever <koraq@xs4all.nl> | 2024-04-10 07:50:17 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-10 07:50:17 +0200 |
commit | 1fda1776e32b5582bfcfcbd8094f3c280d936cec (patch) | |
tree | 3c01802c3c491cc65484e6cf82225f8277a286ed | |
parent | ee284d2da0720dc21191d6f545504cbfcf5dcbcf (diff) | |
download | llvm-1fda1776e32b5582bfcfcbd8094f3c280d936cec.zip llvm-1fda1776e32b5582bfcfcbd8094f3c280d936cec.tar.gz llvm-1fda1776e32b5582bfcfcbd8094f3c280d936cec.tar.bz2 |
[libc++][chrono] Adds the sys_info class. (#85619)
Adds the sys_info class and time_zone::get_info(). The code still has a
few quirks and has not been optimized for performance yet.
The returned sys_info is compared against the output of the zdump tool
in the test giving confidence the implementation is correct.
Implements parts of:
- P0355 Extending <chrono> to Calendars and Time Zones
Implements:
- LWGXXXX The sys_info range should be affected by save
22 files changed, 2916 insertions, 6 deletions
diff --git a/libcxx/docs/Status/Cxx2cIssues.csv b/libcxx/docs/Status/Cxx2cIssues.csv index b402fb8..008f741 100644 --- a/libcxx/docs/Status/Cxx2cIssues.csv +++ b/libcxx/docs/Status/Cxx2cIssues.csv @@ -63,4 +63,5 @@ "`4054 <https://wg21.link/LWG4054>`__","Repeating a ``repeat_view`` should repeat the view","Tokyo March 2024","","","|ranges|" "","","","","","" "`3343 <https://wg21.link/LWG3343>`__","Ordering of calls to ``unlock()`` and ``notify_all()`` in Effects element of ``notify_all_at_thread_exit()`` should be reversed","Not Yet Adopted","|Complete|","16.0","" +"XXXX","","The sys_info range should be affected by save","Not Yet Adopted","|Complete|","19.0" "","","","","","" diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt index 3cbca9f..a4a58a7 100644 --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -291,6 +291,7 @@ set(files __chrono/parser_std_format_spec.h __chrono/statically_widen.h __chrono/steady_clock.h + __chrono/sys_info.h __chrono/system_clock.h __chrono/time_point.h __chrono/time_zone.h diff --git a/libcxx/include/__chrono/sys_info.h b/libcxx/include/__chrono/sys_info.h new file mode 100644 index 0000000..794d22f --- /dev/null +++ b/libcxx/include/__chrono/sys_info.h @@ -0,0 +1,51 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// For information see https://libcxx.llvm.org/DesignDocs/TimeZone.html + +#ifndef _LIBCPP___CHRONO_SYS_INFO_H +#define _LIBCPP___CHRONO_SYS_INFO_H + +#include <version> +// Enable the contents of the header only when libc++ was built with experimental features enabled. +#if !defined(_LIBCPP_HAS_NO_INCOMPLETE_TZDB) + +# include <__chrono/duration.h> +# include <__chrono/system_clock.h> +# include <__chrono/time_point.h> +# include <__config> +# include <string> + +# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +# endif + +_LIBCPP_BEGIN_NAMESPACE_STD + +# if _LIBCPP_STD_VER >= 20 + +namespace chrono { + +struct sys_info { + sys_seconds begin; + sys_seconds end; + seconds offset; + minutes save; + string abbrev; +}; + +} // namespace chrono + +# endif //_LIBCPP_STD_VER >= 20 + +_LIBCPP_END_NAMESPACE_STD + +#endif // !defined(_LIBCPP_HAS_NO_INCOMPLETE_TZDB) + +#endif // _LIBCPP___CHRONO_SYS_INFO_H diff --git a/libcxx/include/__chrono/time_zone.h b/libcxx/include/__chrono/time_zone.h index 7d97327..8e30034 100644 --- a/libcxx/include/__chrono/time_zone.h +++ b/libcxx/include/__chrono/time_zone.h @@ -16,6 +16,9 @@ // Enable the contents of the header only when libc++ was built with experimental features enabled. #if !defined(_LIBCPP_HAS_NO_INCOMPLETE_TZDB) +# include <__chrono/duration.h> +# include <__chrono/sys_info.h> +# include <__chrono/system_clock.h> # include <__compare/strong_order.h> # include <__config> # include <__memory/unique_ptr.h> @@ -55,10 +58,18 @@ public: _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI string_view name() const noexcept { return __name(); } + template <class _Duration> + _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI sys_info get_info(const sys_time<_Duration>& __time) const { + return __get_info(chrono::time_point_cast<seconds>(__time)); + } + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI const __impl& __implementation() const noexcept { return *__impl_; } private: [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI string_view __name() const noexcept; + + [[nodiscard]] _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI sys_info __get_info(sys_seconds __time) const; + unique_ptr<__impl> __impl_; }; diff --git a/libcxx/include/chrono b/libcxx/include/chrono index 8fdc30a..5eddd05 100644 --- a/libcxx/include/chrono +++ b/libcxx/include/chrono @@ -724,6 +724,15 @@ const time_zone* current_zone() const tzdb& reload_tzdb(); // C++20 string remote_version(); // C++20 +// [time.zone.info], information classes +struct sys_info { // C++20 + sys_seconds begin; + sys_seconds end; + seconds offset; + minutes save; + string abbrev; +}; + // 25.10.5, class time_zone // C++20 enum class choose {earliest, latest}; class time_zone { @@ -733,6 +742,9 @@ class time_zone { // unspecified additional constructors string_view name() const noexcept; + + template<class Duration> + sys_info get_info(const sys_time<Duration>& st) const; }; bool operator==(const time_zone& x, const time_zone& y) noexcept; // C++20 strong_ordering operator<=>(const time_zone& x, const time_zone& y) noexcept; // C++20 @@ -881,6 +893,7 @@ constexpr chrono::year operator ""y(unsigned lo #include <__chrono/month_weekday.h> #include <__chrono/monthday.h> #include <__chrono/steady_clock.h> +#include <__chrono/sys_info.h> #include <__chrono/system_clock.h> #include <__chrono/time_point.h> #include <__chrono/weekday.h> diff --git a/libcxx/include/libcxx.imp b/libcxx/include/libcxx.imp index a79e8fe..6c77ba83 100644 --- a/libcxx/include/libcxx.imp +++ b/libcxx/include/libcxx.imp @@ -288,6 +288,7 @@ { include: [ "<__chrono/parser_std_format_spec.h>", "private", "<chrono>", "public" ] }, { include: [ "<__chrono/statically_widen.h>", "private", "<chrono>", "public" ] }, { include: [ "<__chrono/steady_clock.h>", "private", "<chrono>", "public" ] }, + { include: [ "<__chrono/sys_info.h>", "private", "<chrono>", "public" ] }, { include: [ "<__chrono/system_clock.h>", "private", "<chrono>", "public" ] }, { include: [ "<__chrono/time_point.h>", "private", "<chrono>", "public" ] }, { include: [ "<__chrono/time_zone.h>", "private", "<chrono>", "public" ] }, diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap index e163790..011a481 100644 --- a/libcxx/include/module.modulemap +++ b/libcxx/include/module.modulemap @@ -1167,6 +1167,9 @@ module std_private_chrono_time_zone [system] { module std_private_chrono_time_zone_link [system] { header "__chrono/time_zone_link.h" } +module std_private_chrono_sys_info [system] { + header "__chrono/sys_info.h" +} module std_private_chrono_system_clock [system] { header "__chrono/system_clock.h" export std_private_chrono_time_point diff --git a/libcxx/modules/std/chrono.inc b/libcxx/modules/std/chrono.inc index e142280..575e634 100644 --- a/libcxx/modules/std/chrono.inc +++ b/libcxx/modules/std/chrono.inc @@ -212,10 +212,12 @@ export namespace std { // [time.zone.exception], exception classes using std::chrono::ambiguous_local_time; using std::chrono::nonexistent_local_time; +# endif // if 0 // [time.zone.info], information classes using std::chrono::sys_info; +# if 0 // [time.zone.timezone], class time_zone using std::chrono::choose; # endif // if 0 diff --git a/libcxx/src/include/tzdb/time_zone_private.h b/libcxx/src/include/tzdb/time_zone_private.h index 039a3b0..2c47e9f 100644 --- a/libcxx/src/include/tzdb/time_zone_private.h +++ b/libcxx/src/include/tzdb/time_zone_private.h @@ -24,7 +24,8 @@ namespace chrono { class time_zone::__impl { public: - explicit _LIBCPP_HIDE_FROM_ABI __impl(string&& __name) : __name_(std::move(__name)) {} + explicit _LIBCPP_HIDE_FROM_ABI __impl(string&& __name, const __tz::__rules_storage_type& __rules_db) + : __name_(std::move(__name)), __rules_db_(__rules_db) {} [[nodiscard]] _LIBCPP_HIDE_FROM_ABI string_view __name() const noexcept { return __name_; } @@ -33,12 +34,20 @@ public: return __continuations_; } + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI const __tz::__rules_storage_type& __rules_db() const { return __rules_db_; } + private: string __name_; // Note the first line has a name + __continuation, the other lines // are just __continuations. So there is always at least one item in // the vector. vector<__tz::__continuation> __continuations_; + + // Continuations often depend on a set of rules. The rules are stored in + // parallel data structurs in tzdb_list. From the time_zone it's not possible + // to find its associated tzdb entry and thus not possible to find its + // associated rules. Therefore a link to the rules in stored in this class. + const __tz::__rules_storage_type& __rules_db_; }; } // namespace chrono diff --git a/libcxx/src/include/tzdb/types_private.h b/libcxx/src/include/tzdb/types_private.h index 4604b9fc..c869829 100644 --- a/libcxx/src/include/tzdb/types_private.h +++ b/libcxx/src/include/tzdb/types_private.h @@ -33,7 +33,17 @@ namespace chrono::__tz { // Sun>=8 first Sunday on or after the eighth // Sun<=25 last Sunday on or before the 25th struct __constrained_weekday { - /* year_month_day operator()(year __year, month __month);*/ // needed but not implemented + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI year_month_day operator()(year __year, month __month) const { + auto __result = static_cast<sys_days>(year_month_day{__year, __month, __day}); + weekday __wd{static_cast<sys_days>(__result)}; + + if (__comparison == __le) + __result -= __wd - __weekday; + else + __result += __weekday - __wd; + + return __result; + } weekday __weekday; enum __comparison_t { __le, __ge } __comparison; @@ -85,7 +95,8 @@ struct __continuation { // used. // If this field contains - then standard time always // applies. This is indicated by the monostate. - using __rules_t = variant<monostate, __tz::__save, string, size_t>; + // TODO TZDB Investigate implantation the size_t based caching. + using __rules_t = variant<monostate, __tz::__save, string /*, size_t*/>; __rules_t __rules; diff --git a/libcxx/src/time_zone.cpp b/libcxx/src/time_zone.cpp index b6bf06a..aef6ac6 100644 --- a/libcxx/src/time_zone.cpp +++ b/libcxx/src/time_zone.cpp @@ -8,14 +8,712 @@ // For information see https://libcxx.llvm.org/DesignDocs/TimeZone.html +// TODO TZDB look at optimizations +// +// The current algorithm is correct but not efficient. For example, in a named +// rule based continuation finding the next rule does quite a bit of work, +// returns the next rule and "forgets" its state. This could be better. +// +// It would be possible to cache lookups. If a time for a zone is calculated its +// sys_info could be kept and the next lookup could test whether the time is in +// a "known" sys_info. The wording in the Standard hints at this slowness by +// "suggesting" this could be implemented on the user's side. + +// TODO TZDB look at removing quirks +// +// The code has some special rules to adjust the timing at the continuation +// switches. This works correctly, but some of the places feel odd. It would be +// good to investigate this further and see whether all quirks are needed or +// that there are better fixes. +// +// These quirks often use a 12h interval; this is the scan interval of zdump, +// which implies there are no sys_info objects with a duration of less than 12h. + +#include <algorithm> +#include <cctype> #include <chrono> +#include <expected> +#include <map> +#include <ranges> #include "include/tzdb/time_zone_private.h" +#include "include/tzdb/tzdb_list_private.h" + +// TODO TZDB remove debug printing +#ifdef PRINT +# include <print> +#endif _LIBCPP_BEGIN_NAMESPACE_STD +#ifdef PRINT +template <> +struct formatter<chrono::sys_info, char> { + template <class ParseContext> + constexpr typename ParseContext::iterator parse(ParseContext& ctx) { + return ctx.begin(); + } + + template <class FormatContext> + typename FormatContext::iterator format(const chrono::sys_info& info, FormatContext& ctx) const { + return std::format_to( + ctx.out(), "[{}, {}) {:%Q%q} {:%Q%q} {}", info.begin, info.end, info.offset, info.save, info.abbrev); + } +}; +#endif + namespace chrono { +//===----------------------------------------------------------------------===// +// Details +//===----------------------------------------------------------------------===// + +struct __sys_info { + sys_info __info; + bool __can_merge; // Can the returned sys_info object be merged with +}; + +// Return type for helper function to get a sys_info. +// - The expected result returns the "best" sys_info object. This object can be +// before the requested time. Sometimes sys_info objects from different +// continuations share their offset, save, and abbrev and these objects are +// merged to one sys_info object. The __can_merge flag determines whether the +// current result can be merged with the next result. +// - The unexpected result means no sys_info object was found and the time is +// the time to be used for the next search iteration. +using __sys_info_result = expected<__sys_info, sys_seconds>; + +template <ranges::forward_range _Range, + class _Type, + class _Proj = identity, + indirect_strict_weak_order<const _Type*, projected<ranges::iterator_t<_Range>, _Proj>> _Comp = ranges::less> +[[nodiscard]] static ranges::borrowed_iterator_t<_Range> +__binary_find(_Range&& __r, const _Type& __value, _Comp __comp = {}, _Proj __proj = {}) { + auto __end = ranges::end(__r); + auto __ret = ranges::lower_bound(ranges::begin(__r), __end, __value, __comp, __proj); + if (__ret == __end) + return __end; + + // When the value does not match the predicate it's equal and a valid result + // was found. + return !std::invoke(__comp, __value, std::invoke(__proj, *__ret)) ? __ret : __end; +} + +// Format based on https://data.iana.org/time-zones/tz-how-to.html +// +// 1 a time zone abbreviation that is a string of three or more characters that +// are either ASCII alphanumerics, "+", or "-" +// 2 the string "%z", in which case the "%z" will be replaced by a numeric time +// zone abbreviation +// 3 a pair of time zone abbreviations separated by a slash ('/'), in which +// case the first string is the abbreviation for the standard time name and +// the second string is the abbreviation for the daylight saving time name +// 4 a string containing "%s", in which case the "%s" will be replaced by the +// text in the appropriate Rule's LETTER column, and the resulting string +// should be a time zone abbreviation +// +// Rule 1 is not strictly validated since America/Barbados uses a two letter +// abbreviation AT. +[[nodiscard]] static string +__format(const __tz::__continuation& __continuation, const string& __letters, seconds __save) { + bool __shift = false; + string __result; + for (char __c : __continuation.__format) { + if (__shift) { + switch (__c) { + case 's': + std::ranges::copy(__letters, std::back_inserter(__result)); + break; + + case 'z': { + if (__continuation.__format.size() != 2) + std::__throw_runtime_error( + std::format("corrupt tzdb FORMAT field: %z should be the entire contents, instead contains '{}'", + __continuation.__format) + .c_str()); + chrono::hh_mm_ss __offset{__continuation.__stdoff + __save}; + if (__offset.is_negative()) { + __result += '-'; + __offset = chrono::hh_mm_ss{-(__continuation.__stdoff + __save)}; + } else + __result += '+'; + + if (__offset.minutes() != 0min) + std::format_to(std::back_inserter(__result), "{:%H%M}", __offset); + else + std::format_to(std::back_inserter(__result), "{:%H}", __offset); + } break; + + default: + std::__throw_runtime_error( + std::format("corrupt tzdb FORMAT field: invalid sequence '%{}' found, expected %s or %z", __c).c_str()); + } + __shift = false; + + } else if (__c == '/') { + if (__save != 0s) + __result.clear(); + else + break; + + } else if (__c == '%') { + __shift = true; + } else if (__c == '+' || __c == '-' || std::isalnum(__c)) { + __result.push_back(__c); + } else { + std::__throw_runtime_error( + std::format( + "corrupt tzdb FORMAT field: invalid character '{}' found, expected +, -, or an alphanumeric value", __c) + .c_str()); + } + } + + if (__shift) + std::__throw_runtime_error("corrupt tzdb FORMAT field: input ended with the start of the escape sequence '%'"); + + if (__result.empty()) + std::__throw_runtime_error("corrupt tzdb FORMAT field: result is empty"); + + return __result; +} + +[[nodiscard]] static sys_seconds __to_sys_seconds(year_month_day __ymd, seconds __seconds) { + seconds __result = static_cast<sys_days>(__ymd).time_since_epoch() + __seconds; + return sys_seconds{__result}; +} + +[[nodiscard]] static seconds __at_to_sys_seconds(const __tz::__continuation& __continuation) { + switch (__continuation.__at.__clock) { + case __tz::__clock::__local: + return __continuation.__at.__time - __continuation.__stdoff - + std::visit( + [](const auto& __value) { + using _Tp = decay_t<decltype(__value)>; + if constexpr (same_as<_Tp, monostate>) + return chrono::seconds{0}; + else if constexpr (same_as<_Tp, __tz::__save>) + return chrono::duration_cast<seconds>(__value.__time); + else if constexpr (same_as<_Tp, std::string>) + // For a named rule based continuation the SAVE depends on the RULE + // active at the end. This should be determined separately. + return chrono::seconds{0}; + else + static_assert(sizeof(_Tp) == 0); // TODO TZDB static_assert(false); after droping clang-16 support + + std::__libcpp_unreachable(); + }, + __continuation.__rules); + + case __tz::__clock::__universal: + return __continuation.__at.__time; + + case __tz::__clock::__standard: + return __continuation.__at.__time - __continuation.__stdoff; + } + std::__libcpp_unreachable(); +} + +[[nodiscard]] static year_month_day __to_year_month_day(year __year, month __month, __tz::__on __on) { + return std::visit( + [&](const auto& __value) { + using _Tp = decay_t<decltype(__value)>; + if constexpr (same_as<_Tp, chrono::day>) + return year_month_day{__year, __month, __value}; + else if constexpr (same_as<_Tp, weekday_last>) + return year_month_day{static_cast<sys_days>(year_month_weekday_last{__year, __month, __value})}; + else if constexpr (same_as<_Tp, __tz::__constrained_weekday>) + return __value(__year, __month); + else + static_assert(sizeof(_Tp) == 0); // TODO TZDB static_assert(false); after droping clang-16 support + + std::__libcpp_unreachable(); + }, + __on); +} + +[[nodiscard]] static sys_seconds __until_to_sys_seconds(const __tz::__continuation& __continuation) { + // Does UNTIL contain the magic value for the last continuation? + if (__continuation.__year == chrono::year::min()) + return sys_seconds::max(); + + year_month_day __ymd = chrono::__to_year_month_day(__continuation.__year, __continuation.__in, __continuation.__on); + return chrono::__to_sys_seconds(__ymd, chrono::__at_to_sys_seconds(__continuation)); +} + +// Holds the UNTIL time for a continuation with a named rule. +// +// Unlike continuations with an fixed SAVE named rules have a variable SAVE. +// This means when the UNTIL uses the local wall time the actual UNTIL value can +// only be determined when the SAVE is known. This class holds that abstraction. +class __named_rule_until { +public: + explicit __named_rule_until(const __tz::__continuation& __continuation) + : __until_{chrono::__until_to_sys_seconds(__continuation)}, + __needs_adjustment_{ + // The last continuation of a ZONE has no UNTIL which basically is + // until the end of _local_ time. This value is expressed by + // sys_seconds::max(). Subtracting the SAVE leaves large value. + // However SAVE can be negative, which would add a value to maximum + // leading to undefined behaviour. In practice this often results in + // an overflow to a very small value. + __until_ != sys_seconds::max() && __continuation.__at.__clock == __tz::__clock::__local} {} + + // Gives the unadjusted until value, this is useful when the SAVE is not known + // at all. + sys_seconds __until() const noexcept { return __until_; } + + bool __needs_adjustment() const noexcept { return __needs_adjustment_; } + + // Returns the UNTIL adjusted for SAVE. + sys_seconds operator()(seconds __save) const noexcept { return __until_ - __needs_adjustment_ * __save; } + +private: + sys_seconds __until_; + bool __needs_adjustment_; +}; + +[[nodiscard]] static seconds __at_to_seconds(seconds __stdoff, const __tz::__rule& __rule) { + switch (__rule.__at.__clock) { + case __tz::__clock::__local: + // Local time and standard time behave the same. This is not + // correct. Local time needs to adjust for the current saved time. + // To know the saved time the rules need to be known and sorted. + // This needs a time so to avoid the chicken and egg adjust the + // saving of the local time later. + return __rule.__at.__time - __stdoff; + + case __tz::__clock::__universal: + return __rule.__at.__time; + + case __tz::__clock::__standard: + return __rule.__at.__time - __stdoff; + } + std::__libcpp_unreachable(); +} + +[[nodiscard]] static sys_seconds __from_to_sys_seconds(seconds __stdoff, const __tz::__rule& __rule, year __year) { + year_month_day __ymd = chrono::__to_year_month_day(__year, __rule.__in, __rule.__on); + + seconds __at = chrono::__at_to_seconds(__stdoff, __rule); + return chrono::__to_sys_seconds(__ymd, __at); +} + +[[nodiscard]] static sys_seconds __from_to_sys_seconds(seconds __stdoff, const __tz::__rule& __rule) { + return chrono::__from_to_sys_seconds(__stdoff, __rule, __rule.__from); +} + +[[nodiscard]] static const vector<__tz::__rule>& +__get_rules(const __tz::__rules_storage_type& __rules_db, const string& __rule_name) { + auto __result = chrono::__binary_find(__rules_db, __rule_name, {}, [](const auto& __p) { return __p.first; }); + if (__result == std::end(__rules_db)) + std::__throw_runtime_error(("corrupt tzdb: rule '" + __rule_name + " 'does not exist").c_str()); + + return __result->second; +} + +// Returns the letters field for a time before the first rule. +// +// Per https://data.iana.org/time-zones/tz-how-to.html +// One wrinkle, not fully explained in zic.8.txt, is what happens when switching +// to a named rule. To what values should the SAVE and LETTER data be +// initialized? +// +// 1 If at least one transition has happened, use the SAVE and LETTER data from +// the most recent. +// 2 If switching to a named rule before any transition has happened, assume +// standard time (SAVE zero), and use the LETTER data from the earliest +// transition with a SAVE of zero. +// +// This function implements case 2. +[[nodiscard]] static string __letters_before_first_rule(const vector<__tz::__rule>& __rules) { + auto __letters = + __rules // + | views::filter([](const __tz::__rule& __rule) { return __rule.__save.__time == 0s; }) // + | views::transform([](const __tz::__rule& __rule) { return __rule.__letters; }) // + | views::take(1); + + if (__letters.empty()) + std::__throw_runtime_error("corrupt tzdb: rule has zero entries"); + + return __letters.front(); +} + +// Determines the information based on the continuation and the rules. +// +// There are several special cases to take into account +// +// === Entries before the first rule becomes active === +// Asia/Hong_Kong +// 9 - JST 1945 N 18 2 // (1) +// 8 HK HK%sT // (2) +// R HK 1946 o - Ap 21 0 1 S // (3) +// There (1) is active until Novemer 18th 1945 at 02:00, after this time +// (2) becomes active. The first rule entry for HK (3) becomes active +// from April 21st 1945 at 01:00. In the period between (2) is active. +// This entry has an offset. +// This entry has no save, letters, or dst flag. So in the period +// after (1) and until (3) no rule entry is associated with the time. + +[[nodiscard]] static sys_info __get_sys_info_before_first_rule( + sys_seconds __begin, + sys_seconds __end, + const __tz::__continuation& __continuation, + const vector<__tz::__rule>& __rules) { + return sys_info{ + __begin, + __end, + __continuation.__stdoff, + chrono::minutes(0), + chrono::__format(__continuation, __letters_before_first_rule(__rules), 0s)}; +} + +// Returns the sys_info object for a time before the first rule. +// When this first rule has a SAVE of 0s the sys_info for the time before the +// first rule and for the first rule are identical and will be merged. +[[nodiscard]] static sys_info __get_sys_info_before_first_rule( + sys_seconds __begin, + sys_seconds __rule_end, // The end used when SAVE != 0s + sys_seconds __next_end, // The end used when SAVE == 0s the times are merged + const __tz::__continuation& __continuation, + const vector<__tz::__rule>& __rules, + vector<__tz::__rule>::const_iterator __rule) { + if (__rule->__save.__time != 0s) + return __get_sys_info_before_first_rule(__begin, __rule_end, __continuation, __rules); + + return sys_info{ + __begin, __next_end, __continuation.__stdoff, 0min, chrono::__format(__continuation, __rule->__letters, 0s)}; +} + +[[nodiscard]] static seconds __at_to_seconds(seconds __stdoff, seconds __save, const __tz::__rule& __rule) { + switch (__rule.__at.__clock) { + case __tz::__clock::__local: + return __rule.__at.__time - __stdoff - __save; + + case __tz::__clock::__universal: + return __rule.__at.__time; + + case __tz::__clock::__standard: + return __rule.__at.__time - __stdoff; + } + std::__libcpp_unreachable(); +} + +[[nodiscard]] static sys_seconds +__rule_to_sys_seconds(seconds __stdoff, seconds __save, const __tz::__rule& __rule, year __year) { + year_month_day __ymd = chrono::__to_year_month_day(__year, __rule.__in, __rule.__on); + + seconds __at = chrono::__at_to_seconds(__stdoff, __save, __rule); + return chrono::__to_sys_seconds(__ymd, __at); +} + +// Returns the first rule after __time. +// Note that a rule can be "active" in multiple years, this may result in an +// infinite loop where the same rule is returned every time, use __current to +// guard against that. +// +// When no next rule exists the returned time will be sys_seconds::max(). This +// can happen in practice. For example, +// +// R So 1945 o - May 24 2 2 M +// R So 1945 o - S 24 3 1 S +// R So 1945 o - N 18 2s 0 - +// +// Has 3 rules that are all only active in 1945. +[[nodiscard]] static pair<sys_seconds, vector<__tz::__rule>::const_iterator> +__next_rule(sys_seconds __time, + seconds __stdoff, + seconds __save, + const vector<__tz::__rule>& __rules, + vector<__tz::__rule>::const_iterator __current) { + year __year = year_month_day{chrono::floor<days>(__time)}.year(); + + // Note it would probably be better to store the pairs in a vector and then + // use min() to get the smallest element + map<sys_seconds, vector<__tz::__rule>::const_iterator> __candidates; + // Note this evaluates all rules which is a waste of effort; when the entries + // are beyond the current year's "next year" (where "next year" is not always + // year + 1) the algorithm should end. + for (auto __it = __rules.begin(); __it != __rules.end(); ++__it) { + for (year __y = __it->__from; __y <= __it->__to; ++__y) { + // Adding the current entry for the current year may lead to infinite + // loops due to the SAVE adjustment. Skip these entries. + if (__y == __year && __it == __current) + continue; + + sys_seconds __t = chrono::__rule_to_sys_seconds(__stdoff, __save, *__it, __y); + if (__t <= __time) + continue; + + _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(!__candidates.contains(__t), "duplicated rule"); + __candidates[__t] = __it; + break; + } + } + + if (!__candidates.empty()) [[likely]] { + auto __it = __candidates.begin(); + + // When no rule is selected the time before the first rule and the first rule + // should not be merged. + if (__time == sys_seconds::min()) + return *__it; + + // There can be two constitutive rules that are the same. For example, + // Hong Kong + // + // R HK 1973 o - D 30 3:30 1 S (R1) + // R HK 1965 1976 - Ap Su>=16 3:30 1 S (R2) + // + // 1973-12-29 19:30:00 R1 becomes active. + // 1974-04-20 18:30:00 R2 becomes active. + // Both rules have a SAVE of 1 hour and LETTERS are S for both of them. + while (__it != __candidates.end()) { + if (__current->__save.__time != __it->second->__save.__time || __current->__letters != __it->second->__letters) + return *__it; + + ++__it; + } + } + + return {sys_seconds::max(), __rules.end()}; +} + +// Returns the first rule of a set of rules. +// This is not always the first of the listed rules. For example +// R Sa 2008 2009 - Mar Su>=8 0 0 - +// R Sa 2007 2008 - O Su>=8 0 1 - +// The transition in October 2007 happens before the transition in March 2008. +[[nodiscard]] static vector<__tz::__rule>::const_iterator +__first_rule(seconds __stdoff, const vector<__tz::__rule>& __rules) { + return chrono::__next_rule(sys_seconds::min(), __stdoff, 0s, __rules, __rules.end()).second; +} + +[[nodiscard]] static __sys_info_result __get_sys_info_rule( + sys_seconds __time, + sys_seconds __continuation_begin, + const __tz::__continuation& __continuation, + const vector<__tz::__rule>& __rules) { + auto __rule = chrono::__first_rule(__continuation.__stdoff, __rules); + _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(__rule != __rules.end(), "the set of rules has no first rule"); + + // Avoid selecting a time before the start of the continuation + __time = std::max(__time, __continuation_begin); + + sys_seconds __rule_begin = chrono::__from_to_sys_seconds(__continuation.__stdoff, *__rule); + + // The time sought is very likely inside the current rule. + // When the continuation's UNTIL uses the local clock there are edge cases + // where this is not true. + // + // Start to walk the rules to find the proper one. + // + // For now we just walk all the rules TODO TZDB investigate whether a smarter + // algorithm would work. + auto __next = chrono::__next_rule(__rule_begin, __continuation.__stdoff, __rule->__save.__time, __rules, __rule); + + // Ignore small steps, this happens with America/Punta_Arenas for the + // transition + // -4:42:46 - SMT 1927 S + // -5 x -05/-04 1932 S + // ... + // + // R x 1927 1931 - S 1 0 1 - + // R x 1928 1932 - Ap 1 0 0 - + // + // America/Punta_Arenas Thu Sep 1 04:42:45 1927 UT = Thu Sep 1 00:42:45 1927 -04 isdst=1 gmtoff=-14400 + // America/Punta_Arenas Sun Apr 1 03:59:59 1928 UT = Sat Mar 31 23:59:59 1928 -04 isdst=1 gmtoff=-14400 + // America/Punta_Arenas Sun Apr 1 04:00:00 1928 UT = Sat Mar 31 23:00:00 1928 -05 isdst=0 gmtoff=-18000 + // + // Without this there will be a transition + // [1927-09-01 04:42:45, 1927-09-01 05:00:00) -05:00:00 0min -05 + + if (sys_seconds __begin = __rule->__save.__time != 0s ? __rule_begin : __next.first; __time < __begin) { + if (__continuation_begin == sys_seconds::min() || __begin - __continuation_begin > 12h) + return __sys_info{__get_sys_info_before_first_rule( + __continuation_begin, __rule_begin, __next.first, __continuation, __rules, __rule), + false}; + + // Europe/Berlin + // 1 c CE%sT 1945 May 24 2 (C1) + // 1 So CE%sT 1946 (C2) + // + // R c 1944 1945 - Ap M>=1 2s 1 S (R1) + // + // R So 1945 o - May 24 2 2 M (R2) + // + // When C2 becomes active the time would be before the first rule R2, + // giving a 1 hour sys_info. + seconds __save = __rule->__save.__time; + __named_rule_until __continuation_end{__continuation}; + sys_seconds __sys_info_end = std::min(__continuation_end(__save), __next.first); + + return __sys_info{ + sys_info{__continuation_begin, + __sys_info_end, + __continuation.__stdoff + __save, + chrono::duration_cast<minutes>(__save), + chrono::__format(__continuation, __rule->__letters, __save)}, + __sys_info_end == __continuation_end(__save)}; + } + + // See above for America/Asuncion + if (__rule->__save.__time == 0s && __time < __next.first) { + return __sys_info{ + sys_info{__continuation_begin, + __next.first, + __continuation.__stdoff, + 0min, + chrono::__format(__continuation, __rule->__letters, 0s)}, + false}; + } + + __named_rule_until __continuation_end{__continuation}; + if (__time >= __continuation_end.__until() && !__continuation_end.__needs_adjustment()) + // note std::unexpected<sys_seconds>(__end); is ambiguous with std::unexpected() in <exception>, + return __sys_info_result{std::unexpect, __continuation_end.__until()}; + + while (__next.second != __rules.end()) { +#ifdef PRINT + std::print( + stderr, + "Rule for {}: [{}, {}) off={} save={} duration={}\n", + __time, + __rule_begin, + __next.first, + __continuation.__stdoff, + __rule->__save.__time, + __next.first - __rule_begin); +#endif + + sys_seconds __end = __continuation_end(__rule->__save.__time); + + sys_seconds __sys_info_begin = std::max(__continuation_begin, __rule_begin); + sys_seconds __sys_info_end = std::min(__end, __next.first); + seconds __diff = chrono::abs(__sys_info_end - __sys_info_begin); + + if (__diff < 12h) { + // Z America/Argentina/Buenos_Aires -3:53:48 - LMT 1894 O 31 + // -4:16:48 - CMT 1920 May + // -4 - -04 1930 D + // -4 A -04/-03 1969 O 5 + // -3 A -03/-02 1999 O 3 + // -4 A -04/-03 2000 Mar 3 + // ... + // + // ... + // R A 1989 1992 - O Su>=15 0 1 - + // R A 1999 o - O Su>=1 0 1 - + // R A 2000 o - Mar 3 0 0 - + // R A 2007 o - D 30 0 1 - + // ... + + // The 1999 switch uses the same rule, but with a different stdoff. + // R A 1999 o - O Su>=1 0 1 - + // stdoff -3 -> 1999-10-03 03:00:00 + // stdoff -4 -> 1999-10-03 04:00:00 + // This generates an invalid entry and this is evaluated as a transition. + // Looking at the zdump like output in libc++ this generates jumps in + // the UTC time. + + __rule = __next.second; + __next = __next_rule(__next.first, __continuation.__stdoff, __rule->__save.__time, __rules, __rule); + __end = __continuation_end(__rule->__save.__time); + __sys_info_end = std::min(__end, __next.first); + } + + if ((__time >= __rule_begin && __time < __next.first) || __next.first >= __end) { + __sys_info_begin = std::max(__continuation_begin, __rule_begin); + __sys_info_end = std::min(__end, __next.first); + + return __sys_info{ + sys_info{__sys_info_begin, + __sys_info_end, + __continuation.__stdoff + __rule->__save.__time, + chrono::duration_cast<minutes>(__rule->__save.__time), + chrono::__format(__continuation, __rule->__letters, __rule->__save.__time)}, + __sys_info_end == __end}; + } + + __rule_begin = __next.first; + __rule = __next.second; + __next = __next_rule(__rule_begin, __continuation.__stdoff, __rule->__save.__time, __rules, __rule); + } + + return __sys_info{ + sys_info{std::max(__continuation_begin, __rule_begin), + __continuation_end(__rule->__save.__time), + __continuation.__stdoff + __rule->__save.__time, + chrono::duration_cast<minutes>(__rule->__save.__time), + chrono::__format(__continuation, __rule->__letters, __rule->__save.__time)}, + true}; +} + +[[nodiscard]] static __sys_info_result __get_sys_info_basic( + sys_seconds __time, sys_seconds __continuation_begin, const __tz::__continuation& __continuation, seconds __save) { + sys_seconds __continuation_end = chrono::__until_to_sys_seconds(__continuation); + return __sys_info{ + sys_info{__continuation_begin, + __continuation_end, + __continuation.__stdoff + __save, + chrono::duration_cast<minutes>(__save), + __continuation.__format}, + true}; +} + +[[nodiscard]] static __sys_info_result +__get_sys_info(sys_seconds __time, + sys_seconds __continuation_begin, + const __tz::__continuation& __continuation, + const __tz::__rules_storage_type& __rules_db) { + return std::visit( + [&](const auto& __value) { + using _Tp = decay_t<decltype(__value)>; + if constexpr (same_as<_Tp, std::string>) + return chrono::__get_sys_info_rule( + __time, __continuation_begin, __continuation, __get_rules(__rules_db, __value)); + else if constexpr (same_as<_Tp, monostate>) + return chrono::__get_sys_info_basic(__time, __continuation_begin, __continuation, chrono::seconds(0)); + else if constexpr (same_as<_Tp, __tz::__save>) + return chrono::__get_sys_info_basic(__time, __continuation_begin, __continuation, __value.__time); + else + static_assert(sizeof(_Tp) == 0); // TODO TZDB static_assert(false); after droping clang-16 support + + std::__libcpp_unreachable(); + }, + __continuation.__rules); +} + +// The transition from one continuation to the next continuation may result in +// two constitutive continuations with the same "offset" information. +// [time.zone.info.sys]/3 +// The begin and end data members indicate that, for the associated time_zone +// and time_point, the offset and abbrev are in effect in the range +// [begin, end). This information can be used to efficiently iterate the +// transitions of a time_zone. +// +// Note that this does considers a change in the SAVE field not to be a +// different sys_info, zdump does consider this different. +// LWG XXXX The sys_info range should be affected by save +// matches the behaviour of the Standard and zdump. +// +// Iff the "offsets" are the same '__current.__end' is replaced with +// '__next.__end', which effectively merges the two objects in one object. The +// function returns true if a merge occurred. +[[nodiscard]] bool __merge_continuation(sys_info& __current, const sys_info& __next) { + if (__current.end != __next.begin) + return false; + + if (__current.offset != __next.offset || __current.abbrev != __next.abbrev || __current.save != __next.save) + return false; + + __current.end = __next.end; + return true; +} + +//===----------------------------------------------------------------------===// +// Public API +//===----------------------------------------------------------------------===// + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI time_zone time_zone::__create(unique_ptr<time_zone::__impl>&& __p) { _LIBCPP_ASSERT_NON_NULL(__p != nullptr, "initialized time_zone without a valid pimpl object"); time_zone result; @@ -27,6 +725,173 @@ _LIBCPP_EXPORTED_FROM_ABI time_zone::~time_zone() = default; [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI string_view time_zone::__name() const noexcept { return __impl_->__name(); } +[[nodiscard]] _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI sys_info +time_zone::__get_info(sys_seconds __time) const { + optional<sys_info> __result; + bool __valid_result = false; // true iff __result.has_value() is true and + // __result.begin <= __time < __result.end is true. + bool __can_merge = false; + sys_seconds __continuation_begin = sys_seconds::min(); + // Iterates over the Zone entry and its continuations. Internally the Zone + // entry is split in a Zone information and the first continuation. The last + // continuation has no UNTIL field. This means the loop should always find a + // continuation. + // + // For more information on background of zone information please consult the + // following information + // [zic manual](https://www.man7.org/linux/man-pages/man8/zic.8.html) + // [tz source info](https://data.iana.org/time-zones/tz-how-to.html) + // On POSIX systems the zdump tool can be useful: + // zdump -v Asia/Hong_Kong + // Gives all transitions in the Hong Kong time zone. + // + // During iteration the result for the current continuation is returned. If + // no continuation is applicable it will return the end time as "error". When + // two continuations are contiguous and contain the "same" information these + // ranges are merged as one range. + // The merging requires keeping any result that occurs before __time, + // likewise when a valid result is found the algorithm needs to test the next + // continuation to see whether it can be merged. For example, Africa/Ceuta + // Continuations + // 0 s WE%sT 1929 (C1) + // 0 - WET 1967 (C2) + // 0 Sp WE%sT 1984 Mar 16 (C3) + // + // Rules + // R s 1926 1929 - O Sa>=1 24s 0 - (R1) + // + // R Sp 1967 o - Jun 3 12 1 S (R2) + // + // The rule R1 is the last rule used in C1. The rule R2 is the first rule in + // C3. Since R2 is the first rule this means when a continuation uses this + // rule its value prior to R2 will be SAVE 0 LETTERS of the first entry with a + // SAVE of 0, in this case WET. + // This gives the following changes in the information. + // 1928-10-07 00:00:00 C1 R1 becomes active: offset 0 save 0 abbrev WET + // 1929-01-01 00:00:00 C2 becomes active: offset 0 save 0 abbrev WET + // 1967-01-01 00:00:00 C3 becomes active: offset 0 save 0 abbrev WET + // 1967-06-03 12:00:00 C3 R2 becomes active: offset 0 save 1 abbrev WEST + // + // The first 3 entries are contiguous and contain the same information, this + // means the period [1928-10-07 00:00:00, 1967-06-03 12:00:00) should be + // returned in one sys_info object. + + const auto& __continuations = __impl_->__continuations(); + const __tz::__rules_storage_type& __rules_db = __impl_->__rules_db(); + for (auto __it = __continuations.begin(); __it != __continuations.end(); ++__it) { + const auto& __continuation = *__it; + __sys_info_result __sys_info = chrono::__get_sys_info(__time, __continuation_begin, __continuation, __rules_db); + + if (__sys_info) { + _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN( + __sys_info->__info.begin < __sys_info->__info.end, "invalid sys_info range"); + + // Filters out dummy entries + // Z America/Argentina/Buenos_Aires -3:53:48 - LMT 1894 O 31 + // ... + // -4 A -04/-03 2000 Mar 3 (C1) + // -3 A -03/-02 (C2) + // + // ... + // R A 2000 o - Mar 3 0 0 - + // R A 2007 o - D 30 0 1 - + // ... + // + // This results in an entry + // [2000-03-03 03:00:00, 2000-03-03 04:00:00) -10800s 60min -03 + // for [C1 & R1, C1, R2) which due to the end of the continuation is an + // one hour "sys_info". Instead the entry should be ignored and replaced + // by [C2 & R1, C2 & R2) which is the proper range + // "[2000-03-03 03:00:00, 2007-12-30 03:00:00) -02:00:00 60min -02 + + if (std::holds_alternative<string>(__continuation.__rules) && __sys_info->__can_merge && + __sys_info->__info.begin + 12h > __sys_info->__info.end) { + __continuation_begin = __sys_info->__info.begin; + continue; + } + + if (!__result) { + // First entry found, always keep it. + __result = __sys_info->__info; + + __valid_result = __time >= __result->begin && __time < __result->end; + __can_merge = __sys_info->__can_merge; + } else if (__can_merge && chrono::__merge_continuation(*__result, __sys_info->__info)) { + // The results are merged, update the result state. This may + // "overwrite" a valid sys_info object with another valid sys_info + // object. + __valid_result = __time >= __result->begin && __time < __result->end; + __can_merge = __sys_info->__can_merge; + } else { + // Here things get interesting: + // For example, America/Argentina/San_Luis + // + // -3 A -03/-02 2008 Ja 21 (C1) + // -4 Sa -04/-03 2009 O 11 (C2) + // + // R A 2007 o - D 30 0 1 - (R1) + // + // R Sa 2007 2008 - O Su>=8 0 1 - (R2) + // + // Based on C1 & R1 the end time of C1 is 2008-01-21 03:00:00 + // Based on C2 & R2 the end time of C1 is 2008-01-21 02:00:00 + // In this case the earlier time is the real time of the transition. + // However the algorithm used gives 2008-01-21 03:00:00. + // + // So we need to calculate the previous UNTIL in the current context and + // see whether it's earlier. + + // The results could not be merged. + // - When we have a valid result that result is the final result. + // - Otherwise the result we had is before __time and the result we got + // is at a later time (possibly valid). This result is always better + // than the previous result. + if (__valid_result) { + return *__result; + } else { + _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN( + __it != __continuations.begin(), "the first rule should always seed the result"); + const auto& __last = *(__it - 1); + if (std::holds_alternative<string>(__last.__rules)) { + // Europe/Berlin + // 1 c CE%sT 1945 May 24 2 (C1) + // 1 So CE%sT 1946 (C2) + // + // R c 1944 1945 - Ap M>=1 2s 1 S (R1) + // + // R So 1945 o - May 24 2 2 M (R2) + // + // When C2 becomes active the time would be before the first rule R2, + // giving a 1 hour sys_info. This is not valid and the results need + // merging. + + if (__result->end != __sys_info->__info.begin) { + // When the UTC gap between the rules is due to the change of + // offsets adjust the new time to remove the gap. + sys_seconds __end = __result->end - __result->offset; + sys_seconds __begin = __sys_info->__info.begin - __sys_info->__info.offset; + if (__end == __begin) { + __sys_info->__info.begin = __result->end; + } + } + } + + __result = __sys_info->__info; + __valid_result = __time >= __result->begin && __time < __result->end; + __can_merge = __sys_info->__can_merge; + } + } + __continuation_begin = __result->end; + } else { + __continuation_begin = __sys_info.error(); + } + } + if (__valid_result) + return *__result; + + std::__throw_runtime_error("tzdb: corrupt db"); +} + } // namespace chrono _LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/src/tzdb.cpp b/libcxx/src/tzdb.cpp index 9d06eb9..8909ecd 100644 --- a/libcxx/src/tzdb.cpp +++ b/libcxx/src/tzdb.cpp @@ -561,7 +561,7 @@ static void __parse_rule(tzdb& __tzdb, __tz::__rules_storage_type& __rules, istr static void __parse_zone(tzdb& __tzdb, __tz::__rules_storage_type& __rules, istream& __input) { chrono::__skip_mandatory_whitespace(__input); - auto __p = std::make_unique<time_zone::__impl>(chrono::__parse_string(__input)); + auto __p = std::make_unique<time_zone::__impl>(chrono::__parse_string(__input), __rules); vector<__tz::__continuation>& __continuations = __p->__continuations(); chrono::__skip_mandatory_whitespace(__input); diff --git a/libcxx/test/libcxx/diagnostics/chrono.nodiscard_extensions.compile.pass.cpp b/libcxx/test/libcxx/diagnostics/chrono.nodiscard_extensions.compile.pass.cpp index 9acb57f..cbdb2ab 100644 --- a/libcxx/test/libcxx/diagnostics/chrono.nodiscard_extensions.compile.pass.cpp +++ b/libcxx/test/libcxx/diagnostics/chrono.nodiscard_extensions.compile.pass.cpp @@ -50,6 +50,7 @@ void test() { { tz.name(); + tz.get_info(std::chrono::sys_seconds{}); operator==(tz, tz); operator<=>(tz, tz); } diff --git a/libcxx/test/libcxx/diagnostics/chrono.nodiscard_extensions.verify.cpp b/libcxx/test/libcxx/diagnostics/chrono.nodiscard_extensions.verify.cpp index 8795a4e..e88c176 100644 --- a/libcxx/test/libcxx/diagnostics/chrono.nodiscard_extensions.verify.cpp +++ b/libcxx/test/libcxx/diagnostics/chrono.nodiscard_extensions.verify.cpp @@ -47,7 +47,9 @@ void test() { crno::remote_version(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} { + std::chrono::sys_seconds s{}; tz.name(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} + tz.get_info(s); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} operator==(tz, tz); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} operator<=>(tz, tz); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} } diff --git a/libcxx/test/libcxx/time/time.zone/time.zone.timezone/time.zone.members/get_info.sys_time.pass.cpp b/libcxx/test/libcxx/time/time.zone/time.zone.timezone/time.zone.members/get_info.sys_time.pass.cpp new file mode 100644 index 0000000..194f582 --- /dev/null +++ b/libcxx/test/libcxx/time/time.zone/time.zone.timezone/time.zone.members/get_info.sys_time.pass.cpp @@ -0,0 +1,199 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: no-filesystem, no-localization, no-tzdb + +// XFAIL: libcpp-has-no-incomplete-tzdb +// XFAIL: availability-tzdb-missing + +// <chrono> + +// class time_zone; + +// template <class _Duration> +// sys_info get_info(const sys_time<_Duration>& time) const; + +// tests the parts not validated in the public test +// - Validates a zone with an UNTIL in its last continuation is corrupt +// - The formatting of the FORMAT field's constrains +// - Formatting of "%z", this is valid but not present in the actual database + +#include <algorithm> +#include <cassert> +#include <fstream> +#include <chrono> +#include <format> + +#include "test_macros.h" +#include "assert_macros.h" +#include "concat_macros.h" +#include "filesystem_test_helper.h" +#include "test_tzdb.h" + +/***** ***** HELPERS ***** *****/ + +scoped_test_env env; +[[maybe_unused]] const std::filesystem::path dir = env.create_dir("zoneinfo"); +const std::filesystem::path file = env.create_file("zoneinfo/tzdata.zi"); + +std::string_view std::chrono::__libcpp_tzdb_directory() { + static std::string result = dir.string(); + return result; +} + +static void write(std::string_view input) { + static int version = 0; + + std::ofstream f{file}; + f << "# version " << version++ << '\n'; + f.write(input.data(), input.size()); +} + +static const std::chrono::tzdb& parse(std::string_view input) { + write(input); + return std::chrono::reload_tzdb(); +} + +[[nodiscard]] static std::chrono::sys_seconds to_sys_seconds(int year) { + std::chrono::year_month_day result{std::chrono::year{year}, std::chrono::January, std::chrono::day{1}}; + + return std::chrono::time_point_cast<std::chrono::seconds>(static_cast<std::chrono::sys_days>(result)); +} + +static void test_exception([[maybe_unused]] std::string_view input, [[maybe_unused]] std::string_view what) { +#ifndef TEST_HAS_NO_EXCEPTIONS + const std::chrono::tzdb& tzdb = parse(input); + const std::chrono::time_zone* tz = tzdb.locate_zone("Format"); + TEST_VALIDATE_EXCEPTION( + std::runtime_error, + [&]([[maybe_unused]] const std::runtime_error& e) { + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + TEST_IGNORE_NODISCARD tz->get_info(to_sys_seconds(2000))); +#endif // TEST_HAS_NO_EXCEPTIONS +} + +static void zone_without_until_entry() { +#ifndef TEST_HAS_NO_EXCEPTIONS + const std::chrono::tzdb& tzdb = parse( + R"( +Z America/Paramaribo -3:40:40 - LMT 1911 +-3:40:52 - PMT 1935 +-3:40:36 - PMT 1945 O +-3:30 - -0330 1984 O +# -3 - -03 Commented out so the last entry has an UNTIL field. +)"); + const std::chrono::time_zone* tz = tzdb.locate_zone("America/Paramaribo"); + + TEST_IGNORE_NODISCARD tz->get_info(to_sys_seconds(1984)); + TEST_VALIDATE_EXCEPTION( + std::runtime_error, + [&]([[maybe_unused]] const std::runtime_error& e) { + std::string what = "tzdb: corrupt db"; + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + TEST_IGNORE_NODISCARD tz->get_info(to_sys_seconds(1985))); +#endif // TEST_HAS_NO_EXCEPTIONS +} + +static void invalid_format() { + test_exception( + R"( +R F 2000 max - Jan 5 0 0 foo +Z Format 0 F %zandfoo)", + "corrupt tzdb FORMAT field: %z should be the entire contents, instead contains '%zandfoo'"); + + test_exception( + R"( +R F 2000 max - Jan 5 0 0 foo +Z Format 0 F %q)", + "corrupt tzdb FORMAT field: invalid sequence '%q' found, expected %s or %z"); + + test_exception( + R"( +R F 2000 max - Jan 5 0 0 foo +Z Format 0 F !)", + "corrupt tzdb FORMAT field: invalid character '!' found, expected +, -, or an alphanumeric value"); + + test_exception( + R"( +R F 2000 max - Jan 5 0 0 foo +Z Format 0 F @)", + "corrupt tzdb FORMAT field: invalid character '@' found, expected +, -, or an alphanumeric value"); + + test_exception( + R"( +R F 2000 max - Jan 5 0 0 foo +Z Format 0 F $)", + "corrupt tzdb FORMAT field: invalid character '$' found, expected +, -, or an alphanumeric value"); + + test_exception( + R"( +R F 1970 max - Jan 5 0 0 foo +Z Format 0 F %)", + "corrupt tzdb FORMAT field: input ended with the start of the escape sequence '%'"); + + test_exception( + R"( +R F 2000 max - Jan 5 0 0 - +Z Format 0 F %s)", + "corrupt tzdb FORMAT field: result is empty"); +} + +static void test_abbrev(std::string_view input, std::string_view expected) { + const std::chrono::tzdb& tzdb = parse(input); + const std::chrono::time_zone* tz = tzdb.locate_zone("Format"); + std::string result = tz->get_info(to_sys_seconds(2000)).abbrev; + TEST_LIBCPP_REQUIRE(result == expected, TEST_WRITE_CONCATENATED("\nExpected ", expected, "\nActual ", result, '\n')); +} + +// This format is valid, however is not used in the tzdata.zi. +static void percentage_z_format() { + test_abbrev( + R"( +R F 1999 max - Jan 5 0 0 foo +Z Format 0 F %z)", + "+00"); + + test_abbrev( + R"( +R F 1999 max - Jan 5 0 1 foo +Z Format 0 F %z)", + "+01"); + + test_abbrev( + R"( +R F 1999 max - Jan 5 0 -1 foo +Z Format 0 F %z)", + "-01"); + + test_abbrev( + R"( +R F 1999 max - Jan 5 0 0 foo +Z Format 0:45 F %z)", + "+0045"); + + test_abbrev( + R"( +R F 1999 max - Jan 5 0 -1 foo +Z Format 0:45 F %z)", + "-0015"); +} + +int main(int, const char**) { + zone_without_until_entry(); + invalid_format(); + percentage_z_format(); + + return 0; +} diff --git a/libcxx/test/libcxx/time/time.zone/time.zone.timezone/time.zone.members/get_info.sys_time.rule_selection.pass.cpp b/libcxx/test/libcxx/time/time.zone/time.zone.timezone/time.zone.members/get_info.sys_time.rule_selection.pass.cpp new file mode 100644 index 0000000..accd5bc --- /dev/null +++ b/libcxx/test/libcxx/time/time.zone/time.zone.timezone/time.zone.members/get_info.sys_time.rule_selection.pass.cpp @@ -0,0 +1,185 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: no-filesystem, no-localization, no-tzdb + +// XFAIL: libcpp-has-no-incomplete-tzdb +// XFAIL: availability-tzdb-missing + +// <chrono> + +// class time_zone; + +// template <class _Duration> +// sys_info get_info(const sys_time<_Duration>& time) const; + +// The time zone database contains of the following entries +// - Zones, +// - Rules, +// - Links, and +// - Leapseconds. +// +// The public tzdb struct stores all entries except the Rules. How +// implementations keep track of the Rules is not specified. When the sys_info +// for a time_zone is requested it needs to use the correct Rules. This lookup +// cannot rely on 'get_tzdb()` since that returns the most recently loaded +// database. +// +// A reload could change the rules of a time zone or the time zone could no +// longer be present in the current database. These two conditions are tested. +// +// It is possible the tzdb entry has been removed by the user from the tzdb_list +// after a reload. This is UB and not tested. + +#include <cassert> +#include <fstream> +#include <chrono> + +#include "test_macros.h" +#include "assert_macros.h" +#include "concat_macros.h" +#include "filesystem_test_helper.h" +#include "test_tzdb.h" + +/***** ***** HELPERS ***** *****/ + +scoped_test_env env; +[[maybe_unused]] const std::filesystem::path dir = env.create_dir("zoneinfo"); +const std::filesystem::path file = env.create_file("zoneinfo/tzdata.zi"); + +std::string_view std::chrono::__libcpp_tzdb_directory() { + static std::string result = dir.string(); + return result; +} + +static void write(std::string_view input) { + static int version = 0; + + std::ofstream f{file}; + f << "# version " << version++ << '\n'; + f.write(input.data(), input.size()); +} + +static const std::chrono::tzdb& parse(std::string_view input) { + write(input); + return std::chrono::reload_tzdb(); +} + +[[nodiscard]] static std::chrono::sys_seconds to_sys_seconds( + std::chrono::year year, + std::chrono::month month, + std::chrono::day day, + std::chrono::hours h = std::chrono::hours(0), + std::chrono::minutes m = std::chrono::minutes{0}, + std::chrono::seconds s = std::chrono::seconds{0}) { + std::chrono::year_month_day result{year, month, day}; + + return std::chrono::time_point_cast<std::chrono::seconds>(static_cast<std::chrono::sys_days>(result)) + h + m + s; +} + +static void assert_equal(const std::chrono::sys_info& lhs, const std::chrono::sys_info& rhs) { + TEST_REQUIRE(lhs.begin == rhs.begin, + TEST_WRITE_CONCATENATED("\nBegin:\nExpected output ", lhs.begin, "\nActual output ", rhs.begin, '\n')); + TEST_REQUIRE(lhs.end == rhs.end, + TEST_WRITE_CONCATENATED("\nEnd:\nExpected output ", lhs.end, "\nActual output ", rhs.end, '\n')); + TEST_REQUIRE( + lhs.offset == rhs.offset, + TEST_WRITE_CONCATENATED("\nOffset:\nExpected output ", lhs.offset, "\nActual output ", rhs.offset, '\n')); + TEST_REQUIRE(lhs.save == rhs.save, + TEST_WRITE_CONCATENATED("\nSave:\nExpected output ", lhs.save, "\nActual output ", rhs.save, '\n')); + TEST_REQUIRE( + lhs.abbrev == rhs.abbrev, + TEST_WRITE_CONCATENATED("\nAbbrev:\nExpected output ", lhs.abbrev, "\nActual output ", rhs.abbrev, '\n')); +} + +/***** ***** TESTS ***** *****/ + +int main(int, const char**) { + using namespace std::literals::chrono_literals; + + // DST starts on the first of March. + const std::chrono::tzdb& tzdb_1 = parse( + R"( +Z Test 0 - LMT 1900 +0 Rule %s + +R Rule 1900 max - Mar 1 2u 1 Summer +R Rule 1900 max - Oct 1 2u 0 Winter +)"); + + const std::chrono::time_zone* tz_1 = tzdb_1.locate_zone("Test"); + assert_equal( + std::chrono::sys_info( + to_sys_seconds(1901y, std::chrono::March, 1d, 2h), + to_sys_seconds(1901y, std::chrono::October, 1d, 2h), + 1h, + 60min, + "Summer"), + tz_1->get_info(to_sys_seconds(1901y, std::chrono::March, 1d, 2h))); + + // The DST start changes from the first of March to the first of April. + const std::chrono::tzdb& tzdb_2 = parse( + R"( +Z Test 0 - LMT 1900 +0 Rule %s + +R Rule 1900 max - Apr 1 2u 1 Summer +R Rule 1900 max - Oct 1 2u 0 Winter +)"); + + const std::chrono::time_zone* tz_2 = tzdb_2.locate_zone("Test"); + assert_equal( + std::chrono::sys_info( + to_sys_seconds(1900y, std::chrono::October, 1d, 2h), + to_sys_seconds(1901y, std::chrono::April, 1d, 2h), + 0s, + 0min, + "Winter"), + tz_2->get_info(to_sys_seconds(1901y, std::chrono::March, 1d, 2h))); + + // Validate when using tz_1 the DST still starts on the first of March. + assert_equal( + std::chrono::sys_info( + to_sys_seconds(1901y, std::chrono::March, 1d, 2h), + to_sys_seconds(1901y, std::chrono::October, 1d, 2h), + 1h, + 60min, + "Summer"), + tz_1->get_info(to_sys_seconds(1901y, std::chrono::March, 1d, 2h))); + + // The zone Test is no longer present + [[maybe_unused]] const std::chrono::tzdb& tzdb_3 = parse("Z Etc/UTC 0 - UTC"); +#ifndef TEST_HAS_NO_EXCEPTIONS + TEST_VALIDATE_EXCEPTION( + std::runtime_error, + [&]([[maybe_unused]] const std::runtime_error& e) { + std::string what = "tzdb: requested time zone not found"; + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + TEST_IGNORE_NODISCARD tzdb_3.locate_zone("Test")); +#endif // TEST_HAS_NO_EXCEPTIONS + + // Search the zone Test in the original version 1 of the TZDB. + // This database should be unaffected by the removal in version 3. + tz_1 = tzdb_1.locate_zone("Test"); + + // Validate the rules still uses version 1's DST switch in March. + assert_equal( + std::chrono::sys_info( + to_sys_seconds(1901y, std::chrono::March, 1d, 2h), + to_sys_seconds(1901y, std::chrono::October, 1d, 2h), + 1h, + 60min, + "Summer"), + tz_1->get_info(to_sys_seconds(1901y, std::chrono::March, 1d, 2h))); + + return 0; +} diff --git a/libcxx/test/libcxx/transitive_includes/cxx23.csv b/libcxx/test/libcxx/transitive_includes/cxx23.csv index 69429b5..9ae422a 100644 --- a/libcxx/test/libcxx/transitive_includes/cxx23.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx23.csv @@ -80,7 +80,6 @@ chrono cstring chrono ctime chrono cwchar chrono forward_list -chrono initializer_list chrono limits chrono new chrono optional diff --git a/libcxx/test/libcxx/transitive_includes/cxx26.csv b/libcxx/test/libcxx/transitive_includes/cxx26.csv index 69429b5..9ae422a 100644 --- a/libcxx/test/libcxx/transitive_includes/cxx26.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx26.csv @@ -80,7 +80,6 @@ chrono cstring chrono ctime chrono cwchar chrono forward_list -chrono initializer_list chrono limits chrono new chrono optional diff --git a/libcxx/test/std/time/time.zone/time.zone.info/time.zone.info.sys/sys_info.members.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.info/time.zone.info.sys/sys_info.members.pass.cpp new file mode 100644 index 0000000..2510792 --- /dev/null +++ b/libcxx/test/std/time/time.zone/time.zone.info/time.zone.info.sys/sys_info.members.pass.cpp @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 + +// XFAIL: libcpp-has-no-incomplete-tzdb + +// <chrono> + +// struct sys_info { +// sys_seconds begin; +// sys_seconds end; +// seconds offset; +// minutes save; +// string abbrev; +// }; + +// Validates whether: +// - The members are present as non-const members. +// - The struct is an aggregate. + +#include <chrono> +#include <string> +#include <type_traits> + +int main(int, const char**) { + static_assert(std::is_aggregate_v<std::chrono::sys_info>); + + std::chrono::sys_info sys_info{ + .begin = std::chrono::sys_seconds::min(), + .end = std::chrono::sys_seconds::max(), + .offset = std::chrono::seconds(0), + .save = std::chrono::minutes(0), + .abbrev = "UTC"}; + + [[maybe_unused]] std::chrono::sys_seconds& begin = sys_info.begin; + [[maybe_unused]] std::chrono::sys_seconds& end = sys_info.end; + [[maybe_unused]] std::chrono::seconds& offset = sys_info.offset; + [[maybe_unused]] std::chrono::minutes& save = sys_info.save; + [[maybe_unused]] std::string& abbrev = sys_info.abbrev; + + return 0; +} diff --git a/libcxx/test/std/time/time.zone/time.zone.timezone/time.zone.members/get_info.sys_time.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.timezone/time.zone.members/get_info.sys_time.pass.cpp new file mode 100644 index 0000000..2ad4089 --- /dev/null +++ b/libcxx/test/std/time/time.zone/time.zone.timezone/time.zone.members/get_info.sys_time.pass.cpp @@ -0,0 +1,1374 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: no-filesystem, no-localization, no-tzdb + +// XFAIL: libcpp-has-no-incomplete-tzdb +// XFAIL: availability-tzdb-missing + +// <chrono> + +// class time_zone; + +// template <class _Duration> +// sys_info get_info(const sys_time<_Duration>& time) const; + +// This test uses the system provided database. This makes the test portable, +// but may cause failures when the database information changes. Historic data +// may change if new facts are uncovered, future data may change when regions +// change their time zone or daylight saving time. Most tests will not look in +// the future to attempt to avoid issues. All tests list the data on which they +// are based, this makes debugging easier upon failure; including to see whether +// the provided data has not been changed +// +// +// The data in the tests can be validated by using the zdump tool. For +// example +// zdump -v Asia/Hong_Kong +// show all transistions in the Hong Kong time zone. Or +// zdump -c1970,1980 -v Asia/Hong_Kong +// shows all transitions in Hong Kong between 1970 and 1980. + +#include <algorithm> +#include <cassert> +#include <chrono> +#include <format> + +#include "test_macros.h" +#include "assert_macros.h" +#include "concat_macros.h" + +/***** ***** HELPERS ***** *****/ + +[[nodiscard]] static std::chrono::sys_seconds to_sys_seconds( + std::chrono::year year, + std::chrono::month month, + std::chrono::day day, + std::chrono::hours h = std::chrono::hours(0), + std::chrono::minutes m = std::chrono::minutes{0}, + std::chrono::seconds s = std::chrono::seconds{0}) { + std::chrono::year_month_day result{year, month, day}; + + return std::chrono::time_point_cast<std::chrono::seconds>(static_cast<std::chrono::sys_days>(result)) + h + m + s; +} + +static void assert_equal(const std::chrono::sys_info& lhs, const std::chrono::sys_info& rhs) { + TEST_REQUIRE(lhs.begin == rhs.begin, + TEST_WRITE_CONCATENATED("\nBegin:\nExpected output ", lhs.begin, "\nActual output ", rhs.begin, '\n')); + TEST_REQUIRE(lhs.end == rhs.end, + TEST_WRITE_CONCATENATED("\nEnd:\nExpected output ", lhs.end, "\nActual output ", rhs.end, '\n')); + TEST_REQUIRE( + lhs.offset == rhs.offset, + TEST_WRITE_CONCATENATED("\nOffset:\nExpected output ", lhs.offset, "\nActual output ", rhs.offset, '\n')); + TEST_REQUIRE(lhs.save == rhs.save, + TEST_WRITE_CONCATENATED("\nSave:\nExpected output ", lhs.save, "\nActual output ", rhs.save, '\n')); + TEST_REQUIRE( + lhs.abbrev == rhs.abbrev, + TEST_WRITE_CONCATENATED("\nAbbrev:\nExpected output ", lhs.abbrev, "\nActual output ", rhs.abbrev, '\n')); +} + +static void assert_equal(std::string_view expected, const std::chrono::sys_info& value) { + // Note the output of operator<< is implementation defined, use this + // format to keep the test portable. + std::string result = std::format( + "[{}, {}) {:%T} {:%Q%q} {}", + value.begin, + value.end, + std::chrono::hh_mm_ss{value.offset}, + value.save, + value.abbrev); + + TEST_REQUIRE(expected == result, + TEST_WRITE_CONCATENATED("\nExpected output ", expected, "\nActual output ", result, '\n')); +} + +static void +assert_range(std::string_view expected, const std::chrono::sys_info& begin, const std::chrono::sys_info& end) { + assert_equal(expected, begin); + assert_equal(expected, end); +} + +static void assert_cycle( + std::string_view expected_1, + const std::chrono::sys_info& begin_1, + const std::chrono::sys_info& end_1, + std::string_view expected_2, + const std::chrono::sys_info& begin_2, + const std::chrono::sys_info& end_2 + +) { + assert_range(expected_1, begin_1, end_1); + assert_range(expected_2, begin_2, end_2); +} + +/***** ***** TESTS ***** *****/ + +static void test_gmt() { + // Simple zone always valid, no rule entries, lookup using a link. + // L Etc/GMT GMT + // Z Etc/GMT 0 - GMT + + const std::chrono::time_zone* tz = std::chrono::locate_zone("GMT"); + + assert_equal( + std::chrono::sys_info( + std::chrono::sys_seconds::min(), + std::chrono::sys_seconds::max(), + std::chrono::seconds(0), + std::chrono::minutes(0), + "GMT"), + tz->get_info(std::chrono::sys_seconds::min())); + assert_equal( + std::chrono::sys_info( + std::chrono::sys_seconds::min(), + std::chrono::sys_seconds::max(), + std::chrono::seconds(0), + std::chrono::minutes(0), + "GMT"), + tz->get_info(std::chrono::sys_seconds(std::chrono::seconds{0}))); + + assert_equal( + std::chrono::sys_info( + std::chrono::sys_seconds::min(), + std::chrono::sys_seconds::max(), + std::chrono::seconds(0), + std::chrono::minutes(0), + "GMT"), + tz->get_info(std::chrono::sys_seconds::max() - std::chrono::seconds{1})); // max is not valid +} + +static void test_durations() { + // Doesn't test a location, instead tests whether different duration + // specializations work. + const std::chrono::time_zone* tz = std::chrono::locate_zone("GMT"); + + // Using the GMT zone means every call gives the same result. + std::chrono::sys_info expected( + std::chrono::sys_seconds::min(), + std::chrono::sys_seconds::max(), + std::chrono::seconds(0), + std::chrono::minutes(0), + "GMT"); + + assert_equal(expected, tz->get_info(std::chrono::sys_time<std::chrono::nanoseconds>{})); + assert_equal(expected, tz->get_info(std::chrono::sys_time<std::chrono::microseconds>{})); + assert_equal(expected, tz->get_info(std::chrono::sys_time<std::chrono::milliseconds>{})); + assert_equal(expected, tz->get_info(std::chrono::sys_time<std::chrono::seconds>{})); + assert_equal(expected, tz->get_info(std::chrono::sys_time<std::chrono::minutes>{})); + assert_equal(expected, tz->get_info(std::chrono::sys_time<std::chrono::minutes>{})); + assert_equal(expected, tz->get_info(std::chrono::sys_time<std::chrono::hours>{})); + assert_equal(expected, tz->get_info(std::chrono::sys_time<std::chrono::days>{})); + assert_equal(expected, tz->get_info(std::chrono::sys_time<std::chrono::weeks>{})); + assert_equal(expected, tz->get_info(std::chrono::sys_time<std::chrono::months>{})); + assert_equal(expected, tz->get_info(std::chrono::sys_time<std::chrono::years>{})); +} + +static void test_indian_kerguelen() { + // One change, no rules, no dst changes. + + // Z Indian/Kerguelen 0 - -00 1950 + // 5 - +05 + + const std::chrono::time_zone* tz = std::chrono::locate_zone("Indian/Kerguelen"); + + std::chrono::sys_seconds transition = + to_sys_seconds(std::chrono::year(1950), std::chrono::January, std::chrono::day(1)); + + assert_equal( + std::chrono::sys_info( + std::chrono::sys_seconds::min(), // + transition, // + std::chrono::seconds(0), // + std::chrono::minutes(0), // + "-00"), // + tz->get_info(std::chrono::sys_seconds::min())); + + assert_equal( + std::chrono::sys_info( + std::chrono::sys_seconds::min(), // + transition, // + std::chrono::seconds(0), // + std::chrono::minutes(0), // + "-00"), // + tz->get_info(transition - std::chrono::seconds{1})); + + assert_equal( + std::chrono::sys_info( + transition, // + std::chrono::sys_seconds::max(), // + std::chrono::hours(5), // + std::chrono::minutes(0), // + "+05"), // + tz->get_info(transition)); +} + +static void test_antarctica_syowa() { + // One change, no rules, no dst changes + // This change uses an ON field with a day number + // + // There don't seem to be rule-less zones that use last day or a + // contrained day + + // Z Antarctica/Syowa 0 - -00 1957 Ja 29 + // 3 - +03 + + const std::chrono::time_zone* tz = std::chrono::locate_zone("Antarctica/Syowa"); + + std::chrono::sys_seconds transition = + to_sys_seconds(std::chrono::year(1957), std::chrono::January, std::chrono::day(29)); + + assert_equal( + std::chrono::sys_info( + std::chrono::sys_seconds::min(), // + transition, // + std::chrono::seconds(0), // + std::chrono::minutes(0), // + "-00"), // + tz->get_info(std::chrono::sys_seconds::min())); + + assert_equal( + std::chrono::sys_info( + std::chrono::sys_seconds::min(), // + transition, // + std::chrono::seconds(0), // + std::chrono::minutes(0), // + "-00"), // + tz->get_info(transition - std::chrono::seconds(1))); + + assert_equal( + std::chrono::sys_info( + transition, // + std::chrono::sys_seconds::max(), // + std::chrono::hours(3), // + std::chrono::minutes(0), // + "+03"), // + tz->get_info(transition)); +} + +static void test_asia_hong_kong() { + // A more typical entry, first some hard-coded entires and then at the + // end a rules based entry. This rule is valid for its entire period + // + // Z Asia/Hong_Kong 7:36:42 - LMT 1904 O 30 0:36:42 + // 8 - HKT 1941 Jun 15 3 + // 8 1 HKST 1941 O 1 4 + // 8 0:30 HKWT 1941 D 25 + // 9 - JST 1945 N 18 2 + // 8 HK HK%sT + // + // R HK 1946 o - Ap 21 0 1 S + // R HK 1946 o - D 1 3:30s 0 - + // R HK 1947 o - Ap 13 3:30s 1 S + // R HK 1947 o - N 30 3:30s 0 - + // R HK 1948 o - May 2 3:30s 1 S + // R HK 1948 1952 - O Su>=28 3:30s 0 - + // R HK 1949 1953 - Ap Su>=1 3:30 1 S + // R HK 1953 1964 - O Su>=31 3:30 0 - + // R HK 1954 1964 - Mar Su>=18 3:30 1 S + // R HK 1965 1976 - Ap Su>=16 3:30 1 S + // R HK 1965 1976 - O Su>=16 3:30 0 - + // R HK 1973 o - D 30 3:30 1 S + // R HK 1979 o - May 13 3:30 1 S + // R HK 1979 o - O 21 3:30 0 - + + using namespace std::literals::chrono_literals; + const std::chrono::time_zone* tz = std::chrono::locate_zone("Asia/Hong_Kong"); + + assert_equal( + std::chrono::sys_info( + std::chrono::sys_seconds::min(), + to_sys_seconds(1904y, std::chrono::October, 29d, 17h), // 7:36:42 - LMT 1904 O 30 0:36:42 + 7h + 36min + 42s, + 0min, + "LMT"), + tz->get_info(std::chrono::sys_seconds::min())); + + assert_equal( + std::chrono::sys_info( + std::chrono::sys_seconds::min(), + to_sys_seconds(1904y, std::chrono::October, 29d, 17h), // 7:36:42 - LMT 1904 O 30 0:36:42 + 7h + 36min + 42s, + 0min, + "LMT"), + tz->get_info(to_sys_seconds(1904y, std::chrono::October, 29d, 16h, 59min, 59s))); + + assert_range("[1904-10-29 17:00:00, 1941-06-14 19:00:00) 08:00:00 0min HKT", // 8 - HKT 1941 Jun 15 3 + tz->get_info(to_sys_seconds(1904y, std::chrono::October, 29d, 17h)), + tz->get_info(to_sys_seconds(1941y, std::chrono::June, 14d, 18h, 59min, 59s))); + + assert_range("[1941-06-14 19:00:00, 1941-09-30 19:00:00) 09:00:00 60min HKST", // 8 1 HKST 1941 O 1 4 + tz->get_info(to_sys_seconds(1941y, std::chrono::June, 14d, 19h)), + tz->get_info(to_sys_seconds(1941y, std::chrono::September, 30d, 18h, 59min, 59s))); + + assert_range("[1941-09-30 19:00:00, 1941-12-24 15:30:00) 08:30:00 30min HKWT", // 8 0:30 HKWT 1941 D 25 + tz->get_info(to_sys_seconds(1941y, std::chrono::September, 30d, 19h)), + tz->get_info(to_sys_seconds(1941y, std::chrono::December, 24d, 15h, 29min, 59s))); + + assert_range("[1941-12-24 15:30:00, 1945-11-17 17:00:00) 09:00:00 0min JST", // 9 - JST 1945 N 18 2 + tz->get_info(to_sys_seconds(1941y, std::chrono::December, 24d, 15h, 30min)), + tz->get_info(to_sys_seconds(1945y, std::chrono::November, 17d, 16h, 59min, 59s))); + + assert_range("[1945-11-17 17:00:00, 1946-04-20 16:00:00) 08:00:00 0min HKT", // 8 HK%sT + tz->get_info(to_sys_seconds(1945y, std::chrono::November, 17d, 17h)), + tz->get_info(to_sys_seconds(1946y, std::chrono::April, 20d, 15h, 59min, 59s))); + + assert_cycle( // 8 HK%sT + "[1946-04-20 16:00:00, 1946-11-30 19:30:00) 09:00:00 60min HKST", + tz->get_info(to_sys_seconds(1946y, std::chrono::April, 20d, 16h)), // 1946 o Ap 21 0 1 S + tz->get_info(to_sys_seconds(1946y, std::chrono::November, 30d, 19h, 29min, 59s)), // 1946 o D 1 3:30s 0 - + "[1946-11-30 19:30:00, 1947-04-12 19:30:00) 08:00:00 0min HKT", + tz->get_info(to_sys_seconds(1946y, std::chrono::November, 30d, 19h, 30min)), // 1946 o D 1 3:30s 0 - + tz->get_info(to_sys_seconds(1947y, std::chrono::April, 12d, 19h, 29min, 59s))); // 1947 o Ap 13 3:30s 1 S + + assert_cycle( // 8 HK%sT + "[1947-04-12 19:30:00, 1947-11-29 19:30:00) 09:00:00 60min HKST", + tz->get_info(to_sys_seconds(1947y, std::chrono::April, 12d, 19h, 30min)), // 1947 o Ap 13 3:30s 1 S + tz->get_info(to_sys_seconds(1947y, std::chrono::November, 29d, 19h, 29min, 59s)), // 1947 o N 30 3:30s 0 - + "[1947-11-29 19:30:00, 1948-05-01 19:30:00) 08:00:00 0min HKT", + tz->get_info(to_sys_seconds(1947y, std::chrono::November, 29d, 19h, 30min)), // 1947 o N 30 3:30s 0 - + tz->get_info(to_sys_seconds(1948y, std::chrono::May, 1d, 19h, 29min, 59s))); // 1948 o May 2 3:30s 1 S + + assert_cycle( // 8 HK%sT + "[1948-05-01 19:30:00, 1948-10-30 19:30:00) 09:00:00 60min HKST", + tz->get_info(to_sys_seconds(1948y, std::chrono::May, 1d, 19h, 30min)), // 1948 o May 2 3:30s 1 S + tz->get_info(to_sys_seconds(1948y, std::chrono::October, 30d, 19h, 29min, 59s)), // 1948 1952 O Su>=28 3:30s 0 - + "[1948-10-30 19:30:00, 1949-04-02 19:30:00) 08:00:00 0min HKT", + tz->get_info(to_sys_seconds(1948y, std::chrono::October, 30d, 19h, 30min)), // 1948 1952 O Su>=28 3:30s 0 - + tz->get_info(to_sys_seconds(1949y, std::chrono::April, 2d, 19h, 29min, 59s))); // 1949 1953 Ap Su>=1 3:30 1 S + + assert_cycle( // 8 HK%sT + "[1949-04-02 19:30:00, 1949-10-29 19:30:00) 09:00:00 60min HKST", + tz->get_info(to_sys_seconds(1949y, std::chrono::April, 2d, 19h, 30min)), // 1949 1953 Ap Su>=1 3:30 1 S + tz->get_info(to_sys_seconds(1949y, std::chrono::October, 29d, 19h, 29min, 59s)), // 1948 1952 O Su>=28 3:30s 0 + "[1949-10-29 19:30:00, 1950-04-01 19:30:00) 08:00:00 0min HKT", + tz->get_info(to_sys_seconds(1949y, std::chrono::October, 29d, 19h, 30min)), // 1948 1952 O Su>=28 3:30s 0 + tz->get_info(to_sys_seconds(1950y, std::chrono::April, 1d, 19h, 29min, 59s))); // 1949 1953 Ap Su>=1 3:30 1 S + + assert_range( + "[1953-10-31 18:30:00, 1954-03-20 19:30:00) 08:00:00 0min HKT", + tz->get_info(to_sys_seconds(1953y, std::chrono::October, 31d, 18h, 30min)), // 1953 1964 - O Su>=31 3:30 0 - + tz->get_info(to_sys_seconds(1954y, std::chrono::March, 20d, 19h, 29min, 59s))); // 1954 1964 - Mar Su>=18 3:30 1 S + + assert_cycle( // 8 HK%sT + "[1953-04-04 19:30:00, 1953-10-31 18:30:00) 09:00:00 60min HKST", + tz->get_info(to_sys_seconds(1953y, std::chrono::April, 4d, 19h, 30min)), // 1949 1953 Ap Su>=1 3:30 1 S + tz->get_info(to_sys_seconds(1953y, std::chrono::October, 31d, 18h, 29min, 59s)), // 1953 1964 - O Su>=31 3:30 0 - + "[1953-10-31 18:30:00, 1954-03-20 19:30:00) 08:00:00 0min HKT", + tz->get_info(to_sys_seconds(1953y, std::chrono::October, 31d, 18h, 30min)), // 1953 1964 - O Su>=31 3:30 0 - + tz->get_info(to_sys_seconds(1954y, std::chrono::March, 20d, 19h, 29min, 59s))); // 1954 1964 - Mar Su>=18 3:30 1 S + + assert_cycle( // 8 HK%sT + "[1972-04-15 19:30:00, 1972-10-21 18:30:00) 09:00:00 60min HKST", + tz->get_info(to_sys_seconds(1972y, std::chrono::April, 19d, 19h, 30min)), // 1965 1976 - Ap Su>=16 3:30 1 S + tz->get_info(to_sys_seconds(1972y, std::chrono::October, 21d, 18h, 29min, 59s)), // 1965 1976 - O Su>=16 3:30 0 - + "[1972-10-21 18:30:00, 1973-04-21 19:30:00) 08:00:00 0min HKT", + tz->get_info(to_sys_seconds(1972y, std::chrono::October, 21d, 18h, 30min)), // 1965 1976 - O Su>=16 3:30 0 - + tz->get_info(to_sys_seconds(1973y, std::chrono::April, 21d, 19h, 29min, 59s))); // 1965 1976 - Ap Su>=16 3:30 1 S + + assert_range( // 8 HK%sT + "[1973-04-21 19:30:00, 1973-10-20 18:30:00) 09:00:00 60min HKST", + tz->get_info(to_sys_seconds(1973y, std::chrono::April, 21d, 19h, 30min)), // 1965 1976 - Ap Su>=16 3:30 1 S + tz->get_info(to_sys_seconds(1973y, std::chrono::October, 20d, 18h, 29min, 59s))); // 1965 1976 - O Su>=16 3:30 0 - + + assert_range( // 8 HK%sT, test "1973 o - D 30 3:30 1 S" + "[1973-10-20 18:30:00, 1973-12-29 19:30:00) 08:00:00 0min HKT", + tz->get_info(to_sys_seconds(1973y, std::chrono::October, 20d, 18h, 30min)), // 1965 1976 - O Su>=16 3:30 + tz->get_info(to_sys_seconds(1973y, std::chrono::December, 29d, 19h, 29min, 59s))); // 1973 o - D 30 3:30 1 S + + assert_range( // 8 HK%sT + "[1973-12-29 19:30:00, 1974-10-19 18:30:00) 09:00:00 60min HKST", + tz->get_info(to_sys_seconds(1973y, std::chrono::December, 29d, 19h, 30min)), // 1973 o - D 30 3:30 1 S + tz->get_info(to_sys_seconds(1974y, std::chrono::October, 19d, 18h, 29min, 59s))); // 1965 1976 - O Su>=16 3:30 + + assert_range( // 8 HK%sT, between 1973 and 1979 no rule is active so falls back to default + "[1976-04-17 19:30:00, 1976-10-16 18:30:00) 09:00:00 60min HKST", + tz->get_info(to_sys_seconds(1976y, std::chrono::April, 17d, 19h, 30min)), // 1965 1976 - Ap Su>=16 3:30 1 S + tz->get_info(to_sys_seconds(1976y, std::chrono::October, 16d, 18h, 29min, 59s))); // 1965 1976 - O Su>=16 3:30 0 - + + assert_range( // 8 HK%sT, between 1973 and 1979 no rule is active so falls back to default + "[1976-10-16 18:30:00, 1979-05-12 19:30:00) 08:00:00 0min HKT", + tz->get_info(to_sys_seconds(1976y, std::chrono::October, 16d, 18h, 30min)), // 1965 1976 - O Su>=16 3:30 0 - + tz->get_info(to_sys_seconds(1979y, std::chrono::May, 12d, 19h, 29min, 59s))); // 1979 o - May 13 3:30 1 S + + assert_range( // 8 HK%sT + "[1979-05-12 19:30:00, 1979-10-20 18:30:00) 09:00:00 60min HKST", + tz->get_info(to_sys_seconds(1979y, std::chrono::May, 12d, 19h, 30min)), // 1979 o - May 13 3:30 1 S + tz->get_info(to_sys_seconds(1979y, std::chrono::October, 20d, 18h, 29min, 59s))); // 1979 o - O 21 3:30 0 - + + assert_equal( + std::chrono::sys_info( + to_sys_seconds(1979y, std::chrono::October, 20d, 18h, 30min), + std::chrono::sys_seconds::max(), + 8h, + std::chrono::minutes(0), + "HKT"), + tz->get_info(to_sys_seconds(1979y, std::chrono::October, 20d, 18h, 30min))); + + assert_equal( + std::chrono::sys_info( + to_sys_seconds(1979y, std::chrono::October, 20d, 18h, 30min), + std::chrono::sys_seconds::max(), + 8h, + std::chrono::minutes(0), + "HKT"), + tz->get_info(std::chrono::sys_seconds::max() - std::chrono::seconds{1})); // max is not valid +} + +static void test_europe_berlin() { + // A more typical entry, first some hard-coded entires and then at the + // end a rules based entry. This rule is valid for its entire period + // + + // Z Europe/Berlin 0:53:28 - LMT 1893 Ap + // 1 c CE%sT 1945 May 24 2 + // 1 So CE%sT 1946 + // 1 DE CE%sT 1980 + // 1 E CE%sT + // + // R c 1916 o - Ap 30 23 1 S + // R c 1916 o - O 1 1 0 - + // R c 1917 1918 - Ap M>=15 2s 1 S + // R c 1917 1918 - S M>=15 2s 0 - + // R c 1940 o - Ap 1 2s 1 S + // R c 1942 o - N 2 2s 0 - + // R c 1943 o - Mar 29 2s 1 S + // R c 1943 o - O 4 2s 0 - + // R c 1944 1945 - Ap M>=1 2s 1 S + // R c 1944 o - O 2 2s 0 - + // R c 1945 o - S 16 2s 0 - + // R c 1977 1980 - Ap Su>=1 2s 1 S + // R c 1977 o - S lastSu 2s 0 - + // R c 1978 o - O 1 2s 0 - + // R c 1979 1995 - S lastSu 2s 0 - + // R c 1981 ma - Mar lastSu 2s 1 S + // R c 1996 ma - O lastSu 2s 0 - + // + // R So 1945 o - May 24 2 2 M + // R So 1945 o - S 24 3 1 S + // R So 1945 o - N 18 2s 0 - + // + // R DE 1946 o - Ap 14 2s 1 S + // R DE 1946 o - O 7 2s 0 - + // R DE 1947 1949 - O Su>=1 2s 0 - + // R DE 1947 o - Ap 6 3s 1 S + // R DE 1947 o - May 11 2s 2 M + // R DE 1947 o - Jun 29 3 1 S + // R DE 1948 o - Ap 18 2s 1 S + // R DE 1949 o - Ap 10 2s 1 S + // + // R E 1977 1980 - Ap Su>=1 1u 1 S + // R E 1977 o - S lastSu 1u 0 - + // R E 1978 o - O 1 1u 0 - + // R E 1979 1995 - S lastSu 1u 0 - + // R E 1981 ma - Mar lastSu 1u 1 S + // R E 1996 ma - O lastSu 1u 0 - + // + // Note the European Union decided to stop the seasonal change in + // 2021. In 2023 seasonal changes are still in effect. + + using namespace std::literals::chrono_literals; + const std::chrono::time_zone* tz = std::chrono::locate_zone("Europe/Berlin"); + + assert_equal( + std::chrono::sys_info( + std::chrono::sys_seconds::min(), + to_sys_seconds(1893y, std::chrono::March, 31d, 23h, 6min, 32s), // 0:53:28 - LMT 1893 Ap + 53min + 28s, + 0min, + "LMT"), + tz->get_info(std::chrono::sys_seconds::min())); + + assert_equal( + std::chrono::sys_info( + std::chrono::sys_seconds::min(), + to_sys_seconds(1893y, std::chrono::March, 31d, 23h, 6min, 32s), // 0:53:28 - LMT 1893 Ap + 53min + 28s, + 0min, + "LMT"), + tz->get_info(to_sys_seconds(1893y, std::chrono::March, 31d, 23h, 6min, 31s))); + + assert_range( + // 1 CE%sT before 1916 o - Ap 30 23 1 S + "[1893-03-31 23:06:32, 1916-04-30 22:00:00) 01:00:00 0min CET", + tz->get_info(to_sys_seconds(1893y, std::chrono::March, 31d, 23h, 6min, 32s)), + tz->get_info(to_sys_seconds(1916y, std::chrono::April, 30d, 21h, 59min, 59s))); + + assert_cycle( + // 1 CE%sT + "[1916-04-30 22:00:00, 1916-09-30 23:00:00) 02:00:00 60min CEST", + tz->get_info(to_sys_seconds(1916y, std::chrono::April, 30d, 22h)), // 1916 o - Ap 30 23 1 S + tz->get_info(to_sys_seconds(1916y, std::chrono::September, 30d, 22h, 59min, 59s)), // o - O 1 1 0 - + "[1916-09-30 23:00:00, 1917-04-16 01:00:00) 01:00:00 0min CET", + tz->get_info(to_sys_seconds(1916y, std::chrono::September, 30d, 23h)), // o - O 1 1 0 - + tz->get_info(to_sys_seconds(1917y, std::chrono::April, 16d, 0h, 59min, 59s))); // 1917 1918 - Ap M>=15 2s 1 S + + assert_cycle( + // 1 CE%sT + "[1917-04-16 01:00:00, 1917-09-17 01:00:00) 02:00:00 60min CEST", + tz->get_info(to_sys_seconds(1917y, std::chrono::April, 16d, 1h)), // 1917 1918 Ap M>=15 2s 1 S + tz->get_info(to_sys_seconds(1917y, std::chrono::September, 17d, 0h, 59min, 59s)), // 1917 1918 S M>=15 2s 0 - + "[1917-09-17 01:00:00, 1918-04-15 01:00:00) 01:00:00 0min CET", + tz->get_info(to_sys_seconds(1917y, std::chrono::September, 17d, 1h)), // 1917 1918 S M>=15 2s 0 - + tz->get_info(to_sys_seconds(1918y, std::chrono::April, 15d, 0h, 59min, 59s))); // 1917 1918 Ap M>=15 2s 1 S + + assert_cycle( + // 1 CE%sT (The cycle is more than 1 year) + "[1918-04-15 01:00:00, 1918-09-16 01:00:00) 02:00:00 60min CEST", + tz->get_info(to_sys_seconds(1918y, std::chrono::April, 15d, 1h)), // 1917 1918 Ap M>=15 2s 1 S + tz->get_info(to_sys_seconds(1918y, std::chrono::September, 16d, 0h, 59min, 59s)), // 1917 1918 S M>=15 2s 0 - + "[1918-09-16 01:00:00, 1940-04-01 01:00:00) 01:00:00 0min CET", + tz->get_info(to_sys_seconds(1918y, std::chrono::September, 16d, 1h)), // 1917 1918 S M>=15 2s 0 - + tz->get_info(to_sys_seconds(1940y, std::chrono::April, 1d, 0h, 59min, 59s))); // 1940 o Ap 1 2s 1 S + + assert_cycle( + // 1 CE%sT (The cycle is more than 1 year) + "[1940-04-01 01:00:00, 1942-11-02 01:00:00) 02:00:00 60min CEST", + tz->get_info(to_sys_seconds(1940y, std::chrono::April, 1d, 1h)), // 1940 o Ap 1 2s 1 S + tz->get_info(to_sys_seconds(1942y, std::chrono::November, 2d, 0h, 59min, 59s)), // 1942 o N 2 2s 0 - + "[1942-11-02 01:00:00, 1943-03-29 01:00:00) 01:00:00 0min CET", + tz->get_info(to_sys_seconds(1942y, std::chrono::November, 2d, 1h)), // 1942 o N 2 2s 0 - + tz->get_info(to_sys_seconds(1943y, std::chrono::March, 29d, 0h, 59min, 59s))); // 1943 o Mar 29 2s 1 S + + assert_range( + // Here the zone changes from c (C-Eur) to So (SovietZone). + // The rule c ends on 1945-09-16, instead it ends at the zone change date/time + // There is a tricky part in the time + // "1 c CE%sT" has an offset of 1 at the moment the rule + // ends there is a save of 60 minutes. This means the + // local offset to UTC is 2 hours. The rule ends at + // 1945-05-24 02:00:00 local time, which is + // 1945-05-24 00:00:00 UTC. + "[1945-04-02 01:00:00, 1945-05-24 00:00:00) 02:00:00 60min CEST", + tz->get_info(to_sys_seconds(1945y, std::chrono::April, 2d, 1h)), // 1 CE%sT & 1945 Ap M>=1 2s 1 S + tz->get_info(to_sys_seconds(1945y, std::chrono::May, 23d, 23h, 59min, 59s))); // 1 c CE%sT & 1945 May 24 2 + + assert_range( // -- + "[1945-05-24 00:00:00, 1945-09-24 00:00:00) 03:00:00 120min CEMT", + tz->get_info(to_sys_seconds(1945y, std::chrono::May, 24d)), // 1 c CE%sT & 1945 May 24 2 + tz->get_info(to_sys_seconds(1945y, std::chrono::September, 23d, 23h, 59min, 59s))); // 1945 o S 24 3 1 S + + assert_range( + // 1 c CE%sT 1945 May 24 2 + "[1945-09-24 00:00:00, 1945-11-18 01:00:00) 02:00:00 60min CEST", + tz->get_info(to_sys_seconds(1945y, std::chrono::September, 24d)), // 1945 o S 24 3 1 S + tz->get_info(to_sys_seconds(1945y, std::chrono::November, 18d, 0h, 59min, 59s))); // 1945 o N 18 2s 0 - + assert_range( // -- + // Merges 2 continuations + "[1945-11-18 01:00:00, 1946-04-14 01:00:00) 01:00:00 0min CET", + tz->get_info(to_sys_seconds(1945y, std::chrono::November, 18d, 1h)), // 1 c CE%sT & 1945 o N 18 2s 0 - + tz->get_info(to_sys_seconds(1946y, std::chrono::April, 14d, 0h, 59min, 59s))); // 1 So CE%sT & 1946 o Ap 14 2s 1 S + + assert_range( + // 1 DE CE%sT 1980 + "[1946-04-14 01:00:00, 1946-10-07 01:00:00) 02:00:00 60min CEST", + tz->get_info(to_sys_seconds(1946y, std::chrono::April, 14d, 1h)), // 1946 o Ap 14 2s 1 S + tz->get_info(to_sys_seconds(1946y, std::chrono::October, 7d, 0h, 59min, 59s))); // 1946 o O 7 2s 0 - + + // Note 1947 is an interesting year with 4 rules + // R DE 1947 1949 - O Su>=1 2s 0 - + // R DE 1947 o - Ap 6 3s 1 S + // R DE 1947 o - May 11 2s 2 M + // R DE 1947 o - Jun 29 3 1 S + assert_range( + // 1 DE CE%sT 1980 + "[1946-10-07 01:00:00, 1947-04-06 02:00:00) 01:00:00 0min CET", + tz->get_info(to_sys_seconds(1946y, std::chrono::October, 7d, 1h)), // 1946 o O 7 2s 0 - + tz->get_info(to_sys_seconds(1947y, std::chrono::April, 6d, 1h, 59min, 59s))); // 1947 o Ap 6 3s 1 S + + assert_range( + // 1 DE CE%sT 1980 + "[1947-04-06 02:00:00, 1947-05-11 01:00:00) 02:00:00 60min CEST", + tz->get_info(to_sys_seconds(1947y, std::chrono::April, 6d, 2h)), // 1947 o Ap 6 3s 1 S + tz->get_info(to_sys_seconds(1947y, std::chrono::May, 11d, 0h, 59min, 59s))); // 1947 o May 11 2s 2 M + + assert_range( + // 1 DE CE%sT 1980 + "[1947-05-11 01:00:00, 1947-06-29 00:00:00) 03:00:00 120min CEMT", + tz->get_info(to_sys_seconds(1947y, std::chrono::May, 11d, 1h)), // 1947 o May 11 2s 2 M + tz->get_info(to_sys_seconds(1947y, std::chrono::June, 28d, 23h, 59min, 59s))); // 1947 o Jun 29 3 1 S + + assert_cycle( + // 1 DE CE%sT 1980 + "[1947-06-29 00:00:00, 1947-10-05 01:00:00) 02:00:00 60min CEST", + tz->get_info(to_sys_seconds(1947y, std::chrono::June, 29d)), // 1947 o Jun 29 3 1 S + tz->get_info(to_sys_seconds(1947y, std::chrono::October, 5d, 0h, 59min, 59s)), // 1947 1949 O Su>=1 2s 0 - + "[1947-10-05 01:00:00, 1948-04-18 01:00:00) 01:00:00 0min CET", + tz->get_info(to_sys_seconds(1947y, std::chrono::October, 5d, 1h)), // 1947 1949 O Su>=1 2s 0 - + tz->get_info(to_sys_seconds(1948y, std::chrono::April, 18d, 0h, 59min, 59s))); // 1948 o Ap 18 2s 1 S + + assert_cycle( + // 1 DE CE%sT 1980 + "[1948-04-18 01:00:00, 1948-10-03 01:00:00) 02:00:00 60min CEST", + tz->get_info(to_sys_seconds(1948y, std::chrono::April, 18d, 1h)), // 1948 o Ap 18 2s 1 S + tz->get_info(to_sys_seconds(1948y, std::chrono::October, 3d, 0h, 59min, 59s)), // 1947 1949 O Su>=1 2s 0 - + "[1948-10-03 01:00:00, 1949-04-10 01:00:00) 01:00:00 0min CET", + tz->get_info(to_sys_seconds(1948y, std::chrono::October, 3d, 1h)), // 1947 1949 O Su>=1 2s 0 - + tz->get_info(to_sys_seconds(1949y, std::chrono::April, 10d, 0h, 59min, 59s))); // 1949 o Ap 10 2s 1 S + + assert_cycle( // Note the end time is in a different continuation. + "[1949-04-10 01:00:00, 1949-10-02 01:00:00) 02:00:00 60min CEST", // 1 DE CE%sT 1980 + tz->get_info(to_sys_seconds(1949y, std::chrono::April, 10d, 1h)), // 1949 o Ap 10 2s 1 S + tz->get_info(to_sys_seconds(1949y, std::chrono::October, 2d, 0h, 59min, 59s)), // 1947 1949 O Su>=1 2s 0 - + "[1949-10-02 01:00:00, 1980-04-06 01:00:00) 01:00:00 0min CET", + tz->get_info(to_sys_seconds(1949y, std::chrono::October, 2d, 1h)), // 1947 1949 O Su>=1 2s 0 - + tz->get_info( // 1 E CE%sT + to_sys_seconds(1980y, std::chrono::April, 6d, 0h, 59min, 59s))); // 1977 1980 Ap Su>=1 1u 1 S + + assert_cycle( + // 1 E CE%sT + "[2020-03-29 01:00:00, 2020-10-25 01:00:00) 02:00:00 60min CEST", + tz->get_info(to_sys_seconds(2020y, std::chrono::March, 29d, 1h)), // 1981 ma Mar lastSu 1u 1 S + tz->get_info(to_sys_seconds(2020y, std::chrono::October, 25d, 0h, 59min, 59s)), // 1996 ma O lastSu 1u 0 - + "[2020-10-25 01:00:00, 2021-03-28 01:00:00) 01:00:00 0min CET", + tz->get_info(to_sys_seconds(2020y, std::chrono::October, 25d, 1h)), // 1996 ma O lastSu 1u 0 - + tz->get_info(to_sys_seconds(2021y, std::chrono::March, 28d, 0h, 59min, 59s))); // 1981 ma Mar lastSu 1u 1 S + + assert_cycle( + // 1 E CE%sT + "[2021-03-28 01:00:00, 2021-10-31 01:00:00) 02:00:00 60min CEST", + tz->get_info(to_sys_seconds(2021y, std::chrono::March, 28d, 1h)), // 1981 ma Mar lastSu 1u 1 S + tz->get_info(to_sys_seconds(2021y, std::chrono::October, 31d, 0h, 59min, 59s)), // 1996 ma O lastSu 1u 0 - + "[2021-10-31 01:00:00, 2022-03-27 01:00:00) 01:00:00 0min CET", + tz->get_info(to_sys_seconds(2021y, std::chrono::October, 31d, 1h)), // 1996 ma O lastSu 1u 0 - + tz->get_info(to_sys_seconds(2022y, std::chrono::March, 27d, 0h, 59min, 59s))); // 1981 ma Mar lastSu 1u 1 S +} + +static void test_america_st_johns() { + // A more typical entry, + // Uses letters both when DST is ative and not and has multiple + // letters. Uses negetive offsets. + // Switches several times between their own and Canadian rules + // Switches the stdoff from -3:30:52 to -3:30 while observing the same rule + + // Z America/St_Johns -3:30:52 - LMT 1884 + // -3:30:52 j N%sT 1918 + // -3:30:52 C N%sT 1919 + // -3:30:52 j N%sT 1935 Mar 30 + // -3:30 j N%sT 1942 May 11 + // -3:30 C N%sT 1946 + // -3:30 j N%sT 2011 N + // -3:30 C N%sT + // + // R j 1917 o - Ap 8 2 1 D + // R j 1917 o - S 17 2 0 S + // R j 1919 o - May 5 23 1 D + // R j 1919 o - Au 12 23 0 S + // R j 1920 1935 - May Su>=1 23 1 D + // R j 1920 1935 - O lastSu 23 0 S + // R j 1936 1941 - May M>=9 0 1 D + // R j 1936 1941 - O M>=2 0 0 S + // R j 1946 1950 - May Su>=8 2 1 D + // R j 1946 1950 - O Su>=2 2 0 S + // R j 1951 1986 - Ap lastSu 2 1 D + // R j 1951 1959 - S lastSu 2 0 S + // R j 1960 1986 - O lastSu 2 0 S + // R j 1987 o - Ap Su>=1 0:1 1 D + // R j 1987 2006 - O lastSu 0:1 0 S + // R j 1988 o - Ap Su>=1 0:1 2 DD + // R j 1989 2006 - Ap Su>=1 0:1 1 D + // R j 2007 2011 - Mar Su>=8 0:1 1 D + // R j 2007 2010 - N Su>=1 0:1 0 S + // + // R C 1918 o - Ap 14 2 1 D + // R C 1918 o - O 27 2 0 S + // R C 1942 o - F 9 2 1 W + // R C 1945 o - Au 14 23u 1 P + // R C 1945 o - S 30 2 0 S + // R C 1974 1986 - Ap lastSu 2 1 D + // R C 1974 2006 - O lastSu 2 0 S + // R C 1987 2006 - Ap Su>=1 2 1 D + // R C 2007 ma - Mar Su>=8 2 1 D + // R C 2007 ma - N Su>=1 2 0 S + + using namespace std::literals::chrono_literals; + const std::chrono::time_zone* tz = std::chrono::locate_zone("America/St_Johns"); + + assert_equal( // -- + std::chrono::sys_info( + std::chrono::sys_seconds::min(), + to_sys_seconds(1884y, std::chrono::January, 1d, 3h, 30min, 52s), // -3:30:52 - LMT 1884 + -(3h + 30min + 52s), + 0min, + "LMT"), + tz->get_info(std::chrono::sys_seconds::min())); + + assert_equal( // -- + std::chrono::sys_info( + std::chrono::sys_seconds::min(), + to_sys_seconds(1884y, std::chrono::January, 1d, 3h, 30min, 52s), // -3:30:52 - LMT 1884 + -(3h + 30min + 52s), + 0min, + "LMT"), + tz->get_info(to_sys_seconds(1884y, std::chrono::January, 1d, 3h, 30min, 51s))); + + assert_range( // -3:30:52 j N%sT 1918 + "[1884-01-01 03:30:52, 1917-04-08 05:30:52) -03:30:52 0min NST", + tz->get_info(to_sys_seconds(1884y, std::chrono::January, 1d, 3h, 30min, 52s)), // no rule active + tz->get_info(to_sys_seconds(1917y, std::chrono::April, 8d, 5h, 30min, 51s))); // 1917 o Ap 8 2 1 D + + assert_range( // -3:30:52 j N%sT 1918 + "[1917-04-08 05:30:52, 1917-09-17 04:30:52) -02:30:52 60min NDT", + tz->get_info(to_sys_seconds(1917y, std::chrono::April, 8d, 5h, 30min, 52s)), // 1917 o Ap 8 2 1 D + tz->get_info(to_sys_seconds(1917y, std::chrono::September, 17d, 4h, 30min, 51s))); // 1917 o S 17 2 0 S + + assert_range("[1917-09-17 04:30:52, 1918-04-14 05:30:52) -03:30:52 0min NST", + tz->get_info( // -3:30:52 j N%sT 1918 + to_sys_seconds(1917y, std::chrono::September, 17d, 4h, 30min, 52s)), // 1917 o S 17 2 0 S + tz->get_info( // -3:30:52 C N%sT 1919 + to_sys_seconds(1918y, std::chrono::April, 14d, 5h, 30min, 51s))); // 1918 o Ap 14 2 1 D + + assert_range( // -3:30:52 C N%sT 1919 + "[1918-04-14 05:30:52, 1918-10-27 04:30:52) -02:30:52 60min NDT", + tz->get_info(to_sys_seconds(1918y, std::chrono::April, 14d, 5h, 30min, 52s)), // 1918 o Ap 14 2 1 D + tz->get_info(to_sys_seconds(1918y, std::chrono::October, 27d, 4h, 30min, 51s))); // 1918 o O 27 2 0 S + + assert_range("[1918-10-27 04:30:52, 1919-05-06 02:30:52) -03:30:52 0min NST", + tz->get_info( // -3:30:52 C N%sT 1919 + to_sys_seconds(1918y, std::chrono::October, 27d, 4h, 30min, 52s)), // 1918 o O 27 2 0 S + tz->get_info( // -3:30:52 j N%sT 1935 Mar 30 + to_sys_seconds(1919y, std::chrono::May, 6d, 2h, 30min, 51s))); // 1919 o May 5 23 1 D + + assert_range( // -3:30:52 j N%sT 1935 Mar 30 + "[1934-10-29 01:30:52, 1935-03-30 03:30:52) -03:30:52 0min NST", + tz->get_info(to_sys_seconds(1934y, std::chrono::October, 29d, 1h, 30min, 52s)), // 1920 1935 O lastSu 23 0 S + tz->get_info(to_sys_seconds(1935y, std::chrono::March, 30d, 3h, 30min, 51s))); // 1920 1935 May Su>=1 23 1 D + + assert_range( // -3:30 j N%sT 1942 May 11 + // Changed the stdoff while the same rule remains active. + "[1935-03-30 03:30:52, 1935-05-06 02:30:00) -03:30:00 0min NST", + tz->get_info(to_sys_seconds(1935y, std::chrono::March, 30d, 3h, 30min, 52s)), // 1920 1935 O lastSu 23 0 S + tz->get_info(to_sys_seconds(1935y, std::chrono::May, 6d, 2h, 29min, 59s))); // 1920 1935 May Su>=1 23 1 D + + assert_range( // -3:30 j N%sT 1942 May 11 + "[1935-05-06 02:30:00, 1935-10-28 01:30:00) -02:30:00 60min NDT", + tz->get_info(to_sys_seconds(1935y, std::chrono::May, 6d, 2h, 30min, 0s)), // 1920 1935 May Su>=1 23 1 D + tz->get_info(to_sys_seconds(1935y, std::chrono::October, 28d, 1h, 29min, 59s))); // 1920 1935 O lastSu 23 0 S + + assert_range( // -3:30 j N%sT 1942 May 11 + "[1941-10-06 02:30:00, 1942-05-11 03:30:00) -03:30:00 0min NST", + tz->get_info(to_sys_seconds(1941y, std::chrono::October, 6d, 2h, 30min, 0s)), // 1936 1941 O M>=2 0 0 S + tz->get_info(to_sys_seconds(1942y, std::chrono::May, 11d, 3h, 29min, 59s))); // 1946 1950 May Su>=8 2 1 D + + assert_range( // -3:30 C N%sT 1946 + "[1942-05-11 03:30:00, 1945-08-14 23:00:00) -02:30:00 60min NWT", + tz->get_info(to_sys_seconds(1942y, std::chrono::May, 11d, 3h, 30min, 0s)), // 1942 o F 9 2 1 W + tz->get_info(to_sys_seconds(1945y, std::chrono::August, 14d, 22h, 59min, 59s))); // 1945 o Au 14 23u 1 P + + assert_range( // -3:30 C N%sT 1946 + "[1945-08-14 23:00:00, 1945-09-30 04:30:00) -02:30:00 60min NPT", + tz->get_info(to_sys_seconds(1945y, std::chrono::August, 14d, 23h, 0min, 0s)), // 1945 o Au 14 23u 1 P + tz->get_info(to_sys_seconds(1945y, std::chrono::September, 30d, 4h, 29min, 59s))); // 1945 o S 30 2 0 S + + assert_range( + "[1945-09-30 04:30:00, 1946-05-12 05:30:00) -03:30:00 0min NST", + tz->get_info( + to_sys_seconds(1945y, std::chrono::September, 30d, 4h, 30min, 0s)), // -3:30 C N%sT 1946 & 945 o S 30 2 0 S + tz->get_info(to_sys_seconds( + 1946y, std::chrono::May, 12d, 5h, 29min, 59s))); // -3:30 j N%sT 2011 N & 1946 1950 May Su>=8 2 1 D + + assert_range( // -3:30 j N%sT 2011 N + "[1988-04-03 03:31:00, 1988-10-30 01:31:00) -01:30:00 120min NDDT", + tz->get_info(to_sys_seconds(1988y, std::chrono::April, 3d, 3h, 31min, 0s)), // 1988 o Ap Su>=1 0:1 2 DD + tz->get_info(to_sys_seconds(1988y, std::chrono::October, 30d, 1h, 30min, 59s))); // 1987 2006 O lastSu 0:1 0 S + + assert_range("[2011-03-13 03:31:00, 2011-11-06 04:30:00) -02:30:00 60min NDT", + tz->get_info( // -3:30 j N%sT 2011 N + to_sys_seconds(2011y, std::chrono::March, 13d, 3h, 31min, 0s)), // 2007 2011 Mar Su>=8 0:1 1 D + tz->get_info( // -3:30 C N%sT + to_sys_seconds(2011y, std::chrono::November, 6d, 04h, 29min, 59s))); // 2007 ma N Su>=1 2 0 S +} + +static void test_get_at_standard_time_universal() { + // Z Asia/Barnaul 5:35 - LMT 1919 D 10 + // ... + // 7 R +07/+08 1995 May 28 + // 6 R +06/+07 2011 Mar 27 2s + // ... + // + // ... + // R R 1985 2010 - Mar lastSu 2s 1 S + // R R 1996 2010 - O lastSu 2s 0 - + + using namespace std::literals::chrono_literals; + const std::chrono::time_zone* tz = std::chrono::locate_zone("Asia/Barnaul"); + + assert_equal( + std::chrono::sys_info( + to_sys_seconds(2010y, std::chrono::October, 30d, 20h), + to_sys_seconds(2011y, std::chrono::March, 26d, 20h), + 6h, + 0min, + "+06"), + tz->get_info(to_sys_seconds(2010y, std::chrono::October, 31d, 10h))); +} + +static void test_get_at_standard_time_standard() { + // Z Africa/Bissau -1:2:20 - LMT 1912 Ja 1 1u + using namespace std::literals::chrono_literals; + const std::chrono::time_zone* tz = std::chrono::locate_zone("Africa/Bissau"); + + assert_equal( + std::chrono::sys_info( + std::chrono::sys_seconds::min(), + to_sys_seconds(1912y, std::chrono::January, 1d, 1h), + -(1h + 2min + 20s), + 0min, + "LMT"), + tz->get_info(std::chrono::sys_seconds::min())); +} + +static void test_get_at_save_universal() { + // Z America/Tijuana -7:48:4 - LMT 1922 Ja 1 0:11:56 + // -7 - MST 1924 + // -8 - PST 1927 Jun 10 23 + // -7 - MST 1930 N 15 + // -8 - PST 1931 Ap + // -8 1 PDT 1931 S 30 + // -8 - PST 1942 Ap 24 + // -8 1 PWT 1945 Au 14 23u + // ... + + using namespace std::literals::chrono_literals; + const std::chrono::time_zone* tz = std::chrono::locate_zone("America/Tijuana"); + + assert_equal( + std::chrono::sys_info( + to_sys_seconds(1942y, std::chrono::April, 24d, 8h), + to_sys_seconds(1945y, std::chrono::August, 14d, 23h), + -7h, + 60min, + "PWT"), + tz->get_info(to_sys_seconds(1942y, std::chrono::April, 24d, 8h))); +} + +static void test_get_at_rule_standard() { + // Z Antarctica/Macquarie 0 - -00 1899 N + // 10 - AEST 1916 O 1 2 + // 10 1 AEDT 1917 F + // 10 AU AE%sT 1919 Ap 1 0s + // ... + // + // R AU 1917 o - Ja 1 2s 1 D + // R AU 1917 o - Mar lastSu 2s 0 S + // R AU 1942 o - Ja 1 2s 1 D + // ... + + using namespace std::literals::chrono_literals; + const std::chrono::time_zone* tz = std::chrono::locate_zone("Antarctica/Macquarie"); + + // Another rule where the S propagates? + assert_equal( + std::chrono::sys_info( + to_sys_seconds(1916y, std::chrono::September, 30d, 16h), + to_sys_seconds(1917y, std::chrono::March, 24d, 16h), + 11h, + 60min, + "AEDT"), + tz->get_info(to_sys_seconds(1916y, std::chrono::September, 30d, 16h))); +} + +static void test_get_at_rule_universal() { + // Z America/Nuuk -3:26:56 - LMT 1916 Jul 28 + // -3 - -03 1980 Ap 6 2 + // -3 E -03/-02 2023 O 29 1u + // -2 E -02/-01 + // + // R E 1977 1980 - Ap Su>=1 1u 1 S + // R E 1977 o - S lastSu 1u 0 - + // R E 1978 o - O 1 1u 0 - + // R E 1979 1995 - S lastSu 1u 0 - + // R E 1981 ma - Mar lastSu 1u 1 S + // R E 1996 ma - O lastSu 1u 0 - + + using namespace std::literals::chrono_literals; + const std::chrono::time_zone* tz = std::chrono::locate_zone("America/Nuuk"); + + assert_equal( + std::chrono::sys_info( + to_sys_seconds(1980y, std::chrono::April, 6d, 5h), + to_sys_seconds(1980y, std::chrono::September, 28d, 1h), + -2h, + 60min, + "-02"), + tz->get_info(to_sys_seconds(1980y, std::chrono::April, 6d, 5h))); +} + +static void test_format_with_alternatives_west() { + // Z America/Nuuk -3:26:56 - LMT 1916 Jul 28 + // -3 - -03 1980 Ap 6 2 + // -3 E -03/-02 2023 O 29 1u + // -2 E -02/-01 + // + // ... + // R E 1981 ma - Mar lastSu 1u 1 S + // R E 1996 ma - O lastSu 1u 0 - + + using namespace std::literals::chrono_literals; + const std::chrono::time_zone* tz = std::chrono::locate_zone("America/Nuuk"); + + assert_cycle( // -3 E -03/-02 + "[2019-10-27 01:00:00, 2020-03-29 01:00:00) -03:00:00 0min -03", + tz->get_info(to_sys_seconds(2019y, std::chrono::October, 27d, 1h)), // 1981 ma Mar lastSu 1u 1 S + tz->get_info(to_sys_seconds(2020y, std::chrono::March, 29d, 0h, 59min, 59s)), // 1996 ma O lastSu 1u 0 - + "[2020-03-29 01:00:00, 2020-10-25 01:00:00) -02:00:00 60min -02", + tz->get_info(to_sys_seconds(2020y, std::chrono::March, 29d, 1h)), // 1996 ma O lastSu 1u 0 - + tz->get_info(to_sys_seconds(2020y, std::chrono::October, 25d, 0h, 59min, 59s))); // 1981 ma Mar lastSu 1u 1 S +} + +static void test_format_with_alternatives_east() { + // Z Asia/Barnaul 5:35 - LMT 1919 D 10 + // ... + // 6 R +06/+07 2011 Mar 27 2s + // ... + // + // ... + // R R 1985 2010 - Mar lastSu 2s 1 S + // R R 1996 2010 - O lastSu 2s 0 - + + using namespace std::literals::chrono_literals; + const std::chrono::time_zone* tz = std::chrono::locate_zone("Asia/Barnaul"); + + assert_cycle( // 6 R +06/+07 2011 Mar 27 2s + "[2000-03-25 20:00:00, 2000-10-28 20:00:00) 07:00:00 60min +07", + tz->get_info(to_sys_seconds(2000y, std::chrono::March, 25d, 20h)), // 1985 2010 Mar lastSu 2s 1 S + tz->get_info(to_sys_seconds(2000y, std::chrono::October, 28d, 19h, 59min, 59s)), // 1996 2010 O lastSu 2s 0 - + "[2000-10-28 20:00:00, 2001-03-24 20:00:00) 06:00:00 0min +06", + tz->get_info(to_sys_seconds(2000y, std::chrono::October, 28d, 20h)), // 1996 2010 O lastSu 2s 0 - + tz->get_info(to_sys_seconds(2001y, std::chrono::March, 24d, 19h, 59min, 59s))); // 1985 2010 Mar lastSu 2s 1 S +} + +static void test_africa_algiers() { + using namespace std::literals::chrono_literals; + const std::chrono::time_zone* tz = std::chrono::locate_zone("Africa/Algiers"); + + assert_equal( + std::chrono::sys_info( + to_sys_seconds(1977y, std::chrono::October, 20d, 23h), + to_sys_seconds(1978y, std::chrono::March, 24d), + 1h, + std::chrono::minutes(0), + "CET"), + tz->get_info(to_sys_seconds(1977y, std::chrono::October, 20d, 23h))); + + assert_range("[1977-05-06 00:00:00, 1977-10-20 23:00:00) 01:00:00 60min WEST", // 0 d WE%sT 1977 O 21 + tz->get_info(to_sys_seconds(1977y, std::chrono::May, 6d)), + tz->get_info(to_sys_seconds(1977y, std::chrono::October, 20d, 22h, 59min, 59s))); + + assert_range("[1977-10-20 23:00:00, 1978-03-24 00:00:00) 01:00:00 0min CET", // 1 d CE%sT 1979 O 26 + tz->get_info(to_sys_seconds(1977y, std::chrono::October, 20d, 23h)), + tz->get_info(to_sys_seconds(1978y, std::chrono::March, 23d, 23h, 59min, 59s))); +} + +static void test_africa_casablanca() { + // Z Africa/Casablanca -0:30:20 - LMT 1913 O 26 + // 0 M +00/+01 1984 Mar 16 + // 1 - +01 1986 + // 0 M +00/+01 2018 O 28 3 + // 1 M +01/+00 + // + // ... + // R M 2013 2018 - O lastSu 3 0 - + // R M 2014 2018 - Mar lastSu 2 1 - + // R M 2014 o - Jun 28 3 0 - + // R M 2014 o - Au 2 2 1 - + // R M 2015 o - Jun 14 3 0 - + // R M 2015 o - Jul 19 2 1 - + // R M 2016 o - Jun 5 3 0 - + // R M 2016 o - Jul 10 2 1 - + // R M 2017 o - May 21 3 0 - + // R M 2017 o - Jul 2 2 1 - + // R M 2018 o - May 13 3 0 - + // R M 2018 o - Jun 17 2 1 - + // R M 2019 o - May 5 3 -1 - + // R M 2019 o - Jun 9 2 0 - + // R M 2020 o - Ap 19 3 -1 - + // ... + + using namespace std::literals::chrono_literals; + const std::chrono::time_zone* tz = std::chrono::locate_zone("Africa/Casablanca"); + + assert_range("[2018-06-17 02:00:00, 2018-10-28 02:00:00) 01:00:00 60min +01", + tz->get_info(to_sys_seconds(2018y, std::chrono::June, 17d, 2h)), + tz->get_info(to_sys_seconds(2018y, std::chrono::October, 28d, 1h, 59min, 59s))); + + assert_range("[2018-10-28 02:00:00, 2019-05-05 02:00:00) 01:00:00 0min +01", + tz->get_info( // 1 M +01/+00 & R M 2018 o - Jun 17 2 1 - + to_sys_seconds(2018y, std::chrono::October, 28d, 2h)), + tz->get_info( // 1 M +01/+00 & R M 2019 o - May 5 3 -1 - + to_sys_seconds(2019y, std::chrono::May, 5d, 1h, 59min, 59s))); + + // 1 M +01/+00 + // Note the SAVE contains a negative value + assert_range("[2019-05-05 02:00:00, 2019-06-09 02:00:00) 00:00:00 -60min +00", + tz->get_info(to_sys_seconds(2019y, std::chrono::May, 5d, 2h)), // R M 2019 o - May 5 3 -1 - + tz->get_info(to_sys_seconds(2019y, std::chrono::June, 9d, 1h, 59min, 59s))); // R M 2019 o - Jun 9 2 0 - + + assert_range("[2019-06-09 02:00:00, 2020-04-19 02:00:00) 01:00:00 0min +01", + tz->get_info( // 1 M +01/+00 & R M 2019 o - Jun 9 2 0 - + to_sys_seconds(2019y, std::chrono::June, 9d, 2h)), + tz->get_info( // 1 M +01/+00 & R M 2020 o - Ap 19 3 -1 - + to_sys_seconds(2020y, std::chrono::April, 19d, 1h, 59min, 59s))); // +} + +static void test_africa_ceuta() { + // Z Africa/Ceuta -0:21:16 - LMT 1900 D 31 23:38:44 + // 0 - WET 1918 May 6 23 + // 0 1 WEST 1918 O 7 23 + // 0 - WET 1924 + // 0 s WE%sT 1929 + // 0 - WET 1967 + // 0 Sp WE%sT 1984 Mar 16 + // 1 - CET 1986 + // 1 E CE%sT + // + // ... + // R s 1926 o - Ap 17 23 1 S + // R s 1926 1929 - O Sa>=1 24s 0 - + // R s 1927 o - Ap 9 23 1 S + // R s 1928 o - Ap 15 0 1 S + // R s 1929 o - Ap 20 23 1 S + // R s 1937 o - Jun 16 23 1 S + // ... + // + // R Sp 1967 o - Jun 3 12 1 S + // R Sp 1967 o - O 1 0 0 - + // R Sp 1974 o - Jun 24 0 1 S + // R Sp 1974 o - S 1 0 0 - + // R Sp 1976 1977 - May 1 0 1 S + // R Sp 1976 o - Au 1 0 0 - + // R Sp 1977 o - S 28 0 0 - + // R Sp 1978 o - Jun 1 0 1 S + // R Sp 1978 o - Au 4 0 0 - + + using namespace std::literals::chrono_literals; + const std::chrono::time_zone* tz = std::chrono::locate_zone("Africa/Ceuta"); + + assert_range( + + "[1928-10-07 00:00:00, 1967-06-03 12:00:00) 00:00:00 0min WET", + tz->get_info(to_sys_seconds(1928y, std::chrono::October, 7d)), // 0 s WE%sT 1929 & 1926 1929 O Sa>=1 24s 0 - + tz->get_info( // No transitions in "0 - WET 1967" + to_sys_seconds(1967y, std::chrono::June, 3d, 11h, 59min, 59s))); // 0 - WET 1967 & 1967 o Jun 3 12 1 S +} + +static void test_africa_freetown() { + // Z Africa/Freetown -0:53 - LMT 1882 + // -0:53 - FMT 1913 Jul + // -1 SL %s 1939 S 5 + // -1 - -01 1941 D 6 24 + // 0 - GMT + // + // R SL 1932 o - D 1 0 0:20 -0040 + // R SL 1933 1938 - Mar 31 24 0 -01 + // R SL 1933 1939 - Au 31 24 0:20 -0040 + // R SL 1939 o - May 31 24 0 -01 + + using namespace std::literals::chrono_literals; + const std::chrono::time_zone* tz = std::chrono::locate_zone("Africa/Freetown"); + + // When a continuation has a named rule, the tranisition time determined by + // the active rule can be wrong. The next continuation may set the clock to an + // earlier time. This is tested for San Luis. This tests the rule is not used + // when the rule is not a named rule. + // + // Fixes: + // Expected output [1882-01-01 00:53:00, 1913-07-01 00:53:00) -00:53:00 0min FMT + // Actual output [1882-01-01 00:53:00, 1913-07-01 00:46:00) -00:53:00 0min FMT + + assert_range("[1882-01-01 00:53:00, 1913-07-01 00:53:00) -00:53:00 0min FMT", + tz->get_info(to_sys_seconds(1882y, std::chrono::January, 1d, 0h, 53min)), // -0:53 - FMT 1913 Jul + tz->get_info( // -1 SL %s 1939 S 5 & before first rule + to_sys_seconds(1913y, std::chrono::July, 1d, 0h, 52min, 59s))); + + // Tests whether the "-1 SL %s 1939 S 5" until gets the proper local time + // adjustment. + assert_range("[1939-09-01 01:00:00, 1939-09-05 00:40:00) -00:40:00 20min -0040", + tz->get_info( // -1 SL %s 1939 S 5 & R SL 1933 1939 - Au 31 24 0:20 -0040 + to_sys_seconds(1939y, std::chrono::September, 1d, 1h)), + tz->get_info( // -1 - -01 1941 D 6 24 + to_sys_seconds(1939y, std::chrono::September, 5d, 0h, 39min, 59s))); +} + +static void test_africa_windhoek() { + // Tests the LETTER/S used before the first rule per + // https://data.iana.org/time-zones/tz-how-to.html + // If switching to a named rule before any transition has happened, + // assume standard time (SAVE zero), and use the LETTER data from + // the earliest transition with a SAVE of zero. + + // Z Africa/Windhoek 1:8:24 - LMT 1892 F 8 + // 1:30 - +0130 1903 Mar + // 2 - SAST 1942 S 20 2 + // 2 1 SAST 1943 Mar 21 2 + // 2 - SAST 1990 Mar 21 + // 2 NA %s + // + // R NA 1994 o - Mar 21 0 -1 WAT + // R NA 1994 2017 - S Su>=1 2 0 CAT + // R NA 1995 2017 - Ap Su>=1 2 -1 WAT + + using namespace std::literals::chrono_literals; + const std::chrono::time_zone* tz = std::chrono::locate_zone("Africa/Windhoek"); + + assert_range( // 2 - EET 2012 N 10 2 + "[1990-03-20 22:00:00, 1994-03-20 22:00:00) 02:00:00 0min CAT", + tz->get_info(to_sys_seconds(1990y, std::chrono::March, 20d, 22h)), + tz->get_info(to_sys_seconds(1994y, std::chrono::March, 20d, 21h, 59min, 59s))); +} + +static void test_america_adak() { + // Z America/Adak 12:13:22 - LMT 1867 O 19 12:44:35 + // ... + // -11 u B%sT 1983 O 30 2 + // -10 u AH%sT 1983 N 30 + // -10 u H%sT + // + // ... + // R u 1945 o - S 30 2 0 S + // R u 1967 2006 - O lastSu 2 0 S + // R u 1967 1973 - Ap lastSu 2 1 D + // R u 1974 o - Ja 6 2 1 D + // R u 1975 o - F lastSu 2 1 D + // R u 1976 1986 - Ap lastSu 2 1 D + // R u 1987 2006 - Ap Su>=1 2 1 D + // ... + + using namespace std::literals::chrono_literals; + const std::chrono::time_zone* tz = std::chrono::locate_zone("America/Adak"); + + assert_range( // 2 - EET 2012 N 10 2 + "[1983-10-30 12:00:00, 1983-11-30 10:00:00) -10:00:00 0min AHST", + tz->get_info(to_sys_seconds(1983y, std::chrono::October, 30d, 12h)), // -11 u B%sT 1983 O 30 2 + tz->get_info(to_sys_seconds(1983y, std::chrono::November, 30d, 9h, 59min, 59s))); // -10 u AH%sT 1983 N 30 +} + +static void test_america_auncion() { + // R y 2013 ma - Mar Su>=22 0 0 - + // Z America/Asuncion -3:50:40 - LMT 1890 + // -3:50:40 - AMT 1931 O 10 + // -4 - -04 1972 O + // -3 - -03 1974 Ap + // -4 y -04/-03 + // + // R y 1975 1988 - O 1 0 1 - + // R y 1975 1978 - Mar 1 0 0 - + // R y 1979 1991 - Ap 1 0 0 - + // ... + + using namespace std::literals::chrono_literals; + const std::chrono::time_zone* tz = std::chrono::locate_zone("America/Asuncion"); + + assert_range("[1974-04-01 03:00:00, 1975-10-01 04:00:00) -04:00:00 0min -04", + tz->get_info(to_sys_seconds(1974y, std::chrono::April, 1d, 3h)), + tz->get_info(to_sys_seconds(1975y, std::chrono::October, 1d, 3h, 59min, 59s))); + + assert_range("[1975-10-01 04:00:00, 1976-03-01 03:00:00) -03:00:00 60min -03", + tz->get_info(to_sys_seconds(1975y, std::chrono::October, 1d, 4h)), + tz->get_info(to_sys_seconds(1976y, std::chrono::March, 1d, 2h, 59min, 59s))); +} + +static void test_america_ciudad_juarez() { + // Z America/Ciudad_Juarez -7:5:56 - LMT 1922 Ja 1 7u + // -7 - MST 1927 Jun 10 23 + // -6 - CST 1930 N 15 + // -7 m MST 1932 Ap + // -6 - CST 1996 + // -6 m C%sT 1998 + // ... + // + // R m 1939 o - F 5 0 1 D + // R m 1939 o - Jun 25 0 0 S + // R m 1940 o - D 9 0 1 D + // R m 1941 o - Ap 1 0 0 S + // R m 1943 o - D 16 0 1 W + // R m 1944 o - May 1 0 0 S + // R m 1950 o - F 12 0 1 D + // R m 1950 o - Jul 30 0 0 S + // R m 1996 2000 - Ap Su>=1 2 1 D + // R m 1996 2000 - O lastSu 2 0 S + // ... + + using namespace std::literals::chrono_literals; + const std::chrono::time_zone* tz = std::chrono::locate_zone("America/Ciudad_Juarez"); + + // 1996 has a similar issue, instead of __time the __until end before + // the first rule in 1939. Between the two usages of RULE Mexico + // a different continuation RULE is active + assert_range("[1996-04-07 08:00:00, 1996-10-27 07:00:00) -05:00:00 60min CDT", + tz->get_info(to_sys_seconds(1996y, std::chrono::April, 7d, 8h)), + tz->get_info(to_sys_seconds(1996y, std::chrono::October, 27d, 6h, 59min, 59s))); +} + +static void test_america_argentina_buenos_aires() { + // Z America/Argentina/Buenos_Aires -3:53:48 - LMT 1894 O 31 + // -4:16:48 - CMT 1920 May + // -4 - -04 1930 D + // -4 A -04/-03 1969 O 5 + // -3 A -03/-02 1999 O 3 + // -4 A -04/-03 2000 Mar 3 + // -3 A -03/-02 + // + // ... + // R A 1989 1992 - O Su>=15 0 1 - + // R A 1999 o - O Su>=1 0 1 - + // R A 2000 o - Mar 3 0 0 - + // R A 2007 o - D 30 0 1 - + // ... + + // The 1999 switch uses the same rule, but with a different stdoff. + // R A 1999 o - O Su>=1 0 1 - + // stdoff -3 -> 1999-10-03 03:00:00 + // stdoff -4 -> 1999-10-03 04:00:00 + // This generates an invalid entry and this is evaluated as a transition. + // Looking at the zdump like output in libc++ this generates jumps in + // the UTC time + + using namespace std::literals::chrono_literals; + const std::chrono::time_zone* tz = std::chrono::locate_zone("America/Argentina/Buenos_Aires"); + + assert_range("[1999-10-03 03:00:00, 2000-03-03 03:00:00) -03:00:00 60min -03", + tz->get_info(to_sys_seconds(1999y, std::chrono::October, 3d, 3h)), + tz->get_info(to_sys_seconds(2000y, std::chrono::March, 3d, 2h, 59min, 59s))); + assert_range("[2000-03-03 03:00:00, 2007-12-30 03:00:00) -03:00:00 0min -03", + tz->get_info(to_sys_seconds(2000y, std::chrono::March, 3d, 3h)), + tz->get_info(to_sys_seconds(2007y, std::chrono::December, 30d, 2h, 59min, 59s))); +} + +static void test_america_argentina_la_rioja() { + // Z America/Argentina/La_Rioja -4:27:24 - LMT 1894 O 31 + // ... + // -4 A -04/-03 1969 O 5 + // -3 A -03/-02 1991 Mar + // -4 - -04 1991 May 7 + // -3 A -03/-02 1999 O 3 + // ... + // + // ... + // R A 1988 o - D 1 0 1 - + // R A 1989 1993 - Mar Su>=1 0 0 - + // R A 1989 1992 - O Su>=15 0 1 - + // R A 1999 o - O Su>=1 0 1 - + // ... + + using namespace std::literals::chrono_literals; + const std::chrono::time_zone* tz = std::chrono::locate_zone("America/Argentina/La_Rioja"); + + assert_range("[1990-10-21 03:00:00, 1991-03-01 02:00:00) -02:00:00 60min -02", + tz->get_info(to_sys_seconds(1990y, std::chrono::October, 21d, 3h)), + tz->get_info(to_sys_seconds(1991y, std::chrono::March, 1d, 1h, 59min, 59s))); +} + +static void test_america_argentina_san_luis() { + // Z America/Argentina/San_Luis -4:25:24 - LMT 1894 O 31 + // ... + // -4 A -04/-03 1969 O 5 + // -3 A -03/-02 1990 + // -3 1 -02 1990 Mar 14 + // -4 - -04 1990 O 15 + // -4 1 -03 1991 Mar + // -4 - -04 1991 Jun + // -3 - -03 1999 O 3 + // -4 1 -03 2000 Mar 3 + // -4 - -04 2004 Jul 25 + // -3 A -03/-02 2008 Ja 21 + // -4 Sa -04/-03 2009 O 11 + // -3 - -03 + // + // ... + // R A 1988 o - D 1 0 1 - + // R A 1989 1993 - Mar Su>=1 0 0 - + // R A 1989 1992 - O Su>=15 0 1 - + // R A 1999 o - O Su>=1 0 1 - + // R A 2000 o - Mar 3 0 0 - + // R A 2007 o - D 30 0 1 - + // R A 2008 2009 - Mar Su>=15 0 0 - + // R A 2008 o - O Su>=15 0 1 - + // + // R Sa 2008 2009 - Mar Su>=8 0 0 - + // R Sa 2007 2008 - O Su>=8 0 1 - + + using namespace std::literals::chrono_literals; + const std::chrono::time_zone* tz = std::chrono::locate_zone("America/Argentina/San_Luis"); + + assert_range("[1989-10-15 03:00:00, 1990-03-14 02:00:00) -02:00:00 60min -02", + tz->get_info( // -3 A -03/-02 1990 & R A 1989 1992 - O Su>=15 0 1 - + to_sys_seconds(1989y, std::chrono::October, 15d, 3h)), + tz->get_info( // UNTIL -3 1 -02 1990 Mar 14 + to_sys_seconds(1990y, std::chrono::March, 14d, 1h, 59min, 59s))); + + assert_range("[2008-01-21 02:00:00, 2008-03-09 03:00:00) -03:00:00 60min -03", + tz->get_info(to_sys_seconds(2008y, std::chrono::January, 21d, 2h)), + tz->get_info(to_sys_seconds(2008y, std::chrono::March, 9d, 2h, 59min, 59s))); +} + +static void test_america_indiana_knox() { + // Z America/Indiana/Knox -5:46:30 - LMT 1883 N 18 12:13:30 + // -6 u C%sT 1947 + // -6 St C%sT 1962 Ap 29 2 + // -5 - EST 1963 O 27 2 + // -6 u C%sT 1991 O 27 2 + // -5 - EST 2006 Ap 2 2 + // -6 u C%sT + // + // ... + // R u 1976 1986 - Ap lastSu 2 1 D + // R u 1987 2006 - Ap Su>=1 2 1 D + // R u 2007 ma - Mar Su>=8 2 1 D + // R u 2007 ma - N Su>=1 2 0 S + + using namespace std::literals::chrono_literals; + const std::chrono::time_zone* tz = std::chrono::locate_zone("America/Indiana/Knox"); + + // The continuations + // -5 - EST + // -6 u C%sT + // have different offsets. The start time of the first active rule in + // RULE u should use the offset at the end of -5 - EST. + assert_range("[2006-04-02 07:00:00, 2006-10-29 07:00:00) -05:00:00 60min CDT", + tz->get_info(to_sys_seconds(2006y, std::chrono::April, 2d, 7h)), + tz->get_info(to_sys_seconds(2006y, std::chrono::October, 29d, 6h, 59min, 59s))); +} + +int main(int, const char**) { + // Basic tests + test_gmt(); + test_durations(); + test_indian_kerguelen(); + test_antarctica_syowa(); + test_asia_hong_kong(); + test_europe_berlin(); + + test_america_st_johns(); + + // Small tests for not-yet tested conditions + test_get_at_standard_time_universal(); + test_get_at_standard_time_standard(); + test_get_at_save_universal(); + test_get_at_rule_standard(); + test_get_at_rule_universal(); + + test_format_with_alternatives_west(); + test_format_with_alternatives_east(); + + // Tests based on bugs found + test_africa_algiers(); + test_africa_casablanca(); + test_africa_ceuta(); + test_africa_freetown(); + test_africa_windhoek(); + test_america_adak(); + test_america_argentina_buenos_aires(); + test_america_argentina_la_rioja(); + test_america_argentina_san_luis(); + test_america_auncion(); + test_america_ciudad_juarez(); + test_america_indiana_knox(); + + return 0; +} diff --git a/libcxx/test/std/time/time.zone/time.zone.timezone/time.zone.members/sys_info.zdump.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.timezone/time.zone.members/sys_info.zdump.pass.cpp new file mode 100644 index 0000000..05328e2 --- /dev/null +++ b/libcxx/test/std/time/time.zone/time.zone.timezone/time.zone.members/sys_info.zdump.pass.cpp @@ -0,0 +1,129 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: no-filesystem, no-localization, no-tzdb, has-no-zdump + +// XFAIL: libcpp-has-no-incomplete-tzdb +// XFAIL: availability-tzdb-missing + +// TODO TZDB Investigate +// XFAIL: target={{armv(7|8)l-linux-gnueabihf}} + +#include <chrono> +#include <format> +#include <fstream> +#include <cassert> + +#include "filesystem_test_helper.h" +#include "assert_macros.h" +#include "concat_macros.h" + +// The year range to validate. The dates used in practice are expected to be +// inside the tested range. +constexpr std::chrono::year first{1800}; +constexpr std::chrono::year last{2100}; + +// A custom sys_info class that also stores the name of the time zone. +// Its formatter matches the output of zdump. +struct sys_info : public std::chrono::sys_info { + sys_info(std::string_view name_, std::chrono::sys_info info) : std::chrono::sys_info{info}, name{name_} {} + + std::string name; +}; + +template <> +struct std::formatter<sys_info, char> { + template <class ParseContext> + constexpr typename ParseContext::iterator parse(ParseContext& ctx) { + return ctx.begin(); + } + + template <class FormatContext> + typename FormatContext::iterator format(const sys_info& info, FormatContext& ctx) const { + using namespace std::literals::chrono_literals; + + // Every "sys_info" entry of zdump consists of 2 lines. + // - 1 for first second of the range + // - 1 for last second of the range + // For example: + // Africa/Casablanca Sun Mar 25 02:00:00 2018 UT = Sun Mar 25 03:00:00 2018 +01 isdst=1 gmtoff=3600 + // Africa/Casablanca Sun May 13 01:59:59 2018 UT = Sun May 13 02:59:59 2018 +01 isdst=1 gmtoff=3600 + + if (info.begin != std::chrono::sys_seconds::min()) + ctx.advance_to(std::format_to( + ctx.out(), + "{} {:%a %b %e %H:%M:%S %Y} UT = {:%a %b %e %H:%M:%S %Y} {} isdst={:d} gmtoff={:%Q}\n", + info.name, + info.begin, + info.begin + info.offset, + info.abbrev, + info.save != 0s, + info.offset)); + + if (info.end != std::chrono::sys_seconds::max()) + ctx.advance_to(std::format_to( + ctx.out(), + "{} {:%a %b %e %H:%M:%S %Y} UT = {:%a %b %e %H:%M:%S %Y} {} isdst={:d} gmtoff={:%Q}\n", + info.name, + info.end - 1s, + info.end - 1s + info.offset, + info.abbrev, + info.save != 0s, + info.offset)); + + return ctx.out(); + } +}; + +void process(std::ostream& stream, const std::chrono::time_zone& zone) { + using namespace std::literals::chrono_literals; + + constexpr auto begin = std::chrono::time_point_cast<std::chrono::seconds>( + static_cast<std::chrono::sys_days>(std::chrono::year_month_day{first, std::chrono::January, 1d})); + constexpr auto end = std::chrono::time_point_cast<std::chrono::seconds>( + static_cast<std::chrono::sys_days>(std::chrono::year_month_day{last, std::chrono::January, 1d})); + + std::chrono::sys_seconds s = begin; + do { + sys_info info{zone.name(), zone.get_info(s)}; + + if (info.end >= end) + info.end = std::chrono::sys_seconds::max(); + + stream << std::format("{}", info); + s = info.end; + } while (s != std::chrono::sys_seconds::max()); +} + +// This test compares the output of the zdump against the output based on the +// standard library implementation. It tests all available time zones and +// validates them. The specification of how to use the IANA database is limited +// and the real database contains quite a number of "interesting" cases. +int main(int, const char**) { + scoped_test_env env; + const std::string file = env.create_file("zdump.txt"); + + const std::chrono::tzdb& tzdb = std::chrono::get_tzdb(); + for (const auto& zone : tzdb.zones) { + std::stringstream libcxx; + process(libcxx, zone); + + int result = std::system(std::format("zdump -V -c{},{} {} > {}", first, last, zone.name(), file).c_str()); + assert(result == 0); + + std::stringstream zdump; + zdump << std::ifstream(file).rdbuf(); + + TEST_REQUIRE( + libcxx.str() == zdump.str(), + TEST_WRITE_CONCATENATED("\nTZ=", zone.name(), "\nlibc++\n", libcxx.str(), "|\n\nzdump\n", zdump.str(), "|")); + } + + return 0; +} diff --git a/libcxx/utils/libcxx/test/features.py b/libcxx/utils/libcxx/test/features.py index 0793c34f..6ff1630 100644 --- a/libcxx/utils/libcxx/test/features.py +++ b/libcxx/utils/libcxx/test/features.py @@ -286,6 +286,12 @@ DEFAULT_FEATURES = [ # Avoid building on platforms that don't support modules properly. or not hasCompileFlag(cfg, "-Wno-reserved-module-identifier"), ), + # The time zone validation tests compare the output of zdump against the + # output generated by <chrono>'s time zone support. + Feature( + name="has-no-zdump", + when=lambda cfg: runScriptExitCode(cfg, ["zdump --version"]) != 0, + ), ] # Deduce and add the test features that that are implied by the #defines in |