[libcxx-commits] [libcxx] [libc++][TZDB] Implements time_zone::to_sys. (PR #90394)

Mark de Wever via libcxx-commits libcxx-commits at lists.llvm.org
Sun Jun 9 09:22:51 PDT 2024


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

>From 77dc1f471569a7469d08ea4f891e9e11de8d0272 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] [libc++][TZDB] Implements time_zone::to_sys.

This implements the throwing overload and the exception classes throw by
this overload.

Implements parts of:
- P0355 Extending chrono to Calendars and Time Zones
---
 libcxx/include/CMakeLists.txt                 |   1 +
 libcxx/include/__chrono/exception.h           | 129 ++++++++++
 libcxx/include/__chrono/time_zone.h           |  27 ++
 libcxx/include/chrono                         |   9 +
 libcxx/include/module.modulemap               |   6 +-
 libcxx/modules/std/chrono.inc                 |   7 +-
 libcxx/src/CMakeLists.txt                     |   3 +
 libcxx/src/chrono_exception.cpp               |  22 ++
 .../assert.ctor.pass.cpp                      |  53 ++++
 .../assert.ctor.pass.cpp                      |  53 ++++
 .../time.zone.members/assert.to_sys.pass.cpp  |  39 +++
 .../test/libcxx/transitive_includes/cxx03.csv |   3 -
 .../test/libcxx/transitive_includes/cxx11.csv |   3 -
 .../test/libcxx/transitive_includes/cxx14.csv |   3 -
 .../test/libcxx/transitive_includes/cxx17.csv |   3 -
 .../test/libcxx/transitive_includes/cxx20.csv |   9 +-
 .../test/libcxx/transitive_includes/cxx23.csv |  12 +-
 .../test/libcxx/transitive_includes/cxx26.csv |  35 +--
 .../time.zone.exception.ambig/ctor.pass.cpp   | 171 +++++++++++++
 .../time.zone.exception.ambig/types.pass.cpp  |  48 ++++
 .../ctor.pass.cpp                             | 172 +++++++++++++
 .../types.pass.cpp                            |  48 ++++
 .../time.zone.members/to_sys.pass.cpp         | 237 ++++++++++++++++++
 23 files changed, 1023 insertions(+), 70 deletions(-)
 create mode 100644 libcxx/include/__chrono/exception.h
 create mode 100644 libcxx/src/chrono_exception.cpp
 create mode 100644 libcxx/test/libcxx/time/time.zone/time.zone.exception/time.zone.exception.ambig/assert.ctor.pass.cpp
 create mode 100644 libcxx/test/libcxx/time/time.zone/time.zone.exception/time.zone.exception.nonexist/assert.ctor.pass.cpp
 create mode 100644 libcxx/test/libcxx/time/time.zone/time.zone.timezone/time.zone.members/assert.to_sys.pass.cpp
 create mode 100644 libcxx/test/std/time/time.zone/time.zone.exception/time.zone.exception.ambig/ctor.pass.cpp
 create mode 100644 libcxx/test/std/time/time.zone/time.zone.exception/time.zone.exception.ambig/types.pass.cpp
 create mode 100644 libcxx/test/std/time/time.zone/time.zone.exception/time.zone.exception.nonexist/ctor.pass.cpp
 create mode 100644 libcxx/test/std/time/time.zone/time.zone.exception/time.zone.exception.nonexist/types.pass.cpp
 create mode 100644 libcxx/test/std/time/time.zone/time.zone.timezone/time.zone.members/to_sys.pass.cpp

diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index cfe1f44777bca..cbbcc74e21b44 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -250,6 +250,7 @@ set(files
   __chrono/convert_to_tm.h
   __chrono/day.h
   __chrono/duration.h
+  __chrono/exception.h
   __chrono/file_clock.h
   __chrono/formatter.h
   __chrono/hh_mm_ss.h
diff --git a/libcxx/include/__chrono/exception.h b/libcxx/include/__chrono/exception.h
new file mode 100644
index 0000000000000..75fd0615b7e08
--- /dev/null
+++ b/libcxx/include/__chrono/exception.h
@@ -0,0 +1,129 @@
+// -*- 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_EXCEPTION_H
+#define _LIBCPP___CHRONO_EXCEPTION_H
+
+#include <version>
+// Enable the contents of the header only when libc++ was built with experimental features enabled.
+#if !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_TZDB)
+
+#  include <__chrono/calendar.h>
+#  include <__chrono/local_info.h>
+#  include <__chrono/time_point.h>
+#  include <__config>
+#  include <__configuration/availability.h>
+#  include <__verbose_abort>
+#  include <format>
+#  include <stdexcept>
+#  include <string>
+
+#  if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#    pragma GCC system_header
+#  endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#  if _LIBCPP_STD_VER >= 20
+
+namespace chrono {
+
+class nonexistent_local_time : public runtime_error {
+public:
+  template <class _Duration>
+  _LIBCPP_HIDE_FROM_ABI nonexistent_local_time(const local_time<_Duration>& __time, const local_info& __info)
+      : runtime_error{__create_message(__time, __info)} {
+    // [time.zone.exception.nonexist]/2
+    //   Preconditions: i.result == local_info::nonexistent is true.
+    // The value of __info.result is not used.
+    _LIBCPP_ASSERT_PEDANTIC(__info.result == local_info::nonexistent,
+                            "creating an nonexistent_local_time from a local_info that is not non-existent");
+  }
+
+  _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI ~nonexistent_local_time() override; // exported as key function
+
+private:
+  template <class _Duration>
+  _LIBCPP_HIDE_FROM_ABI string __create_message(const local_time<_Duration>& __time, const local_info& __info) {
+    return std::format(
+        R"({} is in a gap between
+{} {} and
+{} {} which are both equivalent to
+{} UTC)",
+        __time,
+        local_seconds{__info.first.end.time_since_epoch()} + __info.first.offset,
+        __info.first.abbrev,
+        local_seconds{__info.second.begin.time_since_epoch()} + __info.second.offset,
+        __info.second.abbrev,
+        __info.first.end);
+  }
+};
+
+template <class _Duration>
+_LIBCPP_NORETURN _LIBCPP_AVAILABILITY_TZDB _LIBCPP_HIDE_FROM_ABI void __throw_nonexistent_local_time(
+    [[maybe_unused]] const local_time<_Duration>& __time, [[maybe_unused]] const local_info& __info) {
+#    ifndef _LIBCPP_HAS_NO_EXCEPTIONS
+  throw nonexistent_local_time(__time, __info);
+#    else
+  _LIBCPP_VERBOSE_ABORT("nonexistent_local_time was thrown in -fno-exceptions mode");
+#    endif
+}
+
+class ambiguous_local_time : public runtime_error {
+public:
+  template <class _Duration>
+  _LIBCPP_HIDE_FROM_ABI ambiguous_local_time(const local_time<_Duration>& __time, const local_info& __info)
+      : runtime_error{__create_message(__time, __info)} {
+    // [time.zone.exception.ambig]/2
+    //   Preconditions: i.result == local_info::ambiguous is true.
+    // The value of __info.result is not used.
+    _LIBCPP_ASSERT_PEDANTIC(__info.result == local_info::ambiguous,
+                            "creating an ambiguous_local_time from a local_info that is not ambiguous");
+  }
+
+  _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI ~ambiguous_local_time() override; // exported as key function
+
+private:
+  template <class _Duration>
+  _LIBCPP_HIDE_FROM_ABI string __create_message(const local_time<_Duration>& __time, const local_info& __info) {
+    return std::format(
+        // There are two spaces after the full-stop; this has been verified
+        // in the sources of the Standard.
+        R"({0} is ambiguous.  It could be
+{0} {1} == {2} UTC or
+{0} {3} == {4} UTC)",
+        __time,
+        __info.first.abbrev,
+        __time - __info.first.offset,
+        __info.second.abbrev,
+        __time - __info.second.offset);
+  }
+};
+
+template <class _Duration>
+_LIBCPP_NORETURN _LIBCPP_AVAILABILITY_TZDB _LIBCPP_HIDE_FROM_ABI void __throw_ambiguous_local_time(
+    [[maybe_unused]] const local_time<_Duration>& __time, [[maybe_unused]] const local_info& __info) {
+#    ifndef _LIBCPP_HAS_NO_EXCEPTIONS
+  throw ambiguous_local_time(__time, __info);
+#    else
+  _LIBCPP_VERBOSE_ABORT("ambiguous_local_time was thrown in -fno-exceptions mode");
+#    endif
+}
+
+} // namespace chrono
+
+#  endif // _LIBCPP_STD_VER >= 20
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_TZDB)
+
+#endif // _LIBCPP___CHRONO_EXCEPTION_H
diff --git a/libcxx/include/__chrono/time_zone.h b/libcxx/include/__chrono/time_zone.h
index e28c9189c381b..b4069823dbe74 100644
--- a/libcxx/include/__chrono/time_zone.h
+++ b/libcxx/include/__chrono/time_zone.h
@@ -18,12 +18,14 @@
 
 #  include <__chrono/calendar.h>
 #  include <__chrono/duration.h>
+#  include <__chrono/exception.h>
 #  include <__chrono/local_info.h>
 #  include <__chrono/sys_info.h>
 #  include <__chrono/system_clock.h>
 #  include <__compare/strong_order.h>
 #  include <__config>
 #  include <__memory/unique_ptr.h>
+#  include <__type_traits/common_type.h>
 #  include <string_view>
 
 #  if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
@@ -70,6 +72,31 @@ class _LIBCPP_AVAILABILITY_TZDB time_zone {
     return __get_info(chrono::time_point_cast<seconds>(__time));
   }
 
+  // We don't apply nodiscard here since this function throws on many inputs,
+  // so it could be used as a validation.
+  template <class _Duration>
+  _LIBCPP_HIDE_FROM_ABI sys_time<common_type_t<_Duration, seconds>> to_sys(const local_time<_Duration>& __time) const {
+    local_info __info = get_info(__time);
+    switch (__info.result) {
+    case local_info::unique:
+      return sys_time<common_type_t<_Duration, seconds>>{__time.time_since_epoch() - __info.first.offset};
+
+    case local_info::nonexistent:
+      chrono::__throw_nonexistent_local_time(__time, __info);
+
+    case local_info::ambiguous:
+      chrono::__throw_ambiguous_local_time(__time, __info);
+    }
+
+    // TODO TZDB The Standard does not specify anything in these cases.
+    _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
+        __info.result != -1, "cannot convert the local time; it would be before the minimum system clock value");
+    _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
+        __info.result != -2, "cannot convert the local time; it would be after the maximum system clock value");
+
+    return {};
+  }
+
   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI const __impl& __implementation() const noexcept { return *__impl_; }
 
 private:
diff --git a/libcxx/include/chrono b/libcxx/include/chrono
index 4d8398af1a108..4b0ea938710bd 100644
--- a/libcxx/include/chrono
+++ b/libcxx/include/chrono
@@ -724,6 +724,10 @@ const time_zone* current_zone()
 const tzdb& reload_tzdb();                                                       // C++20
 string remote_version();                                                         // C++20
 
+// [time.zone.exception], exception classes
+class nonexistent_local_time;                                                    // C++20
+class ambiguous_local_time;                                                      // C++20
+
 // [time.zone.info], information classes
 struct sys_info {                                                                // C++20
   sys_seconds   begin;
@@ -766,6 +770,10 @@ class time_zone {
 
   template<class Duration>
   local_info get_info(const local_time<Duration>& tp) const;
+
+  template<class Duration>
+  sys_time<common_type_t<Duration, seconds>>
+    to_sys(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
@@ -916,6 +924,7 @@ constexpr chrono::year                                  operator ""y(unsigned lo
 #  include <__chrono/calendar.h>
 #  include <__chrono/day.h>
 #  include <__chrono/hh_mm_ss.h>
+#  include <__chrono/exception.h>
 #  include <__chrono/literals.h>
 #  include <__chrono/local_info.h>
 #  include <__chrono/month.h>
diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap
index 48391b2a12095..e03ea989eb371 100644
--- a/libcxx/include/module.modulemap
+++ b/libcxx/include/module.modulemap
@@ -1101,6 +1101,7 @@ module std_private_chrono_duration               [system] {
   header "__chrono/duration.h"
   export std_private_type_traits_is_convertible
 }
+module std_private_chrono_exception              [system] { header "__chrono/exception.h" }
 module std_private_chrono_file_clock             [system] { header "__chrono/file_clock.h" }
 module std_private_chrono_formatter              [system] {
   header "__chrono/formatter.h"
@@ -1113,7 +1114,10 @@ module std_private_chrono_high_resolution_clock  [system] {
 }
 module std_private_chrono_leap_second            [system] { header "__chrono/leap_second.h" }
 module std_private_chrono_literals               [system] { header "__chrono/literals.h" }
-module std_private_chrono_local_info             [system] { header "__chrono/local_info.h" }
+module std_private_chrono_local_info             [system] {
+  header "__chrono/local_info.h"
+  export std_private_chrono_sys_info
+}
 module std_private_chrono_month                  [system] { header "__chrono/month.h" }
 module std_private_chrono_month_weekday          [system] { header "__chrono/month_weekday.h" }
 module std_private_chrono_monthday               [system] { header "__chrono/monthday.h" }
diff --git a/libcxx/modules/std/chrono.inc b/libcxx/modules/std/chrono.inc
index 813322a1797f6..2953709d6f44d 100644
--- a/libcxx/modules/std/chrono.inc
+++ b/libcxx/modules/std/chrono.inc
@@ -209,13 +209,12 @@ export namespace std {
     using std::chrono::reload_tzdb;
     using std::chrono::remote_version;
 
-#    if 0
     // [time.zone.exception], exception classes
     using std::chrono::ambiguous_local_time;
     using std::chrono::nonexistent_local_time;
-#    endif // if 0
-#  endif   //  !defined(_LIBCPP_HAS_NO_TIME_ZONE_DATABASE) && !defined(_LIBCPP_HAS_NO_FILESYSTEM) &&
-           //  !defined(_LIBCPP_HAS_NO_LOCALIZATION)
+
+#  endif //  !defined(_LIBCPP_HAS_NO_TIME_ZONE_DATABASE) && !defined(_LIBCPP_HAS_NO_FILESYSTEM) &&
+         //  !defined(_LIBCPP_HAS_NO_LOCALIZATION)
 
     // [time.zone.info], information classes
     using std::chrono::local_info;
diff --git a/libcxx/src/CMakeLists.txt b/libcxx/src/CMakeLists.txt
index 65e6ce2c4da43..9e6c70335a794 100644
--- a/libcxx/src/CMakeLists.txt
+++ b/libcxx/src/CMakeLists.txt
@@ -340,6 +340,9 @@ if (LIBCXX_ENABLE_LOCALIZATION AND LIBCXX_ENABLE_FILESYSTEM AND LIBCXX_ENABLE_TI
     include/tzdb/types_private.h
     include/tzdb/tzdb_list_private.h
     include/tzdb/tzdb_private.h
+    # TODO TZDB The exception could be moved in chrono once the TZDB library
+    # is no longer experimental.
+    chrono_exception.cpp
     time_zone.cpp
     tzdb.cpp
     tzdb_list.cpp
diff --git a/libcxx/src/chrono_exception.cpp b/libcxx/src/chrono_exception.cpp
new file mode 100644
index 0000000000000..bea2ad110310a
--- /dev/null
+++ b/libcxx/src/chrono_exception.cpp
@@ -0,0 +1,22 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include <chrono>
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+namespace chrono {
+
+_LIBCPP_AVAILABILITY_TZDB
+_LIBCPP_EXPORTED_FROM_ABI nonexistent_local_time::~nonexistent_local_time() = default; // key function
+_LIBCPP_AVAILABILITY_TZDB
+_LIBCPP_EXPORTED_FROM_ABI ambiguous_local_time::~ambiguous_local_time() = default; // key function
+
+} // namespace chrono
+
+_LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/test/libcxx/time/time.zone/time.zone.exception/time.zone.exception.ambig/assert.ctor.pass.cpp b/libcxx/test/libcxx/time/time.zone/time.zone.exception/time.zone.exception.ambig/assert.ctor.pass.cpp
new file mode 100644
index 0000000000000..73e6bf2846f0e
--- /dev/null
+++ b/libcxx/test/libcxx/time/time.zone/time.zone.exception/time.zone.exception.ambig/assert.ctor.pass.cpp
@@ -0,0 +1,53 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// REQUIRES: has-unix-headers
+// REQUIRES: libcpp-hardening-mode={{extensive|debug}}
+// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
+
+// XFAIL: libcpp-has-no-experimental-tzdb
+
+// <chrono>
+
+//  class ambiguous_local_time
+//
+//  template<class Duration>
+//    ambiguous_local_time(const local_time<Duration>& tp, const local_info& i);
+
+#include <chrono>
+
+#include "check_assertion.h"
+
+// [time.zone.exception.ambig]/2
+//   Preconditions: i.result == local_info::ambiguous is true.
+int main(int, char**) {
+  TEST_LIBCPP_ASSERT_FAILURE(
+      (std::chrono::ambiguous_local_time{
+          std::chrono::local_seconds{},
+          std::chrono::local_info{-1, //  this is not one of the "named" result values
+                                  std::chrono::sys_info{},
+                                  std::chrono::sys_info{}}}),
+      "creating an ambiguous_local_time from a local_info that is not ambiguous");
+
+  TEST_LIBCPP_ASSERT_FAILURE(
+      (std::chrono::ambiguous_local_time{
+          std::chrono::local_seconds{},
+          std::chrono::local_info{std::chrono::local_info::unique, std::chrono::sys_info{}, std::chrono::sys_info{}}}),
+      "creating an ambiguous_local_time from a local_info that is not ambiguous");
+
+  TEST_LIBCPP_ASSERT_FAILURE(
+      (std::chrono::ambiguous_local_time{
+          std::chrono::local_seconds{},
+          std::chrono::local_info{
+              std::chrono::local_info::nonexistent, std::chrono::sys_info{}, std::chrono::sys_info{}}}),
+      "creating an ambiguous_local_time from a local_info that is not ambiguous");
+
+  return 0;
+}
diff --git a/libcxx/test/libcxx/time/time.zone/time.zone.exception/time.zone.exception.nonexist/assert.ctor.pass.cpp b/libcxx/test/libcxx/time/time.zone/time.zone.exception/time.zone.exception.nonexist/assert.ctor.pass.cpp
new file mode 100644
index 0000000000000..fdd9f79958f98
--- /dev/null
+++ b/libcxx/test/libcxx/time/time.zone/time.zone.exception/time.zone.exception.nonexist/assert.ctor.pass.cpp
@@ -0,0 +1,53 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// REQUIRES: has-unix-headers
+// REQUIRES: libcpp-hardening-mode={{extensive|debug}}
+// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
+
+// XFAIL: libcpp-has-no-experimental-tzdb
+
+// <chrono>
+
+//  class nonexistent_local_time
+//
+//  template<class Duration>
+//    nonexistent_local_time(const local_time<Duration>& tp, const local_info& i);
+
+#include <chrono>
+
+#include "check_assertion.h"
+
+// [time.zone.exception.nonexist]/2
+//   Preconditions: i.result == local_info::nonexistent is true.
+int main(int, char**) {
+  TEST_LIBCPP_ASSERT_FAILURE(
+      (std::chrono::nonexistent_local_time{
+          std::chrono::local_seconds{},
+          std::chrono::local_info{-1, //  this is not one of the "named" result values
+                                  std::chrono::sys_info{},
+                                  std::chrono::sys_info{}}}),
+      "creating an nonexistent_local_time from a local_info that is not non-existent");
+
+  TEST_LIBCPP_ASSERT_FAILURE(
+      (std::chrono::nonexistent_local_time{
+          std::chrono::local_seconds{},
+          std::chrono::local_info{std::chrono::local_info::unique, std::chrono::sys_info{}, std::chrono::sys_info{}}}),
+      "creating an nonexistent_local_time from a local_info that is not non-existent");
+
+  TEST_LIBCPP_ASSERT_FAILURE(
+      (std::chrono::nonexistent_local_time{
+          std::chrono::local_seconds{},
+          std::chrono::local_info{
+              std::chrono::local_info::ambiguous, std::chrono::sys_info{}, std::chrono::sys_info{}}}),
+      "creating an nonexistent_local_time from a local_info that is not non-existent");
+
+  return 0;
+}
diff --git a/libcxx/test/libcxx/time/time.zone/time.zone.timezone/time.zone.members/assert.to_sys.pass.cpp b/libcxx/test/libcxx/time/time.zone/time.zone.timezone/time.zone.members/assert.to_sys.pass.cpp
new file mode 100644
index 0000000000000..3a2ff00088676
--- /dev/null
+++ b/libcxx/test/libcxx/time/time.zone/time.zone.timezone/time.zone.members/assert.to_sys.pass.cpp
@@ -0,0 +1,39 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// REQUIRES: has-unix-headers
+// REQUIRES: libcpp-hardening-mode={{extensive|debug}}
+// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
+
+// XFAIL: libcpp-has-no-experimental-tzdb
+
+// <chrono>
+
+// template <class _Duration>
+//   sys_time<common_type_t<Duration, seconds>>
+//     to_sys(const local_time<Duration>& tp) const;
+
+#include <chrono>
+
+#include "check_assertion.h"
+
+// Tests values that cannot be converted. To make sure the test is does not depend on changes
+// in the database it uses a time zone with a fixed offset.
+int main(int, char**) {
+  TEST_LIBCPP_ASSERT_FAILURE(std::chrono::locate_zone("Etc/GMT-1")->to_sys(std::chrono::local_seconds::min()),
+                             "cannot convert the local time; it would be before the minimum system clock value");
+
+  // TODO TZDB look why std::chrono::local_seconds::max()  fails
+  TEST_LIBCPP_ASSERT_FAILURE(
+      std::chrono::locate_zone("Etc/GMT+1")->to_sys(std::chrono::local_seconds::max() - std::chrono::seconds(1)),
+      "cannot convert the local time; it would be after the maximum system clock value");
+
+  return 0;
+}
diff --git a/libcxx/test/libcxx/transitive_includes/cxx03.csv b/libcxx/test/libcxx/transitive_includes/cxx03.csv
index 92601fab5b773..b0431d9b2b551 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx03.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx03.csv
@@ -187,9 +187,6 @@ condition_variable type_traits
 condition_variable typeinfo
 condition_variable version
 coroutine compare
-coroutine cstddef
-coroutine cstdint
-coroutine cstring
 coroutine iosfwd
 coroutine limits
 coroutine type_traits
diff --git a/libcxx/test/libcxx/transitive_includes/cxx11.csv b/libcxx/test/libcxx/transitive_includes/cxx11.csv
index c05eb42deb9a1..6fc8fe5858c0d 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx11.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx11.csv
@@ -188,9 +188,6 @@ condition_variable type_traits
 condition_variable typeinfo
 condition_variable version
 coroutine compare
-coroutine cstddef
-coroutine cstdint
-coroutine cstring
 coroutine iosfwd
 coroutine limits
 coroutine type_traits
diff --git a/libcxx/test/libcxx/transitive_includes/cxx14.csv b/libcxx/test/libcxx/transitive_includes/cxx14.csv
index 09252b7b7d2db..5771e2ba0761c 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx14.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx14.csv
@@ -189,9 +189,6 @@ condition_variable type_traits
 condition_variable typeinfo
 condition_variable version
 coroutine compare
-coroutine cstddef
-coroutine cstdint
-coroutine cstring
 coroutine iosfwd
 coroutine limits
 coroutine type_traits
diff --git a/libcxx/test/libcxx/transitive_includes/cxx17.csv b/libcxx/test/libcxx/transitive_includes/cxx17.csv
index 09252b7b7d2db..5771e2ba0761c 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx17.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx17.csv
@@ -189,9 +189,6 @@ condition_variable type_traits
 condition_variable typeinfo
 condition_variable version
 coroutine compare
-coroutine cstddef
-coroutine cstdint
-coroutine cstring
 coroutine iosfwd
 coroutine limits
 coroutine type_traits
diff --git a/libcxx/test/libcxx/transitive_includes/cxx20.csv b/libcxx/test/libcxx/transitive_includes/cxx20.csv
index ce4ccc3d11615..406665a213536 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx20.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx20.csv
@@ -111,26 +111,19 @@ charconv limits
 charconv new
 charconv type_traits
 charconv version
-chrono array
 chrono bit
-chrono cctype
-chrono cerrno
 chrono charconv
-chrono clocale
 chrono cmath
 chrono compare
 chrono concepts
 chrono cstddef
 chrono cstdint
-chrono cstdlib
 chrono cstring
 chrono ctime
-chrono cwchar
+chrono format
 chrono forward_list
 chrono limits
 chrono locale
-chrono new
-chrono optional
 chrono ostream
 chrono ratio
 chrono sstream
diff --git a/libcxx/test/libcxx/transitive_includes/cxx23.csv b/libcxx/test/libcxx/transitive_includes/cxx23.csv
index 62d931c0eebad..05c7a6ea18d79 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx23.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx23.csv
@@ -67,30 +67,20 @@ charconv initializer_list
 charconv limits
 charconv new
 charconv version
-chrono array
-chrono cctype
-chrono cerrno
-chrono clocale
 chrono cmath
 chrono compare
 chrono cstddef
 chrono cstdint
-chrono cstdlib
-chrono cstring
 chrono ctime
-chrono cwchar
+chrono format
 chrono forward_list
 chrono limits
-chrono new
-chrono optional
 chrono ostream
 chrono ratio
 chrono sstream
 chrono stdexcept
 chrono string
 chrono string_view
-chrono tuple
-chrono typeinfo
 chrono vector
 chrono version
 cinttypes cstdint
diff --git a/libcxx/test/libcxx/transitive_includes/cxx26.csv b/libcxx/test/libcxx/transitive_includes/cxx26.csv
index f68249aeec78c..05c7a6ea18d79 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx26.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx26.csv
@@ -67,30 +67,20 @@ charconv initializer_list
 charconv limits
 charconv new
 charconv version
-chrono array
-chrono cctype
-chrono cerrno
-chrono clocale
 chrono cmath
 chrono compare
 chrono cstddef
 chrono cstdint
-chrono cstdlib
-chrono cstring
 chrono ctime
-chrono cwchar
+chrono format
 chrono forward_list
 chrono limits
-chrono new
-chrono optional
 chrono ostream
 chrono ratio
 chrono sstream
 chrono stdexcept
 chrono string
 chrono string_view
-chrono tuple
-chrono typeinfo
 chrono vector
 chrono version
 cinttypes cstdint
@@ -176,29 +166,6 @@ experimental/simd limits
 experimental/type_traits initializer_list
 experimental/type_traits type_traits
 experimental/utility utility
-experimental/vector experimental/memory_resource
-experimental/vector vector
-ext/hash_map algorithm
-ext/hash_map cmath
-ext/hash_map cstddef
-ext/hash_map cstdint
-ext/hash_map cstring
-ext/hash_map functional
-ext/hash_map initializer_list
-ext/hash_map limits
-ext/hash_map new
-ext/hash_map stdexcept
-ext/hash_map string
-ext/hash_set algorithm
-ext/hash_set cmath
-ext/hash_set cstddef
-ext/hash_set cstdint
-ext/hash_set cstring
-ext/hash_set functional
-ext/hash_set initializer_list
-ext/hash_set limits
-ext/hash_set new
-ext/hash_set string
 filesystem compare
 filesystem cstddef
 filesystem cstdint
diff --git a/libcxx/test/std/time/time.zone/time.zone.exception/time.zone.exception.ambig/ctor.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.exception/time.zone.exception.ambig/ctor.pass.cpp
new file mode 100644
index 0000000000000..a0b141631f831
--- /dev/null
+++ b/libcxx/test/std/time/time.zone/time.zone.exception/time.zone.exception.ambig/ctor.pass.cpp
@@ -0,0 +1,171 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 ambiguous_local_time
+//
+//  template<class Duration>
+//    ambiguous_local_time(const local_time<Duration>& tp, const local_info& i);
+
+#include <chrono>
+#include <string_view>
+
+#include "assert_macros.h"
+#include "concat_macros.h"
+
+template <class Duration>
+static void
+test(const std::chrono::local_time<Duration>& tp, const std::chrono::local_info& i, std::string_view expected) {
+  std::chrono::ambiguous_local_time exception{tp, i};
+  std::string_view result = exception.what();
+  TEST_REQUIRE(result == expected,
+               TEST_WRITE_CONCATENATED("Expected output\n", expected, "\n\nActual output\n", result, '\n'));
+}
+
+// The constructor constructs the runtime_error base class with a specific
+// message. This implicitly tests what() too, since that is inherited from
+// runtime_error there is no separate test for what().
+int main(int, char**) {
+  using namespace std::literals::chrono_literals;
+
+  // There is no requirement on the ordering of PREV and NEXT so an "invalid"
+  // overlap is allowed. All tests with negative dates use the same order as
+  // positive tests.
+
+  test(std::chrono::local_time<std::chrono::nanoseconds>{-1ns},
+       std::chrono::local_info{
+           std::chrono::local_info::ambiguous,
+           std::chrono::sys_info{
+               std::chrono::sys_days{std::chrono::January / 1 / 1970},
+               std::chrono::sys_days{std::chrono::March / 1 / 1970},
+               1h,
+               60min,
+               "PREV"},
+
+           std::chrono::sys_info{
+               std::chrono::sys_days{std::chrono::September / 1 / 1969},
+               std::chrono::sys_days{std::chrono::December / 31 / 1969} + 23h,
+               0s,
+               0min,
+               "NEXT"}
+
+       },
+       R"(1969-12-31 23:59:59.999999999 is ambiguous.  It could be
+1969-12-31 23:59:59.999999999 PREV == 1969-12-31 22:59:59.999999999 UTC or
+1969-12-31 23:59:59.999999999 NEXT == 1969-12-31 23:59:59.999999999 UTC)");
+
+  test(std::chrono::local_time<std::chrono::microseconds>{0us},
+       std::chrono::local_info{
+           std::chrono::local_info::ambiguous,
+           std::chrono::sys_info{
+               std::chrono::sys_days{std::chrono::January / 1 / 1970},
+               std::chrono::sys_days{std::chrono::March / 1 / 1970},
+               1h,
+               60min,
+               "PREV"},
+
+           std::chrono::sys_info{
+               std::chrono::sys_days{std::chrono::September / 1 / 1969},
+               std::chrono::sys_days{std::chrono::December / 31 / 1969} + 23h,
+               0s,
+               0min,
+               "NEXT"}},
+       R"(1970-01-01 00:00:00.000000 is ambiguous.  It could be
+1970-01-01 00:00:00.000000 PREV == 1969-12-31 23:00:00.000000 UTC or
+1970-01-01 00:00:00.000000 NEXT == 1970-01-01 00:00:00.000000 UTC)");
+
+  test(std::chrono::local_time<std::chrono::milliseconds>{1ms},
+       std::chrono::local_info{
+           std::chrono::local_info::ambiguous,
+           std::chrono::sys_info{
+               std::chrono::sys_days{std::chrono::January / 1 / 1970},
+               std::chrono::sys_days{std::chrono::March / 1 / 1970},
+               1h,
+               60min,
+               "PREV"},
+
+           std::chrono::sys_info{
+               std::chrono::sys_days{std::chrono::September / 1 / 1969},
+               std::chrono::sys_days{std::chrono::December / 31 / 1969} + 23h,
+               0s,
+               0min,
+               "NEXT"}},
+       R"(1970-01-01 00:00:00.001 is ambiguous.  It could be
+1970-01-01 00:00:00.001 PREV == 1969-12-31 23:00:00.001 UTC or
+1970-01-01 00:00:00.001 NEXT == 1970-01-01 00:00:00.001 UTC)");
+
+  test(std::chrono::local_seconds{(std::chrono::sys_days{std::chrono::January / 1 / -21970}).time_since_epoch()},
+       std::chrono::local_info{
+           std::chrono::local_info::ambiguous,
+           std::chrono::sys_info{
+               std::chrono::sys_days{std::chrono::September / 1 / -21969},
+               std::chrono::sys_days{std::chrono::December / 31 / -21969},
+               0s,
+               0min,
+               "PREV"},
+           std::chrono::sys_info{
+               std::chrono::sys_days{std::chrono::January / 1 / -21970},
+               std::chrono::sys_days{std::chrono::March / 1 / -21970} + 23h,
+               1h,
+               60min,
+               "NEXT"}},
+       R"(-21970-01-01 00:00:00 is ambiguous.  It could be
+-21970-01-01 00:00:00 PREV == -21970-01-01 00:00:00 UTC or
+-21970-01-01 00:00:00 NEXT == -21971-12-31 23:00:00 UTC)");
+
+  test(
+      std::chrono::local_time<std::chrono::days>{
+          (std::chrono::sys_days{std::chrono::January / 1 / 21970}).time_since_epoch()},
+      std::chrono::local_info{
+          std::chrono::local_info::ambiguous,
+          std::chrono::sys_info{
+              std::chrono::sys_days{std::chrono::September / 1 / 21969},
+              std::chrono::sys_days{std::chrono::December / 31 / 21969},
+              0s,
+              0min,
+              "PREV"},
+          std::chrono::sys_info{
+              std::chrono::sys_days{std::chrono::January / 1 / 21970},
+              std::chrono::sys_days{std::chrono::March / 1 / 21970} + 23h,
+              1h,
+              60min,
+              "NEXT"}},
+      R"(21970-01-01 is ambiguous.  It could be
+21970-01-01 PREV == 21970-01-01 00:00:00 UTC or
+21970-01-01 NEXT == 21969-12-31 23:00:00 UTC)");
+
+  test(std::chrono::local_time<std::chrono::weeks>{},
+       std::chrono::local_info{
+           std::chrono::local_info::ambiguous,
+           std::chrono::sys_info{
+               std::chrono::sys_days{std::chrono::September / 1 / 1969},
+               std::chrono::sys_days{std::chrono::December / 31 / 1969},
+               0s,
+               0min,
+               "PREV"},
+           std::chrono::sys_info{
+               std::chrono::sys_days{std::chrono::January / 1 / 1970},
+               std::chrono::sys_days{std::chrono::March / 1 / 1970} + 23h,
+               1h,
+               60min,
+               "NEXT"}},
+       R"(1970-01-01 is ambiguous.  It could be
+1970-01-01 PREV == 1970-01-01 00:00:00 UTC or
+1970-01-01 NEXT == 1969-12-31 23:00:00 UTC)");
+
+  // Note months and years can not be streamed.
+
+  return 0;
+}
diff --git a/libcxx/test/std/time/time.zone/time.zone.exception/time.zone.exception.ambig/types.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.exception/time.zone.exception.ambig/types.pass.cpp
new file mode 100644
index 0000000000000..17549d2404540
--- /dev/null
+++ b/libcxx/test/std/time/time.zone/time.zone.exception/time.zone.exception.ambig/types.pass.cpp
@@ -0,0 +1,48 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// ADDITIONAL_COMPILE_FLAGS(gcc-style-warnings): -Wno-deprecated
+
+// XFAIL: libcpp-has-no-experimental-tzdb
+// XFAIL: availability-tzdb-missing
+
+// <chrono>
+
+// class ambiguous_local_time : public runtime_error {
+// public:
+//   template<class Duration>
+//     ambiguous_local_time(const local_time<Duration>& tp, const local_info& i);
+// };
+
+#include <chrono>
+#include <stdexcept>
+#include <type_traits>
+
+// Basic properties
+static_assert(std::is_base_of_v<std::runtime_error, std::chrono::ambiguous_local_time>);
+static_assert(!std::is_default_constructible_v<std::chrono::ambiguous_local_time>);
+static_assert(std::is_destructible_v<std::chrono::ambiguous_local_time>);
+static_assert(std::is_copy_constructible_v<std::chrono::ambiguous_local_time>);
+static_assert(std::is_move_constructible_v<std::chrono::ambiguous_local_time>);
+static_assert(std::is_copy_assignable_v<std::chrono::ambiguous_local_time>);
+static_assert(std::is_move_assignable_v<std::chrono::ambiguous_local_time>);
+
+int main(int, char**) {
+  std::chrono::ambiguous_local_time e{
+      std::chrono::local_seconds{}, std::chrono::local_info{std::chrono::local_info::ambiguous, {}, {}}};
+
+  std::chrono::ambiguous_local_time copy = e;
+  copy                                   = e;
+
+  std::chrono::ambiguous_local_time move = std::move(e);
+  e                                      = move;
+  move                                   = std::move(e);
+
+  return 0;
+}
diff --git a/libcxx/test/std/time/time.zone/time.zone.exception/time.zone.exception.nonexist/ctor.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.exception/time.zone.exception.nonexist/ctor.pass.cpp
new file mode 100644
index 0000000000000..ca03d832a7a9f
--- /dev/null
+++ b/libcxx/test/std/time/time.zone/time.zone.exception/time.zone.exception.nonexist/ctor.pass.cpp
@@ -0,0 +1,172 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 nonexistent_local_time
+//
+// template<class Duration>
+// nonexistent_local_time(const local_time<Duration>& tp, const local_info& i);
+
+#include <chrono>
+#include <string_view>
+
+#include "assert_macros.h"
+#include "concat_macros.h"
+
+template <class Duration>
+static void
+test(const std::chrono::local_time<Duration>& tp, const std::chrono::local_info& i, std::string_view expected) {
+  std::chrono::nonexistent_local_time exception{tp, i};
+  std::string_view result = exception.what();
+  TEST_REQUIRE(result == expected,
+               TEST_WRITE_CONCATENATED("Expected output\n", expected, "\n\nActual output\n", result, '\n'));
+}
+
+// The constructor constructs the runtime_error base class with a specific
+// message. This implicitly tests what() too, since that is inherited from
+// runtime_error there is no separate test for what().
+int main(int, char**) {
+  using namespace std::literals::chrono_literals;
+
+  // There is no requirement on the ordering of PREV and NEXT so an "invalid"
+  // gap is allowed. All tests with negative dates use the same order as
+  // positive tests.
+
+  test(std::chrono::local_time<std::chrono::nanoseconds>{-1ns},
+       std::chrono::local_info{
+           std::chrono::local_info::nonexistent,
+           std::chrono::sys_info{
+               std::chrono::sys_days{std::chrono::September / 1 / 1969},
+               std::chrono::sys_days{std::chrono::December / 31 / 1969} + 23h,
+               0s,
+               0min,
+               "PREV"},
+           std::chrono::sys_info{
+               std::chrono::sys_days{std::chrono::January / 1 / 1970},
+               std::chrono::sys_days{std::chrono::March / 1 / 1970},
+               1h,
+               60min,
+               "NEXT"}},
+       R"(1969-12-31 23:59:59.999999999 is in a gap between
+1969-12-31 23:00:00 PREV and
+1970-01-01 01:00:00 NEXT which are both equivalent to
+1969-12-31 23:00:00 UTC)");
+
+  test(std::chrono::local_time<std::chrono::microseconds>{0us},
+       std::chrono::local_info{
+           std::chrono::local_info::nonexistent,
+           std::chrono::sys_info{
+               std::chrono::sys_days{std::chrono::September / 1 / 1969},
+               std::chrono::sys_days{std::chrono::December / 31 / 1969} + 23h,
+               0s,
+               0min,
+               "PREV"},
+           std::chrono::sys_info{
+               std::chrono::sys_days{std::chrono::January / 1 / 1970},
+               std::chrono::sys_days{std::chrono::March / 1 / 1970},
+               1h,
+               60min,
+               "NEXT"}},
+       R"(1970-01-01 00:00:00.000000 is in a gap between
+1969-12-31 23:00:00 PREV and
+1970-01-01 01:00:00 NEXT which are both equivalent to
+1969-12-31 23:00:00 UTC)");
+
+  test(std::chrono::local_time<std::chrono::milliseconds>{1ms},
+       std::chrono::local_info{
+           std::chrono::local_info::nonexistent,
+           std::chrono::sys_info{
+               std::chrono::sys_days{std::chrono::September / 1 / 1969},
+               std::chrono::sys_days{std::chrono::December / 31 / 1969} + 23h,
+               0s,
+               0min,
+               "PREV"},
+           std::chrono::sys_info{
+               std::chrono::sys_days{std::chrono::January / 1 / 1970},
+               std::chrono::sys_days{std::chrono::March / 1 / 1970},
+               1h,
+               60min,
+               "NEXT"}},
+       R"(1970-01-01 00:00:00.001 is in a gap between
+1969-12-31 23:00:00 PREV and
+1970-01-01 01:00:00 NEXT which are both equivalent to
+1969-12-31 23:00:00 UTC)");
+
+  test(std::chrono::local_seconds{(std::chrono::sys_days{std::chrono::January / 1 / -21970}).time_since_epoch()},
+       std::chrono::local_info{
+           std::chrono::local_info::nonexistent,
+           std::chrono::sys_info{
+               std::chrono::sys_days{std::chrono::September / 1 / -21969},
+               std::chrono::sys_days{std::chrono::December / 31 / -21969} + 23h,
+               0s,
+               0min,
+               "PREV"},
+           std::chrono::sys_info{
+               std::chrono::sys_days{std::chrono::January / 1 / -21970},
+               std::chrono::sys_days{std::chrono::March / 1 / -21970},
+               1h,
+               60min,
+               "NEXT"}},
+       R"(-21970-01-01 00:00:00 is in a gap between
+-21969-12-31 23:00:00 PREV and
+-21970-01-01 01:00:00 NEXT which are both equivalent to
+-21969-12-31 23:00:00 UTC)");
+
+  test(
+      std::chrono::local_time<std::chrono::days>{
+          (std::chrono::sys_days{std::chrono::January / 1 / 21970}).time_since_epoch()},
+      std::chrono::local_info{
+          std::chrono::local_info::nonexistent,
+          std::chrono::sys_info{
+              std::chrono::sys_days{std::chrono::September / 1 / 21969},
+              std::chrono::sys_days{std::chrono::December / 31 / 21969} + 23h,
+              0s,
+              0min,
+              "PREV"},
+          std::chrono::sys_info{
+              std::chrono::sys_days{std::chrono::January / 1 / 21970},
+              std::chrono::sys_days{std::chrono::March / 1 / 21970},
+              1h,
+              60min,
+              "NEXT"}},
+      R"(21970-01-01 is in a gap between
+21969-12-31 23:00:00 PREV and
+21970-01-01 01:00:00 NEXT which are both equivalent to
+21969-12-31 23:00:00 UTC)");
+
+  test(std::chrono::local_time<std::chrono::weeks>{},
+       std::chrono::local_info{
+           std::chrono::local_info::nonexistent,
+           std::chrono::sys_info{
+               std::chrono::sys_days{std::chrono::September / 1 / 1969},
+               std::chrono::sys_days{std::chrono::December / 31 / 1969} + 23h,
+               0s,
+               0min,
+               "PREV"},
+           std::chrono::sys_info{
+               std::chrono::sys_days{std::chrono::January / 1 / 1970},
+               std::chrono::sys_days{std::chrono::March / 1 / 1970},
+               1h,
+               60min,
+               "NEXT"}},
+       R"(1970-01-01 is in a gap between
+1969-12-31 23:00:00 PREV and
+1970-01-01 01:00:00 NEXT which are both equivalent to
+1969-12-31 23:00:00 UTC)");
+
+  // Note months and years can not be streamed.
+
+  return 0;
+}
diff --git a/libcxx/test/std/time/time.zone/time.zone.exception/time.zone.exception.nonexist/types.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.exception/time.zone.exception.nonexist/types.pass.cpp
new file mode 100644
index 0000000000000..dde4471bf52cf
--- /dev/null
+++ b/libcxx/test/std/time/time.zone/time.zone.exception/time.zone.exception.nonexist/types.pass.cpp
@@ -0,0 +1,48 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// ADDITIONAL_COMPILE_FLAGS(gcc-style-warnings): -Wno-deprecated
+
+// XFAIL: libcpp-has-no-experimental-tzdb
+// XFAIL: availability-tzdb-missing
+
+// <chrono>
+
+// class nonexistent_local_time : public runtime_error {
+// public:
+//   template<class Duration>
+//     nonexistent_local_time(const local_time<Duration>& tp, const local_info& i);
+// };
+
+#include <chrono>
+#include <stdexcept>
+#include <type_traits>
+
+// Basic properties
+static_assert(std::is_base_of_v<std::runtime_error, std::chrono::nonexistent_local_time>);
+static_assert(!std::is_default_constructible_v<std::chrono::nonexistent_local_time>);
+static_assert(std::is_destructible_v<std::chrono::nonexistent_local_time>);
+static_assert(std::is_copy_constructible_v<std::chrono::nonexistent_local_time>);
+static_assert(std::is_move_constructible_v<std::chrono::nonexistent_local_time>);
+static_assert(std::is_copy_assignable_v<std::chrono::nonexistent_local_time>);
+static_assert(std::is_move_assignable_v<std::chrono::nonexistent_local_time>);
+
+int main(int, char**) {
+  std::chrono::nonexistent_local_time e{
+      std::chrono::local_seconds{}, std::chrono::local_info{std::chrono::local_info::nonexistent, {}, {}}};
+
+  std::chrono::nonexistent_local_time copy = e;
+  copy                                     = e;
+
+  std::chrono::nonexistent_local_time move = std::move(e);
+  e                                        = move;
+  move                                     = std::move(e);
+
+  return 0;
+}
diff --git a/libcxx/test/std/time/time.zone/time.zone.timezone/time.zone.members/to_sys.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.timezone/time.zone.members/to_sys.pass.cpp
new file mode 100644
index 0000000000000..874c3d52e460b
--- /dev/null
+++ b/libcxx/test/std/time/time.zone/time.zone.timezone/time.zone.members/to_sys.pass.cpp
@@ -0,0 +1,237 @@
+//===----------------------------------------------------------------------===//
+//
+// 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>
+// sys_time<common_type_t<Duration, seconds>>
+//   to_sys(const local_time<Duration>& tp) const;
+
+#include <chrono>
+#include <format>
+#include <cassert>
+#include <string_view>
+
+#include "test_macros.h"
+#include "assert_macros.h"
+#include "concat_macros.h"
+
+// Tests unique conversions. To make sure the test is does not depend on changes
+// in the database it uses a time zone with a fixed offset.
+static void test_unique() {
+  using namespace std::literals::chrono_literals;
+
+  const std::chrono::time_zone* tz = std::chrono::locate_zone("Etc/GMT+1");
+
+  assert(tz->to_sys(std::chrono::local_time<std::chrono::nanoseconds>{-1ns}) ==
+         std::chrono::sys_time<std::chrono::nanoseconds>{-1ns + 1h});
+
+  assert(tz->to_sys(std::chrono::local_time<std::chrono::microseconds>{0us}) ==
+         std::chrono::sys_time<std::chrono::microseconds>{1h});
+
+  assert(tz->to_sys(std::chrono::local_time<std::chrono::seconds>{
+             (std::chrono::sys_days{std::chrono::January / 1 / -21970}).time_since_epoch()}) ==
+         std::chrono::sys_time<std::chrono::seconds>{
+             (std::chrono::sys_days{std::chrono::January / 1 / -21970}).time_since_epoch() + 1h});
+
+  // sys_time<common_type_t<Duration, seconds>> is seconds for the larger types
+  assert(tz->to_sys(std::chrono::local_time<std::chrono::days>{
+             (std::chrono::sys_days{std::chrono::January / 1 / 21970}).time_since_epoch()}) ==
+         std::chrono::sys_time<std::chrono::seconds>{
+             (std::chrono::sys_days{std::chrono::January / 1 / 21970}).time_since_epoch() + 1h});
+
+  assert(tz->to_sys(std::chrono::local_time<std::chrono::weeks>{}) ==
+         std::chrono::sys_time<std::chrono::seconds>{
+             (std::chrono::sys_days{std::chrono::January / 1 / 1970}).time_since_epoch() + 1h});
+
+  // Note months and years can not be streamed; thus the function cannot be
+  // instantiated for these types. (Even when there is no exception thrown.)
+}
+
+// Tests non-existant conversions.
+static void test_nonexistent() {
+#ifndef TEST_HAS_NO_EXCEPTIONS
+  using namespace std::literals::chrono_literals;
+
+  const std::chrono::time_zone* tz = std::chrono::locate_zone("Europe/Berlin");
+
+  // Z Europe/Berlin 0:53:28 - LMT 1893 Ap
+  // ...
+  // 1 DE CE%sT 1980
+  // 1 E CE%sT
+  //
+  // ...
+  // R E 1981 ma - Mar lastSu 1u 1 S
+  // R E 1996 ma - O lastSu 1u 0 -
+
+  // Pick an historic date where it's well known what the time zone rules were.
+  // This makes it unlikely updates to the database change these rules.
+  std::chrono::local_time<std::chrono::seconds> time{
+      (std::chrono::sys_days{std::chrono::March / 30 / 1986} + 2h + 30min).time_since_epoch()};
+
+  // Validates whether the database did not change.
+  std::chrono::local_info info = tz->get_info(time);
+  assert(info.result == std::chrono::local_info::nonexistent);
+
+  TEST_VALIDATE_EXCEPTION(
+      std::chrono::nonexistent_local_time,
+      [&]([[maybe_unused]] const std::chrono::nonexistent_local_time& e) {
+        std::string_view what =
+            R"(1986-03-30 02:30:00.000000000 is in a gap between
+1986-03-30 02:00:00 CET and
+1986-03-30 03:00:00 CEST which are both equivalent to
+1986-03-30 01:00:00 UTC)";
+        TEST_LIBCPP_REQUIRE(
+            e.what() == what,
+            TEST_WRITE_CONCATENATED("Expected exception\n", what, "\n\nActual exception\n", e.what(), '\n'));
+      },
+      tz->to_sys(time + 0ns));
+
+  TEST_VALIDATE_EXCEPTION(
+      std::chrono::nonexistent_local_time,
+      [&]([[maybe_unused]] const std::chrono::nonexistent_local_time& e) {
+        std::string_view what =
+            R"(1986-03-30 02:30:00.000000 is in a gap between
+1986-03-30 02:00:00 CET and
+1986-03-30 03:00:00 CEST which are both equivalent to
+1986-03-30 01:00:00 UTC)";
+        TEST_LIBCPP_REQUIRE(
+            e.what() == what,
+            TEST_WRITE_CONCATENATED("Expected exception\n", what, "\n\nActual exception\n", e.what(), '\n'));
+      },
+      tz->to_sys(time + 0us));
+
+  TEST_VALIDATE_EXCEPTION(
+      std::chrono::nonexistent_local_time,
+      [&]([[maybe_unused]] const std::chrono::nonexistent_local_time& e) {
+        std::string_view what =
+            R"(1986-03-30 02:30:00.000 is in a gap between
+1986-03-30 02:00:00 CET and
+1986-03-30 03:00:00 CEST which are both equivalent to
+1986-03-30 01:00:00 UTC)";
+        TEST_LIBCPP_REQUIRE(
+            e.what() == what,
+            TEST_WRITE_CONCATENATED("Expected exception\n", what, "\n\nActual exception\n", e.what(), '\n'));
+      },
+      tz->to_sys(time + 0ms));
+
+  TEST_VALIDATE_EXCEPTION(
+      std::chrono::nonexistent_local_time,
+      [&]([[maybe_unused]] const std::chrono::nonexistent_local_time& e) {
+        std::string_view what =
+            R"(1986-03-30 02:30:00 is in a gap between
+1986-03-30 02:00:00 CET and
+1986-03-30 03:00:00 CEST which are both equivalent to
+1986-03-30 01:00:00 UTC)";
+        TEST_LIBCPP_REQUIRE(
+            e.what() == what,
+            TEST_WRITE_CONCATENATED("Expected exception\n", what, "\n\nActual exception\n", e.what(), '\n'));
+      },
+      tz->to_sys(time + 0s));
+
+#endif // TEST_HAS_NO_EXCEPTIONS
+}
+
+// Tests ambiguous conversions.
+static void test_ambiguous() {
+#ifndef TEST_HAS_NO_EXCEPTIONS
+  using namespace std::literals::chrono_literals;
+
+  const std::chrono::time_zone* tz = std::chrono::locate_zone("Europe/Berlin");
+
+  // Z Europe/Berlin 0:53:28 - LMT 1893 Ap
+  // ...
+  // 1 DE CE%sT 1980
+  // 1 E CE%sT
+  //
+  // ...
+  // R E 1981 ma - Mar lastSu 1u 1 S
+  // R E 1996 ma - O lastSu 1u 0 -
+
+  // Pick an historic date where it's well known what the time zone rules were.
+  // This makes it unlikely updates to the database change these rules.
+  std::chrono::local_time<std::chrono::seconds> time{
+      (std::chrono::sys_days{std::chrono::September / 28 / 1986} + 2h + 30min).time_since_epoch()};
+
+  // Validates whether the database did not change.
+  std::chrono::local_info info = tz->get_info(time);
+  assert(info.result == std::chrono::local_info::ambiguous);
+
+  TEST_VALIDATE_EXCEPTION(
+      std::chrono::ambiguous_local_time,
+      [&]([[maybe_unused]] const std::chrono::ambiguous_local_time& e) {
+        std::string_view what =
+            R"(1986-09-28 02:30:00.000000000 is ambiguous.  It could be
+1986-09-28 02:30:00.000000000 CEST == 1986-09-28 00:30:00.000000000 UTC or
+1986-09-28 02:30:00.000000000 CET == 1986-09-28 01:30:00.000000000 UTC)";
+        TEST_LIBCPP_REQUIRE(
+            e.what() == what,
+            TEST_WRITE_CONCATENATED("Expected exception\n", what, "\n\nActual exception\n", e.what(), '\n'));
+      },
+      tz->to_sys(time + 0ns));
+
+  TEST_VALIDATE_EXCEPTION(
+      std::chrono::ambiguous_local_time,
+      [&]([[maybe_unused]] const std::chrono::ambiguous_local_time& e) {
+        std::string_view what =
+            R"(1986-09-28 02:30:00.000000 is ambiguous.  It could be
+1986-09-28 02:30:00.000000 CEST == 1986-09-28 00:30:00.000000 UTC or
+1986-09-28 02:30:00.000000 CET == 1986-09-28 01:30:00.000000 UTC)";
+        TEST_LIBCPP_REQUIRE(
+            e.what() == what,
+            TEST_WRITE_CONCATENATED("Expected exception\n", what, "\n\nActual exception\n", e.what(), '\n'));
+      },
+      tz->to_sys(time + 0us));
+
+  TEST_VALIDATE_EXCEPTION(
+      std::chrono::ambiguous_local_time,
+      [&]([[maybe_unused]] const std::chrono::ambiguous_local_time& e) {
+        std::string_view what =
+            R"(1986-09-28 02:30:00.000 is ambiguous.  It could be
+1986-09-28 02:30:00.000 CEST == 1986-09-28 00:30:00.000 UTC or
+1986-09-28 02:30:00.000 CET == 1986-09-28 01:30:00.000 UTC)";
+        TEST_LIBCPP_REQUIRE(
+            e.what() == what,
+            TEST_WRITE_CONCATENATED("Expected exception\n", what, "\n\nActual exception\n", e.what(), '\n'));
+      },
+      tz->to_sys(time + 0ms));
+
+  TEST_VALIDATE_EXCEPTION(
+      std::chrono::ambiguous_local_time,
+      [&]([[maybe_unused]] const std::chrono::ambiguous_local_time& e) {
+        std::string_view what =
+            R"(1986-09-28 02:30:00 is ambiguous.  It could be
+1986-09-28 02:30:00 CEST == 1986-09-28 00:30:00 UTC or
+1986-09-28 02:30:00 CET == 1986-09-28 01:30:00 UTC)";
+        TEST_LIBCPP_REQUIRE(
+            e.what() == what,
+            TEST_WRITE_CONCATENATED("Expected exception\n", what, "\n\nActual exception\n", e.what(), '\n'));
+      },
+      tz->to_sys(time + 0s));
+
+#endif // TEST_HAS_NO_EXCEPTIONS
+}
+
+// This test does the basic validations of this function. The library function
+// uses `local_info get_info(const local_time<Duration>& tp)` as implementation
+// detail. The get_info function does extensive testing of the data.
+int main(int, char**) {
+  test_unique();
+  test_nonexistent();
+  test_ambiguous();
+
+  return 0;
+}



More information about the libcxx-commits mailing list