[libcxx-commits] [libcxx] [libc++][TZDB] Implements time_zone get_info(local_time). (PR #89537)

Mark de Wever via libcxx-commits libcxx-commits at lists.llvm.org
Wed Apr 24 09:24:27 PDT 2024


https://github.com/mordante updated https://github.com/llvm/llvm-project/pull/89537

>From 5ba9e6ec646bc4cc1408c5f70dcbbe98bbe0702a Mon Sep 17 00:00:00 2001
From: Mark de Wever <koraq at xs4all.nl>
Date: Wed, 17 Apr 2024 21:00:22 +0200
Subject: [PATCH 1/2] [libc++][TZDB] Implements time zone get_info(local_time).

Implements parts of:
- P0355 Extending to Calendars and Time Zones
---
 libcxx/include/__chrono/time_zone.h           |    7 +
 libcxx/include/chrono                         |    3 +
 libcxx/src/time_zone.cpp                      |  175 +++
 .../diagnostics/chrono.nodiscard.verify.cpp   |    2 +
 .../get_info.local_time.pass.cpp              | 1302 +++++++++++++++++
 5 files changed, 1489 insertions(+)
 create mode 100644 libcxx/test/std/time/time.zone/time.zone.timezone/time.zone.members/get_info.local_time.pass.cpp

diff --git a/libcxx/include/__chrono/time_zone.h b/libcxx/include/__chrono/time_zone.h
index 91ddab8903fe21..2cb1bbe72a906d 100644
--- a/libcxx/include/__chrono/time_zone.h
+++ b/libcxx/include/__chrono/time_zone.h
@@ -17,6 +17,7 @@
 #if !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_TZDB)
 
 #  include <__chrono/duration.h>
+#  include <__chrono/local_info.h>
 #  include <__chrono/sys_info.h>
 #  include <__chrono/system_clock.h>
 #  include <__compare/strong_order.h>
@@ -63,12 +64,18 @@ class _LIBCPP_AVAILABILITY_TZDB time_zone {
     return __get_info(chrono::time_point_cast<seconds>(__time));
   }
 
+  template <class _Duration>
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI local_info get_info(const local_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;
+  [[nodiscard]] _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI local_info __get_info(local_seconds __time) const;
 
   unique_ptr<__impl> __impl_;
 };
diff --git a/libcxx/include/chrono b/libcxx/include/chrono
index 96a3e92faa81f2..4d8398af1a108f 100644
--- a/libcxx/include/chrono
+++ b/libcxx/include/chrono
@@ -763,6 +763,9 @@ class time_zone {
 
   template<class Duration>
   sys_info get_info(const sys_time<Duration>& st) const;
+
+  template<class Duration>
+  local_info get_info(const local_time<Duration>& tp) const;
 };
 bool operator==(const time_zone& x, const time_zone& y) noexcept;                // C++20
 strong_ordering operator<=>(const time_zone& x, const time_zone& y) noexcept;    // C++20
diff --git a/libcxx/src/time_zone.cpp b/libcxx/src/time_zone.cpp
index 928f3d2855e456..24c22859080e10 100644
--- a/libcxx/src/time_zone.cpp
+++ b/libcxx/src/time_zone.cpp
@@ -34,6 +34,7 @@
 #include <chrono>
 #include <expected>
 #include <map>
+#include <numeric>
 #include <ranges>
 
 #include "include/tzdb/time_zone_private.h"
@@ -903,6 +904,180 @@ time_zone::__get_info(sys_seconds __time) const {
   std::__throw_runtime_error("tzdb: corrupt db");
 }
 
+enum class __position {
+  __beginning,
+  __middle,
+  __end,
+};
+
+// Determines the position of "__time" inside "__info".
+//
+// The code picks an arbitrary value to determine the "middle"
+// - Every time that is more than the threshold from a boundary, or
+// - Every value that is at the boundary sys_seconds::min() or
+//   sys_seconds::max().
+//
+// If not in the middle, it returns __beginning or __end.
+[[nodiscard]] static __position __get_position(sys_seconds __time, const sys_info __info) {
+  _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
+      __time >= __info.begin && __time < __info.end, "A value outside the range's position can't be determined.");
+
+  using _Tp = sys_seconds::rep;
+  // Africa/Freetown has a 4 day "zone"
+  // Africa/Freetown  Fri Sep  1 00:59:59 1939 UT = Thu Aug 31 23:59:59 1939 -01 isdst=0 gmtoff=-3600
+  // Africa/Freetown  Fri Sep  1 01:00:00 1939 UT = Fri Sep  1 00:20:00 1939 -0040 isdst=1 gmtoff=-2400
+  // Africa/Freetown  Tue Sep  5 00:39:59 1939 UT = Mon Sep  4 23:59:59 1939 -0040 isdst=1 gmtoff=-2400
+  // Africa/Freetown  Tue Sep  5 00:40:00 1939 UT = Mon Sep  4 23:40:00 1939 -01 isdst=0 gmtoff=-3600
+  //
+  // Originally used a one week threshold, but due to this switched to 1 day.
+  // This seems to work in practice.
+  //
+  // TODO TZDB Evaluate the proper threshold.
+  constexpr _Tp __threshold = 24 * 3600;
+
+  _Tp __upper = std::__add_sat(__info.begin.time_since_epoch().count(), __threshold);
+  if (__time >= __info.begin && __time.time_since_epoch().count() < __upper)
+    return __info.begin != sys_seconds::min() ? __position::__beginning : __position::__middle;
+
+  _Tp __lower = std::__sub_sat(__info.end.time_since_epoch().count(), __threshold);
+  if (__time < __info.end && __time.time_since_epoch().count() >= __lower)
+    return __info.end != sys_seconds::max() ? __position::__end : __position::__middle;
+
+  return __position::__middle;
+}
+
+[[nodiscard]] static local_info
+__get_info(local_seconds __local_time, const sys_info& __first, const sys_info& __second) {
+  std::chrono::local_seconds __end_first{__first.end.time_since_epoch() + __first.offset};
+  std::chrono::local_seconds __begin_second{__second.begin.time_since_epoch() + __second.offset};
+
+  if (__local_time < __end_first) {
+    if (__local_time >= __begin_second)
+      // |--------|
+      //        |------|
+      //         ^
+      return {local_info::ambiguous, __first, __second};
+
+    // |--------|
+    //          |------|
+    //         ^
+    return {local_info::unique, __first, sys_info{}};
+  }
+
+  if (__local_time < __begin_second)
+    // |--------|
+    //             |------|
+    //           ^
+    return {local_info::nonexistent, __first, __second};
+
+  // |--------|
+  //          |------|
+  //           ^
+  return {local_info::unique, __second, sys_info{}};
+}
+
+[[nodiscard]] _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI local_info
+time_zone::__get_info(local_seconds __local_time) const {
+  seconds __local_seconds = __local_time.time_since_epoch();
+
+  /* An example of a typical year with a DST switch displayed in local time.
+   *
+   * At the first of April the time goes forward one hour. This means the
+   * time marked with ~~ is not a valid local time. This is represented by the
+   * nonexistent value in local_info.result.
+   *
+   * At the first of November the time goes backward one hour. This means the
+   * time marked with ^^ happens twice. This is represented by the ambiguous
+   * value in local_info.result.
+   *
+   * 2020.11.01                  2021.04.01              2021.11.01
+   * offset +05                  offset +05              offset +05
+   * save    0s                  save    1h              save    0s
+   * |-------------W----------|
+   *                             |----------W--------------|
+   *                                                    |-------------
+   *                           ~~                        ^^
+   *
+   * These shifts can happen due to changes in the current time zone for a
+   * location. For example, Indian/Kerguelen switched only once. In 1950 from an
+   * offset of 0 hours to an offset of +05 hours.
+   *
+   * During all these shifts the UTC time will have not gaps.
+   */
+
+  // The code needs to determine the system time for the local time. There is no
+  // information available. Assume the offset between system time and local time
+  // is 0s. This gives an initial estimate.
+  sys_seconds __guess{__local_seconds};
+  sys_info __info = __get_info(__guess);
+
+  // At this point the offset can be used to determine an estimate for the local
+  // time. Before doing the determine the offset validate whether the local time
+  // is the range [chrono::local_seconds::min(), chrono::local_seconds::max()).
+  if (__local_seconds < 0s && __info.offset > 0s)
+    if (__local_seconds - chrono::local_seconds::min().time_since_epoch() < __info.offset)
+      return {-1, __info, {}};
+
+  if (__local_seconds > 0s && __info.offset < 0s)
+    if (chrono::local_seconds::max().time_since_epoch() - __local_seconds < -__info.offset)
+      return {-2, __info, {}};
+
+  // Based on the information in the sys_info found the local time can be
+  // converted to a system time. This resulting time can be in the following
+  // locations of the sys_info found:
+  //
+  //                             |----------W--------------|
+  //                           1   2        3             4  5
+  //
+  // 1. The estimate is before the returned sys_info object.
+  //    The result is either non-existent or unique in the previous sys_info.
+  // 2. The estimate is in the beginning of the returned sys_info object.
+  //    The result is either unique or ambiguous with the previous sys_info.
+  // 3. The estimate is in the "middle" of the returned sys_info.
+  //    The result is unique.
+  // 4. The result is at the end of the returned sys_info object.
+  //    The result is either unique or ambiguous with the next sys_info.
+  // 5. The estimate is after the returned sys_info object.
+  //    The result is either non-existent or unique in the next sys_info.
+  //
+  // There is no specification where the "middle" starts. Similar issues can
+  // happen when sys_info objects are "short", then "unique in the next" could
+  // become "ambiguous in the next and the one following". Theoretically there
+  // is the option of the following time-line
+  //
+  // |------------|
+  //           |----|
+  //       |-----------------|
+  //
+  // However the local_info object only has 2 sys_info objects, so this option
+  // is not tested.
+  //
+  // The positions 2, 3, or 4 are determined by __get_position. This function
+  // also contains the definition of "middle".
+
+  sys_seconds __sys_time{__local_seconds - __info.offset};
+  if (__sys_time < __info.begin)
+    // Case 1 before __info
+    return chrono::__get_info(__local_time, __get_info(__info.begin - 1s), __info);
+
+  if (__sys_time >= __info.end)
+    // Case 5 after __info
+    return chrono::__get_info(__local_time, __info, __get_info(__info.end));
+
+  switch (__get_position(__sys_time, __info)) {
+  case __position::__beginning: // Case 2
+    return chrono::__get_info(__local_time, __get_info(__info.begin - 1s), __info);
+
+  case __position::__middle: // Case 3
+    return {local_info::unique, __info, {}};
+
+  case __position::__end: // Case 4
+    return chrono::__get_info(__local_time, __info, __get_info(__info.end));
+  }
+
+  std::__libcpp_unreachable();
+}
+
 } // namespace chrono
 
 _LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/test/libcxx/diagnostics/chrono.nodiscard.verify.cpp b/libcxx/test/libcxx/diagnostics/chrono.nodiscard.verify.cpp
index a5ce5d16581306..fea1e4417cc12c 100644
--- a/libcxx/test/libcxx/diagnostics/chrono.nodiscard.verify.cpp
+++ b/libcxx/test/libcxx/diagnostics/chrono.nodiscard.verify.cpp
@@ -48,8 +48,10 @@ void test() {
 
   {
     std::chrono::sys_seconds s{};
+    std::chrono::local_seconds l{};
     tz.name();           // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
     tz.get_info(s);      // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+    tz.get_info(l);      // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
     operator==(tz, tz);  // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
     operator<=>(tz, tz); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
   }
diff --git a/libcxx/test/std/time/time.zone/time.zone.timezone/time.zone.members/get_info.local_time.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.timezone/time.zone.members/get_info.local_time.pass.cpp
new file mode 100644
index 00000000000000..6dc15974c44843
--- /dev/null
+++ b/libcxx/test/std/time/time.zone/time.zone.timezone/time.zone.members/get_info.local_time.pass.cpp
@@ -0,0 +1,1302 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-filesystem, no-localization, no-tzdb
+
+// XFAIL: libcpp-has-no-experimental-tzdb
+// XFAIL: availability-tzdb-missing
+
+// <chrono>
+
+// class time_zone;
+
+// template <class _Duration>
+//   local_info get_info(const local_time<_Duration>& time) const;
+
+// This test uses the system provided database. This makes the test portable,
+// but may cause failures when the database information changes. Historic data
+// may change if new facts are uncovered, future data may change when regions
+// change their time zone or daylight saving time. Most tests will not look in
+// the future to attempt to avoid issues. All tests list the data on which they
+// are based, this makes debugging easier upon failure; including to see whether
+// the provided data has not been changed.
+//
+// The first part of the test is manually crafted, the second part compares the
+// transitions for all time zones in the database.
+
+#include <algorithm>
+#include <cassert>
+#include <chrono>
+#include <format>
+
+#include "test_macros.h"
+#include "assert_macros.h"
+#include "concat_macros.h"
+
+// The year range to validate. The dates used in practice are expected to be
+// inside the tested range.
+constexpr std::chrono::year first{1800};
+constexpr std::chrono::year last{2100};
+
+/***** ***** HELPERS ***** *****/
+
+[[nodiscard]] static std::chrono::sys_seconds to_sys_seconds(
+    std::chrono::year year,
+    std::chrono::month month,
+    std::chrono::day day,
+    std::chrono::hours h   = std::chrono::hours(0),
+    std::chrono::minutes m = std::chrono::minutes{0},
+    std::chrono::seconds s = std::chrono::seconds{0}) {
+  std::chrono::year_month_day result{year, month, day};
+
+  return std::chrono::time_point_cast<std::chrono::seconds>(static_cast<std::chrono::sys_days>(result)) + h + m + s;
+}
+
+[[nodiscard]] static std::chrono::local_seconds to_local_seconds(
+    std::chrono::year year,
+    std::chrono::month month,
+    std::chrono::day day,
+    std::chrono::hours h   = std::chrono::hours(0),
+    std::chrono::minutes m = std::chrono::minutes{0},
+    std::chrono::seconds s = std::chrono::seconds{0}) {
+  std::chrono::year_month_day result{year, month, day};
+
+  return std::chrono::time_point_cast<std::chrono::seconds>(static_cast<std::chrono::local_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(const std::chrono::local_info& lhs, const std::chrono::local_info& rhs) {
+  TEST_REQUIRE(
+      lhs.result == rhs.result,
+      TEST_WRITE_CONCATENATED("\nResult:\nExpected output ", lhs.result, "\nActual output   ", rhs.result, '\n'));
+
+  assert_equal(lhs.first, rhs.first);
+  assert_equal(lhs.second, rhs.second);
+}
+
+/***** ***** TESTS ***** *****/
+
+static void test_gmt() {
+  // Simple zone always valid, no rule entries, lookup using a link.
+  // L Etc/GMT GMT
+  // Z Etc/GMT 0 - GMT
+
+  using namespace std::literals::chrono_literals;
+  const std::chrono::time_zone* tz = std::chrono::locate_zone("GMT");
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(std::chrono::sys_seconds::min(), std::chrono::sys_seconds::max(), 0s, 0min, "GMT"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(std::chrono::local_seconds::min()));
+}
+
+static void test_local_time_out_of_range() {
+  // Fixed positive offset
+  // Etc/GMT-1 1 - +01
+
+  using namespace std::literals::chrono_literals;
+  { // lower bound
+    const std::chrono::time_zone* tz = std::chrono::locate_zone("Etc/GMT-1");
+
+    assert_equal(
+        std::chrono::local_info(
+            -1,
+            std::chrono::sys_info(std::chrono::sys_seconds::min(), std::chrono::sys_seconds::max(), 1h, 0min, "+01"),
+            std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+        tz->get_info(std::chrono::local_seconds::min()));
+
+    assert_equal(
+        std::chrono::local_info(
+            -1,
+            std::chrono::sys_info(std::chrono::sys_seconds::min(), std::chrono::sys_seconds::max(), 1h, 0min, "+01"),
+            std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+        tz->get_info(std::chrono::local_seconds::min() + 59min + 59s));
+
+    assert_equal(
+        std::chrono::local_info(
+            std::chrono::local_info::unique,
+            std::chrono::sys_info(std::chrono::sys_seconds::min(), std::chrono::sys_seconds::max(), 1h, 0min, "+01"),
+            std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+        tz->get_info(std::chrono::local_seconds::min() + 1h));
+  }
+
+  { // upper bound
+    const std::chrono::time_zone* tz = std::chrono::locate_zone("Etc/GMT+1");
+
+    assert_equal(
+        std::chrono::local_info(
+            -2,
+            std::chrono::sys_info(std::chrono::sys_seconds::min(), std::chrono::sys_seconds::max(), -1h, 0min, "-01"),
+            std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+        tz->get_info(std::chrono::local_seconds::max() - 1s));
+
+    assert_equal(
+        std::chrono::local_info(
+            std::chrono::local_info::unique,
+            std::chrono::sys_info(std::chrono::sys_seconds::min(), std::chrono::sys_seconds::max(), -1h, 0min, "-01"),
+            std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+        tz->get_info(std::chrono::local_seconds::max() - 1h - 1s));
+  }
+}
+
+static void test_indian_kerguelen() {
+  // One change, no rules, no dst changes.
+
+  // Z Indian/Kerguelen 0 - -00 1950
+  // 5 - +05
+
+  using namespace std::literals::chrono_literals;
+  const std::chrono::time_zone* tz = std::chrono::locate_zone("Indian/Kerguelen");
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              std::chrono::sys_seconds::min(), to_sys_seconds(1950y, std::chrono::January, 1d), 0s, 0min, "-00"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(std::chrono::local_seconds::min()));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::nonexistent,
+          std::chrono::sys_info(
+              std::chrono::sys_seconds::min(), to_sys_seconds(1950y, std::chrono::January, 1d), 0s, 0min, "-00"),
+          std::chrono::sys_info(
+              to_sys_seconds(1950y, std::chrono::January, 1d), std::chrono::sys_seconds::max(), 5h, 0min, "+05")),
+      tz->get_info(to_local_seconds(1950y, std::chrono::January, 1d)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              to_sys_seconds(1950y, std::chrono::January, 1d), std::chrono::sys_seconds::max(), 5h, 0min, "+05"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1950y, std::chrono::January, 1d, 5h)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              to_sys_seconds(1950y, std::chrono::January, 1d), std::chrono::sys_seconds::max(), 5h, 0min, "+05"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(std::chrono::local_seconds::max() - 1s));
+}
+
+static void test_antarctica_rothera() {
+  // One change, no rules, no dst changes
+
+  // Z Antarctica/Rothera 0 - -00 1976 D
+  // -3 - -03
+
+  using namespace std::literals::chrono_literals;
+  const std::chrono::time_zone* tz = std::chrono::locate_zone("Antarctica/Rothera");
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              std::chrono::sys_seconds::min(), to_sys_seconds(1976y, std::chrono::December, 1d), 0s, 0min, "-00"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(std::chrono::local_seconds::min()));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              std::chrono::sys_seconds::min(), to_sys_seconds(1976y, std::chrono::December, 1d), 0s, 0min, "-00"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1976y, std::chrono::November, 30d, 20h, 59min, 59s)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::ambiguous,
+          std::chrono::sys_info(
+              std::chrono::sys_seconds::min(), to_sys_seconds(1976y, std::chrono::December, 1d), 0s, 0min, "-00"),
+          std::chrono::sys_info(
+              to_sys_seconds(1976y, std::chrono::December, 1d), std::chrono::sys_seconds::max(), -3h, 0min, "-03")),
+      tz->get_info(to_local_seconds(1976y, std::chrono::November, 30d, 21h)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::ambiguous,
+          std::chrono::sys_info(
+              std::chrono::sys_seconds::min(), to_sys_seconds(1976y, std::chrono::December, 1d), 0s, 0min, "-00"),
+          std::chrono::sys_info(
+              to_sys_seconds(1976y, std::chrono::December, 1d), std::chrono::sys_seconds::max(), -3h, 0min, "-03")),
+      tz->get_info(to_local_seconds(1976y, std::chrono::November, 30d, 23h, 59min, 59s)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              to_sys_seconds(1976y, std::chrono::December, 1d), std::chrono::sys_seconds::max(), -3h, 0min, "-03"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1976y, std::chrono::December, 1d)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              to_sys_seconds(1976y, std::chrono::December, 1d), std::chrono::sys_seconds::max(), -3h, 0min, "-03"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(std::chrono::local_seconds::max() - 3h - 1s));
+
+  assert_equal(
+      std::chrono::local_info(
+          -2,
+          std::chrono::sys_info(
+              to_sys_seconds(1976y, std::chrono::December, 1d), std::chrono::sys_seconds::max(), -3h, 0min, "-03"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(std::chrono::local_seconds::max() - 1s));
+}
+
+static void test_asia_hong_kong() {
+  // A more typical entry, first some hard-coded entires and then at the
+  // end a rules based entry. This rule is valid for its entire period
+  //
+  // Z Asia/Hong_Kong 7:36:42 - LMT 1904 O 30 0:36:42
+  // 8 - HKT 1941 Jun 15 3
+  // 8 1 HKST 1941 O 1 4
+  // 8 0:30 HKWT 1941 D 25
+  // 9 - JST 1945 N 18 2
+  // 8 HK HK%sT
+  //
+  // R HK 1946 o - Ap 21 0 1 S
+  // R HK 1946 o - D 1 3:30s 0 -
+  // R HK 1947 o - Ap 13 3:30s 1 S
+  // R HK 1947 o - N 30 3:30s 0 -
+  // R HK 1948 o - May 2 3:30s 1 S
+  // R HK 1948 1952 - O Su>=28 3:30s 0 -
+  // R HK 1949 1953 - Ap Su>=1 3:30 1 S
+  // R HK 1953 1964 - O Su>=31 3:30 0 -
+  // R HK 1954 1964 - Mar Su>=18 3:30 1 S
+  // R HK 1965 1976 - Ap Su>=16 3:30 1 S
+  // R HK 1965 1976 - O Su>=16 3:30 0 -
+  // R HK 1973 o - D 30 3:30 1 S
+  // R HK 1979 o - May 13 3:30 1 S
+  // R HK 1979 o - O 21 3:30 0 -
+
+  using namespace std::literals::chrono_literals;
+  const std::chrono::time_zone* tz = std::chrono::locate_zone("Asia/Hong_Kong");
+
+  assert_equal(
+      std::chrono::local_info(
+          -1,
+          std::chrono::sys_info(
+              std::chrono::sys_seconds::min(),
+              to_sys_seconds(1904y, std::chrono::October, 29d, 17h),
+              7h + 36min + 42s,
+              0min,
+              "LMT"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(std::chrono::local_seconds::min()));
+
+  assert_equal(
+      std::chrono::local_info(
+          -1,
+          std::chrono::sys_info(
+              std::chrono::sys_seconds::min(),
+              to_sys_seconds(1904y, std::chrono::October, 29d, 17h),
+              7h + 36min + 42s,
+              0min,
+              "LMT"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(std::chrono::local_seconds::min() + 7h + 36min + 41s));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              std::chrono::sys_seconds::min(),
+              to_sys_seconds(1904y, std::chrono::October, 29d, 17h),
+              7h + 36min + 42s,
+              0min,
+              "LMT"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(std::chrono::local_seconds::min() + 7h + 36min + 42s));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              std::chrono::sys_seconds::min(),
+              to_sys_seconds(1904y, std::chrono::October, 29d, 17h),
+              7h + 36min + 42s,
+              0min,
+              "LMT"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1904y, std::chrono::October, 30d, 0h, 36min, 41s)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::nonexistent,
+          std::chrono::sys_info(
+              std::chrono::sys_seconds::min(),
+              to_sys_seconds(1904y, std::chrono::October, 29d, 17h),
+              7h + 36min + 42s,
+              0min,
+              "LMT"),
+          std::chrono::sys_info(
+              to_sys_seconds(1904y, std::chrono::October, 29d, 17h),
+              to_sys_seconds(1941y, std::chrono::June, 14d, 19h),
+              8h,
+              0min,
+              "HKT")),
+      tz->get_info(to_local_seconds(1904y, std::chrono::October, 30d, 0h, 36min, 42s)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::nonexistent,
+          std::chrono::sys_info(
+              std::chrono::sys_seconds::min(),
+              to_sys_seconds(1904y, std::chrono::October, 29d, 17h),
+              7h + 36min + 42s,
+              0min,
+              "LMT"),
+          std::chrono::sys_info(
+              to_sys_seconds(1904y, std::chrono::October, 29d, 17h),
+              to_sys_seconds(1941y, std::chrono::June, 14d, 19h),
+              8h,
+              0min,
+              "HKT")),
+      tz->get_info(to_local_seconds(1904y, std::chrono::October, 30d, 0h, 59min, 59s)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              to_sys_seconds(1904y, std::chrono::October, 29d, 17h),
+              to_sys_seconds(1941y, std::chrono::June, 14d, 19h),
+              8h,
+              0min,
+              "HKT"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1904y, std::chrono::October, 30d, 1h)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              to_sys_seconds(1904y, std::chrono::October, 29d, 17h),
+              to_sys_seconds(1941y, std::chrono::June, 14d, 19h),
+              8h,
+              0min,
+              "HKT"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1941y, std::chrono::June, 15d, 2h, 59min, 59s)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::nonexistent,
+          std::chrono::sys_info(
+              to_sys_seconds(1904y, std::chrono::October, 29d, 17h),
+              to_sys_seconds(1941y, std::chrono::June, 14d, 19h),
+              8h,
+              0min,
+              "HKT"),
+          std::chrono::sys_info(
+              to_sys_seconds(1941y, std::chrono::June, 14d, 19h),
+              to_sys_seconds(1941y, std::chrono::September, 30d, 19h),
+              9h,
+              60min,
+              "HKST")),
+      tz->get_info(to_local_seconds(1941y, std::chrono::June, 15d, 3h)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::nonexistent,
+          std::chrono::sys_info(
+              to_sys_seconds(1904y, std::chrono::October, 29d, 17h),
+              to_sys_seconds(1941y, std::chrono::June, 14d, 19h),
+              8h,
+              0min,
+              "HKT"),
+          std::chrono::sys_info(
+              to_sys_seconds(1941y, std::chrono::June, 14d, 19h),
+              to_sys_seconds(1941y, std::chrono::September, 30d, 19h),
+              9h,
+              60min,
+              "HKST")),
+      tz->get_info(to_local_seconds(1941y, std::chrono::June, 15d, 3h, 59min, 59s)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              to_sys_seconds(1941y, std::chrono::June, 14d, 19h),
+              to_sys_seconds(1941y, std::chrono::September, 30d, 19h),
+              9h,
+              60min,
+              "HKST"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1941y, std::chrono::June, 15d, 4h)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              to_sys_seconds(1941y, std::chrono::June, 14d, 19h),
+              to_sys_seconds(1941y, std::chrono::September, 30d, 19h),
+              9h,
+              60min,
+              "HKST"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1941y, std::chrono::October, 1d, 3h, 29min, 29s)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::ambiguous,
+          std::chrono::sys_info(
+              to_sys_seconds(1941y, std::chrono::June, 14d, 19h),
+              to_sys_seconds(1941y, std::chrono::September, 30d, 19h),
+              9h,
+              60min,
+              "HKST"),
+          std::chrono::sys_info(
+              to_sys_seconds(1941y, std::chrono::September, 30d, 19h),
+              to_sys_seconds(1941y, std::chrono::December, 24d, 15h, 30min),
+              8h + 30min,
+              30min,
+              "HKWT")),
+      tz->get_info(to_local_seconds(1941y, std::chrono::October, 1d, 3h, 30min)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::ambiguous,
+          std::chrono::sys_info(
+              to_sys_seconds(1941y, std::chrono::June, 14d, 19h),
+              to_sys_seconds(1941y, std::chrono::September, 30d, 19h),
+              9h,
+              60min,
+              "HKST"),
+          std::chrono::sys_info(
+              to_sys_seconds(1941y, std::chrono::September, 30d, 19h),
+              to_sys_seconds(1941y, std::chrono::December, 24d, 15h, 30min),
+              8h + 30min,
+              30min,
+              "HKWT")),
+      tz->get_info(to_local_seconds(1941y, std::chrono::October, 1d, 3h, 59min, 59s)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              to_sys_seconds(1941y, std::chrono::September, 30d, 19h),
+              to_sys_seconds(1941y, std::chrono::December, 24d, 15h, 30min),
+              8h + 30min,
+              30min,
+              "HKWT"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1941y, std::chrono::October, 1d, 4h)));
+}
+
+static void test_europe_berlin() {
+  // A more typical entry, first some hard-coded entires and then at the
+  // end a rules based entry. This rule is valid for its entire period
+  //
+
+  // Z Europe/Berlin 0:53:28 - LMT 1893 Ap
+  // 1 c CE%sT 1945 May 24 2
+  // 1 So CE%sT 1946
+  // 1 DE CE%sT 1980
+  // 1 E CE%sT
+  //
+  // R c 1916 o - Ap 30 23 1 S
+  // R c 1916 o - O 1 1 0 -
+  // R c 1917 1918 - Ap M>=15 2s 1 S
+  // R c 1917 1918 - S M>=15 2s 0 -
+  // R c 1940 o - Ap 1 2s 1 S
+  // R c 1942 o - N 2 2s 0 -
+  // R c 1943 o - Mar 29 2s 1 S
+  // R c 1943 o - O 4 2s 0 -
+  // R c 1944 1945 - Ap M>=1 2s 1 S
+  // R c 1944 o - O 2 2s 0 -
+  // R c 1945 o - S 16 2s 0 -
+  // R c 1977 1980 - Ap Su>=1 2s 1 S
+  // R c 1977 o - S lastSu 2s 0 -
+  // R c 1978 o - O 1 2s 0 -
+  // R c 1979 1995 - S lastSu 2s 0 -
+  // R c 1981 ma - Mar lastSu 2s 1 S
+  // R c 1996 ma - O lastSu 2s 0 -
+  //
+  // R So 1945 o - May 24 2 2 M
+  // R So 1945 o - S 24 3 1 S
+  // R So 1945 o - N 18 2s 0 -
+  //
+  // R DE 1946 o - Ap 14 2s 1 S
+  // R DE 1946 o - O 7 2s 0 -
+  // R DE 1947 1949 - O Su>=1 2s 0 -
+  // R DE 1947 o - Ap 6 3s 1 S
+  // R DE 1947 o - May 11 2s 2 M
+  // R DE 1947 o - Jun 29 3 1 S
+  // R DE 1948 o - Ap 18 2s 1 S
+  // R DE 1949 o - Ap 10 2s 1 S
+  //
+  // R E 1977 1980 - Ap Su>=1 1u 1 S
+  // R E 1977 o - S lastSu 1u 0 -
+  // R E 1978 o - O 1 1u 0 -
+  // R E 1979 1995 - S lastSu 1u 0 -
+  // R E 1981 ma - Mar lastSu 1u 1 S
+  // R E 1996 ma - O lastSu 1u 0 -
+  //
+  // Note the European Union decided to stop the seasonal change in
+  // 2021. In 2023 seasonal changes are still in effect.
+
+  using namespace std::literals::chrono_literals;
+  const std::chrono::time_zone* tz = std::chrono::locate_zone("Europe/Berlin");
+
+  assert_equal(
+      std::chrono::local_info(
+          -1,
+          std::chrono::sys_info(
+              std::chrono::sys_seconds::min(),
+              to_sys_seconds(1893y, std::chrono::March, 31d, 23h, 6min, 32s),
+              53min + 28s,
+              0min,
+              "LMT"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(std::chrono::local_seconds::min()));
+
+  assert_equal(
+      std::chrono::local_info(
+          -1,
+          std::chrono::sys_info(
+              std::chrono::sys_seconds::min(),
+              to_sys_seconds(1893y, std::chrono::March, 31d, 23h, 6min, 32s),
+              53min + 28s,
+              0min,
+              "LMT"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(std::chrono::local_seconds::min() + 53min + 27s));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              std::chrono::sys_seconds::min(),
+              to_sys_seconds(1893y, std::chrono::March, 31d, 23h, 6min, 32s),
+              53min + 28s,
+              0min,
+              "LMT"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(std::chrono::local_seconds::min() + 53min + 28s));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              std::chrono::sys_seconds::min(),
+              to_sys_seconds(1893y, std::chrono::March, 31d, 23h, 6min, 32s),
+              53min + 28s,
+              0min,
+              "LMT"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1893y, std::chrono::March, 31d, 23h, 59min, 59s)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              to_sys_seconds(1946y, std::chrono::October, 7d, 1h),
+              to_sys_seconds(1947y, std::chrono::April, 6d, 2h),
+              1h,
+              0min,
+              "CET"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1947y, std::chrono::April, 6d, 2h, 59min, 59s)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::nonexistent,
+          std::chrono::sys_info(
+              to_sys_seconds(1946y, std::chrono::October, 7d, 1h),
+              to_sys_seconds(1947y, std::chrono::April, 6d, 2h),
+              1h,
+              0min,
+              "CET"),
+          std::chrono::sys_info(
+              to_sys_seconds(1947y, std::chrono::April, 6d, 2h),
+              to_sys_seconds(1947y, std::chrono::May, 11d, 1h),
+              2h,
+              60min,
+              "CEST")),
+      tz->get_info(to_local_seconds(1947y, std::chrono::April, 6d, 3h)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::nonexistent,
+          std::chrono::sys_info(
+              to_sys_seconds(1946y, std::chrono::October, 7d, 1h),
+              to_sys_seconds(1947y, std::chrono::April, 6d, 2h),
+              1h,
+              0min,
+              "CET"),
+          std::chrono::sys_info(
+              to_sys_seconds(1947y, std::chrono::April, 6d, 2h),
+              to_sys_seconds(1947y, std::chrono::May, 11d, 1h),
+              2h,
+              60min,
+              "CEST")),
+      tz->get_info(to_local_seconds(1947y, std::chrono::April, 6d, 3h, 59min, 59s)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              to_sys_seconds(1947y, std::chrono::April, 6d, 2h),
+              to_sys_seconds(1947y, std::chrono::May, 11d, 1h),
+              2h,
+              60min,
+              "CEST"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1947y, std::chrono::April, 6d, 4h)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              to_sys_seconds(1947y, std::chrono::April, 6d, 2h),
+              to_sys_seconds(1947y, std::chrono::May, 11d, 1h),
+              2h,
+              60min,
+              "CEST"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1947y, std::chrono::May, 11d, 2h, 59min, 59s)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::nonexistent,
+          std::chrono::sys_info(
+              to_sys_seconds(1947y, std::chrono::April, 6d, 2h),
+              to_sys_seconds(1947y, std::chrono::May, 11d, 1h),
+              2h,
+              60min,
+              "CEST"),
+          std::chrono::sys_info(
+              to_sys_seconds(1947y, std::chrono::May, 11d, 1h),
+              to_sys_seconds(1947y, std::chrono::June, 29d),
+              3h,
+              120min,
+              "CEMT")),
+      tz->get_info(to_local_seconds(1947y, std::chrono::May, 11d, 3h)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::nonexistent,
+          std::chrono::sys_info(
+              to_sys_seconds(1947y, std::chrono::April, 6d, 2h),
+              to_sys_seconds(1947y, std::chrono::May, 11d, 1h),
+              2h,
+              60min,
+              "CEST"),
+          std::chrono::sys_info(
+              to_sys_seconds(1947y, std::chrono::May, 11d, 1h),
+              to_sys_seconds(1947y, std::chrono::June, 29d),
+              3h,
+              120min,
+              "CEMT")),
+      tz->get_info(to_local_seconds(1947y, std::chrono::May, 11d, 3h, 59min, 59s)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              to_sys_seconds(1947y, std::chrono::May, 11d, 1h),
+              to_sys_seconds(1947y, std::chrono::June, 29d),
+              3h,
+              120min,
+              "CEMT"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1947y, std::chrono::May, 11d, 4h)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              to_sys_seconds(1947y, std::chrono::May, 11d, 1h),
+              to_sys_seconds(1947y, std::chrono::June, 29d),
+              3h,
+              120min,
+              "CEMT"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1947y, std::chrono::June, 29d, 1h, 59min, 59s)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::ambiguous,
+          std::chrono::sys_info(
+              to_sys_seconds(1947y, std::chrono::May, 11d, 1h),
+              to_sys_seconds(1947y, std::chrono::June, 29d),
+              3h,
+              120min,
+              "CEMT"),
+          std::chrono::sys_info(
+              to_sys_seconds(1947y, std::chrono::June, 29d),
+              to_sys_seconds(1947y, std::chrono::October, 5d, 1h),
+              2h,
+              60min,
+              "CEST")),
+      tz->get_info(to_local_seconds(1947y, std::chrono::June, 29d, 2h)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::ambiguous,
+          std::chrono::sys_info(
+              to_sys_seconds(1947y, std::chrono::May, 11d, 1h),
+              to_sys_seconds(1947y, std::chrono::June, 29d),
+              3h,
+              120min,
+              "CEMT"),
+          std::chrono::sys_info(
+              to_sys_seconds(1947y, std::chrono::June, 29d),
+              to_sys_seconds(1947y, std::chrono::October, 5d, 1h),
+              2h,
+              60min,
+              "CEST")),
+      tz->get_info(to_local_seconds(1947y, std::chrono::June, 29d, 2h, 59min, 59s)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              to_sys_seconds(1947y, std::chrono::June, 29d),
+              to_sys_seconds(1947y, std::chrono::October, 5d, 1h),
+              2h,
+              60min,
+              "CEST"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1947y, std::chrono::June, 29d, 3h)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              to_sys_seconds(1947y, std::chrono::June, 29d),
+              to_sys_seconds(1947y, std::chrono::October, 5d, 1h),
+              2h,
+              60min,
+              "CEST"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1947y, std::chrono::October, 5d, 1h, 59min, 59s)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::ambiguous,
+          std::chrono::sys_info(
+              to_sys_seconds(1947y, std::chrono::June, 29d),
+              to_sys_seconds(1947y, std::chrono::October, 5d, 1h),
+              2h,
+              60min,
+              "CEST"),
+          std::chrono::sys_info(
+              to_sys_seconds(1947y, std::chrono::October, 5d, 1h),
+              to_sys_seconds(1948y, std::chrono::April, 18d, 1h),
+              1h,
+              0min,
+              "CET")),
+      tz->get_info(to_local_seconds(1947y, std::chrono::October, 5d, 2h)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::ambiguous,
+          std::chrono::sys_info(
+              to_sys_seconds(1947y, std::chrono::June, 29d),
+              to_sys_seconds(1947y, std::chrono::October, 5d, 1h),
+              2h,
+              60min,
+              "CEST"),
+          std::chrono::sys_info(
+              to_sys_seconds(1947y, std::chrono::October, 5d, 1h),
+              to_sys_seconds(1948y, std::chrono::April, 18d, 1h),
+              1h,
+              0min,
+              "CET")),
+      tz->get_info(to_local_seconds(1947y, std::chrono::October, 5d, 2h, 59min, 59s)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              to_sys_seconds(1947y, std::chrono::October, 5d, 1h),
+              to_sys_seconds(1948y, std::chrono::April, 18d, 1h),
+              1h,
+              0min,
+              "CET"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1947y, std::chrono::October, 5d, 3h)));
+}
+
+static void test_europe_dublin() {
+  // Z Europe/Dublin -0:25:21 - LMT 1880 Au 2
+  // -0:25:21 - DMT 1916 May 21 2s
+  // -0:25:21 1 IST 1916 O 1 2s
+  // 0 G %s 1921 D 6
+  // ...
+  //
+  // R G 1916 o - May 21 2s 1 BST
+  // R G 1916 o - O 1 2s 0 GMT
+  // R G 1917 o - Ap 8 2s 1 BST
+  // ...
+
+  using namespace std::literals::chrono_literals;
+  const std::chrono::time_zone* tz = std::chrono::locate_zone("Europe/Dublin");
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              std::chrono::sys_seconds::min(),
+              to_sys_seconds(1880y, std::chrono::August, 2d, 0h, 25min, 21s),
+              -(25min + 21s),
+              0min,
+              "LMT"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(std::chrono::local_seconds::min()));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              std::chrono::sys_seconds::min(),
+              to_sys_seconds(1880y, std::chrono::August, 2d, 0h, 25min, 21s),
+              -(25min + 21s),
+              0min,
+              "LMT"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1880y, std::chrono::August, 1d, 23h, 59min, 59s)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              to_sys_seconds(1880y, std::chrono::August, 2d, 0h, 25min, 21s),
+              to_sys_seconds(1916y, std::chrono::May, 21d, 2h, 25min, 21s),
+              -(25min + 21s),
+              0min,
+              "DMT"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1880y, std::chrono::August, 2d)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              to_sys_seconds(1880y, std::chrono::August, 2d, 0h, 25min, 21s),
+              to_sys_seconds(1916y, std::chrono::May, 21d, 2h, 25min, 21s),
+              -(25min + 21s),
+              0min,
+              "DMT"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1916y, std::chrono::May, 21d, 1h, 59min, 59s)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::nonexistent,
+          std::chrono::sys_info(
+              to_sys_seconds(1880y, std::chrono::August, 2d, 0h, 25min, 21s),
+              to_sys_seconds(1916y, std::chrono::May, 21d, 2h, 25min, 21s),
+              -(25min + 21s),
+              0min,
+              "DMT"),
+          std::chrono::sys_info(
+              to_sys_seconds(1916y, std::chrono::May, 21d, 2h, 25min, 21s),
+              to_sys_seconds(1916y, std::chrono::October, 1d, 02h, 25min, 21s),
+              34min + 39s,
+              60min,
+              "IST")),
+      tz->get_info(to_local_seconds(1916y, std::chrono::May, 21d, 2h)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::nonexistent,
+          std::chrono::sys_info(
+              to_sys_seconds(1880y, std::chrono::August, 2d, 0h, 25min, 21s),
+              to_sys_seconds(1916y, std::chrono::May, 21d, 2h, 25min, 21s),
+              -(25min + 21s),
+              0min,
+              "DMT"),
+          std::chrono::sys_info(
+              to_sys_seconds(1916y, std::chrono::May, 21d, 2h, 25min, 21s),
+              to_sys_seconds(1916y, std::chrono::October, 1d, 02h, 25min, 21s),
+              34min + 39s,
+              60min,
+              "IST")),
+      tz->get_info(to_local_seconds(1916y, std::chrono::May, 21d, 2h, 59min, 59s)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              to_sys_seconds(1916y, std::chrono::May, 21d, 2h, 25min, 21s),
+              to_sys_seconds(1916y, std::chrono::October, 1d, 02h, 25min, 21s),
+              34min + 39s,
+              60min,
+              "IST"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1916y, std::chrono::May, 21d, 6h)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              to_sys_seconds(1916y, std::chrono::May, 21d, 2h, 25min, 21s),
+              to_sys_seconds(1916y, std::chrono::October, 1d, 02h, 25min, 21s),
+              34min + 39s,
+              60min,
+              "IST"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1916y, std::chrono::October, 1d, 2h, 25min, 20s)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::ambiguous,
+          std::chrono::sys_info(
+              to_sys_seconds(1916y, std::chrono::May, 21d, 2h, 25min, 21s),
+              to_sys_seconds(1916y, std::chrono::October, 1d, 02h, 25min, 21s),
+              34min + 39s,
+              60min,
+              "IST"),
+          std::chrono::sys_info(
+              to_sys_seconds(1916y, std::chrono::October, 1d, 02h, 25min, 21s),
+              to_sys_seconds(1917y, std::chrono::April, 8d, 2h),
+              0s,
+              0min,
+              "GMT")),
+      tz->get_info(to_local_seconds(1916y, std::chrono::October, 1d, 2h, 59min, 59s)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              to_sys_seconds(1916y, std::chrono::October, 1d, 02h, 25min, 21s),
+              to_sys_seconds(1917y, std::chrono::April, 8d, 2h),
+              0s,
+              0min,
+              "GMT"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1916y, std::chrono::October, 1d, 3h)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              to_sys_seconds(1916y, std::chrono::October, 1d, 02h, 25min, 21s),
+              to_sys_seconds(1917y, std::chrono::April, 8d, 2h),
+              0s,
+              0min,
+              "GMT"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1917y, std::chrono::April, 8d, 1h, 59min, 59s)));
+}
+
+static void test_america_st_johns() {
+  // A more typical entry,
+  // Uses letters both when DST is ative and not and has multiple
+  // letters. Uses negetive offsets.
+  // Switches several times between their own and Canadian rules
+  // Switches the stdoff from -3:30:52 to -3:30 while observing the same rule
+
+  // Z America/St_Johns -3:30:52 - LMT 1884
+  // -3:30:52 j N%sT 1918
+  // -3:30:52 C N%sT 1919
+  // ...
+  //
+  // R j 1917 o - Ap 8 2 1 D
+  // R j 1917 o - S 17 2 0 S
+  // R j 1919 o - May 5 23 1 D
+  // R j 1919 o - Au 12 23 0 S
+  // R j 1920 1935 - May Su>=1 23 1 D
+  // ...
+  //
+  // R C 1918 o - Ap 14 2 1 D
+  // R C 1918 o - O 27 2 0 S
+  // R C 1942 o - F 9 2 1 W
+  // ...
+
+  using namespace std::literals::chrono_literals;
+  const std::chrono::time_zone* tz = std::chrono::locate_zone("America/St_Johns");
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              std::chrono::sys_seconds::min(),
+              to_sys_seconds(1884y, std::chrono::January, 1d, 3h, 30min, 52s),
+              -(3h + 30min + 52s),
+              0min,
+              "LMT"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(std::chrono::local_seconds::min()));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              std::chrono::sys_seconds::min(),
+              to_sys_seconds(1884y, std::chrono::January, 1d, 3h, 30min, 52s),
+              -(3h + 30min + 52s),
+              0min,
+              "LMT"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1883y, std::chrono::December, 31d, 23h, 59min, 59s)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              to_sys_seconds(1884y, std::chrono::January, 1d, 3h, 30min, 52s),
+              to_sys_seconds(1917y, std::chrono::April, 8d, 5h, 30min, 52s),
+              -(3h + 30min + 52s),
+              0min,
+              "NST"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1884y, std::chrono::January, 1d)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              to_sys_seconds(1884y, std::chrono::January, 1d, 3h, 30min, 52s),
+              to_sys_seconds(1917y, std::chrono::April, 8d, 5h, 30min, 52s),
+              -(3h + 30min + 52s),
+              0min,
+              "NST"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1917y, std::chrono::April, 8d, 1h, 59min, 59s)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::nonexistent,
+          std::chrono::sys_info(
+              to_sys_seconds(1884y, std::chrono::January, 1d, 3h, 30min, 52s),
+              to_sys_seconds(1917y, std::chrono::April, 8d, 5h, 30min, 52s),
+              -(3h + 30min + 52s),
+              0min,
+              "NST"),
+          std::chrono::sys_info(
+              to_sys_seconds(1917y, std::chrono::April, 8d, 5h, 30min, 52s),
+              to_sys_seconds(1917y, std::chrono::September, 17d, 4h, 30min, 52s),
+              -(2h + 30min + 52s),
+              60min,
+              "NDT")),
+      tz->get_info(to_local_seconds(1917y, std::chrono::April, 8d, 2h)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::nonexistent,
+          std::chrono::sys_info(
+              to_sys_seconds(1884y, std::chrono::January, 1d, 3h, 30min, 52s),
+              to_sys_seconds(1917y, std::chrono::April, 8d, 5h, 30min, 52s),
+              -(3h + 30min + 52s),
+              0min,
+              "NST"),
+          std::chrono::sys_info(
+              to_sys_seconds(1917y, std::chrono::April, 8d, 5h, 30min, 52s),
+              to_sys_seconds(1917y, std::chrono::September, 17d, 4h, 30min, 52s),
+              -(2h + 30min + 52s),
+              60min,
+              "NDT")),
+      tz->get_info(to_local_seconds(1917y, std::chrono::April, 8d, 2h, 59min, 59s)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              to_sys_seconds(1917y, std::chrono::April, 8d, 5h, 30min, 52s),
+              to_sys_seconds(1917y, std::chrono::September, 17d, 4h, 30min, 52s),
+              -(2h + 30min + 52s),
+              60min,
+              "NDT"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1917y, std::chrono::April, 8d, 3h)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              to_sys_seconds(1917y, std::chrono::April, 8d, 5h, 30min, 52s),
+              to_sys_seconds(1917y, std::chrono::September, 17d, 4h, 30min, 52s),
+              -(2h + 30min + 52s),
+              60min,
+              "NDT"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1917y, std::chrono::September, 17d, 0h, 59min, 59s)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::ambiguous,
+          std::chrono::sys_info(
+              to_sys_seconds(1917y, std::chrono::April, 8d, 5h, 30min, 52s),
+              to_sys_seconds(1917y, std::chrono::September, 17d, 4h, 30min, 52s),
+              -(2h + 30min + 52s),
+              60min,
+              "NDT"),
+          std::chrono::sys_info(
+              to_sys_seconds(1917y, std::chrono::September, 17d, 4h, 30min, 52s),
+              to_sys_seconds(1918y, std::chrono::April, 14d, 5h, 30min, 52s),
+              -(3h + 30min + 52s),
+              0min,
+              "NST")),
+      tz->get_info(to_local_seconds(1917y, std::chrono::September, 17d, 1h)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::ambiguous,
+          std::chrono::sys_info(
+              to_sys_seconds(1917y, std::chrono::April, 8d, 5h, 30min, 52s),
+              to_sys_seconds(1917y, std::chrono::September, 17d, 4h, 30min, 52s),
+              -(2h + 30min + 52s),
+              60min,
+              "NDT"),
+          std::chrono::sys_info(
+              to_sys_seconds(1917y, std::chrono::September, 17d, 4h, 30min, 52s),
+              to_sys_seconds(1918y, std::chrono::April, 14d, 5h, 30min, 52s),
+              -(3h + 30min + 52s),
+              0min,
+              "NST")),
+      tz->get_info(to_local_seconds(1917y, std::chrono::September, 17d, 1h, 59min, 59s)));
+
+  assert_equal(
+      std::chrono::local_info(
+          std::chrono::local_info::unique,
+          std::chrono::sys_info(
+              to_sys_seconds(1917y, std::chrono::September, 17d, 4h, 30min, 52s),
+              to_sys_seconds(1918y, std::chrono::April, 14d, 5h, 30min, 52s),
+              -(3h + 30min + 52s),
+              0min,
+              "NST"),
+          std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+      tz->get_info(to_local_seconds(1917y, std::chrono::September, 17d, 2h)));
+}
+
+static void validate_transitions(const std::chrono::time_zone& zone) {
+  using namespace std::literals::chrono_literals;
+
+  constexpr auto begin = std::chrono::time_point_cast<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}));
+
+  // Builds the set of sys_info objects for the selected time range.
+  std::vector<std::chrono::sys_info> input;
+  std::chrono::sys_seconds s = begin;
+  do {
+    input.emplace_back(zone.get_info(s));
+    s = input.back().end;
+  } while (s < end);
+
+  for (auto previous = input.begin(), next = previous + 1; next != input.end(); ++previous, ++next) {
+    // Now iterates to all adjacent objects.
+    // For every transition gets the locate time of the
+    // - end of the first          (a)
+    // - the start if the second   (b)
+    // Depending on the difference between 'a' and 'b' different tests are done.
+    std::chrono::local_seconds end_previous{previous->end.time_since_epoch() + previous->offset};
+    std::chrono::local_seconds begin_next{next->begin.time_since_epoch() + next->offset};
+
+    if (end_previous == begin_next) {
+      // unique transition
+      // a |------------|
+      // b              |----------|
+      //                T
+      assert_equal(std::chrono::local_info(
+                       std::chrono::local_info::unique,
+                       *previous,
+                       std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+                   zone.get_info(end_previous - 1s));
+
+      assert_equal(std::chrono::local_info(
+                       std::chrono::local_info::unique,
+                       *next,
+                       std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+                   zone.get_info(begin_next));
+
+    } else if (end_previous < begin_next) {
+      // non-existent transition
+      // a |------------|
+      // b                 |----------|
+      //                T  T
+      assert_equal(std::chrono::local_info(
+                       std::chrono::local_info::unique,
+                       *previous,
+                       std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+                   zone.get_info(end_previous - 1s));
+
+      assert_equal(std::chrono::local_info(std::chrono::local_info::nonexistent, *previous, *next),
+                   zone.get_info(end_previous));
+
+      assert_equal(std::chrono::local_info(std::chrono::local_info::nonexistent, *previous, *next),
+                   zone.get_info(begin_next - 1s));
+
+      assert_equal(std::chrono::local_info(
+                       std::chrono::local_info::unique,
+                       *next,
+                       std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+                   zone.get_info(begin_next));
+
+    } else {
+      // ambiguous transition
+      // a |------------|
+      // b           |----------|
+      //             T  T
+      assert_equal(std::chrono::local_info(
+                       std::chrono::local_info::unique,
+                       *previous,
+                       std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+                   zone.get_info(begin_next - 1s));
+
+      assert_equal(std::chrono::local_info(std::chrono::local_info::ambiguous, *previous, *next),
+                   zone.get_info(begin_next));
+
+      assert_equal(std::chrono::local_info(std::chrono::local_info::ambiguous, *previous, *next),
+                   zone.get_info(end_previous - 1s));
+
+      assert_equal(std::chrono::local_info(
+                       std::chrono::local_info::unique,
+                       *next,
+                       std::chrono::sys_info(std::chrono::sys_seconds(0s), std::chrono::sys_seconds(0s), 0s, 0min, "")),
+                   zone.get_info(end_previous));
+    }
+  }
+}
+
+int main(int, const char**) {
+  test_gmt();
+  test_local_time_out_of_range();
+  test_indian_kerguelen();
+  test_antarctica_rothera();
+
+  test_asia_hong_kong();
+  test_europe_berlin();
+  test_europe_dublin();
+  test_america_st_johns();
+
+  const std::chrono::tzdb& tzdb = std::chrono::get_tzdb();
+  for (const auto& zone : tzdb.zones) {
+    validate_transitions(zone);
+  }
+
+  return 0;
+}

>From 827d11e9494b5a1e0c5e535f8e9a5b63e72cea42 Mon Sep 17 00:00:00 2001
From: Mark de Wever <koraq at xs4all.nl>
Date: Wed, 24 Apr 2024 18:24:05 +0200
Subject: [PATCH 2/2] Address review comments.

---
 libcxx/src/time_zone.cpp | 110 +++++++++++++++------------------------
 1 file changed, 41 insertions(+), 69 deletions(-)

diff --git a/libcxx/src/time_zone.cpp b/libcxx/src/time_zone.cpp
index 24c22859080e10..8777d550a01045 100644
--- a/libcxx/src/time_zone.cpp
+++ b/libcxx/src/time_zone.cpp
@@ -904,48 +904,18 @@ time_zone::__get_info(sys_seconds __time) const {
   std::__throw_runtime_error("tzdb: corrupt db");
 }
 
-enum class __position {
-  __beginning,
-  __middle,
-  __end,
-};
-
-// Determines the position of "__time" inside "__info".
-//
-// The code picks an arbitrary value to determine the "middle"
-// - Every time that is more than the threshold from a boundary, or
-// - Every value that is at the boundary sys_seconds::min() or
-//   sys_seconds::max().
-//
-// If not in the middle, it returns __beginning or __end.
-[[nodiscard]] static __position __get_position(sys_seconds __time, const sys_info __info) {
-  _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
-      __time >= __info.begin && __time < __info.end, "A value outside the range's position can't be determined.");
-
-  using _Tp = sys_seconds::rep;
-  // Africa/Freetown has a 4 day "zone"
-  // Africa/Freetown  Fri Sep  1 00:59:59 1939 UT = Thu Aug 31 23:59:59 1939 -01 isdst=0 gmtoff=-3600
-  // Africa/Freetown  Fri Sep  1 01:00:00 1939 UT = Fri Sep  1 00:20:00 1939 -0040 isdst=1 gmtoff=-2400
-  // Africa/Freetown  Tue Sep  5 00:39:59 1939 UT = Mon Sep  4 23:59:59 1939 -0040 isdst=1 gmtoff=-2400
-  // Africa/Freetown  Tue Sep  5 00:40:00 1939 UT = Mon Sep  4 23:40:00 1939 -01 isdst=0 gmtoff=-3600
-  //
-  // Originally used a one week threshold, but due to this switched to 1 day.
-  // This seems to work in practice.
-  //
-  // TODO TZDB Evaluate the proper threshold.
-  constexpr _Tp __threshold = 24 * 3600;
-
-  _Tp __upper = std::__add_sat(__info.begin.time_since_epoch().count(), __threshold);
-  if (__time >= __info.begin && __time.time_since_epoch().count() < __upper)
-    return __info.begin != sys_seconds::min() ? __position::__beginning : __position::__middle;
-
-  _Tp __lower = std::__sub_sat(__info.end.time_since_epoch().count(), __threshold);
-  if (__time < __info.end && __time.time_since_epoch().count() >= __lower)
-    return __info.end != sys_seconds::max() ? __position::__end : __position::__middle;
+// Is the "__local_time" present in "__first" and "__second". If so the
+// local_info has an ambiguous result.
+[[nodiscard]] static bool
+__is_ambiguous(local_seconds __local_time, const sys_info& __first, const sys_info& __second) {
+  std::chrono::local_seconds __end_first{__first.end.time_since_epoch() + __first.offset};
+  std::chrono::local_seconds __begin_second{__second.begin.time_since_epoch() + __second.offset};
 
-  return __position::__middle;
+  return __local_time < __end_first && __local_time >= __begin_second;
 }
 
+// Determines the result of the "__local_time". This expects the object
+// "__first" to be earlier in time than "__second".
 [[nodiscard]] static local_info
 __get_info(local_seconds __local_time, const sys_info& __first, const sys_info& __second) {
   std::chrono::local_seconds __end_first{__first.end.time_since_epoch() + __first.offset};
@@ -993,8 +963,8 @@ time_zone::__get_info(local_seconds __local_time) const {
    * 2020.11.01                  2021.04.01              2021.11.01
    * offset +05                  offset +05              offset +05
    * save    0s                  save    1h              save    0s
-   * |-------------W----------|
-   *                             |----------W--------------|
+   * |------------//----------|
+   *                             |---------//--------------|
    *                                                    |-------------
    *                           ~~                        ^^
    *
@@ -1002,7 +972,7 @@ time_zone::__get_info(local_seconds __local_time) const {
    * location. For example, Indian/Kerguelen switched only once. In 1950 from an
    * offset of 0 hours to an offset of +05 hours.
    *
-   * During all these shifts the UTC time will have not gaps.
+   * During all these shifts the UTC time will not have gaps.
    */
 
   // The code needs to determine the system time for the local time. There is no
@@ -1012,8 +982,9 @@ time_zone::__get_info(local_seconds __local_time) const {
   sys_info __info = __get_info(__guess);
 
   // At this point the offset can be used to determine an estimate for the local
-  // time. Before doing the determine the offset validate whether the local time
-  // is the range [chrono::local_seconds::min(), chrono::local_seconds::max()).
+  // time. Before doing that, determine the offset and validate whether the
+  // local time is the range [chrono::local_seconds::min(),
+  // chrono::local_seconds::max()).
   if (__local_seconds < 0s && __info.offset > 0s)
     if (__local_seconds - chrono::local_seconds::min().time_since_epoch() < __info.offset)
       return {-1, __info, {}};
@@ -1022,22 +993,23 @@ time_zone::__get_info(local_seconds __local_time) const {
     if (chrono::local_seconds::max().time_since_epoch() - __local_seconds < -__info.offset)
       return {-2, __info, {}};
 
-  // Based on the information in the sys_info found the local time can be
+  // Based on the information found in the sys_info, the local time can be
   // converted to a system time. This resulting time can be in the following
-  // locations of the sys_info found:
+  // locations of the sys_info:
   //
-  //                             |----------W--------------|
-  //                           1   2        3             4  5
+  //                             |---------//--------------|
+  //                           1   2.1      2.2         2.3  5
   //
   // 1. The estimate is before the returned sys_info object.
   //    The result is either non-existent or unique in the previous sys_info.
-  // 2. The estimate is in the beginning of the returned sys_info object.
-  //    The result is either unique or ambiguous with the previous sys_info.
-  // 3. The estimate is in the "middle" of the returned sys_info.
-  //    The result is unique.
-  // 4. The result is at the end of the returned sys_info object.
-  //    The result is either unique or ambiguous with the next sys_info.
-  // 5. The estimate is after the returned sys_info object.
+  // 2. The estimate is in the sys_info object
+  //    - If the sys_info begin is not sys_seconds::min(), then it might be at
+  //      2.1 and could be ambiguous with the previous or unique.
+  //    - If sys_info end is not sys_seconds::max(), then it might be at 2.3
+  //      and could be ambiguous with the next or unique.
+  //    - Else it is at 2.2 and always unique. This case happens when a
+  //      time zone has not transitions. For example, UTC or GMT+1.
+  // 3. The estimate is after the returned sys_info object.
   //    The result is either non-existent or unique in the next sys_info.
   //
   // There is no specification where the "middle" starts. Similar issues can
@@ -1051,9 +1023,6 @@ time_zone::__get_info(local_seconds __local_time) const {
   //
   // However the local_info object only has 2 sys_info objects, so this option
   // is not tested.
-  //
-  // The positions 2, 3, or 4 are determined by __get_position. This function
-  // also contains the definition of "middle".
 
   sys_seconds __sys_time{__local_seconds - __info.offset};
   if (__sys_time < __info.begin)
@@ -1061,21 +1030,24 @@ time_zone::__get_info(local_seconds __local_time) const {
     return chrono::__get_info(__local_time, __get_info(__info.begin - 1s), __info);
 
   if (__sys_time >= __info.end)
-    // Case 5 after __info
+    // Case 3 after __info
     return chrono::__get_info(__local_time, __info, __get_info(__info.end));
 
-  switch (__get_position(__sys_time, __info)) {
-  case __position::__beginning: // Case 2
-    return chrono::__get_info(__local_time, __get_info(__info.begin - 1s), __info);
-
-  case __position::__middle: // Case 3
-    return {local_info::unique, __info, {}};
-
-  case __position::__end: // Case 4
-    return chrono::__get_info(__local_time, __info, __get_info(__info.end));
+  // Case 2 in __info
+  if (__info.begin != sys_seconds::min()) {
+    // Case 2.1 Not at the beginning, when not ambiguous the result should test
+    // case 2.3.
+    sys_info __prev = __get_info(__info.begin - 1s);
+    if (__is_ambiguous(__local_time, __prev, __info))
+      return {local_info::ambiguous, __prev, __info};
   }
 
-  std::__libcpp_unreachable();
+  if (__info.end == sys_seconds::max())
+    // At the end so it's case 2.2
+    return {local_info::unique, __info, sys_info{}};
+
+  // This tests case 2.2 or case 2.3.
+  return chrono::__get_info(__local_time, __info, __get_info(__info.end));
 }
 
 } // namespace chrono



More information about the libcxx-commits mailing list