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