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 <__assert> |
12 | #include <algorithm> |
13 | #include <cctype> |
14 | #include <chrono> |
15 | #include <filesystem> |
16 | #include <fstream> |
17 | #include <stdexcept> |
18 | #include <string> |
19 | #include <string_view> |
20 | #include <vector> |
21 | |
22 | #include "include/tzdb/time_zone_private.h" |
23 | #include "include/tzdb/types_private.h" |
24 | #include "include/tzdb/tzdb_list_private.h" |
25 | #include "include/tzdb/tzdb_private.h" |
26 | |
27 | // Contains a parser for the IANA time zone data files. |
28 | // |
29 | // These files can be found at https://data.iana.org/time-zones/ and are in the |
30 | // public domain. Information regarding the input can be found at |
31 | // https://data.iana.org/time-zones/tz-how-to.html and |
32 | // https://man7.org/linux/man-pages/man8/zic.8.html. |
33 | // |
34 | // As indicated at https://howardhinnant.github.io/date/tz.html#Installation |
35 | // For Windows another file seems to be required |
36 | // https://raw.githubusercontent.com/unicode-org/cldr/master/common/supplemental/windowsZones.xml |
37 | // This file seems to contain the mapping of Windows time zone name to IANA |
38 | // time zone names. |
39 | // |
40 | // However this article mentions another way to do the mapping on Windows |
41 | // https://devblogs.microsoft.com/oldnewthing/20210527-00/?p=105255 |
42 | // This requires Windows 10 Version 1903, which was released in May of 2019 |
43 | // and considered end of life in December 2020 |
44 | // https://learn.microsoft.com/en-us/lifecycle/announcements/windows-10-1903-end-of-servicing |
45 | // |
46 | // TODO TZDB Implement the Windows mapping in tzdb::current_zone |
47 | |
48 | _LIBCPP_BEGIN_NAMESPACE_STD |
49 | |
50 | namespace chrono { |
51 | |
52 | // This function is weak so it can be overriden in the tests. The |
53 | // declaration is in the test header test/support/test_tzdb.h |
54 | _LIBCPP_WEAK string_view __libcpp_tzdb_directory() { |
55 | #if defined(__linux__) |
56 | return "/usr/share/zoneinfo/" ; |
57 | #else |
58 | # error "unknown path to the IANA Time Zone Database" |
59 | #endif |
60 | } |
61 | |
62 | //===----------------------------------------------------------------------===// |
63 | // Details |
64 | //===----------------------------------------------------------------------===// |
65 | |
66 | [[nodiscard]] static bool __is_whitespace(int __c) { return __c == ' ' || __c == '\t'; } |
67 | |
68 | static void __skip_optional_whitespace(istream& __input) { |
69 | while (chrono::__is_whitespace(c: __input.peek())) |
70 | __input.get(); |
71 | } |
72 | |
73 | static void __skip_mandatory_whitespace(istream& __input) { |
74 | if (!chrono::__is_whitespace(c: __input.get())) |
75 | std::__throw_runtime_error("corrupt tzdb: expected whitespace" ); |
76 | |
77 | chrono::__skip_optional_whitespace(__input); |
78 | } |
79 | |
80 | [[nodiscard]] static bool __is_eol(int __c) { return __c == '\n' || __c == std::char_traits<char>::eof(); } |
81 | |
82 | static void __skip_line(istream& __input) { |
83 | while (!chrono::__is_eol(c: __input.peek())) { |
84 | __input.get(); |
85 | } |
86 | __input.get(); |
87 | } |
88 | |
89 | static void __skip(istream& __input, char __suffix) { |
90 | if (std::tolower(c: __input.peek()) == __suffix) |
91 | __input.get(); |
92 | } |
93 | |
94 | static void __skip(istream& __input, string_view __suffix) { |
95 | for (auto __c : __suffix) |
96 | if (std::tolower(c: __input.peek()) == __c) |
97 | __input.get(); |
98 | } |
99 | |
100 | static void __matches(istream& __input, char __expected) { |
101 | _LIBCPP_ASSERT_INTERNAL(!std::isalpha(__expected) || std::islower(__expected), "lowercase characters only here!" ); |
102 | char __c = __input.get(); |
103 | if (std::tolower(__c) != __expected) |
104 | std::__throw_runtime_error( |
105 | (string("corrupt tzdb: expected character '" ) + __expected + "', got '" + __c + "' instead" ).c_str()); |
106 | } |
107 | |
108 | static void __matches(istream& __input, string_view __expected) { |
109 | for (auto __c : __expected) { |
110 | _LIBCPP_ASSERT_INTERNAL(!std::isalpha(__c) || std::islower(__c), "lowercase strings only here!" ); |
111 | char __actual = __input.get(); |
112 | if (std::tolower(c: __actual) != __c) |
113 | std::__throw_runtime_error( |
114 | (string("corrupt tzdb: expected character '" ) + __c + "' from string '" + string(__expected) + "', got '" + |
115 | __actual + "' instead" ) |
116 | .c_str()); |
117 | } |
118 | } |
119 | |
120 | [[nodiscard]] static string __parse_string(istream& __input) { |
121 | string __result; |
122 | while (true) { |
123 | int __c = __input.get(); |
124 | switch (__c) { |
125 | case ' ': |
126 | case '\t': |
127 | case '\n': |
128 | __input.unget(); |
129 | [[fallthrough]]; |
130 | case istream::traits_type::eof(): |
131 | if (__result.empty()) |
132 | std::__throw_runtime_error("corrupt tzdb: expected a string" ); |
133 | |
134 | return __result; |
135 | |
136 | default: |
137 | __result.push_back(__c); |
138 | } |
139 | } |
140 | } |
141 | |
142 | [[nodiscard]] static int64_t __parse_integral(istream& __input, bool __leading_zero_allowed) { |
143 | int64_t __result = __input.get(); |
144 | if (__leading_zero_allowed) { |
145 | if (__result < '0' || __result > '9') |
146 | std::__throw_runtime_error("corrupt tzdb: expected a digit" ); |
147 | } else { |
148 | if (__result < '1' || __result > '9') |
149 | std::__throw_runtime_error("corrupt tzdb: expected a non-zero digit" ); |
150 | } |
151 | __result -= '0'; |
152 | while (true) { |
153 | if (__input.peek() < '0' || __input.peek() > '9') |
154 | return __result; |
155 | |
156 | // In order to avoid possible overflows we limit the accepted range. |
157 | // Most values parsed are expected to be very small: |
158 | // - 8784 hours in a year |
159 | // - 31 days in a month |
160 | // - year no real maximum, these values are expected to be less than |
161 | // the range of the year type. |
162 | // |
163 | // However the leapseconds use a seconds after epoch value. Using an |
164 | // int would run into an overflow in 2038. By using a 64-bit value |
165 | // the range is large enough for the bilions of years. Limiting that |
166 | // range slightly to make the code easier is not an issue. |
167 | if (__result > (std::numeric_limits<int64_t>::max() / 16)) |
168 | std::__throw_runtime_error("corrupt tzdb: integral too large" ); |
169 | |
170 | __result *= 10; |
171 | __result += __input.get() - '0'; |
172 | } |
173 | } |
174 | |
175 | //===----------------------------------------------------------------------===// |
176 | // Calendar |
177 | //===----------------------------------------------------------------------===// |
178 | |
179 | [[nodiscard]] static day __parse_day(istream& __input) { |
180 | unsigned __result = chrono::__parse_integral(__input, leading_zero_allowed: false); |
181 | if (__result > 31) |
182 | std::__throw_runtime_error("corrupt tzdb day: value too large" ); |
183 | return day{__result}; |
184 | } |
185 | |
186 | [[nodiscard]] static weekday __parse_weekday(istream& __input) { |
187 | // TZDB allows the shortest unique name. |
188 | switch (std::tolower(c: __input.get())) { |
189 | case 'f': |
190 | chrono::__skip(__input, suffix: "riday" ); |
191 | return Friday; |
192 | |
193 | case 'm': |
194 | chrono::__skip(__input, suffix: "onday" ); |
195 | return Monday; |
196 | |
197 | case 's': |
198 | switch (std::tolower(c: __input.get())) { |
199 | case 'a': |
200 | chrono::__skip(__input, suffix: "turday" ); |
201 | return Saturday; |
202 | |
203 | case 'u': |
204 | chrono::__skip(__input, suffix: "nday" ); |
205 | return Sunday; |
206 | } |
207 | break; |
208 | |
209 | case 't': |
210 | switch (std::tolower(c: __input.get())) { |
211 | case 'h': |
212 | chrono::__skip(__input, suffix: "ursday" ); |
213 | return Thursday; |
214 | |
215 | case 'u': |
216 | chrono::__skip(__input, suffix: "esday" ); |
217 | return Tuesday; |
218 | } |
219 | break; |
220 | case 'w': |
221 | chrono::__skip(__input, suffix: "ednesday" ); |
222 | return Wednesday; |
223 | } |
224 | |
225 | std::__throw_runtime_error("corrupt tzdb weekday: invalid name" ); |
226 | } |
227 | |
228 | [[nodiscard]] static month __parse_month(istream& __input) { |
229 | // TZDB allows the shortest unique name. |
230 | switch (std::tolower(c: __input.get())) { |
231 | case 'a': |
232 | switch (std::tolower(c: __input.get())) { |
233 | case 'p': |
234 | chrono::__skip(__input, suffix: "ril" ); |
235 | return April; |
236 | |
237 | case 'u': |
238 | chrono::__skip(__input, suffix: "gust" ); |
239 | return August; |
240 | } |
241 | break; |
242 | |
243 | case 'd': |
244 | chrono::__skip(__input, suffix: "ecember" ); |
245 | return December; |
246 | |
247 | case 'f': |
248 | chrono::__skip(__input, suffix: "ebruary" ); |
249 | return February; |
250 | |
251 | case 'j': |
252 | switch (std::tolower(c: __input.get())) { |
253 | case 'a': |
254 | chrono::__skip(__input, suffix: "nuary" ); |
255 | return January; |
256 | |
257 | case 'u': |
258 | switch (std::tolower(c: __input.get())) { |
259 | case 'n': |
260 | chrono::__skip(__input, suffix: 'e'); |
261 | return June; |
262 | |
263 | case 'l': |
264 | chrono::__skip(__input, suffix: 'y'); |
265 | return July; |
266 | } |
267 | } |
268 | break; |
269 | |
270 | case 'm': |
271 | if (std::tolower(c: __input.get()) == 'a') |
272 | switch (std::tolower(c: __input.get())) { |
273 | case 'y': |
274 | return May; |
275 | |
276 | case 'r': |
277 | chrono::__skip(__input, suffix: "ch" ); |
278 | return March; |
279 | } |
280 | break; |
281 | |
282 | case 'n': |
283 | chrono::__skip(__input, suffix: "ovember" ); |
284 | return November; |
285 | |
286 | case 'o': |
287 | chrono::__skip(__input, suffix: "ctober" ); |
288 | return October; |
289 | |
290 | case 's': |
291 | chrono::__skip(__input, suffix: "eptember" ); |
292 | return September; |
293 | } |
294 | std::__throw_runtime_error("corrupt tzdb month: invalid name" ); |
295 | } |
296 | |
297 | [[nodiscard]] static year __parse_year_value(istream& __input) { |
298 | bool __negative = __input.peek() == '-'; |
299 | if (__negative) [[unlikely]] |
300 | __input.get(); |
301 | |
302 | int64_t __result = __parse_integral(__input, leading_zero_allowed: true); |
303 | if (__result > static_cast<int>(year::max())) { |
304 | if (__negative) |
305 | std::__throw_runtime_error("corrupt tzdb year: year is less than the minimum" ); |
306 | |
307 | std::__throw_runtime_error("corrupt tzdb year: year is greater than the maximum" ); |
308 | } |
309 | |
310 | return year{static_cast<int>(__negative ? -__result : __result)}; |
311 | } |
312 | |
313 | [[nodiscard]] static year __parse_year(istream& __input) { |
314 | if (std::tolower(c: __input.peek()) != 'm') [[likely]] |
315 | return chrono::__parse_year_value(__input); |
316 | |
317 | __input.get(); |
318 | switch (std::tolower(c: __input.peek())) { |
319 | case 'i': |
320 | __input.get(); |
321 | chrono::__skip(__input, suffix: 'n'); |
322 | [[fallthrough]]; |
323 | |
324 | case ' ': |
325 | // The m is minimum, even when that is ambiguous. |
326 | return year::min(); |
327 | |
328 | case 'a': |
329 | __input.get(); |
330 | chrono::__skip(__input, suffix: 'x'); |
331 | return year::max(); |
332 | } |
333 | |
334 | std::__throw_runtime_error("corrupt tzdb year: expected 'min' or 'max'" ); |
335 | } |
336 | |
337 | //===----------------------------------------------------------------------===// |
338 | // TZDB fields |
339 | //===----------------------------------------------------------------------===// |
340 | |
341 | [[nodiscard]] static year __parse_to(istream& __input, year __only) { |
342 | if (std::tolower(c: __input.peek()) != 'o') |
343 | return chrono::__parse_year(__input); |
344 | |
345 | __input.get(); |
346 | chrono::__skip(__input, suffix: "nly" ); |
347 | return __only; |
348 | } |
349 | |
350 | [[nodiscard]] static __tz::__constrained_weekday::__comparison_t __parse_comparison(istream& __input) { |
351 | switch (__input.get()) { |
352 | case '>': |
353 | chrono::__matches(__input, expected: '='); |
354 | return __tz::__constrained_weekday::__ge; |
355 | |
356 | case '<': |
357 | chrono::__matches(__input, expected: '='); |
358 | return __tz::__constrained_weekday::__le; |
359 | } |
360 | std::__throw_runtime_error("corrupt tzdb on: expected '>=' or '<='" ); |
361 | } |
362 | |
363 | [[nodiscard]] static __tz::__on __parse_on(istream& __input) { |
364 | if (std::isdigit(c: __input.peek())) |
365 | return chrono::__parse_day(__input); |
366 | |
367 | if (std::tolower(c: __input.peek()) == 'l') { |
368 | chrono::__matches(__input, expected: "last" ); |
369 | return weekday_last(chrono::__parse_weekday(__input)); |
370 | } |
371 | |
372 | return __tz::__constrained_weekday{ |
373 | chrono::__parse_weekday(__input), chrono::__parse_comparison(__input), chrono::__parse_day(__input)}; |
374 | } |
375 | |
376 | [[nodiscard]] static seconds __parse_duration(istream& __input) { |
377 | seconds __result{0}; |
378 | int __c = __input.peek(); |
379 | bool __negative = __c == '-'; |
380 | if (__negative) { |
381 | __input.get(); |
382 | // Negative is either a negative value or a single -. |
383 | // The latter means 0 and the parsing is complete. |
384 | if (!std::isdigit(c: __input.peek())) |
385 | return __result; |
386 | } |
387 | |
388 | __result += hours(__parse_integral(__input, leading_zero_allowed: true)); |
389 | if (__input.peek() != ':') |
390 | return __negative ? -__result : __result; |
391 | |
392 | __input.get(); |
393 | __result += minutes(__parse_integral(__input, leading_zero_allowed: true)); |
394 | if (__input.peek() != ':') |
395 | return __negative ? -__result : __result; |
396 | |
397 | __input.get(); |
398 | __result += seconds(__parse_integral(__input, leading_zero_allowed: true)); |
399 | if (__input.peek() != '.') |
400 | return __negative ? -__result : __result; |
401 | |
402 | __input.get(); |
403 | (void)__parse_integral(__input, leading_zero_allowed: true); // Truncate the digits. |
404 | |
405 | return __negative ? -__result : __result; |
406 | } |
407 | |
408 | [[nodiscard]] static __tz::__clock __parse_clock(istream& __input) { |
409 | switch (__input.get()) { // case sensitive |
410 | case 'w': |
411 | return __tz::__clock::__local; |
412 | case 's': |
413 | return __tz::__clock::__standard; |
414 | |
415 | case 'u': |
416 | case 'g': |
417 | case 'z': |
418 | return __tz::__clock::__universal; |
419 | } |
420 | |
421 | __input.unget(); |
422 | return __tz::__clock::__local; |
423 | } |
424 | |
425 | [[nodiscard]] static bool __parse_dst(istream& __input, seconds __offset) { |
426 | switch (__input.get()) { // case sensitive |
427 | case 's': |
428 | return false; |
429 | |
430 | case 'd': |
431 | return true; |
432 | } |
433 | |
434 | __input.unget(); |
435 | return __offset != 0s; |
436 | } |
437 | |
438 | [[nodiscard]] static __tz::__at __parse_at(istream& __input) { |
439 | return {__parse_duration(__input), __parse_clock(__input)}; |
440 | } |
441 | |
442 | [[nodiscard]] static __tz::__save __parse_save(istream& __input) { |
443 | seconds __time = chrono::__parse_duration(__input); |
444 | return {__time, chrono::__parse_dst(__input, offset: __time)}; |
445 | } |
446 | |
447 | [[nodiscard]] static string __parse_letters(istream& __input) { |
448 | string __result = __parse_string(__input); |
449 | // Canonicalize "-" to "" since they are equivalent in the specification. |
450 | return __result != "-" ? __result : "" ; |
451 | } |
452 | |
453 | [[nodiscard]] static __tz::__continuation::__rules_t __parse_rules(istream& __input) { |
454 | int __c = __input.peek(); |
455 | // A single - is not a SAVE but a special case. |
456 | if (__c == '-') { |
457 | __input.get(); |
458 | if (chrono::__is_whitespace(c: __input.peek())) |
459 | return monostate{}; |
460 | __input.unget(); |
461 | return chrono::__parse_save(__input); |
462 | } |
463 | |
464 | if (std::isdigit(__c) || __c == '+') |
465 | return chrono::__parse_save(__input); |
466 | |
467 | return chrono::__parse_string(__input); |
468 | } |
469 | |
470 | [[nodiscard]] static __tz::__continuation __parse_continuation(__tz::__rules_storage_type& __rules, istream& __input) { |
471 | __tz::__continuation __result; |
472 | |
473 | __result.__rule_database_ = std::addressof(x&: __rules); |
474 | |
475 | // Note STDOFF is specified as |
476 | // This field has the same format as the AT and SAVE fields of rule lines; |
477 | // These fields have different suffix letters, these letters seem |
478 | // not to be used so do not allow any of them. |
479 | |
480 | __result.__stdoff = chrono::__parse_duration(__input); |
481 | chrono::__skip_mandatory_whitespace(__input); |
482 | __result.__rules = chrono::__parse_rules(__input); |
483 | chrono::__skip_mandatory_whitespace(__input); |
484 | __result.__format = chrono::__parse_string(__input); |
485 | chrono::__skip_optional_whitespace(__input); |
486 | |
487 | if (chrono::__is_eol(c: __input.peek())) |
488 | return __result; |
489 | __result.__year = chrono::__parse_year(__input); |
490 | chrono::__skip_optional_whitespace(__input); |
491 | |
492 | if (chrono::__is_eol(c: __input.peek())) |
493 | return __result; |
494 | __result.__in = chrono::__parse_month(__input); |
495 | chrono::__skip_optional_whitespace(__input); |
496 | |
497 | if (chrono::__is_eol(c: __input.peek())) |
498 | return __result; |
499 | __result.__on = chrono::__parse_on(__input); |
500 | chrono::__skip_optional_whitespace(__input); |
501 | |
502 | if (chrono::__is_eol(c: __input.peek())) |
503 | return __result; |
504 | __result.__at = __parse_at(__input); |
505 | |
506 | return __result; |
507 | } |
508 | |
509 | //===----------------------------------------------------------------------===// |
510 | // Time Zone Database entries |
511 | //===----------------------------------------------------------------------===// |
512 | |
513 | static string __parse_version(istream& __input) { |
514 | // The first line in tzdata.zi contains |
515 | // # version YYYYw |
516 | // The parser expects this pattern |
517 | // #\s*version\s*\(.*) |
518 | // This part is not documented. |
519 | chrono::__matches(__input, expected: '#'); |
520 | chrono::__skip_optional_whitespace(__input); |
521 | chrono::__matches(__input, expected: "version" ); |
522 | chrono::__skip_mandatory_whitespace(__input); |
523 | return chrono::__parse_string(__input); |
524 | } |
525 | |
526 | [[nodiscard]] |
527 | static __tz::__rule& __create_entry(__tz::__rules_storage_type& __rules, const string& __name) { |
528 | auto __result = [&]() -> __tz::__rule& { |
529 | auto& __rule = __rules.emplace_back(args: __name, args: vector<__tz::__rule>{}); |
530 | return __rule.second.emplace_back(); |
531 | }; |
532 | |
533 | if (__rules.empty()) |
534 | return __result(); |
535 | |
536 | // Typically rules are in contiguous order in the database. |
537 | // But there are exceptions, some rules are interleaved. |
538 | if (__rules.back().first == __name) |
539 | return __rules.back().second.emplace_back(); |
540 | |
541 | if (auto __it = ranges::find(__rules, __name, [](const auto& __r) { return __r.first; }); |
542 | __it != ranges::end(__rules)) |
543 | return __it->second.emplace_back(); |
544 | |
545 | return __result(); |
546 | } |
547 | |
548 | static void __parse_rule(tzdb& __tzdb, __tz::__rules_storage_type& __rules, istream& __input) { |
549 | chrono::__skip_mandatory_whitespace(__input); |
550 | string __name = chrono::__parse_string(__input); |
551 | |
552 | __tz::__rule& __rule = __create_entry(__rules, __name); |
553 | |
554 | chrono::__skip_mandatory_whitespace(__input); |
555 | __rule.__from = chrono::__parse_year(__input); |
556 | chrono::__skip_mandatory_whitespace(__input); |
557 | __rule.__to = chrono::__parse_to(__input, only: __rule.__from); |
558 | chrono::__skip_mandatory_whitespace(__input); |
559 | chrono::__matches(__input, expected: '-'); |
560 | chrono::__skip_mandatory_whitespace(__input); |
561 | __rule.__in = chrono::__parse_month(__input); |
562 | chrono::__skip_mandatory_whitespace(__input); |
563 | __rule.__on = chrono::__parse_on(__input); |
564 | chrono::__skip_mandatory_whitespace(__input); |
565 | __rule.__at = __parse_at(__input); |
566 | chrono::__skip_mandatory_whitespace(__input); |
567 | __rule.__save = __parse_save(__input); |
568 | chrono::__skip_mandatory_whitespace(__input); |
569 | __rule.__letters = chrono::__parse_letters(__input); |
570 | chrono::__skip_line(__input); |
571 | } |
572 | |
573 | static void __parse_zone(tzdb& __tzdb, __tz::__rules_storage_type& __rules, istream& __input) { |
574 | chrono::__skip_mandatory_whitespace(__input); |
575 | auto __p = std::make_unique<time_zone::__impl>(args: chrono::__parse_string(__input), args&: __rules); |
576 | vector<__tz::__continuation>& __continuations = __p->__continuations(); |
577 | chrono::__skip_mandatory_whitespace(__input); |
578 | |
579 | do { |
580 | // The first line must be valid, continuations are optional. |
581 | __continuations.emplace_back(args: __parse_continuation(__rules, __input)); |
582 | chrono::__skip_line(__input); |
583 | chrono::__skip_optional_whitespace(__input); |
584 | } while (std::isdigit(c: __input.peek()) || __input.peek() == '-'); |
585 | |
586 | __tzdb.zones.emplace_back(args: time_zone::__create(p: std::move(__p))); |
587 | } |
588 | |
589 | static void __parse_link(tzdb& __tzdb, istream& __input) { |
590 | chrono::__skip_mandatory_whitespace(__input); |
591 | string __target = chrono::__parse_string(__input); |
592 | chrono::__skip_mandatory_whitespace(__input); |
593 | string __name = chrono::__parse_string(__input); |
594 | chrono::__skip_line(__input); |
595 | |
596 | __tzdb.links.emplace_back(args: std::__private_constructor_tag{}, args: std::move(__name), args: std::move(__target)); |
597 | } |
598 | |
599 | static void __parse_tzdata(tzdb& __db, __tz::__rules_storage_type& __rules, istream& __input) { |
600 | while (true) { |
601 | int __c = std::tolower(c: __input.get()); |
602 | |
603 | switch (__c) { |
604 | case istream::traits_type::eof(): |
605 | return; |
606 | |
607 | case ' ': |
608 | case '\t': |
609 | case '\n': |
610 | break; |
611 | |
612 | case '#': |
613 | chrono::__skip_line(__input); |
614 | break; |
615 | |
616 | case 'r': |
617 | chrono::__skip(__input, suffix: "ule" ); |
618 | chrono::__parse_rule(tzdb&: __db, __rules, __input); |
619 | break; |
620 | |
621 | case 'z': |
622 | chrono::__skip(__input, suffix: "one" ); |
623 | chrono::__parse_zone(tzdb&: __db, __rules, __input); |
624 | break; |
625 | |
626 | case 'l': |
627 | chrono::__skip(__input, suffix: "ink" ); |
628 | chrono::__parse_link(tzdb&: __db, __input); |
629 | break; |
630 | |
631 | default: |
632 | std::__throw_runtime_error("corrupt tzdb: unexpected input" ); |
633 | } |
634 | } |
635 | } |
636 | |
637 | static void __parse_leap_seconds(vector<leap_second>& __leap_seconds, istream&& __input) { |
638 | // The file stores dates since 1 January 1900, 00:00:00, we want |
639 | // seconds since 1 January 1970. |
640 | constexpr auto __offset = sys_days{1970y / January / 1} - sys_days{1900y / January / 1}; |
641 | |
642 | struct __entry { |
643 | sys_seconds __timestamp; |
644 | seconds __value; |
645 | }; |
646 | vector<__entry> __entries; |
647 | [&] { |
648 | while (true) { |
649 | switch (__input.peek()) { |
650 | case istream::traits_type::eof(): |
651 | return; |
652 | |
653 | case ' ': |
654 | case '\t': |
655 | case '\n': |
656 | __input.get(); |
657 | continue; |
658 | |
659 | case '#': |
660 | chrono::__skip_line(__input); |
661 | continue; |
662 | } |
663 | |
664 | sys_seconds __date = sys_seconds{seconds{chrono::__parse_integral(__input, leading_zero_allowed: false)}} - __offset; |
665 | chrono::__skip_mandatory_whitespace(__input); |
666 | seconds __value{chrono::__parse_integral(__input, leading_zero_allowed: false)}; |
667 | chrono::__skip_line(__input); |
668 | |
669 | __entries.emplace_back(args&: __date, args&: __value); |
670 | } |
671 | }(); |
672 | // The Standard requires the leap seconds to be sorted. The file |
673 | // leap-seconds.list usually provides them in sorted order, but that is not |
674 | // guaranteed so we ensure it here. |
675 | ranges::sort(__entries, {}, &__entry::__timestamp); |
676 | |
677 | // The database should contain the number of seconds inserted by a leap |
678 | // second (1 or -1). So the difference between the two elements is stored. |
679 | // std::ranges::views::adjacent has not been implemented yet. |
680 | (void)ranges::adjacent_find(__entries, [&](const __entry& __first, const __entry& __second) { |
681 | __leap_seconds.emplace_back( |
682 | args: std::__private_constructor_tag{}, args: __second.__timestamp, args: __second.__value - __first.__value); |
683 | return false; |
684 | }); |
685 | } |
686 | |
687 | void __init_tzdb(tzdb& __tzdb, __tz::__rules_storage_type& __rules) { |
688 | filesystem::path __root = chrono::__libcpp_tzdb_directory(); |
689 | ifstream __tzdata{__root / "tzdata.zi" }; |
690 | |
691 | __tzdb.version = chrono::__parse_version(input&: __tzdata); |
692 | chrono::__parse_tzdata(db&: __tzdb, __rules, input&: __tzdata); |
693 | ranges::sort(__tzdb.zones); |
694 | ranges::sort(__tzdb.links); |
695 | ranges::sort(__rules, {}, [](const auto& p) { return p.first; }); |
696 | |
697 | // There are two files with the leap second information |
698 | // - leapseconds as specified by zic |
699 | // - leap-seconds.list the source data |
700 | // The latter is much easier to parse, it seems Howard shares that |
701 | // opinion. |
702 | chrono::__parse_leap_seconds(leap_seconds&: __tzdb.leap_seconds, input: ifstream{__root / "leap-seconds.list" }); |
703 | } |
704 | |
705 | #ifdef _WIN32 |
706 | [[nodiscard]] static const time_zone* __current_zone_windows(const tzdb& tzdb) { |
707 | // TODO TZDB Implement this on Windows. |
708 | std::__throw_runtime_error("unknown time zone" ); |
709 | } |
710 | #else // ifdef _WIN32 |
711 | |
712 | [[nodiscard]] static string __current_zone_environment() { |
713 | if (const char* __tz = std::getenv(name: "TZ" )) |
714 | return __tz; |
715 | |
716 | return {}; |
717 | } |
718 | |
719 | [[nodiscard]] static string __current_zone_etc_localtime() { |
720 | filesystem::path __path = "/etc/localtime" ; |
721 | if (!filesystem::exists(p: __path) || !filesystem::is_symlink(p: __path)) |
722 | return {}; |
723 | |
724 | filesystem::path __tz = filesystem::read_symlink(p: __path); |
725 | // The path may be a relative path, in that case convert it to an absolute |
726 | // path based on the proper initial directory. |
727 | if (__tz.is_relative()) |
728 | __tz = filesystem::canonical(p: "/etc" / __tz); |
729 | |
730 | return filesystem::relative(p: __tz, base: "/usr/share/zoneinfo/" ); |
731 | } |
732 | |
733 | [[nodiscard]] static string __current_zone_etc_timezone() { |
734 | filesystem::path __path = "/etc/timezone" ; |
735 | if (!filesystem::exists(p: __path)) |
736 | return {}; |
737 | |
738 | ifstream __f(__path); |
739 | string __name; |
740 | std::getline(is&: __f, str&: __name); |
741 | return __name; |
742 | } |
743 | |
744 | [[nodiscard]] static const time_zone* __current_zone_posix(const tzdb& tzdb) { |
745 | // On POSIX systems there are several ways to configure the time zone. |
746 | // In order of priority they are: |
747 | // - TZ environment variable |
748 | // https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08 |
749 | // The documentation is unclear whether or not it's allowed to |
750 | // change time zone information. For example the TZ string |
751 | // MST7MDT |
752 | // this is an entry in tzdata.zi. The value |
753 | // MST |
754 | // is also an entry. Is it allowed to use the following? |
755 | // MST-3 |
756 | // Even when this is valid there is no time_zone record in the |
757 | // database. Since the library would need to return a valid pointer, |
758 | // this means the library needs to allocate and leak a pointer. |
759 | // |
760 | // - The time zone name is the target of the symlink /etc/localtime |
761 | // relative to /usr/share/zoneinfo/ |
762 | // |
763 | // - The file /etc/timezone. This text file contains the name of the time |
764 | // zone. |
765 | // |
766 | // On Linux systems it seems /etc/timezone is deprecated and being phased out. |
767 | // This file is used when /etc/localtime does not exist, or when it exists but |
768 | // is not a symlink. For more information and links see |
769 | // https://github.com/llvm/llvm-project/issues/105634 |
770 | |
771 | string __name = chrono::__current_zone_environment(); |
772 | |
773 | // Ignore invalid names in the environment. |
774 | if (!__name.empty()) |
775 | if (const time_zone* __result = tzdb.__locate_zone(__name)) |
776 | return __result; |
777 | |
778 | __name = chrono::__current_zone_etc_localtime(); |
779 | if (__name.empty()) |
780 | __name = chrono::__current_zone_etc_timezone(); |
781 | |
782 | if (__name.empty()) |
783 | std::__throw_runtime_error("tzdb: unable to determine the name of the current time zone" ); |
784 | |
785 | if (const time_zone* __result = tzdb.__locate_zone(__name)) |
786 | return __result; |
787 | |
788 | std::__throw_runtime_error(("tzdb: the time zone '" + __name + "' is not found in the database" ).c_str()); |
789 | } |
790 | #endif // ifdef _WIN32 |
791 | |
792 | //===----------------------------------------------------------------------===// |
793 | // Public API |
794 | //===----------------------------------------------------------------------===// |
795 | |
796 | _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI tzdb_list& get_tzdb_list() { |
797 | static tzdb_list __result{new tzdb_list::__impl()}; |
798 | return __result; |
799 | } |
800 | |
801 | [[nodiscard]] _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI const time_zone* tzdb::__current_zone() const { |
802 | #ifdef _WIN32 |
803 | return chrono::__current_zone_windows(*this); |
804 | #else |
805 | return chrono::__current_zone_posix(tzdb: *this); |
806 | #endif |
807 | } |
808 | |
809 | _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI const tzdb& reload_tzdb() { |
810 | if (chrono::remote_version() == chrono::get_tzdb().version) |
811 | return chrono::get_tzdb(); |
812 | |
813 | return chrono::get_tzdb_list().__implementation().__load(); |
814 | } |
815 | |
816 | _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI string remote_version() { |
817 | filesystem::path __root = chrono::__libcpp_tzdb_directory(); |
818 | ifstream __tzdata{__root / "tzdata.zi" }; |
819 | return chrono::__parse_version(input&: __tzdata); |
820 | } |
821 | |
822 | } // namespace chrono |
823 | |
824 | _LIBCPP_END_NAMESPACE_STD |
825 | |