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