xref: /llvm-project/libcxx/src/experimental/tzdb.cpp (revision 2914ba1c01fdc496082197abf7cd35e2af526634)
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 
50 namespace 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 
68 static void __skip_optional_whitespace(istream& __input) {
69   while (chrono::__is_whitespace(__input.peek()))
70     __input.get();
71 }
72 
73 static void __skip_mandatory_whitespace(istream& __input) {
74   if (!chrono::__is_whitespace(__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 
82 static void __skip_line(istream& __input) {
83   while (!chrono::__is_eol(__input.peek())) {
84     __input.get();
85   }
86   __input.get();
87 }
88 
89 static void __skip(istream& __input, char __suffix) {
90   if (std::tolower(__input.peek()) == __suffix)
91     __input.get();
92 }
93 
94 static void __skip(istream& __input, string_view __suffix) {
95   for (auto __c : __suffix)
96     if (std::tolower(__input.peek()) == __c)
97       __input.get();
98 }
99 
100 static 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 
108 static 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(__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, 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(__input.get())) {
189   case 'f':
190     chrono::__skip(__input, "riday");
191     return Friday;
192 
193   case 'm':
194     chrono::__skip(__input, "onday");
195     return Monday;
196 
197   case 's':
198     switch (std::tolower(__input.get())) {
199     case 'a':
200       chrono::__skip(__input, "turday");
201       return Saturday;
202 
203     case 'u':
204       chrono::__skip(__input, "nday");
205       return Sunday;
206     }
207     break;
208 
209   case 't':
210     switch (std::tolower(__input.get())) {
211     case 'h':
212       chrono::__skip(__input, "ursday");
213       return Thursday;
214 
215     case 'u':
216       chrono::__skip(__input, "esday");
217       return Tuesday;
218     }
219     break;
220   case 'w':
221     chrono::__skip(__input, "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(__input.get())) {
231   case 'a':
232     switch (std::tolower(__input.get())) {
233     case 'p':
234       chrono::__skip(__input, "ril");
235       return April;
236 
237     case 'u':
238       chrono::__skip(__input, "gust");
239       return August;
240     }
241     break;
242 
243   case 'd':
244     chrono::__skip(__input, "ecember");
245     return December;
246 
247   case 'f':
248     chrono::__skip(__input, "ebruary");
249     return February;
250 
251   case 'j':
252     switch (std::tolower(__input.get())) {
253     case 'a':
254       chrono::__skip(__input, "nuary");
255       return January;
256 
257     case 'u':
258       switch (std::tolower(__input.get())) {
259       case 'n':
260         chrono::__skip(__input, 'e');
261         return June;
262 
263       case 'l':
264         chrono::__skip(__input, 'y');
265         return July;
266       }
267     }
268     break;
269 
270   case 'm':
271     if (std::tolower(__input.get()) == 'a')
272       switch (std::tolower(__input.get())) {
273       case 'y':
274         return May;
275 
276       case 'r':
277         chrono::__skip(__input, "ch");
278         return March;
279       }
280     break;
281 
282   case 'n':
283     chrono::__skip(__input, "ovember");
284     return November;
285 
286   case 'o':
287     chrono::__skip(__input, "ctober");
288     return October;
289 
290   case 's':
291     chrono::__skip(__input, "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, 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(__input.peek()) != 'm') [[likely]]
315     return chrono::__parse_year_value(__input);
316 
317   __input.get();
318   switch (std::tolower(__input.peek())) {
319   case 'i':
320     __input.get();
321     chrono::__skip(__input, '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, '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(__input.peek()) != 'o')
343     return chrono::__parse_year(__input);
344 
345   __input.get();
346   chrono::__skip(__input, "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, '=');
354     return __tz::__constrained_weekday::__ge;
355 
356   case '<':
357     chrono::__matches(__input, '=');
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(__input.peek()))
365     return chrono::__parse_day(__input);
366 
367   if (std::tolower(__input.peek()) == 'l') {
368     chrono::__matches(__input, "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(__input.peek()))
385       return __result;
386   }
387 
388   __result += hours(__parse_integral(__input, true));
389   if (__input.peek() != ':')
390     return __negative ? -__result : __result;
391 
392   __input.get();
393   __result += minutes(__parse_integral(__input, true));
394   if (__input.peek() != ':')
395     return __negative ? -__result : __result;
396 
397   __input.get();
398   __result += seconds(__parse_integral(__input, true));
399   if (__input.peek() != '.')
400     return __negative ? -__result : __result;
401 
402   __input.get();
403   (void)__parse_integral(__input, 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, __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(__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(__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(__input.peek()))
488     return __result;
489   __result.__year = chrono::__parse_year(__input);
490   chrono::__skip_optional_whitespace(__input);
491 
492   if (chrono::__is_eol(__input.peek()))
493     return __result;
494   __result.__in = chrono::__parse_month(__input);
495   chrono::__skip_optional_whitespace(__input);
496 
497   if (chrono::__is_eol(__input.peek()))
498     return __result;
499   __result.__on = chrono::__parse_on(__input);
500   chrono::__skip_optional_whitespace(__input);
501 
502   if (chrono::__is_eol(__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 
513 static 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, '#');
520   chrono::__skip_optional_whitespace(__input);
521   chrono::__matches(__input, "version");
522   chrono::__skip_mandatory_whitespace(__input);
523   return chrono::__parse_string(__input);
524 }
525 
526 [[nodiscard]]
527 static __tz::__rule& __create_entry(__tz::__rules_storage_type& __rules, const string& __name) {
528   auto __result = [&]() -> __tz::__rule& {
529     auto& __rule = __rules.emplace_back(__name, 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 
548 static 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, __rule.__from);
558   chrono::__skip_mandatory_whitespace(__input);
559   chrono::__matches(__input, '-');
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 
573 static 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>(chrono::__parse_string(__input), __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(__parse_continuation(__rules, __input));
582     chrono::__skip_line(__input);
583     chrono::__skip_optional_whitespace(__input);
584   } while (std::isdigit(__input.peek()) || __input.peek() == '-');
585 
586   __tzdb.zones.emplace_back(time_zone::__create(std::move(__p)));
587 }
588 
589 static 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(std::__private_constructor_tag{}, std::move(__name), std::move(__target));
597 }
598 
599 static void __parse_tzdata(tzdb& __db, __tz::__rules_storage_type& __rules, istream& __input) {
600   while (true) {
601     int __c = std::tolower(__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, "ule");
618       chrono::__parse_rule(__db, __rules, __input);
619       break;
620 
621     case 'z':
622       chrono::__skip(__input, "one");
623       chrono::__parse_zone(__db, __rules, __input);
624       break;
625 
626     case 'l':
627       chrono::__skip(__input, "ink");
628       chrono::__parse_link(__db, __input);
629       break;
630 
631     default:
632       std::__throw_runtime_error("corrupt tzdb: unexpected input");
633     }
634   }
635 }
636 
637 static 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, false)}} - __offset;
665       chrono::__skip_mandatory_whitespace(__input);
666       seconds __value{chrono::__parse_integral(__input, false)};
667       chrono::__skip_line(__input);
668 
669       __entries.emplace_back(__date, __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         std::__private_constructor_tag{}, __second.__timestamp, __second.__value - __first.__value);
683     return false;
684   });
685 }
686 
687 void __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(__tzdata);
692   chrono::__parse_tzdata(__tzdb, __rules, __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(__tzdb.leap_seconds, 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 [[nodiscard]] static const time_zone* __current_zone_posix(const tzdb& tzdb) {
712   // On POSIX systems there are several ways to configure the time zone.
713   // In order of priority they are:
714   // - TZ environment variable
715   //   https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08
716   //   The documentation is unclear whether or not it's allowed to
717   //   change time zone information. For example the TZ string
718   //     MST7MDT
719   //   this is an entry in tzdata.zi. The value
720   //     MST
721   //   is also an entry. Is it allowed to use the following?
722   //     MST-3
723   //   Even when this is valid there is no time_zone record in the
724   //   database. Since the library would need to return a valid pointer,
725   //   this means the library needs to allocate and leak a pointer.
726   //
727   // - The time zone name is the target of the symlink /etc/localtime
728   //   relative to /usr/share/zoneinfo/
729 
730   // The algorithm is like this:
731   // - If the environment variable TZ is set and points to a valid
732   //   record use this value.
733   // - Else use the name based on the `/etc/localtime` symlink.
734 
735   if (const char* __tz = getenv("TZ"))
736     if (const time_zone* __result = tzdb.__locate_zone(__tz))
737       return __result;
738 
739   filesystem::path __path = "/etc/localtime";
740   if (!filesystem::exists(__path))
741     std::__throw_runtime_error("tzdb: the symlink '/etc/localtime' does not exist");
742 
743   if (!filesystem::is_symlink(__path))
744     std::__throw_runtime_error("tzdb: the path '/etc/localtime' is not a symlink");
745 
746   filesystem::path __tz = filesystem::read_symlink(__path);
747   // The path may be a relative path, in that case convert it to an absolute
748   // path based on the proper initial directory.
749   if (__tz.is_relative())
750     __tz = filesystem::canonical("/etc" / __tz);
751 
752   string __name = filesystem::relative(__tz, "/usr/share/zoneinfo/");
753   if (const time_zone* __result = tzdb.__locate_zone(__name))
754     return __result;
755 
756   std::__throw_runtime_error(("tzdb: the time zone '" + __name + "' is not found in the database").c_str());
757 }
758 #endif // ifdef _WIN32
759 
760 //===----------------------------------------------------------------------===//
761 //                           Public API
762 //===----------------------------------------------------------------------===//
763 
764 _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI tzdb_list& get_tzdb_list() {
765   static tzdb_list __result{new tzdb_list::__impl()};
766   return __result;
767 }
768 
769 [[nodiscard]] _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI const time_zone* tzdb::__current_zone() const {
770 #ifdef _WIN32
771   return chrono::__current_zone_windows(*this);
772 #else
773   return chrono::__current_zone_posix(*this);
774 #endif
775 }
776 
777 _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI const tzdb& reload_tzdb() {
778   if (chrono::remote_version() == chrono::get_tzdb().version)
779     return chrono::get_tzdb();
780 
781   return chrono::get_tzdb_list().__implementation().__load();
782 }
783 
784 _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI string remote_version() {
785   filesystem::path __root = chrono::__libcpp_tzdb_directory();
786   ifstream __tzdata{__root / "tzdata.zi"};
787   return chrono::__parse_version(__tzdata);
788 }
789 
790 } // namespace chrono
791 
792 _LIBCPP_END_NAMESPACE_STD
793