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