From d7b42718303e017acfe3e61c67d6e8a9bb0ffa9d Mon Sep 17 00:00:00 2001 From: Mark de Wever 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 | 26 ++ libcxx/include/chrono | 9 + libcxx/include/module.modulemap | 6 +- libcxx/modules/std/chrono.inc | 2 - libcxx/src/CMakeLists.txt | 3 + libcxx/src/chrono_exception.cpp | 20 ++ .../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 | 8 +- .../test/libcxx/transitive_includes/cxx23.csv | 12 +- .../test/libcxx/transitive_includes/cxx26.csv | 12 +- .../time.zone.exception.ambig/ctor.pass.cpp | 171 +++++++++++++ .../types.compile.pass.cpp | 33 +++ .../ctor.pass.cpp | 172 +++++++++++++ .../types.compile.pass.cpp | 33 +++ .../time.zone.members/to_sys.pass.cpp | 237 ++++++++++++++++++ 23 files changed, 987 insertions(+), 44 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.compile.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.compile.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 1296c536bc882c..386bd967eed7ab 100644 --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -264,6 +264,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 00000000000000..6059a62c3b2ea9 --- /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 +// Enable the contents of the header only when libc++ was built with experimental features enabled. +#if !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_TZDB) + +# include <__availability> +# include <__chrono/calendar.h> +# include <__chrono/local_info.h> +# include <__chrono/time_point.h> +# include <__config> +# include <__verbose_abort> +# include +# include +# include + +# 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 + _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 + _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 +_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 + _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 + _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 +_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 e28c9189c381b4..b7ce4ea659a1af 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 # if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) @@ -70,6 +72,30 @@ class _LIBCPP_AVAILABILITY_TZDB time_zone { return __get_info(chrono::time_point_cast(__time)); } + // Since the interface promisses throwing, don't add nodiscard. + template + _LIBCPP_HIDE_FROM_ABI sys_time> to_sys(const local_time<_Duration>& __time) const { + local_info __info = get_info(__time); + switch (__info.result) { + case local_info::unique: + return sys_time>{__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 4d8398af1a108f..4b0ea938710bdd 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 local_info get_info(const local_time& tp) const; + + template + sys_time> + to_sys(const local_time& tp) const; }; bool operator==(const time_zone& x, const time_zone& y) noexcept; // C++20 strong_ordering operator<=>(const time_zone& x, const time_zone& y) noexcept; // C++20 @@ -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 8727ab88f16c0a..e4c154d99602b8 100644 --- a/libcxx/include/module.modulemap +++ b/libcxx/include/module.modulemap @@ -1112,6 +1112,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" @@ -1124,7 +1125,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 1265e21dc54ef6..38e3c4184521b7 100644 --- a/libcxx/modules/std/chrono.inc +++ b/libcxx/modules/std/chrono.inc @@ -208,11 +208,9 @@ 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 // [time.zone.info], information classes using std::chrono::local_info; diff --git a/libcxx/src/CMakeLists.txt b/libcxx/src/CMakeLists.txt index 8b28d1b8918955..caaaeff309f530 100644 --- a/libcxx/src/CMakeLists.txt +++ b/libcxx/src/CMakeLists.txt @@ -339,6 +339,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 00000000000000..8aeafdeecd69b9 --- /dev/null +++ b/libcxx/src/chrono_exception.cpp @@ -0,0 +1,20 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +_LIBCPP_BEGIN_NAMESPACE_STD + +namespace chrono { + +_LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI nonexistent_local_time::~nonexistent_local_time() = default; +_LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI ambiguous_local_time::~ambiguous_local_time() = default; + +} // 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 00000000000000..73e6bf2846f0e0 --- /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 + +// + +// class ambiguous_local_time +// +// template +// ambiguous_local_time(const local_time& tp, const local_info& i); + +#include + +#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 00000000000000..fdd9f79958f980 --- /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 + +// + +// class nonexistent_local_time +// +// template +// nonexistent_local_time(const local_time& tp, const local_info& i); + +#include + +#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 00000000000000..3a2ff00088676b --- /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 + +// + +// template +// sys_time> +// to_sys(const local_time& tp) const; + +#include + +#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 c2250899a8002b..199f32fab54cec 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 3e929e8f940967..31eef943bc4fa8 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 422db19b6bb8ac..2e4763062d93a7 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 422db19b6bb8ac..2e4763062d93a7 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 6b80790a9d19b5..639a54b77406c4 100644 --- a/libcxx/test/libcxx/transitive_includes/cxx20.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx20.csv @@ -111,25 +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 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 ea01e413458500..d409027db39b2b 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 ea01e413458500..d409027db39b2b 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 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 00000000000000..a0b141631f8314 --- /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 + +// + +// class ambiguous_local_time +// +// template +// ambiguous_local_time(const local_time& tp, const local_info& i); + +#include +#include + +#include "assert_macros.h" +#include "concat_macros.h" + +template +static void +test(const std::chrono::local_time& 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{-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{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{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::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::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.compile.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.exception/time.zone.exception.ambig/types.compile.pass.cpp new file mode 100644 index 00000000000000..0a0b04f349425e --- /dev/null +++ b/libcxx/test/std/time/time.zone/time.zone.exception/time.zone.exception.ambig/types.compile.pass.cpp @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 + +// XFAIL: libcpp-has-no-experimental-tzdb +// XFAIL: availability-tzdb-missing + +// + +// class ambiguous_local_time : public runtime_error { +// public: +// template +// ambiguous_local_time(const local_time& tp, const local_info& i); +// }; + +#include +#include +#include + +// Basic properties +static_assert(std::is_base_of_v); +static_assert(!std::is_default_constructible_v); +static_assert(std::is_destructible_v); +static_assert(std::is_copy_constructible_v); +static_assert(std::is_move_constructible_v); +static_assert(std::is_copy_assignable_v); +static_assert(std::is_move_assignable_v); 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 00000000000000..ca03d832a7a9f5 --- /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 + +// + +// class nonexistent_local_time +// +// template +// nonexistent_local_time(const local_time& tp, const local_info& i); + +#include +#include + +#include "assert_macros.h" +#include "concat_macros.h" + +template +static void +test(const std::chrono::local_time& 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{-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{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{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::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::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.compile.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.exception/time.zone.exception.nonexist/types.compile.pass.cpp new file mode 100644 index 00000000000000..472aaba4baa83f --- /dev/null +++ b/libcxx/test/std/time/time.zone/time.zone.exception/time.zone.exception.nonexist/types.compile.pass.cpp @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 + +// XFAIL: libcpp-has-no-experimental-tzdb +// XFAIL: availability-tzdb-missing + +// + +// class nonexistent_local_time : public runtime_error { +// public: +// template +// nonexistent_local_time(const local_time& tp, const local_info& i); +// }; + +#include +#include +#include + +// Basic properties +static_assert(std::is_base_of_v); +static_assert(!std::is_default_constructible_v); +static_assert(std::is_destructible_v); +static_assert(std::is_copy_constructible_v); +static_assert(std::is_move_constructible_v); +static_assert(std::is_copy_assignable_v); +static_assert(std::is_move_assignable_v); 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 00000000000000..874c3d52e460b1 --- /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 + +// + +// class time_zone; + +// template +// sys_time> +// to_sys(const local_time& tp) const; + +#include +#include +#include +#include + +#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{-1ns}) == + std::chrono::sys_time{-1ns + 1h}); + + assert(tz->to_sys(std::chrono::local_time{0us}) == + std::chrono::sys_time{1h}); + + assert(tz->to_sys(std::chrono::local_time{ + (std::chrono::sys_days{std::chrono::January / 1 / -21970}).time_since_epoch()}) == + std::chrono::sys_time{ + (std::chrono::sys_days{std::chrono::January / 1 / -21970}).time_since_epoch() + 1h}); + + // sys_time> is seconds for the larger types + assert(tz->to_sys(std::chrono::local_time{ + (std::chrono::sys_days{std::chrono::January / 1 / 21970}).time_since_epoch()}) == + std::chrono::sys_time{ + (std::chrono::sys_days{std::chrono::January / 1 / 21970}).time_since_epoch() + 1h}); + + assert(tz->to_sys(std::chrono::local_time{}) == + std::chrono::sys_time{ + (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 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 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& 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; +}