diff options
author | Mark de Wever <koraq@xs4all.nl> | 2024-02-17 14:28:01 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-17 14:28:01 +0100 |
commit | d332d88b919faa15564ab70b0633d94f21bc3e3c (patch) | |
tree | 3108d99ec93b8678cf22b84c05f2cdfdcdbcbed5 /libcxx/src | |
parent | ded3ca224f7c2b1df769d625985ab20bb60dfba4 (diff) | |
download | llvm-d332d88b919faa15564ab70b0633d94f21bc3e3c.zip llvm-d332d88b919faa15564ab70b0633d94f21bc3e3c.tar.gz llvm-d332d88b919faa15564ab70b0633d94f21bc3e3c.tar.bz2 |
[libc++][chrono] Loads tzdata.zi in tzdb. (#74928)
This implements the loading of the tzdata.zi file and store its contents
in the tzdb struct.
This adds all required members except:
- the leap seconds,
- the locate_zone, and
- current_zone.
The class time_zone is incomplete and only contains the parts needed for
storing the parsed data.
The class time_zone_link is fully implemented including its non-member
functions.
Implements parts of:
- P0355 Extending <chrono> to Calendars and Time Zones
- P1614 The Mothership has Landed
Implements:
- P1982 Rename link to time_zone_link
Diffstat (limited to 'libcxx/src')
-rw-r--r-- | libcxx/src/CMakeLists.txt | 8 | ||||
-rw-r--r-- | libcxx/src/include/tzdb/time_zone_link_private.h | 27 | ||||
-rw-r--r-- | libcxx/src/include/tzdb/time_zone_private.h | 48 | ||||
-rw-r--r-- | libcxx/src/include/tzdb/types_private.h | 106 | ||||
-rw-r--r-- | libcxx/src/include/tzdb/tzdb_list_private.h | 104 | ||||
-rw-r--r-- | libcxx/src/include/tzdb/tzdb_private.h | 28 | ||||
-rw-r--r-- | libcxx/src/time_zone.cpp | 32 | ||||
-rw-r--r-- | libcxx/src/tz.cpp | 146 | ||||
-rw-r--r-- | libcxx/src/tzdb.cpp | 641 | ||||
-rw-r--r-- | libcxx/src/tzdb_list.cpp | 70 |
10 files changed, 994 insertions, 216 deletions
diff --git a/libcxx/src/CMakeLists.txt b/libcxx/src/CMakeLists.txt index 44a0886..cc6954a 100644 --- a/libcxx/src/CMakeLists.txt +++ b/libcxx/src/CMakeLists.txt @@ -336,7 +336,13 @@ endif() if (LIBCXX_ENABLE_LOCALIZATION AND LIBCXX_ENABLE_FILESYSTEM AND LIBCXX_ENABLE_TIME_ZONE_DATABASE) list(APPEND LIBCXX_EXPERIMENTAL_SOURCES - tz.cpp + include/tzdb/time_zone_link_private.h + include/tzdb/time_zone_private.h + include/tzdb/types_private.h + include/tzdb/tzdb_list_private.h + include/tzdb/tzdb_private.h + time_zone.cpp + tzdb.cpp tzdb_list.cpp ) endif() diff --git a/libcxx/src/include/tzdb/time_zone_link_private.h b/libcxx/src/include/tzdb/time_zone_link_private.h new file mode 100644 index 0000000..1392376 --- /dev/null +++ b/libcxx/src/include/tzdb/time_zone_link_private.h @@ -0,0 +1,27 @@ +// -*- 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_SRC_INCLUDE_TZDB_TIME_ZONE_LINK_PRIVATE_H +#define _LIBCPP_SRC_INCLUDE_TZDB_TIME_ZONE_LINK_PRIVATE_H + +#include <chrono> + +_LIBCPP_BEGIN_NAMESPACE_STD + +namespace chrono { + +struct time_zone_link::__constructor_tag {}; + +} // namespace chrono + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_SRC_INCLUDE_TZDB_TIME_ZONE_LINK_PRIVATE_H diff --git a/libcxx/src/include/tzdb/time_zone_private.h b/libcxx/src/include/tzdb/time_zone_private.h new file mode 100644 index 0000000..039a3b0 --- /dev/null +++ b/libcxx/src/include/tzdb/time_zone_private.h @@ -0,0 +1,48 @@ +// -*- 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_SRC_INCLUDE_TZDB_TIME_ZONE_PRIVATE_H +#define _LIBCPP_SRC_INCLUDE_TZDB_TIME_ZONE_PRIVATE_H + +#include <chrono> +#include <string> +#include <vector> + +#include "types_private.h" + +_LIBCPP_BEGIN_NAMESPACE_STD + +namespace chrono { + +class time_zone::__impl { +public: + explicit _LIBCPP_HIDE_FROM_ABI __impl(string&& __name) : __name_(std::move(__name)) {} + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI string_view __name() const noexcept { return __name_; } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI vector<__tz::__continuation>& __continuations() { return __continuations_; } + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI const vector<__tz::__continuation>& __continuations() const { + return __continuations_; + } + +private: + string __name_; + // Note the first line has a name + __continuation, the other lines + // are just __continuations. So there is always at least one item in + // the vector. + vector<__tz::__continuation> __continuations_; +}; + +} // namespace chrono + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_SRC_INCLUDE_TZDB_TIME_ZONE_PRIVATE_H diff --git a/libcxx/src/include/tzdb/types_private.h b/libcxx/src/include/tzdb/types_private.h new file mode 100644 index 0000000..4604b9fc --- /dev/null +++ b/libcxx/src/include/tzdb/types_private.h @@ -0,0 +1,106 @@ +// -*- 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_SRC_INCLUDE_TZDB_TYPES_PRIVATE_H +#define __LIBCPP_SRC_INCLUDE_TZDB_TYPES_PRIVATE_H + +#include <chrono> +#include <string> +#include <utility> +#include <variant> +#include <vector> + +_LIBCPP_BEGIN_NAMESPACE_STD + +// TODO TZDB +// The helper classes in this header have no constructor but are loaded with +// dedicated parse functions. In the original design this header was public and +// the parsing was done in the dylib. In that design having constructors would +// expand the ABI interface. Since this header is now in the dylib that design +// should be reconsidered. (For now the design is kept as is, in case this +// header needs to be public for unforseen reasons.) + +namespace chrono::__tz { + +// Sun>=8 first Sunday on or after the eighth +// Sun<=25 last Sunday on or before the 25th +struct __constrained_weekday { + /* year_month_day operator()(year __year, month __month);*/ // needed but not implemented + + weekday __weekday; + enum __comparison_t { __le, __ge } __comparison; + day __day; +}; + +// The on field has a few alternative presentations +// 5 the fifth of the month +// lastSun the last Sunday in the month +// lastMon the last Monday in the month +// Sun>=8 first Sunday on or after the eighth +// Sun<=25 last Sunday on or before the 25th +using __on = variant<day, weekday_last, __constrained_weekday>; + +enum class __clock { __local, __standard, __universal }; + +struct __at { + seconds __time{0}; + __tz::__clock __clock{__tz::__clock::__local}; +}; + +struct __save { + seconds __time; + bool __is_dst; +}; + +// The names of the fields match the fields of a Rule. +struct __rule { + year __from; + year __to; + month __in; + __tz::__on __on; + __tz::__at __at; + __tz::__save __save; + string __letters; +}; + +using __rules_storage_type = std::vector<std::pair<string, vector<__tz::__rule>>>; // TODO TZDB use flat_map; + +struct __continuation { + // Non-owning link to the RULE entries. + __tz::__rules_storage_type* __rule_database_; + + seconds __stdoff; + + // The RULES is either a SAVE or a NAME. + // The size_t is used as cache. After loading the rules they are + // sorted and remain stable, then an index in the vector can be + // used. + // If this field contains - then standard time always + // applies. This is indicated by the monostate. + using __rules_t = variant<monostate, __tz::__save, string, size_t>; + + __rules_t __rules; + + string __format; + // TODO TZDB the until field can contain more than just a year. + // Parts of the UNTIL, the optional parts are default initialized + // optional<year> __until_; + year __year = chrono::year::min(); + month __in{January}; + __tz::__on __on{chrono::day{1}}; + __tz::__at __at{chrono::seconds{0}, __tz::__clock::__local}; +}; + +} // namespace chrono::__tz + +_LIBCPP_END_NAMESPACE_STD + +#endif // __LIBCPP_SRC_INCLUDE_TZDB_TYPES_PRIVATE_H diff --git a/libcxx/src/include/tzdb/tzdb_list_private.h b/libcxx/src/include/tzdb/tzdb_list_private.h new file mode 100644 index 0000000..f43d7d8 --- /dev/null +++ b/libcxx/src/include/tzdb/tzdb_list_private.h @@ -0,0 +1,104 @@ +//===----------------------------------------------------------------------===// +// +// 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_SRC_INCLUDE_TZDB_TZDB_LIST_PRIVATE_H +#define _LIBCPP_SRC_INCLUDE_TZDB_TZDB_LIST_PRIVATE_H + +#include <__mutex/unique_lock.h> +#include <forward_list> + +// When threads are not available the locking is not required. +#ifndef _LIBCPP_HAS_NO_THREADS +# include <shared_mutex> +#endif + +#include "types_private.h" +#include "tzdb_private.h" + +_LIBCPP_BEGIN_NAMESPACE_STD + +namespace chrono { + +//===----------------------------------------------------------------------===// +// Private API +//===----------------------------------------------------------------------===// + +// The tzdb_list stores a list of "tzdb" entries. +// +// The public tzdb database does not store the RULE entries of the IANA +// database. These entries are considered an implementation detail. Since most +// of the tzdb_list interface is exposed as "a list of tzdb entries" it's not +// possible to use a helper struct that stores a tzdb and the RULE database. +// Instead this class stores these in parallel forward lists. +// +// Since the nodes of a forward_list are stable it's possible to store pointers +// and references to these nodes. +class tzdb_list::__impl { +public: + __impl() { __load_no_lock(); } + + [[nodiscard]] const tzdb& __load() { +#ifndef _LIBCPP_HAS_NO_THREADS + unique_lock __lock{__mutex_}; +#endif + __load_no_lock(); + return __tzdb_.front(); + } + + using const_iterator = tzdb_list::const_iterator; + + const tzdb& front() const noexcept { +#ifndef _LIBCPP_HAS_NO_THREADS + shared_lock __lock{__mutex_}; +#endif + return __tzdb_.front(); + } + + const_iterator erase_after(const_iterator __p) { +#ifndef _LIBCPP_HAS_NO_THREADS + unique_lock __lock{__mutex_}; +#endif + + __rules_.erase_after(std::next(__rules_.cbegin(), std::distance(__tzdb_.cbegin(), __p))); + return __tzdb_.erase_after(__p); + } + + const_iterator begin() const noexcept { +#ifndef _LIBCPP_HAS_NO_THREADS + shared_lock __lock{__mutex_}; +#endif + return __tzdb_.begin(); + } + const_iterator end() const noexcept { + // forward_list<T>::end does not access the list, so no need to take a lock. + return __tzdb_.end(); + } + + const_iterator cbegin() const noexcept { return begin(); } + const_iterator cend() const noexcept { return end(); } + +private: + // Loads the tzdbs + // pre: The caller ensures the locking, if needed, is done. + void __load_no_lock() { chrono::__init_tzdb(__tzdb_.emplace_front(), __rules_.emplace_front()); } + +#ifndef _LIBCPP_HAS_NO_THREADS + mutable shared_mutex __mutex_; +#endif + forward_list<tzdb> __tzdb_; + + forward_list<__tz::__rules_storage_type> __rules_; +}; + +} // namespace chrono + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_SRC_INCLUDE_TZDB_TZDB_LIST_PRIVATE_H diff --git a/libcxx/src/include/tzdb/tzdb_private.h b/libcxx/src/include/tzdb/tzdb_private.h new file mode 100644 index 0000000..8ec3f89 --- /dev/null +++ b/libcxx/src/include/tzdb/tzdb_private.h @@ -0,0 +1,28 @@ +//===----------------------------------------------------------------------===// +// +// 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_SRC_INCLUDE_TZDB_TZ_PRIVATE_H +#define _LIBCPP_SRC_INCLUDE_TZDB_TZ_PRIVATE_H + +#include <chrono> + +#include "types_private.h" + +_LIBCPP_BEGIN_NAMESPACE_STD + +namespace chrono { + +void __init_tzdb(tzdb& __tzdb, __tz::__rules_storage_type& __rules); + +} // namespace chrono + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_SRC_INCLUDE_TZDB_TZ_PRIVATE_H diff --git a/libcxx/src/time_zone.cpp b/libcxx/src/time_zone.cpp new file mode 100644 index 0000000..b6bf06a --- /dev/null +++ b/libcxx/src/time_zone.cpp @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +#include <chrono> + +#include "include/tzdb/time_zone_private.h" + +_LIBCPP_BEGIN_NAMESPACE_STD + +namespace chrono { + +[[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI time_zone time_zone::__create(unique_ptr<time_zone::__impl>&& __p) { + _LIBCPP_ASSERT_NON_NULL(__p != nullptr, "initialized time_zone without a valid pimpl object"); + time_zone result; + result.__impl_ = std::move(__p); + return result; +} + +_LIBCPP_EXPORTED_FROM_ABI time_zone::~time_zone() = default; + +[[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI string_view time_zone::__name() const noexcept { return __impl_->__name(); } + +} // namespace chrono + +_LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/src/tz.cpp b/libcxx/src/tz.cpp deleted file mode 100644 index 4425f0e..0000000 --- a/libcxx/src/tz.cpp +++ /dev/null @@ -1,146 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 - -#include <chrono> -#include <filesystem> -#include <fstream> -#include <stdexcept> -#include <string> - -// Contains a parser for the IANA time zone data files. -// -// These files can be found at https://data.iana.org/time-zones/ and are in the -// public domain. Information regarding the input can be found at -// https://data.iana.org/time-zones/tz-how-to.html and -// https://man7.org/linux/man-pages/man8/zic.8.html. -// -// As indicated at https://howardhinnant.github.io/date/tz.html#Installation -// For Windows another file seems to be required -// https://raw.githubusercontent.com/unicode-org/cldr/master/common/supplemental/windowsZones.xml -// This file seems to contain the mapping of Windows time zone name to IANA -// time zone names. -// -// However this article mentions another way to do the mapping on Windows -// https://devblogs.microsoft.com/oldnewthing/20210527-00/?p=105255 -// This requires Windows 10 Version 1903, which was released in May of 2019 -// and considered end of life in December 2020 -// https://learn.microsoft.com/en-us/lifecycle/announcements/windows-10-1903-end-of-servicing -// -// TODO TZDB Implement the Windows mapping in tzdb::current_zone - -_LIBCPP_BEGIN_NAMESPACE_STD - -namespace chrono { - -// This function is weak so it can be overriden in the tests. The -// declaration is in the test header test/support/test_tzdb.h -_LIBCPP_WEAK string_view __libcpp_tzdb_directory() { -#if defined(__linux__) - return "/usr/share/zoneinfo/"; -#else -# error "unknown path to the IANA Time Zone Database" -#endif -} - -[[nodiscard]] static bool __is_whitespace(int __c) { return __c == ' ' || __c == '\t'; } - -static void __skip_optional_whitespace(istream& __input) { - while (chrono::__is_whitespace(__input.peek())) - __input.get(); -} - -static void __skip_mandatory_whitespace(istream& __input) { - if (!chrono::__is_whitespace(__input.get())) - std::__throw_runtime_error("corrupt tzdb: expected whitespace"); - - chrono::__skip_optional_whitespace(__input); -} - -static void __matches(istream& __input, char __expected) { - if (std::tolower(__input.get()) != __expected) - std::__throw_runtime_error((string("corrupt tzdb: expected character '") + __expected + '\'').c_str()); -} - -static void __matches(istream& __input, string_view __expected) { - for (auto __c : __expected) - if (std::tolower(__input.get()) != __c) - std::__throw_runtime_error((string("corrupt tzdb: expected string '") + string(__expected) + '\'').c_str()); -} - -[[nodiscard]] static string __parse_string(istream& __input) { - string __result; - while (true) { - int __c = __input.get(); - switch (__c) { - case ' ': - case '\t': - case '\n': - __input.unget(); - [[fallthrough]]; - case istream::traits_type::eof(): - if (__result.empty()) - std::__throw_runtime_error("corrupt tzdb: expected a string"); - - return __result; - - default: - __result.push_back(__c); - } - } -} - -static string __parse_version(istream& __input) { - // The first line in tzdata.zi contains - // # version YYYYw - // The parser expects this pattern - // #\s*version\s*\(.*) - // This part is not documented. - chrono::__matches(__input, '#'); - chrono::__skip_optional_whitespace(__input); - chrono::__matches(__input, "version"); - chrono::__skip_mandatory_whitespace(__input); - return chrono::__parse_string(__input); -} - -static tzdb __make_tzdb() { - tzdb __result; - - filesystem::path __root = chrono::__libcpp_tzdb_directory(); - ifstream __tzdata{__root / "tzdata.zi"}; - - __result.version = chrono::__parse_version(__tzdata); - return __result; -} - -//===----------------------------------------------------------------------===// -// Public API -//===----------------------------------------------------------------------===// - -_LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI tzdb_list& get_tzdb_list() { - static tzdb_list __result{chrono::__make_tzdb()}; - return __result; -} - -_LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI const tzdb& reload_tzdb() { - if (chrono::remote_version() == chrono::get_tzdb().version) - return chrono::get_tzdb(); - - return chrono::get_tzdb_list().__emplace_front(chrono::__make_tzdb()); -} - -_LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI string remote_version() { - filesystem::path __root = chrono::__libcpp_tzdb_directory(); - ifstream __tzdata{__root / "tzdata.zi"}; - return chrono::__parse_version(__tzdata); -} - -} // namespace chrono - -_LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/src/tzdb.cpp b/libcxx/src/tzdb.cpp new file mode 100644 index 0000000..2bb801e --- /dev/null +++ b/libcxx/src/tzdb.cpp @@ -0,0 +1,641 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +#include <algorithm> +#include <chrono> +#include <filesystem> +#include <fstream> +#include <stdexcept> +#include <string> + +#include "include/tzdb/time_zone_link_private.h" +#include "include/tzdb/time_zone_private.h" +#include "include/tzdb/types_private.h" +#include "include/tzdb/tzdb_list_private.h" +#include "include/tzdb/tzdb_private.h" + +// Contains a parser for the IANA time zone data files. +// +// These files can be found at https://data.iana.org/time-zones/ and are in the +// public domain. Information regarding the input can be found at +// https://data.iana.org/time-zones/tz-how-to.html and +// https://man7.org/linux/man-pages/man8/zic.8.html. +// +// As indicated at https://howardhinnant.github.io/date/tz.html#Installation +// For Windows another file seems to be required +// https://raw.githubusercontent.com/unicode-org/cldr/master/common/supplemental/windowsZones.xml +// This file seems to contain the mapping of Windows time zone name to IANA +// time zone names. +// +// However this article mentions another way to do the mapping on Windows +// https://devblogs.microsoft.com/oldnewthing/20210527-00/?p=105255 +// This requires Windows 10 Version 1903, which was released in May of 2019 +// and considered end of life in December 2020 +// https://learn.microsoft.com/en-us/lifecycle/announcements/windows-10-1903-end-of-servicing +// +// TODO TZDB Implement the Windows mapping in tzdb::current_zone + +_LIBCPP_BEGIN_NAMESPACE_STD + +namespace chrono { + +// This function is weak so it can be overriden in the tests. The +// declaration is in the test header test/support/test_tzdb.h +_LIBCPP_WEAK string_view __libcpp_tzdb_directory() { +#if defined(__linux__) + return "/usr/share/zoneinfo/"; +#else +# error "unknown path to the IANA Time Zone Database" +#endif +} + +//===----------------------------------------------------------------------===// +// Details +//===----------------------------------------------------------------------===// + +[[nodiscard]] static bool __is_whitespace(int __c) { return __c == ' ' || __c == '\t'; } + +static void __skip_optional_whitespace(istream& __input) { + while (chrono::__is_whitespace(__input.peek())) + __input.get(); +} + +static void __skip_mandatory_whitespace(istream& __input) { + if (!chrono::__is_whitespace(__input.get())) + std::__throw_runtime_error("corrupt tzdb: expected whitespace"); + + chrono::__skip_optional_whitespace(__input); +} + +[[nodiscard]] static bool __is_eol(int __c) { return __c == '\n' || __c == std::char_traits<char>::eof(); } + +static void __skip_line(istream& __input) { + while (!chrono::__is_eol(__input.peek())) { + __input.get(); + } + __input.get(); +} + +static void __skip(istream& __input, char __suffix) { + if (std::tolower(__input.peek()) == __suffix) + __input.get(); +} + +static void __skip(istream& __input, string_view __suffix) { + for (auto __c : __suffix) + if (std::tolower(__input.peek()) == __c) + __input.get(); +} + +static void __matches(istream& __input, char __expected) { + if (std::tolower(__input.get()) != __expected) + std::__throw_runtime_error((string("corrupt tzdb: expected character '") + __expected + '\'').c_str()); +} + +static void __matches(istream& __input, string_view __expected) { + for (auto __c : __expected) + if (std::tolower(__input.get()) != __c) + std::__throw_runtime_error((string("corrupt tzdb: expected string '") + string(__expected) + '\'').c_str()); +} + +[[nodiscard]] static string __parse_string(istream& __input) { + string __result; + while (true) { + int __c = __input.get(); + switch (__c) { + case ' ': + case '\t': + case '\n': + __input.unget(); + [[fallthrough]]; + case istream::traits_type::eof(): + if (__result.empty()) + std::__throw_runtime_error("corrupt tzdb: expected a string"); + + return __result; + + default: + __result.push_back(__c); + } + } +} + +[[nodiscard]] static int64_t __parse_integral(istream& __input, bool __leading_zero_allowed) { + int64_t __result = __input.get(); + if (__leading_zero_allowed) { + if (__result < '0' || __result > '9') + std::__throw_runtime_error("corrupt tzdb: expected a digit"); + } else { + if (__result < '1' || __result > '9') + std::__throw_runtime_error("corrupt tzdb: expected a non-zero digit"); + } + __result -= '0'; + while (true) { + if (__input.peek() < '0' || __input.peek() > '9') + return __result; + + // In order to avoid possible overflows we limit the accepted range. + // Most values parsed are expected to be very small: + // - 8784 hours in a year + // - 31 days in a month + // - year no real maximum, these values are expected to be less than + // the range of the year type. + // + // However the leapseconds use a seconds after epoch value. Using an + // int would run into an overflow in 2038. By using a 64-bit value + // the range is large enough for the bilions of years. Limiting that + // range slightly to make the code easier is not an issue. + if (__result > (std::numeric_limits<int64_t>::max() / 16)) + std::__throw_runtime_error("corrupt tzdb: integral too large"); + + __result *= 10; + __result += __input.get() - '0'; + } +} + +//===----------------------------------------------------------------------===// +// Calendar +//===----------------------------------------------------------------------===// + +[[nodiscard]] static day __parse_day(istream& __input) { + unsigned __result = chrono::__parse_integral(__input, false); + if (__result > 31) + std::__throw_runtime_error("corrupt tzdb day: value too large"); + return day{__result}; +} + +[[nodiscard]] static weekday __parse_weekday(istream& __input) { + // TZDB allows the shortest unique name. + switch (std::tolower(__input.get())) { + case 'f': + chrono::__skip(__input, "riday"); + return Friday; + + case 'm': + chrono::__skip(__input, "onday"); + return Monday; + + case 's': + switch (std::tolower(__input.get())) { + case 'a': + chrono::__skip(__input, "turday"); + return Saturday; + + case 'u': + chrono::__skip(__input, "nday"); + return Sunday; + } + break; + + case 't': + switch (std::tolower(__input.get())) { + case 'h': + chrono::__skip(__input, "ursday"); + return Thursday; + + case 'u': + chrono::__skip(__input, "esday"); + return Tuesday; + } + break; + case 'w': + chrono::__skip(__input, "ednesday"); + return Wednesday; + } + + std::__throw_runtime_error("corrupt tzdb weekday: invalid name"); +} + +[[nodiscard]] static month __parse_month(istream& __input) { + // TZDB allows the shortest unique name. + switch (std::tolower(__input.get())) { + case 'a': + switch (std::tolower(__input.get())) { + case 'p': + chrono::__skip(__input, "ril"); + return April; + + case 'u': + chrono::__skip(__input, "gust"); + return August; + } + break; + + case 'd': + chrono::__skip(__input, "ecember"); + return December; + + case 'f': + chrono::__skip(__input, "ebruary"); + return February; + + case 'j': + switch (std::tolower(__input.get())) { + case 'a': + chrono::__skip(__input, "nuary"); + return January; + + case 'u': + switch (std::tolower(__input.get())) { + case 'n': + chrono::__skip(__input, 'e'); + return June; + + case 'l': + chrono::__skip(__input, 'y'); + return July; + } + } + break; + + case 'm': + if (std::tolower(__input.get()) == 'a') + switch (std::tolower(__input.get())) { + case 'y': + return May; + + case 'r': + chrono::__skip(__input, "ch"); + return March; + } + break; + + case 'n': + chrono::__skip(__input, "ovember"); + return November; + + case 'o': + chrono::__skip(__input, "ctober"); + return October; + + case 's': + chrono::__skip(__input, "eptember"); + return September; + } + std::__throw_runtime_error("corrupt tzdb month: invalid name"); +} + +[[nodiscard]] static year __parse_year_value(istream& __input) { + bool __negative = __input.peek() == '-'; + if (__negative) [[unlikely]] + __input.get(); + + int64_t __result = __parse_integral(__input, true); + if (__result > static_cast<int>(year::max())) { + if (__negative) + std::__throw_runtime_error("corrupt tzdb year: year is less than the minimum"); + + std::__throw_runtime_error("corrupt tzdb year: year is greater than the maximum"); + } + + return year{static_cast<int>(__negative ? -__result : __result)}; +} + +[[nodiscard]] static year __parse_year(istream& __input) { + if (std::tolower(__input.peek()) != 'm') [[likely]] + return chrono::__parse_year_value(__input); + + __input.get(); + switch (std::tolower(__input.peek())) { + case 'i': + __input.get(); + chrono::__skip(__input, 'n'); + [[fallthrough]]; + + case ' ': + // The m is minimum, even when that is ambiguous. + return year::min(); + + case 'a': + __input.get(); + chrono::__skip(__input, 'x'); + return year::max(); + } + + std::__throw_runtime_error("corrupt tzdb year: expected 'min' or 'max'"); +} + +//===----------------------------------------------------------------------===// +// TZDB fields +//===----------------------------------------------------------------------===// + +[[nodiscard]] static year __parse_to(istream& __input, year __only) { + if (std::tolower(__input.peek()) != 'o') + return chrono::__parse_year(__input); + + __input.get(); + chrono::__skip(__input, "nly"); + return __only; +} + +[[nodiscard]] static __tz::__constrained_weekday::__comparison_t __parse_comparison(istream& __input) { + switch (__input.get()) { + case '>': + chrono::__matches(__input, '='); + return __tz::__constrained_weekday::__ge; + + case '<': + chrono::__matches(__input, '='); + return __tz::__constrained_weekday::__le; + } + std::__throw_runtime_error("corrupt tzdb on: expected '>=' or '<='"); +} + +[[nodiscard]] static __tz::__on __parse_on(istream& __input) { + if (std::isdigit(__input.peek())) + return chrono::__parse_day(__input); + + if (std::tolower(__input.peek()) == 'l') { + chrono::__matches(__input, "last"); + return weekday_last(chrono::__parse_weekday(__input)); + } + + return __tz::__constrained_weekday{ + chrono::__parse_weekday(__input), chrono::__parse_comparison(__input), chrono::__parse_day(__input)}; +} + +[[nodiscard]] static seconds __parse_duration(istream& __input) { + seconds __result{0}; + int __c = __input.peek(); + bool __negative = __c == '-'; + if (__negative) { + __input.get(); + // Negative is either a negative value or a single -. + // The latter means 0 and the parsing is complete. + if (!std::isdigit(__input.peek())) + return __result; + } + + __result += hours(__parse_integral(__input, true)); + if (__input.peek() != ':') + return __negative ? -__result : __result; + + __input.get(); + __result += minutes(__parse_integral(__input, true)); + if (__input.peek() != ':') + return __negative ? -__result : __result; + + __input.get(); + __result += seconds(__parse_integral(__input, true)); + if (__input.peek() != '.') + return __negative ? -__result : __result; + + __input.get(); + (void)__parse_integral(__input, true); // Truncate the digits. + + return __negative ? -__result : __result; +} + +[[nodiscard]] static __tz::__clock __parse_clock(istream& __input) { + switch (__input.get()) { // case sensitive + case 'w': + return __tz::__clock::__local; + case 's': + return __tz::__clock::__standard; + + case 'u': + case 'g': + case 'z': + return __tz::__clock::__universal; + } + + __input.unget(); + return __tz::__clock::__local; +} + +[[nodiscard]] static bool __parse_dst(istream& __input, seconds __offset) { + switch (__input.get()) { // case sensitive + case 's': + return false; + + case 'd': + return true; + } + + __input.unget(); + return __offset != 0s; +} + +[[nodiscard]] static __tz::__at __parse_at(istream& __input) { + return {__parse_duration(__input), __parse_clock(__input)}; +} + +[[nodiscard]] static __tz::__save __parse_save(istream& __input) { + seconds __time = chrono::__parse_duration(__input); + return {__time, chrono::__parse_dst(__input, __time)}; +} + +[[nodiscard]] static string __parse_letters(istream& __input) { + string __result = __parse_string(__input); + // Canonicalize "-" to "" since they are equivalent in the specification. + return __result != "-" ? __result : ""; +} + +[[nodiscard]] static __tz::__continuation::__rules_t __parse_rules(istream& __input) { + int __c = __input.peek(); + // A single - is not a SAVE but a special case. + if (__c == '-') { + __input.get(); + if (chrono::__is_whitespace(__input.peek())) + return monostate{}; + __input.unget(); + return chrono::__parse_save(__input); + } + + if (std::isdigit(__c) || __c == '+') + return chrono::__parse_save(__input); + + return chrono::__parse_string(__input); +} + +[[nodiscard]] static __tz::__continuation __parse_continuation(__tz::__rules_storage_type& __rules, istream& __input) { + __tz::__continuation __result; + + __result.__rule_database_ = std::addressof(__rules); + + // Note STDOFF is specified as + // This field has the same format as the AT and SAVE fields of rule lines; + // These fields have different suffix letters, these letters seem + // not to be used so do not allow any of them. + + __result.__stdoff = chrono::__parse_duration(__input); + chrono::__skip_mandatory_whitespace(__input); + __result.__rules = chrono::__parse_rules(__input); + chrono::__skip_mandatory_whitespace(__input); + __result.__format = chrono::__parse_string(__input); + chrono::__skip_optional_whitespace(__input); + + if (chrono::__is_eol(__input.peek())) + return __result; + __result.__year = chrono::__parse_year(__input); + chrono::__skip_optional_whitespace(__input); + + if (chrono::__is_eol(__input.peek())) + return __result; + __result.__in = chrono::__parse_month(__input); + chrono::__skip_optional_whitespace(__input); + + if (chrono::__is_eol(__input.peek())) + return __result; + __result.__on = chrono::__parse_on(__input); + chrono::__skip_optional_whitespace(__input); + + if (chrono::__is_eol(__input.peek())) + return __result; + __result.__at = __parse_at(__input); + + return __result; +} + +//===----------------------------------------------------------------------===// +// Time Zone Database entries +//===----------------------------------------------------------------------===// + +static string __parse_version(istream& __input) { + // The first line in tzdata.zi contains + // # version YYYYw + // The parser expects this pattern + // #\s*version\s*\(.*) + // This part is not documented. + chrono::__matches(__input, '#'); + chrono::__skip_optional_whitespace(__input); + chrono::__matches(__input, "version"); + chrono::__skip_mandatory_whitespace(__input); + return chrono::__parse_string(__input); +} + +static void __parse_rule(tzdb& __tzdb, __tz::__rules_storage_type& __rules, istream& __input) { + chrono::__skip_mandatory_whitespace(__input); + string __name = chrono::__parse_string(__input); + + if (__rules.empty() || __rules.back().first != __name) + __rules.emplace_back(__name, vector<__tz::__rule>{}); + + __tz::__rule& __rule = __rules.back().second.emplace_back(); + + chrono::__skip_mandatory_whitespace(__input); + __rule.__from = chrono::__parse_year(__input); + chrono::__skip_mandatory_whitespace(__input); + __rule.__to = chrono::__parse_to(__input, __rule.__from); + chrono::__skip_mandatory_whitespace(__input); + chrono::__matches(__input, '-'); + chrono::__skip_mandatory_whitespace(__input); + __rule.__in = chrono::__parse_month(__input); + chrono::__skip_mandatory_whitespace(__input); + __rule.__on = chrono::__parse_on(__input); + chrono::__skip_mandatory_whitespace(__input); + __rule.__at = __parse_at(__input); + chrono::__skip_mandatory_whitespace(__input); + __rule.__save = __parse_save(__input); + chrono::__skip_mandatory_whitespace(__input); + __rule.__letters = chrono::__parse_letters(__input); + chrono::__skip_line(__input); +} + +static void __parse_zone(tzdb& __tzdb, __tz::__rules_storage_type& __rules, istream& __input) { + chrono::__skip_mandatory_whitespace(__input); + auto __p = std::make_unique<time_zone::__impl>(chrono::__parse_string(__input)); + vector<__tz::__continuation>& __continuations = __p->__continuations(); + chrono::__skip_mandatory_whitespace(__input); + + do { + // The first line must be valid, continuations are optional. + __continuations.emplace_back(__parse_continuation(__rules, __input)); + chrono::__skip_line(__input); + chrono::__skip_optional_whitespace(__input); + } while (std::isdigit(__input.peek()) || __input.peek() == '-'); + + __tzdb.zones.emplace_back(time_zone::__create(std::move(__p))); +} + +static void __parse_link(tzdb& __tzdb, istream& __input) { + chrono::__skip_mandatory_whitespace(__input); + string __target = chrono::__parse_string(__input); + chrono::__skip_mandatory_whitespace(__input); + string __name = chrono::__parse_string(__input); + chrono::__skip_line(__input); + + __tzdb.links.emplace_back(time_zone_link::__constructor_tag{}, std::move(__name), std::move(__target)); +} + +static void __parse_tzdata(tzdb& __db, __tz::__rules_storage_type& __rules, istream& __input) { + while (true) { + int __c = std::tolower(__input.get()); + + switch (__c) { + case istream::traits_type::eof(): + return; + + case ' ': + case '\t': + case '\n': + break; + + case '#': + chrono::__skip_line(__input); + break; + + case 'r': + chrono::__skip(__input, "ule"); + chrono::__parse_rule(__db, __rules, __input); + break; + + case 'z': + chrono::__skip(__input, "one"); + chrono::__parse_zone(__db, __rules, __input); + break; + + case 'l': + chrono::__skip(__input, "ink"); + chrono::__parse_link(__db, __input); + break; + + default: + std::__throw_runtime_error("corrupt tzdb: unexpected input"); + } + } +} + +void __init_tzdb(tzdb& __tzdb, __tz::__rules_storage_type& __rules) { + filesystem::path __root = chrono::__libcpp_tzdb_directory(); + ifstream __tzdata{__root / "tzdata.zi"}; + + __tzdb.version = chrono::__parse_version(__tzdata); + chrono::__parse_tzdata(__tzdb, __rules, __tzdata); + std::ranges::sort(__tzdb.zones); + std::ranges::sort(__tzdb.links); + std::ranges::sort(__rules, {}, [](const auto& p) { return p.first; }); +} + +//===----------------------------------------------------------------------===// +// Public API +//===----------------------------------------------------------------------===// + +_LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI tzdb_list& get_tzdb_list() { + static tzdb_list __result{new tzdb_list::__impl()}; + return __result; +} + +_LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI const tzdb& reload_tzdb() { + if (chrono::remote_version() == chrono::get_tzdb().version) + return chrono::get_tzdb(); + + return chrono::get_tzdb_list().__implementation().__load(); +} + +_LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI string remote_version() { + filesystem::path __root = chrono::__libcpp_tzdb_directory(); + ifstream __tzdata{__root / "tzdata.zi"}; + return chrono::__parse_version(__tzdata); +} + +} // namespace chrono + +_LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/src/tzdb_list.cpp b/libcxx/src/tzdb_list.cpp index 7eaaedc..d3ee8b5 100644 --- a/libcxx/src/tzdb_list.cpp +++ b/libcxx/src/tzdb_list.cpp @@ -10,76 +10,12 @@ #include <chrono> -#include <__mutex/unique_lock.h> -#include <forward_list> - -// When threads are not available the locking is not required. -#ifndef _LIBCPP_HAS_NO_THREADS -# include <shared_mutex> -#endif +#include "include/tzdb/tzdb_list_private.h" _LIBCPP_BEGIN_NAMESPACE_STD namespace chrono { -//===----------------------------------------------------------------------===// -// Private API -//===----------------------------------------------------------------------===// - -class tzdb_list::__impl { -public: - explicit __impl(tzdb&& __tzdb) { __tzdb_.push_front(std::move(__tzdb)); } - - using const_iterator = tzdb_list::const_iterator; - - const tzdb& front() const noexcept { -#ifndef _LIBCPP_HAS_NO_THREADS - shared_lock __lock{__mutex_}; -#endif - return __tzdb_.front(); - } - - const_iterator erase_after(const_iterator __p) { -#ifndef _LIBCPP_HAS_NO_THREADS - unique_lock __lock{__mutex_}; -#endif - return __tzdb_.erase_after(__p); - } - - tzdb& __emplace_front(tzdb&& __tzdb) { -#ifndef _LIBCPP_HAS_NO_THREADS - unique_lock __lock{__mutex_}; -#endif - return __tzdb_.emplace_front(std::move(__tzdb)); - } - - const_iterator begin() const noexcept { -#ifndef _LIBCPP_HAS_NO_THREADS - shared_lock __lock{__mutex_}; -#endif - return __tzdb_.begin(); - } - const_iterator end() const noexcept { - // forward_list<T>::end does not access the list, so no need to take a lock. - return __tzdb_.end(); - } - - const_iterator cbegin() const noexcept { return begin(); } - const_iterator cend() const noexcept { return end(); } - -private: -#ifndef _LIBCPP_HAS_NO_THREADS - mutable shared_mutex __mutex_; -#endif - forward_list<tzdb> __tzdb_; -}; - -//===----------------------------------------------------------------------===// -// Public API -//===----------------------------------------------------------------------===// - -_LIBCPP_EXPORTED_FROM_ABI tzdb_list::tzdb_list(tzdb&& __tzdb) : __impl_{new __impl(std::move(__tzdb))} {} - _LIBCPP_EXPORTED_FROM_ABI tzdb_list::~tzdb_list() { delete __impl_; } _LIBCPP_NODISCARD_EXT _LIBCPP_EXPORTED_FROM_ABI const tzdb& tzdb_list::front() const noexcept { @@ -90,10 +26,6 @@ _LIBCPP_EXPORTED_FROM_ABI tzdb_list::const_iterator tzdb_list::erase_after(const return __impl_->erase_after(__p); } -_LIBCPP_EXPORTED_FROM_ABI tzdb& tzdb_list::__emplace_front(tzdb&& __tzdb) { - return __impl_->__emplace_front(std::move(__tzdb)); -} - _LIBCPP_NODISCARD_EXT _LIBCPP_EXPORTED_FROM_ABI tzdb_list::const_iterator tzdb_list::begin() const noexcept { return __impl_->begin(); } |