[libcxx-commits] [libcxx] [libc++][chrono] Adds the sys_info class. (PR #85619)
Mark de Wever via libcxx-commits
libcxx-commits at lists.llvm.org
Sat Apr 6 06:47:16 PDT 2024
https://github.com/mordante updated https://github.com/llvm/llvm-project/pull/85619
>From 5b2ab38b65efc75f38408c1df3c7a08fa890ab8e Mon Sep 17 00:00:00 2001
From: Mark de Wever <koraq at xs4all.nl>
Date: Tue, 27 Sep 2022 20:20:56 +0200
Subject: [PATCH 1/2] [libc++][chrono] Adds the sys_info class.
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
---
libcxx/docs/Status/Cxx2cIssues.csv | 1 +
libcxx/include/CMakeLists.txt | 1 +
libcxx/include/__chrono/sys_info.h | 53 +
libcxx/include/__chrono/time_zone.h | 11 +
libcxx/include/chrono | 13 +
libcxx/include/libcxx.imp | 1 +
libcxx/include/module.modulemap | 3 +
libcxx/modules/std/chrono.inc | 2 +
libcxx/src/include/tzdb/types_private.h | 15 +-
libcxx/src/include/tzdb/tzdb_list_private.h | 7 +
libcxx/src/time_zone.cpp | 858 ++++++++++
...rono.nodiscard_extensions.compile.pass.cpp | 1 +
.../chrono.nodiscard_extensions.verify.cpp | 2 +
.../get_info.sys_time.pass.cpp | 142 ++
.../sys_info.members.compile.pass.cpp | 33 +
.../get_info.sys_time.pass.cpp | 1374 +++++++++++++++++
.../time.zone.members/sys_info.zdump.pass.cpp | 127 ++
libcxx/utils/libcxx/test/features.py | 6 +
18 files changed, 2648 insertions(+), 2 deletions(-)
create mode 100644 libcxx/include/__chrono/sys_info.h
create mode 100644 libcxx/test/libcxx/time/time.zone/time.zone.timezone/time.zone.members/get_info.sys_time.pass.cpp
create mode 100644 libcxx/test/std/time/time.zone/time.zone.info/time.zone.info.sys/sys_info.members.compile.pass.cpp
create mode 100644 libcxx/test/std/time/time.zone/time.zone.timezone/time.zone.members/get_info.sys_time.pass.cpp
create mode 100644 libcxx/test/std/time/time.zone/time.zone.timezone/time.zone.members/sys_info.zdump.pass.cpp
diff --git a/libcxx/docs/Status/Cxx2cIssues.csv b/libcxx/docs/Status/Cxx2cIssues.csv
index 8a4bf2ef62162a..62603eb2cfe3d3 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 db3980342f50bf..ee85a45ab71a01 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 00000000000000..16ec5dd254d59a
--- /dev/null
+++ b/libcxx/include/__chrono/sys_info.h
@@ -0,0 +1,53 @@
+// -*- 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 && !defined(_LIBCPP_HAS_NO_TIME_ZONE_DATABASE) && !defined(_LIBCPP_HAS_NO_FILESYSTEM) && \
+ !defined(_LIBCPP_HAS_NO_LOCALIZATION)
+
+namespace chrono {
+
+struct sys_info {
+ sys_seconds begin;
+ sys_seconds end;
+ seconds offset;
+ minutes save;
+ string abbrev;
+};
+
+} // namespace chrono
+
+# endif // _LIBCPP_STD_VER >= 20 && !defined(_LIBCPP_HAS_NO_TIME_ZONE_DATABASE) && !defined(_LIBCPP_HAS_NO_FILESYSTEM)
+ // && !defined(_LIBCPP_HAS_NO_LOCALIZATION)
+
+_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 7d97327a6c8e99..8e30034b799ad9 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 @@ class _LIBCPP_AVAILABILITY_TZDB time_zone {
_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 8fdc30a3624dfc..00b940a6610a3a 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
@@ -906,6 +918,7 @@ constexpr chrono::year operator ""y(unsigned lo
#if !defined(_LIBCPP_HAS_NO_TIME_ZONE_DATABASE) && !defined(_LIBCPP_HAS_NO_FILESYSTEM) && \
!defined(_LIBCPP_HAS_NO_LOCALIZATION)
# include <__chrono/leap_second.h>
+# include <__chrono/sys_info.h>
# include <__chrono/time_zone.h>
# include <__chrono/time_zone_link.h>
# include <__chrono/tzdb.h>
diff --git a/libcxx/include/libcxx.imp b/libcxx/include/libcxx.imp
index 2cb1fa5e1e2aa0..6fa2e1d159b398 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 6d4dcc2511f3ec..c0bb97a134a546 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 e14228043d3b84..575e6347aecce1 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/types_private.h b/libcxx/src/include/tzdb/types_private.h
index 4604b9fc88114d..bdc9418a8866be 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 implemention the size_t based caching.
+ using __rules_t = variant<monostate, __tz::__save, string /*, size_t*/>;
__rules_t __rules;
diff --git a/libcxx/src/include/tzdb/tzdb_list_private.h b/libcxx/src/include/tzdb/tzdb_list_private.h
index 969b2b9f8a9f63..a45a280b8e65aa 100644
--- a/libcxx/src/include/tzdb/tzdb_list_private.h
+++ b/libcxx/src/include/tzdb/tzdb_list_private.h
@@ -81,6 +81,13 @@ class tzdb_list::__impl {
return __tzdb_.end();
}
+ const __tz::__rules_storage_type& __rules() const noexcept {
+#ifndef _LIBCPP_HAS_NO_THREADS
+ shared_lock __lock{__mutex_};
+#endif
+ return __rules_.front();
+ }
+
private:
// Loads the tzdbs
// pre: The caller ensures the locking, if needed, is done.
diff --git a/libcxx/src/time_zone.cpp b/libcxx/src/time_zone.cpp
index b6bf06a116f68b..ff9533d7d843f1 100644
--- a/libcxx/src/time_zone.cpp
+++ b/libcxx/src/time_zone.cpp
@@ -8,14 +8,709 @@
// 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 at 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 <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
+//
+// Accepting invalid formats that can be processed in a sensible way would better
+// serve the user than throwing an exception. So some of these rules are not
+// strictly validated.
+// 1 This is not validated. Some examples that will be accepted are, "+04:30",
+// "Q", "42".
+// 2 How this format is formatted is not specified. In the current tzdata.zi
+// this value is not used. This value is accepted in a part of the format. So
+// "a%s%zb" will be considered valid.
+// 3 This is not validated, the output might be incorrect.
+// Proper validation would make the algorithm more complex. Then the first
+// element of the pair is used the parsing of FORMAT can stop. To do proper
+// validation the tail should be validated.
+// 4 This value is accepted in a part of the format. So "a%s%zb" will be
+// considered valid.
+[[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': {
+ 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 {
+ __result.push_back(__c);
+ }
+ }
+
+ if (__shift)
+ std::__throw_runtime_error("corrupt tzdb FORMAT field: input ended with the start of the escape sequence '%'");
+
+ 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 string& __rule_name) {
+ const __tz::__rules_storage_type& __rules = get_tzdb_list().__implementation().__rules();
+
+ auto __result = chrono::__binary_find(__rules, __rule_name, {}, [](const auto& __p) { return __p.first; });
+ if (__result == std::end(__rules))
+ 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 pril 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 = __rule_to_sys_seconds(__stdoff, __save, *__it, __y);
+ if (__t <= __time)
+ continue;
+
+ _LIBCPP_ASSERT(!__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 string& __rule_name) {
+ const vector<__tz::__rule>& __rules = __get_rules(__rule_name);
+
+ auto __rule = chrono::__first_rule(__continuation.__stdoff, __rules);
+ _LIBCPP_ASSERT(__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) {
+ 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, __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 +722,169 @@ _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 to keep results occur before __time, likewise when a
+ // valid result is found the algorithm needs test the next continuation to see
+ // when 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();
+ 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);
+
+ if (__sys_info) {
+ _LIBCPP_ASSERT(__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" valid with valid.
+ __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(__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/test/libcxx/diagnostics/chrono.nodiscard_extensions.compile.pass.cpp b/libcxx/test/libcxx/diagnostics/chrono.nodiscard_extensions.compile.pass.cpp
index 9acb57fa05f75c..cbdb2ab1758e30 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 8795a4eb3c6c13..e88c176af4a8ba 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 00000000000000..a6e20975fa3f12
--- /dev/null
+++ b/libcxx/test/libcxx/time/time.zone/time.zone.timezone/time.zone.members/get_info.sys_time.pass.cpp
@@ -0,0 +1,142 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// ADDITIONAL_COMPILE_FLAGS: -O0 -g
+
+// <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
+
+#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_NOEXCEPT
+ 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
+}
+
+static void invalid_format() {
+ 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 1970 max - Jan 5 0 0 foo
+Z Format 0 F %)",
+ "corrupt tzdb FORMAT field: input ended with the start of the escape sequence '%'");
+}
+
+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**) {
+ invalid_format();
+ percentage_z_format();
+
+ return 0;
+}
diff --git a/libcxx/test/std/time/time.zone/time.zone.info/time.zone.info.sys/sys_info.members.compile.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.info/time.zone.info.sys/sys_info.members.compile.pass.cpp
new file mode 100644
index 00000000000000..4c3a754dd4a773
--- /dev/null
+++ b/libcxx/test/std/time/time.zone/time.zone.info/time.zone.info.sys/sys_info.members.compile.pass.cpp
@@ -0,0 +1,33 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// <chrono>
+
+// struct sys_info {
+// sys_seconds begin;
+// sys_seconds end;
+// seconds offset;
+// minutes save;
+// string abbrev;
+// };
+
+#include <chrono>
+#include <string>
+
+std::chrono::sys_info sys_info;
+
+[[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;
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 00000000000000..2ad408968589e6
--- /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 00000000000000..8a1ba9c43588ea
--- /dev/null
+++ b/libcxx/test/std/time/time.zone/time.zone.timezone/time.zone.members/sys_info.zdump.pass.cpp
@@ -0,0 +1,127 @@
+//===----------------------------------------------------------------------===//
+//
+// 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}}
+
+// 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.
+
+#include <chrono>
+#include <format>
+#include <fstream>
+#include <cassert>
+
+#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());
+}
+
+int main(int, const char**) {
+ const std::chrono::tzdb& tzdb = std::chrono::get_tzdb();
+ std::string file = std::tmpnam(nullptr);
+ 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 0793c34fd7f0bf..6ff16309546bae 100644
--- a/libcxx/utils/libcxx/test/features.py
+++ b/libcxx/utils/libcxx/test/features.py
@@ -286,6 +286,12 @@ def _getAndroidDeviceApi(cfg):
# 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
>From b1e54700277acac2341ab6a3121d536ebbe8ccfb Mon Sep 17 00:00:00 2001
From: Mark de Wever <koraq at xs4all.nl>
Date: Thu, 4 Apr 2024 20:14:54 +0200
Subject: [PATCH 2/2] Addresses review comments.
---
libcxx/include/__chrono/sys_info.h | 6 +-
libcxx/include/chrono | 2 +-
libcxx/src/time_zone.cpp | 57 +++++++++--------
.../get_info.sys_time.pass.cpp | 63 ++++++++++++++++++-
.../sys_info.members.compile.pass.cpp | 9 ++-
.../time.zone.members/sys_info.zdump.pass.cpp | 5 +-
6 files changed, 105 insertions(+), 37 deletions(-)
diff --git a/libcxx/include/__chrono/sys_info.h b/libcxx/include/__chrono/sys_info.h
index 16ec5dd254d59a..794d22f2ccc1ef 100644
--- a/libcxx/include/__chrono/sys_info.h
+++ b/libcxx/include/__chrono/sys_info.h
@@ -28,8 +28,7 @@
_LIBCPP_BEGIN_NAMESPACE_STD
-# if _LIBCPP_STD_VER >= 20 && !defined(_LIBCPP_HAS_NO_TIME_ZONE_DATABASE) && !defined(_LIBCPP_HAS_NO_FILESYSTEM) && \
- !defined(_LIBCPP_HAS_NO_LOCALIZATION)
+# if _LIBCPP_STD_VER >= 20
namespace chrono {
@@ -43,8 +42,7 @@ struct sys_info {
} // namespace chrono
-# endif // _LIBCPP_STD_VER >= 20 && !defined(_LIBCPP_HAS_NO_TIME_ZONE_DATABASE) && !defined(_LIBCPP_HAS_NO_FILESYSTEM)
- // && !defined(_LIBCPP_HAS_NO_LOCALIZATION)
+# endif //_LIBCPP_STD_VER >= 20
_LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/include/chrono b/libcxx/include/chrono
index 00b940a6610a3a..5eddd050196dec 100644
--- a/libcxx/include/chrono
+++ b/libcxx/include/chrono
@@ -893,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>
@@ -918,7 +919,6 @@ constexpr chrono::year operator ""y(unsigned lo
#if !defined(_LIBCPP_HAS_NO_TIME_ZONE_DATABASE) && !defined(_LIBCPP_HAS_NO_FILESYSTEM) && \
!defined(_LIBCPP_HAS_NO_LOCALIZATION)
# include <__chrono/leap_second.h>
-# include <__chrono/sys_info.h>
# include <__chrono/time_zone.h>
# include <__chrono/time_zone_link.h>
# include <__chrono/tzdb.h>
diff --git a/libcxx/src/time_zone.cpp b/libcxx/src/time_zone.cpp
index ff9533d7d843f1..f27c07898944e2 100644
--- a/libcxx/src/time_zone.cpp
+++ b/libcxx/src/time_zone.cpp
@@ -17,7 +17,7 @@
// 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 at the user's side.
+// "suggesting" this could be implemented on the user's side.
// TODO TZDB look at removing quirks
//
@@ -30,6 +30,7 @@
// 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>
@@ -111,20 +112,8 @@ __binary_find(_Range&& __r, const _Type& __value, _Comp __comp = {}, _Proj __pro
// text in the appropriate Rule's LETTER column, and the resulting string
// should be a time zone abbreviation
//
-// Accepting invalid formats that can be processed in a sensible way would better
-// serve the user than throwing an exception. So some of these rules are not
-// strictly validated.
-// 1 This is not validated. Some examples that will be accepted are, "+04:30",
-// "Q", "42".
-// 2 How this format is formatted is not specified. In the current tzdata.zi
-// this value is not used. This value is accepted in a part of the format. So
-// "a%s%zb" will be considered valid.
-// 3 This is not validated, the output might be incorrect.
-// Proper validation would make the algorithm more complex. Then the first
-// element of the pair is used the parsing of FORMAT can stop. To do proper
-// validation the tail should be validated.
-// 4 This value is accepted in a part of the format. So "a%s%zb" will be
-// considered valid.
+// 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;
@@ -137,6 +126,11 @@ __format(const __tz::__continuation& __continuation, const string& __letters, se
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 += '-';
@@ -164,14 +158,22 @@ __format(const __tz::__continuation& __continuation, const string& __letters, se
} else if (__c == '%') {
__shift = true;
- } else {
+ } 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;
}
@@ -348,7 +350,7 @@ class __named_rule_until {
// 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 pril 21st 1945 at 01:00. In the period between (2) is 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.
@@ -439,11 +441,11 @@ __next_rule(sys_seconds __time,
if (__y == __year && __it == __current)
continue;
- sys_seconds __t = __rule_to_sys_seconds(__stdoff, __save, *__it, __y);
+ sys_seconds __t = chrono::__rule_to_sys_seconds(__stdoff, __save, *__it, __y);
if (__t <= __time)
continue;
- _LIBCPP_ASSERT(!__candidates.contains(__t), "duplicated rule");
+ _LIBCPP_ASSERT_INTERNAL(!__candidates.contains(__t), "duplicated rule");
__candidates[__t] = __it;
break;
}
@@ -495,7 +497,7 @@ __first_rule(seconds __stdoff, const vector<__tz::__rule>& __rules) {
const vector<__tz::__rule>& __rules = __get_rules(__rule_name);
auto __rule = chrono::__first_rule(__continuation.__stdoff, __rules);
- _LIBCPP_ASSERT(__rule != __rules.end(), "the set of rules has no first rule");
+ _LIBCPP_ASSERT_INTERNAL(__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);
@@ -726,7 +728,7 @@ _LIBCPP_EXPORTED_FROM_ABI time_zone::~time_zone() = default;
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.
+ // __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
@@ -746,9 +748,9 @@ time_zone::__get_info(sys_seconds __time) const {
// 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 to keep results occur before __time, likewise when a
- // valid result is found the algorithm needs test the next continuation to see
- // when it can be merged. For example, Africa/Ceuta
+ // 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)
@@ -779,7 +781,7 @@ time_zone::__get_info(sys_seconds __time) const {
__sys_info_result __sys_info = chrono::__get_sys_info(__time, __continuation_begin, __continuation);
if (__sys_info) {
- _LIBCPP_ASSERT(__sys_info->__info.begin < __sys_info->__info.end, "invalid sys_info range");
+ _LIBCPP_ASSERT_INTERNAL(__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
@@ -813,7 +815,8 @@ time_zone::__get_info(sys_seconds __time) const {
__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" valid with valid.
+ // "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 {
@@ -843,7 +846,7 @@ time_zone::__get_info(sys_seconds __time) const {
if (__valid_result) {
return *__result;
} else {
- _LIBCPP_ASSERT(__it != __continuations.begin(), "the first rule should always seed the result");
+ _LIBCPP_ASSERT_INTERNAL(__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
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
index a6e20975fa3f12..8fa1096b5fb82b 100644
--- 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
@@ -22,6 +22,9 @@
// 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>
@@ -66,7 +69,7 @@ static const std::chrono::tzdb& parse(std::string_view input) {
}
static void test_exception([[maybe_unused]] std::string_view input, [[maybe_unused]] std::string_view what) {
-#ifndef TEST_NOEXCEPT
+#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(
@@ -77,21 +80,76 @@ static void test_exception([[maybe_unused]] std::string_view input, [[maybe_unus
TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n'));
},
TEST_IGNORE_NODISCARD tz->get_info(to_sys_seconds(2000)));
-#endif
+#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) {
@@ -135,6 +193,7 @@ Z Format 0:45 F %z)",
}
int main(int, const char**) {
+ zone_without_until_entry();
invalid_format();
percentage_z_format();
diff --git a/libcxx/test/std/time/time.zone/time.zone.info/time.zone.info.sys/sys_info.members.compile.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.info/time.zone.info.sys/sys_info.members.compile.pass.cpp
index 4c3a754dd4a773..9e78fb28a6b309 100644
--- a/libcxx/test/std/time/time.zone/time.zone.info/time.zone.info.sys/sys_info.members.compile.pass.cpp
+++ b/libcxx/test/std/time/time.zone/time.zone.info/time.zone.info.sys/sys_info.members.compile.pass.cpp
@@ -7,7 +7,6 @@
//===----------------------------------------------------------------------===//
// UNSUPPORTED: c++03, c++11, c++14, c++17
-// UNSUPPORTED: no-filesystem, no-localization, no-tzdb
// XFAIL: libcpp-has-no-incomplete-tzdb
@@ -21,13 +20,19 @@
// string abbrev;
// };
+// Validates whether:
+// - The members are present as non-const members.
+// - The struct is an aggregate.
+
#include <chrono>
#include <string>
+#include <type_traits>
std::chrono::sys_info sys_info;
-
[[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;
+
+static_assert(std::is_aggregate_v<std::chrono::sys_info>);
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
index 8a1ba9c43588ea..9591a0db5f6bf3 100644
--- 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
@@ -25,6 +25,7 @@
#include <fstream>
#include <cassert>
+#include "filesystem_test_helper.h"
#include "assert_macros.h"
#include "concat_macros.h"
@@ -106,8 +107,10 @@ void process(std::ostream& stream, const std::chrono::time_zone& zone) {
}
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();
- std::string file = std::tmpnam(nullptr);
for (const auto& zone : tzdb.zones) {
std::stringstream libcxx;
process(libcxx, zone);
More information about the libcxx-commits
mailing list