aboutsummaryrefslogtreecommitdiff
path: root/libcxx/src
diff options
context:
space:
mode:
authorMark de Wever <koraq@xs4all.nl>2024-02-17 14:28:01 +0100
committerGitHub <noreply@github.com>2024-02-17 14:28:01 +0100
commitd332d88b919faa15564ab70b0633d94f21bc3e3c (patch)
tree3108d99ec93b8678cf22b84c05f2cdfdcdbcbed5 /libcxx/src
parentded3ca224f7c2b1df769d625985ab20bb60dfba4 (diff)
downloadllvm-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.txt8
-rw-r--r--libcxx/src/include/tzdb/time_zone_link_private.h27
-rw-r--r--libcxx/src/include/tzdb/time_zone_private.h48
-rw-r--r--libcxx/src/include/tzdb/types_private.h106
-rw-r--r--libcxx/src/include/tzdb/tzdb_list_private.h104
-rw-r--r--libcxx/src/include/tzdb/tzdb_private.h28
-rw-r--r--libcxx/src/time_zone.cpp32
-rw-r--r--libcxx/src/tz.cpp146
-rw-r--r--libcxx/src/tzdb.cpp641
-rw-r--r--libcxx/src/tzdb_list.cpp70
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();
}