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
46namespace 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
64static void __skip_optional_whitespace(istream& __input) {
65 while (chrono::__is_whitespace(__input.peek()))
66 __input.get();
67}
68
69static void __skip_mandatory_whitespace(istream& __input) {
70 if (!chrono::__is_whitespace(__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
78static void __skip_line(istream& __input) {
79 while (!chrono::__is_eol(__input.peek())) {
80 __input.get();
81 }
82 __input.get();
83}
84
85static void __skip(istream& __input, char __suffix) {
86 if (std::tolower(__input.peek()) == __suffix)
87 __input.get();
88}
89
90static void __skip(istream& __input, string_view __suffix) {
91 for (auto __c : __suffix)
92 if (std::tolower(__input.peek()) == __c)
93 __input.get();
94}
95
96static 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
101static 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(__input.peek()))
475 return __result;
476 __result.__year = chrono::__parse_year(__input);
477 chrono::__skip_optional_whitespace(__input);
478
479 if (chrono::__is_eol(__input.peek()))
480 return __result;
481 __result.__in = chrono::__parse_month(__input);
482 chrono::__skip_optional_whitespace(__input);
483
484 if (chrono::__is_eol(__input.peek()))
485 return __result;
486 __result.__on = chrono::__parse_on(__input);
487 chrono::__skip_optional_whitespace(__input);
488
489 if (chrono::__is_eol(__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
500static 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]]
514static __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
535static 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
560static 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
576static 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
586static 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
624static 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 struct __entry {
630 sys_seconds __timestamp;
631 seconds __value;
632 };
633 vector<__entry> __entries;
634 [&] {
635 while (true) {
636 switch (__input.peek()) {
637 case istream::traits_type::eof():
638 return;
639
640 case ' ':
641 case '\t':
642 case '\n':
643 __input.get();
644 continue;
645
646 case '#':
647 chrono::__skip_line(__input);
648 continue;
649 }
650
651 sys_seconds __date = sys_seconds{seconds{chrono::__parse_integral(__input, false)}} - __offset;
652 chrono::__skip_mandatory_whitespace(__input);
653 seconds __value{chrono::__parse_integral(__input, false)};
654 chrono::__skip_line(__input);
655
656 __entries.emplace_back(__date, __value);
657 }
658 }();
659 // The Standard requires the leap seconds to be sorted. The file
660 // leap-seconds.list usually provides them in sorted order, but that is not
661 // guaranteed so we ensure it here.
662 ranges::sort(__entries, {}, &__entry::__timestamp);
663
664 // The database should contain the number of seconds inserted by a leap
665 // second (1 or -1). So the difference between the two elements is stored.
666 // std::ranges::views::adjacent has not been implemented yet.
667 (void)ranges::adjacent_find(__entries, [&](const __entry& __first, const __entry& __second) {
668 __leap_seconds.emplace_back(
669 std::__private_constructor_tag{}, __second.__timestamp, __second.__value - __first.__value);
670 return false;
671 });
672}
673
674void __init_tzdb(tzdb& __tzdb, __tz::__rules_storage_type& __rules) {
675 filesystem::path __root = chrono::__libcpp_tzdb_directory();
676 ifstream __tzdata{__root / "tzdata.zi"};
677
678 __tzdb.version = chrono::__parse_version(__tzdata);
679 chrono::__parse_tzdata(__tzdb, __rules, __tzdata);
680 ranges::sort(__tzdb.zones);
681 ranges::sort(__tzdb.links);
682 ranges::sort(__rules, {}, [](const auto& p) { return p.first; });
683
684 // There are two files with the leap second information
685 // - leapseconds as specified by zic
686 // - leap-seconds.list the source data
687 // The latter is much easier to parse, it seems Howard shares that
688 // opinion.
689 chrono::__parse_leap_seconds(__tzdb.leap_seconds, ifstream{__root / "leap-seconds.list"});
690}
691
692#ifdef _WIN32
693[[nodiscard]] static const time_zone* __current_zone_windows(const tzdb& tzdb) {
694 // TODO TZDB Implement this on Windows.
695 std::__throw_runtime_error("unknown time zone");
696}
697#else // ifdef _WIN32
698[[nodiscard]] static const time_zone* __current_zone_posix(const tzdb& tzdb) {
699 // On POSIX systems there are several ways to configure the time zone.
700 // In order of priority they are:
701 // - TZ environment variable
702 // https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08
703 // The documentation is unclear whether or not it's allowed to
704 // change time zone information. For example the TZ string
705 // MST7MDT
706 // this is an entry in tzdata.zi. The value
707 // MST
708 // is also an entry. Is it allowed to use the following?
709 // MST-3
710 // Even when this is valid there is no time_zone record in the
711 // database. Since the library would need to return a valid pointer,
712 // this means the library needs to allocate and leak a pointer.
713 //
714 // - The time zone name is the target of the symlink /etc/localtime
715 // relative to /usr/share/zoneinfo/
716
717 // The algorithm is like this:
718 // - If the environment variable TZ is set and points to a valid
719 // record use this value.
720 // - Else use the name based on the `/etc/localtime` symlink.
721
722 if (const char* __tz = getenv("TZ"))
723 if (const time_zone* __result = tzdb.__locate_zone(__tz))
724 return __result;
725
726 filesystem::path __path = "/etc/localtime";
727 if (!filesystem::exists(__path))
728 std::__throw_runtime_error("tzdb: the symlink '/etc/localtime' does not exist");
729
730 if (!filesystem::is_symlink(__path))
731 std::__throw_runtime_error("tzdb: the path '/etc/localtime' is not a symlink");
732
733 filesystem::path __tz = filesystem::read_symlink(__path);
734 // The path may be a relative path, in that case convert it to an absolute
735 // path based on the proper initial directory.
736 if (__tz.is_relative())
737 __tz = filesystem::canonical("/etc" / __tz);
738
739 string __name = filesystem::relative(__tz, "/usr/share/zoneinfo/");
740 if (const time_zone* __result = tzdb.__locate_zone(__name))
741 return __result;
742
743 std::__throw_runtime_error(("tzdb: the time zone '" + __name + "' is not found in the database").c_str());
744}
745#endif // ifdef _WIN32
746
747//===----------------------------------------------------------------------===//
748// Public API
749//===----------------------------------------------------------------------===//
750
751_LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI tzdb_list& get_tzdb_list() {
752 static tzdb_list __result{new tzdb_list::__impl()};
753 return __result;
754}
755
756[[nodiscard]] _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI const time_zone* tzdb::__current_zone() const {
757#ifdef _WIN32
758 return chrono::__current_zone_windows(*this);
759#else
760 return chrono::__current_zone_posix(*this);
761#endif
762}
763
764_LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI const tzdb& reload_tzdb() {
765 if (chrono::remote_version() == chrono::get_tzdb().version)
766 return chrono::get_tzdb();
767
768 return chrono::get_tzdb_list().__implementation().__load();
769}
770
771_LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI string remote_version() {
772 filesystem::path __root = chrono::__libcpp_tzdb_directory();
773 ifstream __tzdata{__root / "tzdata.zi"};
774 return chrono::__parse_version(__tzdata);
775}
776
777} // namespace chrono
778
779_LIBCPP_END_NAMESPACE_STD
780