1 | //===----------------------------------------------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | // For information see https://libcxx.llvm.org/DesignDocs/TimeZone.html |
10 | |
11 | #include <chrono> |
12 | #include <filesystem> |
13 | #include <fstream> |
14 | #include <stdexcept> |
15 | #include <string> |
16 | |
17 | // Contains a parser for the IANA time zone data files. |
18 | // |
19 | // These files can be found at https://data.iana.org/time-zones/ and are in the |
20 | // public domain. Information regarding the input can be found at |
21 | // https://data.iana.org/time-zones/tz-how-to.html and |
22 | // https://man7.org/linux/man-pages/man8/zic.8.html. |
23 | // |
24 | // As indicated at https://howardhinnant.github.io/date/tz.html#Installation |
25 | // For Windows another file seems to be required |
26 | // https://raw.githubusercontent.com/unicode-org/cldr/master/common/supplemental/windowsZones.xml |
27 | // This file seems to contain the mapping of Windows time zone name to IANA |
28 | // time zone names. |
29 | // |
30 | // However this article mentions another way to do the mapping on Windows |
31 | // https://devblogs.microsoft.com/oldnewthing/20210527-00/?p=105255 |
32 | // This requires Windows 10 Version 1903, which was released in May of 2019 |
33 | // and considered end of life in December 2020 |
34 | // https://learn.microsoft.com/en-us/lifecycle/announcements/windows-10-1903-end-of-servicing |
35 | // |
36 | // TODO TZDB Implement the Windows mapping in tzdb::current_zone |
37 | |
38 | _LIBCPP_BEGIN_NAMESPACE_STD |
39 | |
40 | namespace chrono { |
41 | |
42 | // This function is weak so it can be overriden in the tests. The |
43 | // declaration is in the test header test/support/test_tzdb.h |
44 | _LIBCPP_WEAK string_view __libcpp_tzdb_directory() { |
45 | #if defined(__linux__) |
46 | return "/usr/share/zoneinfo/" ; |
47 | #else |
48 | # error "unknown path to the IANA Time Zone Database" |
49 | #endif |
50 | } |
51 | |
52 | [[nodiscard]] static bool __is_whitespace(int __c) { return __c == ' ' || __c == '\t'; } |
53 | |
54 | static void __skip_optional_whitespace(istream& __input) { |
55 | while (chrono::__is_whitespace(c: __input.peek())) |
56 | __input.get(); |
57 | } |
58 | |
59 | static void __skip_mandatory_whitespace(istream& __input) { |
60 | if (!chrono::__is_whitespace(c: __input.get())) |
61 | std::__throw_runtime_error("corrupt tzdb: expected whitespace" ); |
62 | |
63 | chrono::__skip_optional_whitespace(__input); |
64 | } |
65 | |
66 | static void __matches(istream& __input, char __expected) { |
67 | if (std::tolower(__input.get()) != __expected) |
68 | std::__throw_runtime_error((string("corrupt tzdb: expected character '" ) + __expected + '\'').c_str()); |
69 | } |
70 | |
71 | static void __matches(istream& __input, string_view __expected) { |
72 | for (auto __c : __expected) |
73 | if (std::tolower(__input.get()) != __c) |
74 | std::__throw_runtime_error((string("corrupt tzdb: expected string '" ) + string(__expected) + '\'').c_str()); |
75 | } |
76 | |
77 | [[nodiscard]] static string __parse_string(istream& __input) { |
78 | string __result; |
79 | while (true) { |
80 | int __c = __input.get(); |
81 | switch (__c) { |
82 | case ' ': |
83 | case '\t': |
84 | case '\n': |
85 | __input.unget(); |
86 | [[fallthrough]]; |
87 | case istream::traits_type::eof(): |
88 | if (__result.empty()) |
89 | std::__throw_runtime_error("corrupt tzdb: expected a string" ); |
90 | |
91 | return __result; |
92 | |
93 | default: |
94 | __result.push_back(__c); |
95 | } |
96 | } |
97 | } |
98 | |
99 | static string __parse_version(istream& __input) { |
100 | // The first line in tzdata.zi contains |
101 | // # version YYYYw |
102 | // The parser expects this pattern |
103 | // #\s*version\s*\(.*) |
104 | // This part is not documented. |
105 | chrono::__matches(__input, '#'); |
106 | chrono::__skip_optional_whitespace(__input); |
107 | chrono::__matches(__input, "version" ); |
108 | chrono::__skip_mandatory_whitespace(__input); |
109 | return chrono::__parse_string(__input); |
110 | } |
111 | |
112 | static tzdb __make_tzdb() { |
113 | tzdb __result; |
114 | |
115 | filesystem::path __root = chrono::__libcpp_tzdb_directory(); |
116 | ifstream __tzdata{__root / "tzdata.zi" }; |
117 | |
118 | __result.version = chrono::__parse_version(__tzdata); |
119 | return __result; |
120 | } |
121 | |
122 | //===----------------------------------------------------------------------===// |
123 | // Public API |
124 | //===----------------------------------------------------------------------===// |
125 | |
126 | _LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI tzdb_list& get_tzdb_list() { |
127 | static tzdb_list __result{chrono::__make_tzdb()}; |
128 | return __result; |
129 | } |
130 | |
131 | _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI const tzdb& reload_tzdb() { |
132 | if (chrono::remote_version() == chrono::get_tzdb().version) |
133 | return chrono::get_tzdb(); |
134 | |
135 | return chrono::get_tzdb_list().__emplace_front(chrono::__make_tzdb()); |
136 | } |
137 | |
138 | _LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI string remote_version() { |
139 | filesystem::path __root = chrono::__libcpp_tzdb_directory(); |
140 | ifstream __tzdata{__root / "tzdata.zi" }; |
141 | return chrono::__parse_version(__tzdata); |
142 | } |
143 | |
144 | } // namespace chrono |
145 | |
146 | _LIBCPP_END_NAMESPACE_STD |
147 | |