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# 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
68static void __skip_optional_whitespace(istream& __input) {
69 while (chrono::__is_whitespace(c: __input.peek()))
70 __input.get();
71}
72
73static 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
82static void __skip_line(istream& __input) {
83 while (!chrono::__is_eol(c: __input.peek())) {
84 __input.get();
85 }
86 __input.get();
87}
88
89static void __skip(istream& __input, char __suffix) {
90 if (std::tolower(c: __input.peek()) == __suffix)
91 __input.get();
92}
93
94static 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
100static 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
108static 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
513static 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]]
527static __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
548static 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
573static 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
589static 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
599static 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
637static 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
687void __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