From 4167fec40768fb05b411bcefa186fe2b106ca7e4 Mon Sep 17 00:00:00 2001 From: Mark de Wever Date: Thu, 4 Apr 2024 19:03:01 +0200 Subject: [PATCH] [libc++][chrono] Completes the tzdb class. (#82157) It adds the missing member functions of the tzdb class and adds the free functions that use these member functions. Implements parts of: - P0355 Extending to Calendars and Time Zones --- libcxx/include/__chrono/tzdb.h | 35 ++++++++ libcxx/include/__chrono/tzdb_list.h | 10 +++ libcxx/include/chrono | 5 ++ libcxx/modules/std/chrono.inc | 4 +- libcxx/src/tzdb.cpp | 59 +++++++++++++ ...rono.nodiscard_extensions.compile.pass.cpp | 8 ++ .../chrono.nodiscard_extensions.verify.cpp | 8 ++ .../time.zone.db.tzdb/locate_zone.pass.cpp | 84 +++++++++++++++++++ .../time.zone.db.access/current_zone.pass.cpp | 77 +++++++++++++++++ .../time.zone.db.access/locate_zone.pass.cpp | 62 ++++++++++++++ .../time.zone.db.tzdb/current_zone.pass.cpp | 79 +++++++++++++++++ .../time.zone.db.tzdb/locate_zone.pass.cpp | 64 ++++++++++++++ 12 files changed, 493 insertions(+), 2 deletions(-) create mode 100644 libcxx/test/libcxx/time/time.zone/time.zone.db/time.zone.db.tzdb/locate_zone.pass.cpp create mode 100644 libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.access/current_zone.pass.cpp create mode 100644 libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.access/locate_zone.pass.cpp create mode 100644 libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.tzdb/current_zone.pass.cpp create mode 100644 libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.tzdb/locate_zone.pass.cpp diff --git a/libcxx/include/__chrono/tzdb.h b/libcxx/include/__chrono/tzdb.h index 45c20f279f9c..e0bfedf0d782 100644 --- a/libcxx/include/__chrono/tzdb.h +++ b/libcxx/include/__chrono/tzdb.h @@ -16,6 +16,7 @@ // Enable the contents of the header only when libc++ was built with experimental features enabled. #if !defined(_LIBCPP_HAS_NO_INCOMPLETE_TZDB) +# include <__algorithm/ranges_lower_bound.h> # include <__chrono/leap_second.h> # include <__chrono/time_zone.h> # include <__chrono/time_zone_link.h> @@ -43,6 +44,40 @@ struct tzdb { vector links; vector leap_seconds; + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI const time_zone* __locate_zone(string_view __name) const { + if (const time_zone* __result = __find_in_zone(__name)) + return __result; + + if (auto __it = ranges::lower_bound(links, __name, {}, &time_zone_link::name); + __it != links.end() && __it->name() == __name) + if (const time_zone* __result = __find_in_zone(__it->target())) + return __result; + + return nullptr; + } + + _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI const time_zone* locate_zone(string_view __name) const { + if (const time_zone* __result = __locate_zone(__name)) + return __result; + + std::__throw_runtime_error("tzdb: requested time zone not found"); + } + + _LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_HIDE_FROM_ABI const time_zone* current_zone() const { + return __current_zone(); + } + +private: + _LIBCPP_HIDE_FROM_ABI const time_zone* __find_in_zone(string_view __name) const noexcept { + if (auto __it = ranges::lower_bound(zones, __name, {}, &time_zone::name); + __it != zones.end() && __it->name() == __name) + return std::addressof(*__it); + + return nullptr; + } + + [[nodiscard]] _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI const time_zone* __current_zone() const; }; } // namespace chrono diff --git a/libcxx/include/__chrono/tzdb_list.h b/libcxx/include/__chrono/tzdb_list.h index e8aaf31e3631..693899d37211 100644 --- a/libcxx/include/__chrono/tzdb_list.h +++ b/libcxx/include/__chrono/tzdb_list.h @@ -17,6 +17,7 @@ #if !defined(_LIBCPP_HAS_NO_INCOMPLETE_TZDB) # include <__availability> +# include <__chrono/time_zone.h> # include <__chrono/tzdb.h> # include <__config> # include <__fwd/string.h> @@ -84,6 +85,15 @@ _LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_HIDE_FROM_ABI inline con return get_tzdb_list().front(); } +_LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_HIDE_FROM_ABI inline const time_zone* +locate_zone(string_view __name) { + return get_tzdb().locate_zone(__name); +} + +_LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_HIDE_FROM_ABI inline const time_zone* current_zone() { + return get_tzdb().current_zone(); +} + _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI const tzdb& reload_tzdb(); _LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI string remote_version(); diff --git a/libcxx/include/chrono b/libcxx/include/chrono index 4dd43137b718..8fdc30a3624d 100644 --- a/libcxx/include/chrono +++ b/libcxx/include/chrono @@ -689,6 +689,9 @@ struct tzdb { vector zones; vector links; vector leap_seconds; + + const time_zone* locate_zone(string_view tz_name) const; + const time_zone* current_zone() const; }; class tzdb_list { // C++20 @@ -714,6 +717,8 @@ public: // [time.zone.db.access], time zone database access const tzdb& get_tzdb(); // C++20 tzdb_list& get_tzdb_list(); // C++20 +const time_zone* locate_zone(string_view tz_name); // C++20 +const time_zone* current_zone() // C++20 // [time.zone.db.remote], remote time zone database support const tzdb& reload_tzdb(); // C++20 diff --git a/libcxx/modules/std/chrono.inc b/libcxx/modules/std/chrono.inc index 2c0bd3f98a67..e14228043d3b 100644 --- a/libcxx/modules/std/chrono.inc +++ b/libcxx/modules/std/chrono.inc @@ -199,10 +199,10 @@ export namespace std { using std::chrono::tzdb_list; // [time.zone.db.access], time zone database access - // using std::chrono::current_zone; + using std::chrono::current_zone; using std::chrono::get_tzdb; using std::chrono::get_tzdb_list; - // using std::chrono::locate_zone; + using std::chrono::locate_zone; // [time.zone.db.remote], remote time zone database support using std::chrono::reload_tzdb; diff --git a/libcxx/src/tzdb.cpp b/libcxx/src/tzdb.cpp index 7ba5ceb7ada3..2c82a4a4317a 100644 --- a/libcxx/src/tzdb.cpp +++ b/libcxx/src/tzdb.cpp @@ -675,6 +675,57 @@ void __init_tzdb(tzdb& __tzdb, __tz::__rules_storage_type& __rules) { std::ranges::sort(__tzdb.leap_seconds); } +#ifdef _WIN32 +[[nodiscard]] static const time_zone* __current_zone_windows(const tzdb& tzdb) { + // TODO TZDB Implement this on Windows. + std::__throw_runtime_error("unknown time zone"); +} +#else // ifdef _WIN32 +[[nodiscard]] static const time_zone* __current_zone_posix(const tzdb& tzdb) { + // On POSIX systems there are several ways to configure the time zone. + // In order of priority they are: + // - TZ environment variable + // https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08 + // The documentation is unclear whether or not it's allowed to + // change time zone information. For example the TZ string + // MST7MDT + // this is an entry in tzdata.zi. The value + // MST + // is also an entry. Is it allowed to use the following? + // MST-3 + // Even when this is valid there is no time_zone record in the + // database. Since the library would need to return a valid pointer, + // this means the library needs to allocate and leak a pointer. + // + // - The time zone name is the target of the symlink /etc/localtime + // relative to /usr/share/zoneinfo/ + + // The algorithm is like this: + // - If the environment variable TZ is set and points to a valid + // record use this value. + // - Else use the name based on the `/etc/localtime` symlink. + + if (const char* __tz = getenv("TZ")) + if (const time_zone* __result = tzdb.__locate_zone(__tz)) + return __result; + + filesystem::path __path = "/etc/localtime"; + if (!std::filesystem::exists(__path)) + std::__throw_runtime_error("tzdb: the symlink '/etc/localtime' does not exist"); + + if (!std::filesystem::is_symlink(__path)) + std::__throw_runtime_error("tzdb: the path '/etc/localtime' is not a symlink"); + + filesystem::path __tz = filesystem::read_symlink(__path); + string __name = filesystem::relative(__tz, "/usr/share/zoneinfo/"); + + if (const time_zone* __result = tzdb.__locate_zone(__name)) + return __result; + + std::__throw_runtime_error(("tzdb: the time zone '" + __name + "' is not found in the database").c_str()); +} +#endif // ifdef _WIN32 + //===----------------------------------------------------------------------===// // Public API //===----------------------------------------------------------------------===// @@ -684,6 +735,14 @@ _LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI tzdb_l return __result; } +[[nodiscard]] _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI const time_zone* tzdb::__current_zone() const { +#ifdef _WIN32 + return chrono::__current_zone_windows(*this); +#else + return chrono::__current_zone_posix(*this); +#endif +} + _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI const tzdb& reload_tzdb() { if (chrono::remote_version() == chrono::get_tzdb().version) return chrono::get_tzdb(); diff --git a/libcxx/test/libcxx/diagnostics/chrono.nodiscard_extensions.compile.pass.cpp b/libcxx/test/libcxx/diagnostics/chrono.nodiscard_extensions.compile.pass.cpp index c868832ea74a..9acb57fa05f7 100644 --- a/libcxx/test/libcxx/diagnostics/chrono.nodiscard_extensions.compile.pass.cpp +++ b/libcxx/test/libcxx/diagnostics/chrono.nodiscard_extensions.compile.pass.cpp @@ -38,8 +38,16 @@ void test() { std::chrono::get_tzdb_list(); std::chrono::get_tzdb(); + std::chrono::locate_zone("name"); + std::chrono::current_zone(); std::chrono::remote_version(); + { + const std::chrono::tzdb& t = list.front(); + t.locate_zone("name"); + t.current_zone(); + } + { tz.name(); operator==(tz, tz); diff --git a/libcxx/test/libcxx/diagnostics/chrono.nodiscard_extensions.verify.cpp b/libcxx/test/libcxx/diagnostics/chrono.nodiscard_extensions.verify.cpp index 4d26b46a89c9..8795a4eb3c6c 100644 --- a/libcxx/test/libcxx/diagnostics/chrono.nodiscard_extensions.verify.cpp +++ b/libcxx/test/libcxx/diagnostics/chrono.nodiscard_extensions.verify.cpp @@ -33,9 +33,17 @@ void test() { list.cbegin(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} list.cend(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} + { + const std::chrono::tzdb& t = list.front(); + t.locate_zone("name"); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} + t.current_zone(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} + } + namespace crno = std::chrono; crno::get_tzdb_list(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} crno::get_tzdb(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} + crno::locate_zone("n"); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} + crno::current_zone(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} crno::remote_version(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} { diff --git a/libcxx/test/libcxx/time/time.zone/time.zone.db/time.zone.db.tzdb/locate_zone.pass.cpp b/libcxx/test/libcxx/time/time.zone/time.zone.db/time.zone.db.tzdb/locate_zone.pass.cpp new file mode 100644 index 000000000000..971f7f04c49a --- /dev/null +++ b/libcxx/test/libcxx/time/time.zone/time.zone.db/time.zone.db.tzdb/locate_zone.pass.cpp @@ -0,0 +1,84 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: no-filesystem, no-localization, no-tzdb + +// XFAIL: libcpp-has-no-incomplete-tzdb +// XFAIL: availability-tzdb-missing + +// + +// struct tzdb + +// const time_zone* locate_zone(string_view tz_name) const; + +#include +#include +#include +#include + +#include "test_macros.h" +#include "assert_macros.h" +#include "concat_macros.h" +#include "filesystem_test_helper.h" +#include "test_tzdb.h" + +scoped_test_env env; +[[maybe_unused]] const std::filesystem::path dir = env.create_dir("zoneinfo"); +const std::filesystem::path file = env.create_file("zoneinfo/tzdata.zi"); + +std::string_view std::chrono::__libcpp_tzdb_directory() { + static std::string result = dir.string(); + return result; +} + +void write(std::string_view input) { + static int version = 0; + + std::ofstream f{file}; + f << "# version " << version++ << '\n'; + f.write(input.data(), input.size()); +} + +static const std::chrono::tzdb& parse(std::string_view input) { + write(input); + return std::chrono::reload_tzdb(); +} + +int main(int, const char**) { + const std::chrono::tzdb& tzdb = parse( + R"( +Z zone 0 r f +L zone link +L link link_to_link +)"); + + { + const std::chrono::time_zone* tz = tzdb.locate_zone("zone"); + assert(tz); + assert(tz->name() == "zone"); + } + { + const std::chrono::time_zone* tz = tzdb.locate_zone("link"); + assert(tz); + assert(tz->name() == "zone"); + } + + TEST_VALIDATE_EXCEPTION( + std::runtime_error, + [&]([[maybe_unused]] const std::runtime_error& e) { + std::string_view what{"tzdb: requested time zone not found"}; + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + TEST_IGNORE_NODISCARD tzdb.locate_zone("link_to_link")); + + return 0; +} diff --git a/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.access/current_zone.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.access/current_zone.pass.cpp new file mode 100644 index 000000000000..d85c8ba52622 --- /dev/null +++ b/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.access/current_zone.pass.cpp @@ -0,0 +1,77 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: no-filesystem, no-localization, no-tzdb + +// XFAIL: libcpp-has-no-incomplete-tzdb +// XFAIL: availability-tzdb-missing + +// + +// const time_zone* current_zone(); + +#include +#include +#include +#include + +#include "test_macros.h" +#include "assert_macros.h" +#include "concat_macros.h" + +#ifdef _WIN32 +static void set_tz(std::string zone) { + // Note Windows does not have setenv, only putenv + // https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/putenv-s-wputenv-s?view=msvc-170 + // Unlike POSIX it does not mention the string of putenv becomes part + // of the environment. + + int status = _putenv_s("TZ", zone.c_str(), 1); + assert(status == 0); +} + +#else +static void set_tz(const std::string& zone) { + int status = setenv("TZ", zone.c_str(), 1); + assert(status == 0); +} +#endif + +static void test_zone(const std::string& zone) { + set_tz(zone); + const std::chrono::time_zone* tz = std::chrono::current_zone(); + assert(tz); + assert(tz->name() == zone); +} + +static void test_link(const std::string& link, std::string_view zone) { + set_tz(link); + const std::chrono::time_zone* tz = std::chrono::current_zone(); + assert(tz); + assert(tz->name() == zone); +} + +int main(int, const char**) { + const std::chrono::time_zone* tz = std::chrono::current_zone(); + // Returns a valid time zone, the value depends on the OS settings. + assert(tz); + // setting the environment to an invalid value returns the value of + // the OS setting. + set_tz("This is not a time zone"); + assert(tz == std::chrono::current_zone()); + + const std::chrono::tzdb& db = std::chrono::get_tzdb(); + for (const auto& zone : db.zones) + test_zone(std::string{zone.name()}); + + for (const auto& link : db.links) + test_link(std::string{link.name()}, link.target()); + + return 0; +} diff --git a/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.access/locate_zone.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.access/locate_zone.pass.cpp new file mode 100644 index 000000000000..c3142a86bf9d --- /dev/null +++ b/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.access/locate_zone.pass.cpp @@ -0,0 +1,62 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: no-filesystem, no-localization, no-tzdb + +// XFAIL: libcpp-has-no-incomplete-tzdb +// XFAIL: availability-tzdb-missing + +// + +// const time_zone* locate_zone(string_view tz_name); + +#include +#include +#include + +#include "test_macros.h" +#include "assert_macros.h" +#include "concat_macros.h" + +static void test_zone(std::string_view zone) { + const std::chrono::time_zone* tz = std::chrono::locate_zone(zone); + assert(tz); + assert(tz->name() == zone); +} + +static void test_link(std::string_view link, std::string_view zone) { + const std::chrono::time_zone* tz = std::chrono::locate_zone(link); + assert(tz); + assert(tz->name() == zone); +} + +static void test_exception([[maybe_unused]] std::string_view zone) { + TEST_VALIDATE_EXCEPTION( + std::runtime_error, + [&]([[maybe_unused]] const std::runtime_error& e) { + std::string_view what{"tzdb: requested time zone not found"}; + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + TEST_IGNORE_NODISCARD std::chrono::locate_zone(zone)); +} + +int main(int, const char**) { + const std::chrono::tzdb& db = std::chrono::get_tzdb(); + for (const auto& zone : db.zones) + test_zone(zone.name()); + + for (const auto& link : db.links) + test_link(link.name(), link.target()); + + test_exception("This is not a time zone"); + + return 0; +} diff --git a/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.tzdb/current_zone.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.tzdb/current_zone.pass.cpp new file mode 100644 index 000000000000..7b4218cc8421 --- /dev/null +++ b/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.tzdb/current_zone.pass.cpp @@ -0,0 +1,79 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: no-filesystem, no-localization, no-tzdb + +// XFAIL: libcpp-has-no-incomplete-tzdb +// XFAIL: availability-tzdb-missing + +// + +// struct tzdb + +// const time_zone* current_zone() const; + +#include +#include +#include +#include + +#include "test_macros.h" +#include "assert_macros.h" +#include "concat_macros.h" + +#ifdef _WIN32 +static void set_tz(std::string zone) { + // Note Windows does not have setenv, only putenv + // https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/putenv-s-wputenv-s?view=msvc-170 + // Unlike POSIX it does not mention the string of putenv becomes part + // of the environment. + + int status = _putenv_s("TZ", zone.c_str(), 1); + assert(status == 0); +} + +#else +static void set_tz(const std::string& zone) { + int status = setenv("TZ", zone.c_str(), 1); + assert(status == 0); +} +#endif + +static void test_zone(const std::string& zone) { + set_tz(zone); + const std::chrono::time_zone* tz = std::chrono::get_tzdb().current_zone(); + assert(tz); + assert(tz->name() == zone); +} + +static void test_link(const std::string& link, std::string_view zone) { + set_tz(link); + const std::chrono::time_zone* tz = std::chrono::get_tzdb().current_zone(); + assert(tz); + assert(tz->name() == zone); +} + +int main(int, const char**) { + const std::chrono::time_zone* tz = std::chrono::get_tzdb().current_zone(); + // Returns a valid time zone, the value depends on the OS settings. + assert(tz); + // setting the environment to an invalid value returns the value of + // the OS setting. + set_tz("This is not a time zone"); + assert(tz == std::chrono::get_tzdb().current_zone()); + + const std::chrono::tzdb& db = std::chrono::get_tzdb(); + for (const auto& zone : db.zones) + test_zone(std::string{zone.name()}); + + for (const auto& link : db.links) + test_link(std::string{link.name()}, link.target()); + + return 0; +} diff --git a/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.tzdb/locate_zone.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.tzdb/locate_zone.pass.cpp new file mode 100644 index 000000000000..12987f6c89d8 --- /dev/null +++ b/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.tzdb/locate_zone.pass.cpp @@ -0,0 +1,64 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: no-filesystem, no-localization, no-tzdb + +// XFAIL: libcpp-has-no-incomplete-tzdb +// XFAIL: availability-tzdb-missing + +// + +// struct tzdb + +// const time_zone* locate_zone(string_view tz_name) const; + +#include +#include +#include + +#include "test_macros.h" +#include "assert_macros.h" +#include "concat_macros.h" + +static void test_zone(std::string_view zone) { + const std::chrono::time_zone* tz = std::chrono::get_tzdb().locate_zone(zone); + assert(tz); + assert(tz->name() == zone); +} + +static void test_link(std::string_view link, std::string_view zone) { + const std::chrono::time_zone* tz = std::chrono::get_tzdb().locate_zone(link); + assert(tz); + assert(tz->name() == zone); +} + +static void test_exception([[maybe_unused]] std::string_view zone) { + TEST_VALIDATE_EXCEPTION( + std::runtime_error, + [&]([[maybe_unused]] const std::runtime_error& e) { + std::string_view what{"tzdb: requested time zone not found"}; + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + TEST_IGNORE_NODISCARD std::chrono::get_tzdb().locate_zone(zone)); +} + +int main(int, const char**) { + const std::chrono::tzdb& db = std::chrono::get_tzdb(); + for (const auto& zone : db.zones) + test_zone(zone.name()); + + for (const auto& link : db.links) + test_link(link.name(), link.target()); + + test_exception("This is not a time zone"); + + return 0; +}