diff --git a/libcxx/docs/Status/Cxx20Issues.csv b/libcxx/docs/Status/Cxx20Issues.csv index db57b15256a62..fed1c7e736248 100644 --- a/libcxx/docs/Status/Cxx20Issues.csv +++ b/libcxx/docs/Status/Cxx20Issues.csv @@ -269,7 +269,7 @@ "`3355 `__","The memory algorithms should support move-only input iterators introduced by P1207","Prague","|Complete|","15.0","|ranges|" "`3356 `__","``__cpp_lib_nothrow_convertible``\ should be ``__cpp_lib_is_nothrow_convertible``\ ","Prague","|Complete|","12.0" "`3358 `__","|sect|\ [span.cons] is mistaken that ``to_address``\ can throw","Prague","|Complete|","17.0" -"`3359 `__","````\ leap second support should allow for negative leap seconds","Prague","|Complete|","19.0","|chrono|" +"`3359 `__","````\ leap second support should allow for negative leap seconds","Prague","|In Progress|","","|chrono|" "`3360 `__","``three_way_comparable_with``\ is inconsistent with similar concepts","Prague","|Nothing To Do|","","|spaceship|" "`3362 `__","Strike ``stop_source``\ 's ``operator!=``\ ","Prague","","" "`3363 `__","``drop_while_view``\ should opt-out of ``sized_range``\ ","Prague","|Nothing To Do|","","|ranges|" diff --git a/libcxx/include/__chrono/time_zone.h b/libcxx/include/__chrono/time_zone.h index 91ddab8903fe2..e28c9189c381b 100644 --- a/libcxx/include/__chrono/time_zone.h +++ b/libcxx/include/__chrono/time_zone.h @@ -16,7 +16,9 @@ // Enable the contents of the header only when libc++ was built with experimental features enabled. #if !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_TZDB) +# include <__chrono/calendar.h> # include <__chrono/duration.h> +# include <__chrono/local_info.h> # include <__chrono/sys_info.h> # include <__chrono/system_clock.h> # include <__compare/strong_order.h> @@ -63,12 +65,18 @@ class _LIBCPP_AVAILABILITY_TZDB time_zone { return __get_info(chrono::time_point_cast(__time)); } + template + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI local_info get_info(const local_time<_Duration>& __time) const { + return __get_info(chrono::time_point_cast(__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; + [[nodiscard]] _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI local_info __get_info(local_seconds __time) const; unique_ptr<__impl> __impl_; }; diff --git a/libcxx/include/chrono b/libcxx/include/chrono index 96a3e92faa81f..4d8398af1a108 100644 --- a/libcxx/include/chrono +++ b/libcxx/include/chrono @@ -763,6 +763,9 @@ class time_zone { template sys_info get_info(const sys_time& st) const; + + template + local_info get_info(const local_time& tp) 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 diff --git a/libcxx/src/time_zone.cpp b/libcxx/src/time_zone.cpp index 928f3d2855e45..8777d550a0104 100644 --- a/libcxx/src/time_zone.cpp +++ b/libcxx/src/time_zone.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include "include/tzdb/time_zone_private.h" @@ -903,6 +904,152 @@ time_zone::__get_info(sys_seconds __time) const { std::__throw_runtime_error("tzdb: corrupt db"); } +// Is the "__local_time" present in "__first" and "__second". If so the +// local_info has an ambiguous result. +[[nodiscard]] static bool +__is_ambiguous(local_seconds __local_time, const sys_info& __first, const sys_info& __second) { + std::chrono::local_seconds __end_first{__first.end.time_since_epoch() + __first.offset}; + std::chrono::local_seconds __begin_second{__second.begin.time_since_epoch() + __second.offset}; + + return __local_time < __end_first && __local_time >= __begin_second; +} + +// Determines the result of the "__local_time". This expects the object +// "__first" to be earlier in time than "__second". +[[nodiscard]] static local_info +__get_info(local_seconds __local_time, const sys_info& __first, const sys_info& __second) { + std::chrono::local_seconds __end_first{__first.end.time_since_epoch() + __first.offset}; + std::chrono::local_seconds __begin_second{__second.begin.time_since_epoch() + __second.offset}; + + if (__local_time < __end_first) { + if (__local_time >= __begin_second) + // |--------| + // |------| + // ^ + return {local_info::ambiguous, __first, __second}; + + // |--------| + // |------| + // ^ + return {local_info::unique, __first, sys_info{}}; + } + + if (__local_time < __begin_second) + // |--------| + // |------| + // ^ + return {local_info::nonexistent, __first, __second}; + + // |--------| + // |------| + // ^ + return {local_info::unique, __second, sys_info{}}; +} + +[[nodiscard]] _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI local_info +time_zone::__get_info(local_seconds __local_time) const { + seconds __local_seconds = __local_time.time_since_epoch(); + + /* An example of a typical year with a DST switch displayed in local time. + * + * At the first of April the time goes forward one hour. This means the + * time marked with ~~ is not a valid local time. This is represented by the + * nonexistent value in local_info.result. + * + * At the first of November the time goes backward one hour. This means the + * time marked with ^^ happens twice. This is represented by the ambiguous + * value in local_info.result. + * + * 2020.11.01 2021.04.01 2021.11.01 + * offset +05 offset +05 offset +05 + * save 0s save 1h save 0s + * |------------//----------| + * |---------//--------------| + * |------------- + * ~~ ^^ + * + * These shifts can happen due to changes in the current time zone for a + * location. For example, Indian/Kerguelen switched only once. In 1950 from an + * offset of 0 hours to an offset of +05 hours. + * + * During all these shifts the UTC time will not have gaps. + */ + + // The code needs to determine the system time for the local time. There is no + // information available. Assume the offset between system time and local time + // is 0s. This gives an initial estimate. + sys_seconds __guess{__local_seconds}; + sys_info __info = __get_info(__guess); + + // At this point the offset can be used to determine an estimate for the local + // time. Before doing that, determine the offset and validate whether the + // local time is the range [chrono::local_seconds::min(), + // chrono::local_seconds::max()). + if (__local_seconds < 0s && __info.offset > 0s) + if (__local_seconds - chrono::local_seconds::min().time_since_epoch() < __info.offset) + return {-1, __info, {}}; + + if (__local_seconds > 0s && __info.offset < 0s) + if (chrono::local_seconds::max().time_since_epoch() - __local_seconds < -__info.offset) + return {-2, __info, {}}; + + // Based on the information found in the sys_info, the local time can be + // converted to a system time. This resulting time can be in the following + // locations of the sys_info: + // + // |---------//--------------| + // 1 2.1 2.2 2.3 5 + // + // 1. The estimate is before the returned sys_info object. + // The result is either non-existent or unique in the previous sys_info. + // 2. The estimate is in the sys_info object + // - If the sys_info begin is not sys_seconds::min(), then it might be at + // 2.1 and could be ambiguous with the previous or unique. + // - If sys_info end is not sys_seconds::max(), then it might be at 2.3 + // and could be ambiguous with the next or unique. + // - Else it is at 2.2 and always unique. This case happens when a + // time zone has not transitions. For example, UTC or GMT+1. + // 3. The estimate is after the returned sys_info object. + // The result is either non-existent or unique in the next sys_info. + // + // There is no specification where the "middle" starts. Similar issues can + // happen when sys_info objects are "short", then "unique in the next" could + // become "ambiguous in the next and the one following". Theoretically there + // is the option of the following time-line + // + // |------------| + // |----| + // |-----------------| + // + // However the local_info object only has 2 sys_info objects, so this option + // is not tested. + + sys_seconds __sys_time{__local_seconds - __info.offset}; + if (__sys_time < __info.begin) + // Case 1 before __info + return chrono::__get_info(__local_time, __get_info(__info.begin - 1s), __info); + + if (__sys_time >= __info.end) + // Case 3 after __info + return chrono::__get_info(__local_time, __info, __get_info(__info.end)); + + // Case 2 in __info + if (__info.begin != sys_seconds::min()) { + // Case 2.1 Not at the beginning, when not ambiguous the result should test + // case 2.3. + sys_info __prev = __get_info(__info.begin - 1s); + if (__is_ambiguous(__local_time, __prev, __info)) + return {local_info::ambiguous, __prev, __info}; + } + + if (__info.end == sys_seconds::max()) + // At the end so it's case 2.2 + return {local_info::unique, __info, sys_info{}}; + + // This tests case 2.2 or case 2.3. + return chrono::__get_info(__local_time, __info, __get_info(__info.end)); +} + } // namespace chrono _LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/src/tzdb.cpp b/libcxx/src/tzdb.cpp index 5951e59248e94..8588646bbbc41 100644 --- a/libcxx/src/tzdb.cpp +++ b/libcxx/src/tzdb.cpp @@ -626,29 +626,49 @@ static void __parse_leap_seconds(vector& __leap_seconds, istream&& // seconds since 1 January 1970. constexpr auto __offset = sys_days{1970y / January / 1} - sys_days{1900y / January / 1}; - while (true) { - switch (__input.peek()) { - case istream::traits_type::eof(): - return; - - case ' ': - case '\t': - case '\n': - __input.get(); - continue; + struct __entry { + sys_seconds __timestamp; + seconds __value; + }; + vector<__entry> __entries; + [&] { + while (true) { + switch (__input.peek()) { + case istream::traits_type::eof(): + return; + + case ' ': + case '\t': + case '\n': + __input.get(); + continue; + + case '#': + chrono::__skip_line(__input); + continue; + } - case '#': + sys_seconds __date = sys_seconds{seconds{chrono::__parse_integral(__input, false)}} - __offset; + chrono::__skip_mandatory_whitespace(__input); + seconds __value{chrono::__parse_integral(__input, false)}; chrono::__skip_line(__input); - continue; - } - sys_seconds __date = sys_seconds{seconds{chrono::__parse_integral(__input, false)}} - __offset; - chrono::__skip_mandatory_whitespace(__input); - seconds __value{chrono::__parse_integral(__input, false)}; - chrono::__skip_line(__input); - - __leap_seconds.emplace_back(std::__private_constructor_tag{}, __date, __value); - } + __entries.emplace_back(__date, __value); + } + }(); + // The Standard requires the leap seconds to be sorted. The file + // leap-seconds.list usually provides them in sorted order, but that is not + // guaranteed so we ensure it here. + std::ranges::sort(__entries, {}, &__entry::__timestamp); + + // The database should contain the number of seconds inserted by a leap + // second (1 or -1). So the difference between the two elements are stored. + // std::ranges::views::adjacent has not been implemented yet. + (void)ranges::adjacent_find(__entries, [&](const __entry& __first, const __entry& __second) { + __leap_seconds.emplace_back( + std::__private_constructor_tag{}, __second.__timestamp, __second.__value - __first.__value); + return false; + }); } void __init_tzdb(tzdb& __tzdb, __tz::__rules_storage_type& __rules) { @@ -667,10 +687,6 @@ void __init_tzdb(tzdb& __tzdb, __tz::__rules_storage_type& __rules) { // The latter is much easier to parse, it seems Howard shares that // opinion. chrono::__parse_leap_seconds(__tzdb.leap_seconds, ifstream{__root / "leap-seconds.list"}); - // The Standard requires the leap seconds to be sorted. The file - // leap-seconds.list usually provides them in sorted order, but that is not - // guaranteed so we ensure it here. - std::ranges::sort(__tzdb.leap_seconds); } #ifdef _WIN32 diff --git a/libcxx/test/libcxx/diagnostics/chrono.nodiscard.verify.cpp b/libcxx/test/libcxx/diagnostics/chrono.nodiscard.verify.cpp index a5ce5d1658130..fea1e4417cc12 100644 --- a/libcxx/test/libcxx/diagnostics/chrono.nodiscard.verify.cpp +++ b/libcxx/test/libcxx/diagnostics/chrono.nodiscard.verify.cpp @@ -48,8 +48,10 @@ void test() { { std::chrono::sys_seconds s{}; + std::chrono::local_seconds l{}; 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}} + tz.get_info(l); // 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.db/leap_seconds.pass.cpp b/libcxx/test/libcxx/time/time.zone/time.zone.db/leap_seconds.pass.cpp index 25a0f00003da2..d7ae21926b4b2 100644 --- a/libcxx/test/libcxx/time/time.zone/time.zone.db/leap_seconds.pass.cpp +++ b/libcxx/test/libcxx/time/time.zone/time.zone.db/leap_seconds.pass.cpp @@ -83,32 +83,39 @@ static void test_leap_seconds() { 2303683200 12 # 1 Jan 1973 2287785600 11 # 1 Jul 1972 2272060800 10 # 1 Jan 1972 -86400 1 # 2 Jan 1900 Dummy entry to test before 1970 +86400 9 # 2 Jan 1900 Dummy entry to test before 1970 +1 8 # 2 Jan 1900 Dummy entry to test before 1970 + +# Fictional negative leap second +2303769600 11 # 2 Jan 1973 # largest accepted value by the parser -5764607523034234879 2 +5764607523034234879 12 )"); - assert(result.leap_seconds.size() == 5); + assert(result.leap_seconds.size() == 6); assert(result.leap_seconds[0].date() == sys_seconds{sys_days{1900y / January / 2}}); assert(result.leap_seconds[0].value() == 1s); assert(result.leap_seconds[1].date() == sys_seconds{sys_days{1972y / January / 1}}); - assert(result.leap_seconds[1].value() == 10s); + assert(result.leap_seconds[1].value() == 1s); assert(result.leap_seconds[2].date() == sys_seconds{sys_days{1972y / July / 1}}); - assert(result.leap_seconds[2].value() == 11s); + assert(result.leap_seconds[2].value() == 1s); assert(result.leap_seconds[3].date() == sys_seconds{sys_days{1973y / January / 1}}); - assert(result.leap_seconds[3].value() == 12s); + assert(result.leap_seconds[3].value() == 1s); + + assert(result.leap_seconds[4].date() == sys_seconds{sys_days{1973y / January / 2}}); + assert(result.leap_seconds[4].value() == -1s); - assert(result.leap_seconds[4].date() == + assert(result.leap_seconds[5].date() == sys_seconds{5764607523034234879s // The database uses 1900-01-01 as epoch. - std::chrono::duration_cast( sys_days{1970y / January / 1} - sys_days{1900y / January / 1})}); - assert(result.leap_seconds[4].value() == 2s); + assert(result.leap_seconds[5].value() == 1s); } int main(int, const char**) { diff --git a/libcxx/test/std/time/time.zone/time.zone.db/leap_seconds.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.db/leap_seconds.pass.cpp index f873ad3167819..39023c31aecbb 100644 --- a/libcxx/test/std/time/time.zone/time.zone.db/leap_seconds.pass.cpp +++ b/libcxx/test/std/time/time.zone/time.zone.db/leap_seconds.pass.cpp @@ -33,34 +33,33 @@ using namespace std::literals::chrono_literals; // At the moment of writing that list is the actual list in the IANA database. // If in the future more leap seconds can be added. static const std::array leap_seconds = { - std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1972y / std::chrono::January / 1}}, 10s), - std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1972y / std::chrono::July / 1}}, 11s), - std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1973y / std::chrono::January / 1}}, 12s), - std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1974y / std::chrono::January / 1}}, 13s), - std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1975y / std::chrono::January / 1}}, 14s), - std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1976y / std::chrono::January / 1}}, 15s), - std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1977y / std::chrono::January / 1}}, 16s), - std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1978y / std::chrono::January / 1}}, 17s), - std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1979y / std::chrono::January / 1}}, 18s), - std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1980y / std::chrono::January / 1}}, 19s), - std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1981y / std::chrono::July / 1}}, 20s), - std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1982y / std::chrono::July / 1}}, 21s), - std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1983y / std::chrono::July / 1}}, 22s), - std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1985y / std::chrono::July / 1}}, 23s), - std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1988y / std::chrono::January / 1}}, 24s), - std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1990y / std::chrono::January / 1}}, 25s), - std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1991y / std::chrono::January / 1}}, 26s), - std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1992y / std::chrono::July / 1}}, 27s), - std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1993y / std::chrono::July / 1}}, 28s), - std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1994y / std::chrono::July / 1}}, 29s), - std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1996y / std::chrono::January / 1}}, 30s), - std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1997y / std::chrono::July / 1}}, 31s), - std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1999y / std::chrono::January / 1}}, 32s), - std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{2006y / std::chrono::January / 1}}, 33s), - std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{2009y / std::chrono::January / 1}}, 34s), - std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{2012y / std::chrono::July / 1}}, 35s), - std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{2015y / std::chrono::July / 1}}, 36s), - std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{2017y / std::chrono::January / 1}}, 37s)}; + std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1972y / std::chrono::July / 1}}, 1s), + std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1973y / std::chrono::January / 1}}, 1s), + std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1974y / std::chrono::January / 1}}, 1s), + std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1975y / std::chrono::January / 1}}, 1s), + std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1976y / std::chrono::January / 1}}, 1s), + std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1977y / std::chrono::January / 1}}, 1s), + std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1978y / std::chrono::January / 1}}, 1s), + std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1979y / std::chrono::January / 1}}, 1s), + std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1980y / std::chrono::January / 1}}, 1s), + std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1981y / std::chrono::July / 1}}, 1s), + std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1982y / std::chrono::July / 1}}, 1s), + std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1983y / std::chrono::July / 1}}, 1s), + std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1985y / std::chrono::July / 1}}, 1s), + std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1988y / std::chrono::January / 1}}, 1s), + std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1990y / std::chrono::January / 1}}, 1s), + std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1991y / std::chrono::January / 1}}, 1s), + std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1992y / std::chrono::July / 1}}, 1s), + std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1993y / std::chrono::July / 1}}, 1s), + std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1994y / std::chrono::July / 1}}, 1s), + std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1996y / std::chrono::January / 1}}, 1s), + std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1997y / std::chrono::July / 1}}, 1s), + std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1999y / std::chrono::January / 1}}, 1s), + std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{2006y / std::chrono::January / 1}}, 1s), + std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{2009y / std::chrono::January / 1}}, 1s), + std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{2012y / std::chrono::July / 1}}, 1s), + std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{2015y / std::chrono::July / 1}}, 1s), + std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{2017y / std::chrono::January / 1}}, 1s)}; int main(int, const char**) { const std::chrono::tzdb& tzdb = std::chrono::get_tzdb(); diff --git a/libcxx/test/std/time/time.zone/time.zone.timezone/time.zone.members/get_info.local_time.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.timezone/time.zone.members/get_info.local_time.pass.cpp new file mode 100644 index 0000000000000..6dc15974c4484 --- /dev/null +++ b/libcxx/test/std/time/time.zone/time.zone.timezone/time.zone.members/get_info.local_time.pass.cpp @@ -0,0 +1,1302 @@ +//===----------------------------------------------------------------------===// +// +// 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-experimental-tzdb +// XFAIL: availability-tzdb-missing + +// + +// class time_zone; + +// template +// local_info get_info(const local_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 first part of the test is manually crafted, the second part compares the +// transitions for all time zones in the database. + +#include +#include +#include +#include + +#include "test_macros.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}; + +/***** ***** 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(static_cast(result)) + h + m + s; +} + +[[nodiscard]] static std::chrono::local_seconds to_local_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(static_cast(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(const std::chrono::local_info& lhs, const std::chrono::local_info& rhs) { + TEST_REQUIRE( + lhs.result == rhs.result, + TEST_WRITE_CONCATENATED("\nResult:\nExpected output ", lhs.result, "\nActual output ", rhs.result, '\n')); + + assert_equal(lhs.first, rhs.first); + assert_equal(lhs.second, rhs.second); +} + +/***** ***** 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 + + using namespace std::literals::chrono_literals; + const std::chrono::time_zone* tz = std::chrono::locate_zone("GMT"); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info(std::chrono::sys_seconds::min(), std::chrono::sys_seconds::max(), 0s, 0min, "GMT"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(std::chrono::local_seconds::min())); +} + +static void test_local_time_out_of_range() { + // Fixed positive offset + // Etc/GMT-1 1 - +01 + + using namespace std::literals::chrono_literals; + { // lower bound + const std::chrono::time_zone* tz = std::chrono::locate_zone("Etc/GMT-1"); + + assert_equal( + std::chrono::local_info( + -1, + std::chrono::sys_info(std::chrono::sys_seconds::min(), std::chrono::sys_seconds::max(), 1h, 0min, "+01"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(std::chrono::local_seconds::min())); + + assert_equal( + std::chrono::local_info( + -1, + std::chrono::sys_info(std::chrono::sys_seconds::min(), std::chrono::sys_seconds::max(), 1h, 0min, "+01"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(std::chrono::local_seconds::min() + 59min + 59s)); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info(std::chrono::sys_seconds::min(), std::chrono::sys_seconds::max(), 1h, 0min, "+01"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(std::chrono::local_seconds::min() + 1h)); + } + + { // upper bound + const std::chrono::time_zone* tz = std::chrono::locate_zone("Etc/GMT+1"); + + assert_equal( + std::chrono::local_info( + -2, + std::chrono::sys_info(std::chrono::sys_seconds::min(), std::chrono::sys_seconds::max(), -1h, 0min, "-01"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(std::chrono::local_seconds::max() - 1s)); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info(std::chrono::sys_seconds::min(), std::chrono::sys_seconds::max(), -1h, 0min, "-01"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(std::chrono::local_seconds::max() - 1h - 1s)); + } +} + +static void test_indian_kerguelen() { + // One change, no rules, no dst changes. + + // Z Indian/Kerguelen 0 - -00 1950 + // 5 - +05 + + using namespace std::literals::chrono_literals; + const std::chrono::time_zone* tz = std::chrono::locate_zone("Indian/Kerguelen"); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + std::chrono::sys_seconds::min(), to_sys_seconds(1950y, std::chrono::January, 1d), 0s, 0min, "-00"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(std::chrono::local_seconds::min())); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::nonexistent, + std::chrono::sys_info( + std::chrono::sys_seconds::min(), to_sys_seconds(1950y, std::chrono::January, 1d), 0s, 0min, "-00"), + std::chrono::sys_info( + to_sys_seconds(1950y, std::chrono::January, 1d), std::chrono::sys_seconds::max(), 5h, 0min, "+05")), + tz->get_info(to_local_seconds(1950y, std::chrono::January, 1d))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + to_sys_seconds(1950y, std::chrono::January, 1d), std::chrono::sys_seconds::max(), 5h, 0min, "+05"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1950y, std::chrono::January, 1d, 5h))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + to_sys_seconds(1950y, std::chrono::January, 1d), std::chrono::sys_seconds::max(), 5h, 0min, "+05"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(std::chrono::local_seconds::max() - 1s)); +} + +static void test_antarctica_rothera() { + // One change, no rules, no dst changes + + // Z Antarctica/Rothera 0 - -00 1976 D + // -3 - -03 + + using namespace std::literals::chrono_literals; + const std::chrono::time_zone* tz = std::chrono::locate_zone("Antarctica/Rothera"); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + std::chrono::sys_seconds::min(), to_sys_seconds(1976y, std::chrono::December, 1d), 0s, 0min, "-00"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(std::chrono::local_seconds::min())); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + std::chrono::sys_seconds::min(), to_sys_seconds(1976y, std::chrono::December, 1d), 0s, 0min, "-00"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1976y, std::chrono::November, 30d, 20h, 59min, 59s))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::ambiguous, + std::chrono::sys_info( + std::chrono::sys_seconds::min(), to_sys_seconds(1976y, std::chrono::December, 1d), 0s, 0min, "-00"), + std::chrono::sys_info( + to_sys_seconds(1976y, std::chrono::December, 1d), std::chrono::sys_seconds::max(), -3h, 0min, "-03")), + tz->get_info(to_local_seconds(1976y, std::chrono::November, 30d, 21h))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::ambiguous, + std::chrono::sys_info( + std::chrono::sys_seconds::min(), to_sys_seconds(1976y, std::chrono::December, 1d), 0s, 0min, "-00"), + std::chrono::sys_info( + to_sys_seconds(1976y, std::chrono::December, 1d), std::chrono::sys_seconds::max(), -3h, 0min, "-03")), + tz->get_info(to_local_seconds(1976y, std::chrono::November, 30d, 23h, 59min, 59s))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + to_sys_seconds(1976y, std::chrono::December, 1d), std::chrono::sys_seconds::max(), -3h, 0min, "-03"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1976y, std::chrono::December, 1d))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + to_sys_seconds(1976y, std::chrono::December, 1d), std::chrono::sys_seconds::max(), -3h, 0min, "-03"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(std::chrono::local_seconds::max() - 3h - 1s)); + + assert_equal( + std::chrono::local_info( + -2, + std::chrono::sys_info( + to_sys_seconds(1976y, std::chrono::December, 1d), std::chrono::sys_seconds::max(), -3h, 0min, "-03"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(std::chrono::local_seconds::max() - 1s)); +} + +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::local_info( + -1, + std::chrono::sys_info( + std::chrono::sys_seconds::min(), + to_sys_seconds(1904y, std::chrono::October, 29d, 17h), + 7h + 36min + 42s, + 0min, + "LMT"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(std::chrono::local_seconds::min())); + + assert_equal( + std::chrono::local_info( + -1, + std::chrono::sys_info( + std::chrono::sys_seconds::min(), + to_sys_seconds(1904y, std::chrono::October, 29d, 17h), + 7h + 36min + 42s, + 0min, + "LMT"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(std::chrono::local_seconds::min() + 7h + 36min + 41s)); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + std::chrono::sys_seconds::min(), + to_sys_seconds(1904y, std::chrono::October, 29d, 17h), + 7h + 36min + 42s, + 0min, + "LMT"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(std::chrono::local_seconds::min() + 7h + 36min + 42s)); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + std::chrono::sys_seconds::min(), + to_sys_seconds(1904y, std::chrono::October, 29d, 17h), + 7h + 36min + 42s, + 0min, + "LMT"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1904y, std::chrono::October, 30d, 0h, 36min, 41s))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::nonexistent, + std::chrono::sys_info( + std::chrono::sys_seconds::min(), + to_sys_seconds(1904y, std::chrono::October, 29d, 17h), + 7h + 36min + 42s, + 0min, + "LMT"), + std::chrono::sys_info( + to_sys_seconds(1904y, std::chrono::October, 29d, 17h), + to_sys_seconds(1941y, std::chrono::June, 14d, 19h), + 8h, + 0min, + "HKT")), + tz->get_info(to_local_seconds(1904y, std::chrono::October, 30d, 0h, 36min, 42s))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::nonexistent, + std::chrono::sys_info( + std::chrono::sys_seconds::min(), + to_sys_seconds(1904y, std::chrono::October, 29d, 17h), + 7h + 36min + 42s, + 0min, + "LMT"), + std::chrono::sys_info( + to_sys_seconds(1904y, std::chrono::October, 29d, 17h), + to_sys_seconds(1941y, std::chrono::June, 14d, 19h), + 8h, + 0min, + "HKT")), + tz->get_info(to_local_seconds(1904y, std::chrono::October, 30d, 0h, 59min, 59s))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + to_sys_seconds(1904y, std::chrono::October, 29d, 17h), + to_sys_seconds(1941y, std::chrono::June, 14d, 19h), + 8h, + 0min, + "HKT"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1904y, std::chrono::October, 30d, 1h))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + to_sys_seconds(1904y, std::chrono::October, 29d, 17h), + to_sys_seconds(1941y, std::chrono::June, 14d, 19h), + 8h, + 0min, + "HKT"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1941y, std::chrono::June, 15d, 2h, 59min, 59s))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::nonexistent, + std::chrono::sys_info( + to_sys_seconds(1904y, std::chrono::October, 29d, 17h), + to_sys_seconds(1941y, std::chrono::June, 14d, 19h), + 8h, + 0min, + "HKT"), + std::chrono::sys_info( + to_sys_seconds(1941y, std::chrono::June, 14d, 19h), + to_sys_seconds(1941y, std::chrono::September, 30d, 19h), + 9h, + 60min, + "HKST")), + tz->get_info(to_local_seconds(1941y, std::chrono::June, 15d, 3h))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::nonexistent, + std::chrono::sys_info( + to_sys_seconds(1904y, std::chrono::October, 29d, 17h), + to_sys_seconds(1941y, std::chrono::June, 14d, 19h), + 8h, + 0min, + "HKT"), + std::chrono::sys_info( + to_sys_seconds(1941y, std::chrono::June, 14d, 19h), + to_sys_seconds(1941y, std::chrono::September, 30d, 19h), + 9h, + 60min, + "HKST")), + tz->get_info(to_local_seconds(1941y, std::chrono::June, 15d, 3h, 59min, 59s))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + to_sys_seconds(1941y, std::chrono::June, 14d, 19h), + to_sys_seconds(1941y, std::chrono::September, 30d, 19h), + 9h, + 60min, + "HKST"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1941y, std::chrono::June, 15d, 4h))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + to_sys_seconds(1941y, std::chrono::June, 14d, 19h), + to_sys_seconds(1941y, std::chrono::September, 30d, 19h), + 9h, + 60min, + "HKST"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1941y, std::chrono::October, 1d, 3h, 29min, 29s))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::ambiguous, + std::chrono::sys_info( + to_sys_seconds(1941y, std::chrono::June, 14d, 19h), + to_sys_seconds(1941y, std::chrono::September, 30d, 19h), + 9h, + 60min, + "HKST"), + std::chrono::sys_info( + to_sys_seconds(1941y, std::chrono::September, 30d, 19h), + to_sys_seconds(1941y, std::chrono::December, 24d, 15h, 30min), + 8h + 30min, + 30min, + "HKWT")), + tz->get_info(to_local_seconds(1941y, std::chrono::October, 1d, 3h, 30min))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::ambiguous, + std::chrono::sys_info( + to_sys_seconds(1941y, std::chrono::June, 14d, 19h), + to_sys_seconds(1941y, std::chrono::September, 30d, 19h), + 9h, + 60min, + "HKST"), + std::chrono::sys_info( + to_sys_seconds(1941y, std::chrono::September, 30d, 19h), + to_sys_seconds(1941y, std::chrono::December, 24d, 15h, 30min), + 8h + 30min, + 30min, + "HKWT")), + tz->get_info(to_local_seconds(1941y, std::chrono::October, 1d, 3h, 59min, 59s))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + to_sys_seconds(1941y, std::chrono::September, 30d, 19h), + to_sys_seconds(1941y, std::chrono::December, 24d, 15h, 30min), + 8h + 30min, + 30min, + "HKWT"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1941y, std::chrono::October, 1d, 4h))); +} + +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::local_info( + -1, + std::chrono::sys_info( + std::chrono::sys_seconds::min(), + to_sys_seconds(1893y, std::chrono::March, 31d, 23h, 6min, 32s), + 53min + 28s, + 0min, + "LMT"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(std::chrono::local_seconds::min())); + + assert_equal( + std::chrono::local_info( + -1, + std::chrono::sys_info( + std::chrono::sys_seconds::min(), + to_sys_seconds(1893y, std::chrono::March, 31d, 23h, 6min, 32s), + 53min + 28s, + 0min, + "LMT"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(std::chrono::local_seconds::min() + 53min + 27s)); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + std::chrono::sys_seconds::min(), + to_sys_seconds(1893y, std::chrono::March, 31d, 23h, 6min, 32s), + 53min + 28s, + 0min, + "LMT"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(std::chrono::local_seconds::min() + 53min + 28s)); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + std::chrono::sys_seconds::min(), + to_sys_seconds(1893y, std::chrono::March, 31d, 23h, 6min, 32s), + 53min + 28s, + 0min, + "LMT"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1893y, std::chrono::March, 31d, 23h, 59min, 59s))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + to_sys_seconds(1946y, std::chrono::October, 7d, 1h), + to_sys_seconds(1947y, std::chrono::April, 6d, 2h), + 1h, + 0min, + "CET"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1947y, std::chrono::April, 6d, 2h, 59min, 59s))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::nonexistent, + std::chrono::sys_info( + to_sys_seconds(1946y, std::chrono::October, 7d, 1h), + to_sys_seconds(1947y, std::chrono::April, 6d, 2h), + 1h, + 0min, + "CET"), + std::chrono::sys_info( + to_sys_seconds(1947y, std::chrono::April, 6d, 2h), + to_sys_seconds(1947y, std::chrono::May, 11d, 1h), + 2h, + 60min, + "CEST")), + tz->get_info(to_local_seconds(1947y, std::chrono::April, 6d, 3h))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::nonexistent, + std::chrono::sys_info( + to_sys_seconds(1946y, std::chrono::October, 7d, 1h), + to_sys_seconds(1947y, std::chrono::April, 6d, 2h), + 1h, + 0min, + "CET"), + std::chrono::sys_info( + to_sys_seconds(1947y, std::chrono::April, 6d, 2h), + to_sys_seconds(1947y, std::chrono::May, 11d, 1h), + 2h, + 60min, + "CEST")), + tz->get_info(to_local_seconds(1947y, std::chrono::April, 6d, 3h, 59min, 59s))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + to_sys_seconds(1947y, std::chrono::April, 6d, 2h), + to_sys_seconds(1947y, std::chrono::May, 11d, 1h), + 2h, + 60min, + "CEST"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1947y, std::chrono::April, 6d, 4h))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + to_sys_seconds(1947y, std::chrono::April, 6d, 2h), + to_sys_seconds(1947y, std::chrono::May, 11d, 1h), + 2h, + 60min, + "CEST"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1947y, std::chrono::May, 11d, 2h, 59min, 59s))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::nonexistent, + std::chrono::sys_info( + to_sys_seconds(1947y, std::chrono::April, 6d, 2h), + to_sys_seconds(1947y, std::chrono::May, 11d, 1h), + 2h, + 60min, + "CEST"), + std::chrono::sys_info( + to_sys_seconds(1947y, std::chrono::May, 11d, 1h), + to_sys_seconds(1947y, std::chrono::June, 29d), + 3h, + 120min, + "CEMT")), + tz->get_info(to_local_seconds(1947y, std::chrono::May, 11d, 3h))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::nonexistent, + std::chrono::sys_info( + to_sys_seconds(1947y, std::chrono::April, 6d, 2h), + to_sys_seconds(1947y, std::chrono::May, 11d, 1h), + 2h, + 60min, + "CEST"), + std::chrono::sys_info( + to_sys_seconds(1947y, std::chrono::May, 11d, 1h), + to_sys_seconds(1947y, std::chrono::June, 29d), + 3h, + 120min, + "CEMT")), + tz->get_info(to_local_seconds(1947y, std::chrono::May, 11d, 3h, 59min, 59s))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + to_sys_seconds(1947y, std::chrono::May, 11d, 1h), + to_sys_seconds(1947y, std::chrono::June, 29d), + 3h, + 120min, + "CEMT"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1947y, std::chrono::May, 11d, 4h))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + to_sys_seconds(1947y, std::chrono::May, 11d, 1h), + to_sys_seconds(1947y, std::chrono::June, 29d), + 3h, + 120min, + "CEMT"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1947y, std::chrono::June, 29d, 1h, 59min, 59s))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::ambiguous, + std::chrono::sys_info( + to_sys_seconds(1947y, std::chrono::May, 11d, 1h), + to_sys_seconds(1947y, std::chrono::June, 29d), + 3h, + 120min, + "CEMT"), + std::chrono::sys_info( + to_sys_seconds(1947y, std::chrono::June, 29d), + to_sys_seconds(1947y, std::chrono::October, 5d, 1h), + 2h, + 60min, + "CEST")), + tz->get_info(to_local_seconds(1947y, std::chrono::June, 29d, 2h))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::ambiguous, + std::chrono::sys_info( + to_sys_seconds(1947y, std::chrono::May, 11d, 1h), + to_sys_seconds(1947y, std::chrono::June, 29d), + 3h, + 120min, + "CEMT"), + std::chrono::sys_info( + to_sys_seconds(1947y, std::chrono::June, 29d), + to_sys_seconds(1947y, std::chrono::October, 5d, 1h), + 2h, + 60min, + "CEST")), + tz->get_info(to_local_seconds(1947y, std::chrono::June, 29d, 2h, 59min, 59s))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + to_sys_seconds(1947y, std::chrono::June, 29d), + to_sys_seconds(1947y, std::chrono::October, 5d, 1h), + 2h, + 60min, + "CEST"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1947y, std::chrono::June, 29d, 3h))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + to_sys_seconds(1947y, std::chrono::June, 29d), + to_sys_seconds(1947y, std::chrono::October, 5d, 1h), + 2h, + 60min, + "CEST"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1947y, std::chrono::October, 5d, 1h, 59min, 59s))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::ambiguous, + std::chrono::sys_info( + to_sys_seconds(1947y, std::chrono::June, 29d), + to_sys_seconds(1947y, std::chrono::October, 5d, 1h), + 2h, + 60min, + "CEST"), + std::chrono::sys_info( + to_sys_seconds(1947y, std::chrono::October, 5d, 1h), + to_sys_seconds(1948y, std::chrono::April, 18d, 1h), + 1h, + 0min, + "CET")), + tz->get_info(to_local_seconds(1947y, std::chrono::October, 5d, 2h))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::ambiguous, + std::chrono::sys_info( + to_sys_seconds(1947y, std::chrono::June, 29d), + to_sys_seconds(1947y, std::chrono::October, 5d, 1h), + 2h, + 60min, + "CEST"), + std::chrono::sys_info( + to_sys_seconds(1947y, std::chrono::October, 5d, 1h), + to_sys_seconds(1948y, std::chrono::April, 18d, 1h), + 1h, + 0min, + "CET")), + tz->get_info(to_local_seconds(1947y, std::chrono::October, 5d, 2h, 59min, 59s))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + to_sys_seconds(1947y, std::chrono::October, 5d, 1h), + to_sys_seconds(1948y, std::chrono::April, 18d, 1h), + 1h, + 0min, + "CET"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1947y, std::chrono::October, 5d, 3h))); +} + +static void test_europe_dublin() { + // Z Europe/Dublin -0:25:21 - LMT 1880 Au 2 + // -0:25:21 - DMT 1916 May 21 2s + // -0:25:21 1 IST 1916 O 1 2s + // 0 G %s 1921 D 6 + // ... + // + // R G 1916 o - May 21 2s 1 BST + // R G 1916 o - O 1 2s 0 GMT + // R G 1917 o - Ap 8 2s 1 BST + // ... + + using namespace std::literals::chrono_literals; + const std::chrono::time_zone* tz = std::chrono::locate_zone("Europe/Dublin"); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + std::chrono::sys_seconds::min(), + to_sys_seconds(1880y, std::chrono::August, 2d, 0h, 25min, 21s), + -(25min + 21s), + 0min, + "LMT"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(std::chrono::local_seconds::min())); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + std::chrono::sys_seconds::min(), + to_sys_seconds(1880y, std::chrono::August, 2d, 0h, 25min, 21s), + -(25min + 21s), + 0min, + "LMT"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1880y, std::chrono::August, 1d, 23h, 59min, 59s))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + to_sys_seconds(1880y, std::chrono::August, 2d, 0h, 25min, 21s), + to_sys_seconds(1916y, std::chrono::May, 21d, 2h, 25min, 21s), + -(25min + 21s), + 0min, + "DMT"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1880y, std::chrono::August, 2d))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + to_sys_seconds(1880y, std::chrono::August, 2d, 0h, 25min, 21s), + to_sys_seconds(1916y, std::chrono::May, 21d, 2h, 25min, 21s), + -(25min + 21s), + 0min, + "DMT"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1916y, std::chrono::May, 21d, 1h, 59min, 59s))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::nonexistent, + std::chrono::sys_info( + to_sys_seconds(1880y, std::chrono::August, 2d, 0h, 25min, 21s), + to_sys_seconds(1916y, std::chrono::May, 21d, 2h, 25min, 21s), + -(25min + 21s), + 0min, + "DMT"), + std::chrono::sys_info( + to_sys_seconds(1916y, std::chrono::May, 21d, 2h, 25min, 21s), + to_sys_seconds(1916y, std::chrono::October, 1d, 02h, 25min, 21s), + 34min + 39s, + 60min, + "IST")), + tz->get_info(to_local_seconds(1916y, std::chrono::May, 21d, 2h))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::nonexistent, + std::chrono::sys_info( + to_sys_seconds(1880y, std::chrono::August, 2d, 0h, 25min, 21s), + to_sys_seconds(1916y, std::chrono::May, 21d, 2h, 25min, 21s), + -(25min + 21s), + 0min, + "DMT"), + std::chrono::sys_info( + to_sys_seconds(1916y, std::chrono::May, 21d, 2h, 25min, 21s), + to_sys_seconds(1916y, std::chrono::October, 1d, 02h, 25min, 21s), + 34min + 39s, + 60min, + "IST")), + tz->get_info(to_local_seconds(1916y, std::chrono::May, 21d, 2h, 59min, 59s))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + to_sys_seconds(1916y, std::chrono::May, 21d, 2h, 25min, 21s), + to_sys_seconds(1916y, std::chrono::October, 1d, 02h, 25min, 21s), + 34min + 39s, + 60min, + "IST"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1916y, std::chrono::May, 21d, 6h))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + to_sys_seconds(1916y, std::chrono::May, 21d, 2h, 25min, 21s), + to_sys_seconds(1916y, std::chrono::October, 1d, 02h, 25min, 21s), + 34min + 39s, + 60min, + "IST"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1916y, std::chrono::October, 1d, 2h, 25min, 20s))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::ambiguous, + std::chrono::sys_info( + to_sys_seconds(1916y, std::chrono::May, 21d, 2h, 25min, 21s), + to_sys_seconds(1916y, std::chrono::October, 1d, 02h, 25min, 21s), + 34min + 39s, + 60min, + "IST"), + std::chrono::sys_info( + to_sys_seconds(1916y, std::chrono::October, 1d, 02h, 25min, 21s), + to_sys_seconds(1917y, std::chrono::April, 8d, 2h), + 0s, + 0min, + "GMT")), + tz->get_info(to_local_seconds(1916y, std::chrono::October, 1d, 2h, 59min, 59s))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + to_sys_seconds(1916y, std::chrono::October, 1d, 02h, 25min, 21s), + to_sys_seconds(1917y, std::chrono::April, 8d, 2h), + 0s, + 0min, + "GMT"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1916y, std::chrono::October, 1d, 3h))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + to_sys_seconds(1916y, std::chrono::October, 1d, 02h, 25min, 21s), + to_sys_seconds(1917y, std::chrono::April, 8d, 2h), + 0s, + 0min, + "GMT"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1917y, std::chrono::April, 8d, 1h, 59min, 59s))); +} + +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 + // ... + // + // 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 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 + // ... + + using namespace std::literals::chrono_literals; + const std::chrono::time_zone* tz = std::chrono::locate_zone("America/St_Johns"); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + std::chrono::sys_seconds::min(), + to_sys_seconds(1884y, std::chrono::January, 1d, 3h, 30min, 52s), + -(3h + 30min + 52s), + 0min, + "LMT"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(std::chrono::local_seconds::min())); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + std::chrono::sys_seconds::min(), + to_sys_seconds(1884y, std::chrono::January, 1d, 3h, 30min, 52s), + -(3h + 30min + 52s), + 0min, + "LMT"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1883y, std::chrono::December, 31d, 23h, 59min, 59s))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + to_sys_seconds(1884y, std::chrono::January, 1d, 3h, 30min, 52s), + to_sys_seconds(1917y, std::chrono::April, 8d, 5h, 30min, 52s), + -(3h + 30min + 52s), + 0min, + "NST"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1884y, std::chrono::January, 1d))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + to_sys_seconds(1884y, std::chrono::January, 1d, 3h, 30min, 52s), + to_sys_seconds(1917y, std::chrono::April, 8d, 5h, 30min, 52s), + -(3h + 30min + 52s), + 0min, + "NST"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1917y, std::chrono::April, 8d, 1h, 59min, 59s))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::nonexistent, + std::chrono::sys_info( + to_sys_seconds(1884y, std::chrono::January, 1d, 3h, 30min, 52s), + to_sys_seconds(1917y, std::chrono::April, 8d, 5h, 30min, 52s), + -(3h + 30min + 52s), + 0min, + "NST"), + std::chrono::sys_info( + to_sys_seconds(1917y, std::chrono::April, 8d, 5h, 30min, 52s), + to_sys_seconds(1917y, std::chrono::September, 17d, 4h, 30min, 52s), + -(2h + 30min + 52s), + 60min, + "NDT")), + tz->get_info(to_local_seconds(1917y, std::chrono::April, 8d, 2h))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::nonexistent, + std::chrono::sys_info( + to_sys_seconds(1884y, std::chrono::January, 1d, 3h, 30min, 52s), + to_sys_seconds(1917y, std::chrono::April, 8d, 5h, 30min, 52s), + -(3h + 30min + 52s), + 0min, + "NST"), + std::chrono::sys_info( + to_sys_seconds(1917y, std::chrono::April, 8d, 5h, 30min, 52s), + to_sys_seconds(1917y, std::chrono::September, 17d, 4h, 30min, 52s), + -(2h + 30min + 52s), + 60min, + "NDT")), + tz->get_info(to_local_seconds(1917y, std::chrono::April, 8d, 2h, 59min, 59s))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + to_sys_seconds(1917y, std::chrono::April, 8d, 5h, 30min, 52s), + to_sys_seconds(1917y, std::chrono::September, 17d, 4h, 30min, 52s), + -(2h + 30min + 52s), + 60min, + "NDT"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1917y, std::chrono::April, 8d, 3h))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + to_sys_seconds(1917y, std::chrono::April, 8d, 5h, 30min, 52s), + to_sys_seconds(1917y, std::chrono::September, 17d, 4h, 30min, 52s), + -(2h + 30min + 52s), + 60min, + "NDT"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1917y, std::chrono::September, 17d, 0h, 59min, 59s))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::ambiguous, + std::chrono::sys_info( + to_sys_seconds(1917y, std::chrono::April, 8d, 5h, 30min, 52s), + to_sys_seconds(1917y, std::chrono::September, 17d, 4h, 30min, 52s), + -(2h + 30min + 52s), + 60min, + "NDT"), + std::chrono::sys_info( + to_sys_seconds(1917y, std::chrono::September, 17d, 4h, 30min, 52s), + to_sys_seconds(1918y, std::chrono::April, 14d, 5h, 30min, 52s), + -(3h + 30min + 52s), + 0min, + "NST")), + tz->get_info(to_local_seconds(1917y, std::chrono::September, 17d, 1h))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::ambiguous, + std::chrono::sys_info( + to_sys_seconds(1917y, std::chrono::April, 8d, 5h, 30min, 52s), + to_sys_seconds(1917y, std::chrono::September, 17d, 4h, 30min, 52s), + -(2h + 30min + 52s), + 60min, + "NDT"), + std::chrono::sys_info( + to_sys_seconds(1917y, std::chrono::September, 17d, 4h, 30min, 52s), + to_sys_seconds(1918y, std::chrono::April, 14d, 5h, 30min, 52s), + -(3h + 30min + 52s), + 0min, + "NST")), + tz->get_info(to_local_seconds(1917y, std::chrono::September, 17d, 1h, 59min, 59s))); + + assert_equal( + std::chrono::local_info( + std::chrono::local_info::unique, + std::chrono::sys_info( + to_sys_seconds(1917y, std::chrono::September, 17d, 4h, 30min, 52s), + to_sys_seconds(1918y, std::chrono::April, 14d, 5h, 30min, 52s), + -(3h + 30min + 52s), + 0min, + "NST"), + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + tz->get_info(to_local_seconds(1917y, std::chrono::September, 17d, 2h))); +} + +static void validate_transitions(const std::chrono::time_zone& zone) { + using namespace std::literals::chrono_literals; + + constexpr auto begin = std::chrono::time_point_cast( + static_cast(std::chrono::year_month_day{first, std::chrono::January, 1d})); + constexpr auto end = std::chrono::time_point_cast( + static_cast(std::chrono::year_month_day{last, std::chrono::January, 1d})); + + // Builds the set of sys_info objects for the selected time range. + std::vector input; + std::chrono::sys_seconds s = begin; + do { + input.emplace_back(zone.get_info(s)); + s = input.back().end; + } while (s < end); + + for (auto previous = input.begin(), next = previous + 1; next != input.end(); ++previous, ++next) { + // Now iterates to all adjacent objects. + // For every transition gets the locate time of the + // - end of the first (a) + // - the start if the second (b) + // Depending on the difference between 'a' and 'b' different tests are done. + std::chrono::local_seconds end_previous{previous->end.time_since_epoch() + previous->offset}; + std::chrono::local_seconds begin_next{next->begin.time_since_epoch() + next->offset}; + + if (end_previous == begin_next) { + // unique transition + // a |------------| + // b |----------| + // T + assert_equal(std::chrono::local_info( + std::chrono::local_info::unique, + *previous, + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + zone.get_info(end_previous - 1s)); + + assert_equal(std::chrono::local_info( + std::chrono::local_info::unique, + *next, + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + zone.get_info(begin_next)); + + } else if (end_previous < begin_next) { + // non-existent transition + // a |------------| + // b |----------| + // T T + assert_equal(std::chrono::local_info( + std::chrono::local_info::unique, + *previous, + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + zone.get_info(end_previous - 1s)); + + assert_equal(std::chrono::local_info(std::chrono::local_info::nonexistent, *previous, *next), + zone.get_info(end_previous)); + + assert_equal(std::chrono::local_info(std::chrono::local_info::nonexistent, *previous, *next), + zone.get_info(begin_next - 1s)); + + assert_equal(std::chrono::local_info( + std::chrono::local_info::unique, + *next, + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + zone.get_info(begin_next)); + + } else { + // ambiguous transition + // a |------------| + // b |----------| + // T T + assert_equal(std::chrono::local_info( + std::chrono::local_info::unique, + *previous, + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + zone.get_info(begin_next - 1s)); + + assert_equal(std::chrono::local_info(std::chrono::local_info::ambiguous, *previous, *next), + zone.get_info(begin_next)); + + assert_equal(std::chrono::local_info(std::chrono::local_info::ambiguous, *previous, *next), + zone.get_info(end_previous - 1s)); + + assert_equal(std::chrono::local_info( + std::chrono::local_info::unique, + *next, + std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")), + zone.get_info(end_previous)); + } + } +} + +int main(int, const char**) { + test_gmt(); + test_local_time_out_of_range(); + test_indian_kerguelen(); + test_antarctica_rothera(); + + test_asia_hong_kong(); + test_europe_berlin(); + test_europe_dublin(); + test_america_st_johns(); + + const std::chrono::tzdb& tzdb = std::chrono::get_tzdb(); + for (const auto& zone : tzdb.zones) { + validate_transitions(zone); + } + + return 0; +}