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