summaryrefslogtreecommitdiffstats
path: root/libcxx/src/tzdb.cpp
blob: 7ba5ceb7ada3d21c5857ac3cd15ed8e1a0822a81 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// For information see https://libcxx.llvm.org/DesignDocs/TimeZone.html

#include <algorithm>
#include <chrono>
#include <filesystem>
#include <fstream>
#include <stdexcept>
#include <string>

#include "include/tzdb/leap_second_private.h"
#include "include/tzdb/time_zone_link_private.h"
#include "include/tzdb/time_zone_private.h"
#include "include/tzdb/types_private.h"
#include "include/tzdb/tzdb_list_private.h"
#include "include/tzdb/tzdb_private.h"

// Contains a parser for the IANA time zone data files.
//
// These files can be found at https://data.iana.org/time-zones/ and are in the
// public domain. Information regarding the input can be found at
// https://data.iana.org/time-zones/tz-how-to.html and
// https://man7.org/linux/man-pages/man8/zic.8.html.
//
// As indicated at https://howardhinnant.github.io/date/tz.html#Installation
// For Windows another file seems to be required
// https://raw.githubusercontent.com/unicode-org/cldr/master/common/supplemental/windowsZones.xml
// This file seems to contain the mapping of Windows time zone name to IANA
// time zone names.
//
// However this article mentions another way to do the mapping on Windows
// https://devblogs.microsoft.com/oldnewthing/20210527-00/?p=105255
// This requires Windows 10 Version 1903, which was released in May of 2019
// and considered end of life in December 2020
// https://learn.microsoft.com/en-us/lifecycle/announcements/windows-10-1903-end-of-servicing
//
// TODO TZDB Implement the Windows mapping in tzdb::current_zone

_LIBCPP_BEGIN_NAMESPACE_STD

namespace chrono {

// This function is weak so it can be overriden in the tests. The
// declaration is in the test header test/support/test_tzdb.h
_LIBCPP_WEAK string_view __libcpp_tzdb_directory() {
#if defined(__linux__)
  return "/usr/share/zoneinfo/";
#else
#  error "unknown path to the IANA Time Zone Database"
#endif
}

//===----------------------------------------------------------------------===//
//                           Details
//===----------------------------------------------------------------------===//

[[nodiscard]] static bool __is_whitespace(int __c) { return __c == ' ' || __c == '\t'; }

static void __skip_optional_whitespace(istream& __input) {
  while (chrono::__is_whitespace(__input.peek()))
    __input.get();
}

static void __skip_mandatory_whitespace(istream& __input) {
  if (!chrono::__is_whitespace(__input.get()))
    std::__throw_runtime_error("corrupt tzdb: expected whitespace");

  chrono::__skip_optional_whitespace(__input);
}

[[nodiscard]] static bool __is_eol(int __c) { return __c == '\n' || __c == std::char_traits<char>::eof(); }

static void __skip_line(istream& __input) {
  while (!chrono::__is_eol(__input.peek())) {
    __input.get();
  }
  __input.get();
}

static void __skip(istream& __input, char __suffix) {
  if (std::tolower(__input.peek()) == __suffix)
    __input.get();
}

static void __skip(istream& __input, string_view __suffix) {
  for (auto __c : __suffix)
    if (std::tolower(__input.peek()) == __c)
      __input.get();
}

static void __matches(istream& __input, char __expected) {
  if (std::tolower(__input.get()) != __expected)
    std::__throw_runtime_error((string("corrupt tzdb: expected character '") + __expected + '\'').c_str());
}

static void __matches(istream& __input, string_view __expected) {
  for (auto __c : __expected)
    if (std::tolower(__input.get()) != __c)
      std::__throw_runtime_error((string("corrupt tzdb: expected string '") + string(__expected) + '\'').c_str());
}

[[nodiscard]] static string __parse_string(istream& __input) {
  string __result;
  while (true) {
    int __c = __input.get();
    switch (__c) {
    case ' ':
    case '\t':
    case '\n':
      __input.unget();
      [[fallthrough]];
    case istream::traits_type::eof():
      if (__result.empty())
        std::__throw_runtime_error("corrupt tzdb: expected a string");

      return __result;

    default:
      __result.push_back(__c);
    }
  }
}

[[nodiscard]] static int64_t __parse_integral(istream& __input, bool __leading_zero_allowed) {
  int64_t __result = __input.get();
  if (__leading_zero_allowed) {
    if (__result < '0' || __result > '9')
      std::__throw_runtime_error("corrupt tzdb: expected a digit");
  } else {
    if (__result < '1' || __result > '9')
      std::__throw_runtime_error("corrupt tzdb: expected a non-zero digit");
  }
  __result -= '0';
  while (true) {
    if (__input.peek() < '0' || __input.peek() > '9')
      return __result;

    // In order to avoid possible overflows we limit the accepted range.
    // Most values parsed are expected to be very small:
    // - 8784 hours in a year
    // - 31 days in a month
    // - year no real maximum, these values are expected to be less than
    //   the range of the year type.
    //
    // However the leapseconds use a seconds after epoch value. Using an
    // int would run into an overflow in 2038. By using a 64-bit value
    // the range is large enough for the bilions of years. Limiting that
    // range slightly to make the code easier is not an issue.
    if (__result > (std::numeric_limits<int64_t>::max() / 16))
      std::__throw_runtime_error("corrupt tzdb: integral too large");

    __result *= 10;
    __result += __input.get() - '0';
  }
}

//===----------------------------------------------------------------------===//
//                          Calendar
//===----------------------------------------------------------------------===//

[[nodiscard]] static day __parse_day(istream& __input) {
  unsigned __result = chrono::__parse_integral(__input, false);
  if (__result > 31)
    std::__throw_runtime_error("corrupt tzdb day: value too large");
  return day{__result};
}

[[nodiscard]] static weekday __parse_weekday(istream& __input) {
  // TZDB allows the shortest unique name.
  switch (std::tolower(__input.get())) {
  case 'f':
    chrono::__skip(__input, "riday");
    return Friday;

  case 'm':
    chrono::__skip(__input, "onday");
    return Monday;

  case 's':
    switch (std::tolower(__input.get())) {
    case 'a':
      chrono::__skip(__input, "turday");
      return Saturday;

    case 'u':
      chrono::__skip(__input, "nday");
      return Sunday;
    }
    break;

  case 't':
    switch (std::tolower(__input.get())) {
    case 'h':
      chrono::__skip(__input, "ursday");
      return Thursday;

    case 'u':
      chrono::__skip(__input, "esday");
      return Tuesday;
    }
    break;
  case 'w':
    chrono::__skip(__input, "ednesday");
    return Wednesday;
  }

  std::__throw_runtime_error("corrupt tzdb weekday: invalid name");
}

[[nodiscard]] static month __parse_month(istream& __input) {
  // TZDB allows the shortest unique name.
  switch (std::tolower(__input.get())) {
  case 'a':
    switch (std::tolower(__input.get())) {
    case 'p':
      chrono::__skip(__input, "ril");
      return April;

    case 'u':
      chrono::__skip(__input, "gust");
      return August;
    }
    break;

  case 'd':
    chrono::__skip(__input, "ecember");
    return December;

  case 'f':
    chrono::__skip(__input, "ebruary");
    return February;

  case 'j':
    switch (std::tolower(__input.get())) {
    case 'a':
      chrono::__skip(__input, "nuary");
      return January;

    case 'u':
      switch (std::tolower(__input.get())) {
      case 'n':
        chrono::__skip(__input, 'e');
        return June;

      case 'l':
        chrono::__skip(__input, 'y');
        return July;
      }
    }
    break;

  case 'm':
    if (std::tolower(__input.get()) == 'a')
      switch (std::tolower(__input.get())) {
      case 'y':
        return May;

      case 'r':
        chrono::__skip(__input, "ch");
        return March;
      }
    break;

  case 'n':
    chrono::__skip(__input, "ovember");
    return November;

  case 'o':
    chrono::__skip(__input, "ctober");
    return October;

  case 's':
    chrono::__skip(__input, "eptember");
    return September;
  }
  std::__throw_runtime_error("corrupt tzdb month: invalid name");
}

[[nodiscard]] static year __parse_year_value(istream& __input) {
  bool __negative = __input.peek() == '-';
  if (__negative) [[unlikely]]
    __input.get();

  int64_t __result = __parse_integral(__input, true);
  if (__result > static_cast<int>(year::max())) {
    if (__negative)
      std::__throw_runtime_error("corrupt tzdb year: year is less than the minimum");

    std::__throw_runtime_error("corrupt tzdb year: year is greater than the maximum");
  }

  return year{static_cast<int>(__negative ? -__result : __result)};
}

[[nodiscard]] static year __parse_year(istream& __input) {
  if (std::tolower(__input.peek()) != 'm') [[likely]]
    return chrono::__parse_year_value(__input);

  __input.get();
  switch (std::tolower(__input.peek())) {
  case 'i':
    __input.get();
    chrono::__skip(__input, 'n');
    [[fallthrough]];

  case ' ':
    // The m is minimum, even when that is ambiguous.
    return year::min();

  case 'a':
    __input.get();
    chrono::__skip(__input, 'x');
    return year::max();
  }

  std::__throw_runtime_error("corrupt tzdb year: expected 'min' or 'max'");
}

//===----------------------------------------------------------------------===//
//                        TZDB fields
//===----------------------------------------------------------------------===//

[[nodiscard]] static year __parse_to(istream& __input, year __only) {
  if (std::tolower(__input.peek()) != 'o')
    return chrono::__parse_year(__input);

  __input.get();
  chrono::__skip(__input, "nly");
  return __only;
}

[[nodiscard]] static __tz::__constrained_weekday::__comparison_t __parse_comparison(istream& __input) {
  switch (__input.get()) {
  case '>':
    chrono::__matches(__input, '=');
    return __tz::__constrained_weekday::__ge;

  case '<':
    chrono::__matches(__input, '=');
    return __tz::__constrained_weekday::__le;
  }
  std::__throw_runtime_error("corrupt tzdb on: expected '>=' or '<='");
}

[[nodiscard]] static __tz::__on __parse_on(istream& __input) {
  if (std::isdigit(__input.peek()))
    return chrono::__parse_day(__input);

  if (std::tolower(__input.peek()) == 'l') {
    chrono::__matches(__input, "last");
    return weekday_last(chrono::__parse_weekday(__input));
  }

  return __tz::__constrained_weekday{
      chrono::__parse_weekday(__input), chrono::__parse_comparison(__input), chrono::__parse_day(__input)};
}

[[nodiscard]] static seconds __parse_duration(istream& __input) {
  seconds __result{0};
  int __c         = __input.peek();
  bool __negative = __c == '-';
  if (__negative) {
    __input.get();
    // Negative is either a negative value or a single -.
    // The latter means 0 and the parsing is complete.
    if (!std::isdigit(__input.peek()))
      return __result;
  }

  __result += hours(__parse_integral(__input, true));
  if (__input.peek() != ':')
    return __negative ? -__result : __result;

  __input.get();
  __result += minutes(__parse_integral(__input, true));
  if (__input.peek() != ':')
    return __negative ? -__result : __result;

  __input.get();
  __result += seconds(__parse_integral(__input, true));
  if (__input.peek() != '.')
    return __negative ? -__result : __result;

  __input.get();
  (void)__parse_integral(__input, true); // Truncate the digits.

  return __negative ? -__result : __result;
}

[[nodiscard]] static __tz::__clock __parse_clock(istream& __input) {
  switch (__input.get()) { // case sensitive
  case 'w':
    return __tz::__clock::__local;
  case 's':
    return __tz::__clock::__standard;

  case 'u':
  case 'g':
  case 'z':
    return __tz::__clock::__universal;
  }

  __input.unget();
  return __tz::__clock::__local;
}

[[nodiscard]] static bool __parse_dst(istream& __input, seconds __offset) {
  switch (__input.get()) { // case sensitive
  case 's':
    return false;

  case 'd':
    return true;
  }

  __input.unget();
  return __offset != 0s;
}

[[nodiscard]] static __tz::__at __parse_at(istream& __input) {
  return {__parse_duration(__input), __parse_clock(__input)};
}

[[nodiscard]] static __tz::__save __parse_save(istream& __input) {
  seconds __time = chrono::__parse_duration(__input);
  return {__time, chrono::__parse_dst(__input, __time)};
}

[[nodiscard]] static string __parse_letters(istream& __input) {
  string __result = __parse_string(__input);
  // Canonicalize "-" to "" since they are equivalent in the specification.
  return __result != "-" ? __result : "";
}

[[nodiscard]] static __tz::__continuation::__rules_t __parse_rules(istream& __input) {
  int __c = __input.peek();
  // A single -  is not a SAVE but a special case.
  if (__c == '-') {
    __input.get();
    if (chrono::__is_whitespace(__input.peek()))
      return monostate{};
    __input.unget();
    return chrono::__parse_save(__input);
  }

  if (std::isdigit(__c) || __c == '+')
    return chrono::__parse_save(__input);

  return chrono::__parse_string(__input);
}

[[nodiscard]] static __tz::__continuation __parse_continuation(__tz::__rules_storage_type& __rules, istream& __input) {
  __tz::__continuation __result;

  __result.__rule_database_ = std::addressof(__rules);

  // Note STDOFF is specified as
  //   This field has the same format as the AT and SAVE fields of rule lines;
  // These fields have different suffix letters, these letters seem
  // not to be used so do not allow any of them.

  __result.__stdoff = chrono::__parse_duration(__input);
  chrono::__skip_mandatory_whitespace(__input);
  __result.__rules = chrono::__parse_rules(__input);
  chrono::__skip_mandatory_whitespace(__input);
  __result.__format = chrono::__parse_string(__input);
  chrono::__skip_optional_whitespace(__input);

  if (chrono::__is_eol(__input.peek()))
    return __result;
  __result.__year = chrono::__parse_year(__input);
  chrono::__skip_optional_whitespace(__input);

  if (chrono::__is_eol(__input.peek()))
    return __result;
  __result.__in = chrono::__parse_month(__input);
  chrono::__skip_optional_whitespace(__input);

  if (chrono::__is_eol(__input.peek()))
    return __result;
  __result.__on = chrono::__parse_on(__input);
  chrono::__skip_optional_whitespace(__input);

  if (chrono::__is_eol(__input.peek()))
    return __result;
  __result.__at = __parse_at(__input);

  return __result;
}

//===----------------------------------------------------------------------===//
//                   Time Zone Database entries
//===----------------------------------------------------------------------===//

static string __parse_version(istream& __input) {
  // The first line in tzdata.zi contains
  //    # version YYYYw
  // The parser expects this pattern
  // #\s*version\s*\(.*)
  // This part is not documented.
  chrono::__matches(__input, '#');
  chrono::__skip_optional_whitespace(__input);
  chrono::__matches(__input, "version");
  chrono::__skip_mandatory_whitespace(__input);
  return chrono::__parse_string(__input);
}

[[nodiscard]]
static __tz::__rule& __create_entry(__tz::__rules_storage_type& __rules, const string& __name) {
  auto __result = [&]() -> __tz::__rule& {
    auto& __rule = __rules.emplace_back(__name, vector<__tz::__rule>{});
    return __rule.second.emplace_back();
  };

  if (__rules.empty())
    return __result();

  // Typically rules are in contiguous order in the database.
  // But there are exceptions, some rules are interleaved.
  if (__rules.back().first == __name)
    return __rules.back().second.emplace_back();

  if (auto __it = ranges::find(__rules, __name, [](const auto& __r) { return __r.first; });
      __it != ranges::end(__rules))
    return __it->second.emplace_back();

  return __result();
}

static void __parse_rule(tzdb& __tzdb, __tz::__rules_storage_type& __rules, istream& __input) {
  chrono::__skip_mandatory_whitespace(__input);
  string __name = chrono::__parse_string(__input);

  __tz::__rule& __rule = __create_entry(__rules, __name);

  chrono::__skip_mandatory_whitespace(__input);
  __rule.__from = chrono::__parse_year(__input);
  chrono::__skip_mandatory_whitespace(__input);
  __rule.__to = chrono::__parse_to(__input, __rule.__from);
  chrono::__skip_mandatory_whitespace(__input);
  chrono::__matches(__input, '-');
  chrono::__skip_mandatory_whitespace(__input);
  __rule.__in = chrono::__parse_month(__input);
  chrono::__skip_mandatory_whitespace(__input);
  __rule.__on = chrono::__parse_on(__input);
  chrono::__skip_mandatory_whitespace(__input);
  __rule.__at = __parse_at(__input);
  chrono::__skip_mandatory_whitespace(__input);
  __rule.__save = __parse_save(__input);
  chrono::__skip_mandatory_whitespace(__input);
  __rule.__letters = chrono::__parse_letters(__input);
  chrono::__skip_line(__input);
}

static void __parse_zone(tzdb& __tzdb, __tz::__rules_storage_type& __rules, istream& __input) {
  chrono::__skip_mandatory_whitespace(__input);
  auto __p                                      = std::make_unique<time_zone::__impl>(chrono::__parse_string(__input));
  vector<__tz::__continuation>& __continuations = __p->__continuations();
  chrono::__skip_mandatory_whitespace(__input);

  do {
    // The first line must be valid, continuations are optional.
    __continuations.emplace_back(__parse_continuation(__rules, __input));
    chrono::__skip_line(__input);
    chrono::__skip_optional_whitespace(__input);
  } while (std::isdigit(__input.peek()) || __input.peek() == '-');

  __tzdb.zones.emplace_back(time_zone::__create(std::move(__p)));
}

static void __parse_link(tzdb& __tzdb, istream& __input) {
  chrono::__skip_mandatory_whitespace(__input);
  string __target = chrono::__parse_string(__input);
  chrono::__skip_mandatory_whitespace(__input);
  string __name = chrono::__parse_string(__input);
  chrono::__skip_line(__input);

  __tzdb.links.emplace_back(time_zone_link::__constructor_tag{}, std::move(__name), std::move(__target));
}

static void __parse_tzdata(tzdb& __db, __tz::__rules_storage_type& __rules, istream& __input) {
  while (true) {
    int __c = std::tolower(__input.get());

    switch (__c) {
    case istream::traits_type::eof():
      return;

    case ' ':
    case '\t':
    case '\n':
      break;

    case '#':
      chrono::__skip_line(__input);
      break;

    case 'r':
      chrono::__skip(__input, "ule");
      chrono::__parse_rule(__db, __rules, __input);
      break;

    case 'z':
      chrono::__skip(__input, "one");
      chrono::__parse_zone(__db, __rules, __input);
      break;

    case 'l':
      chrono::__skip(__input, "ink");
      chrono::__parse_link(__db, __input);
      break;

    default:
      std::__throw_runtime_error("corrupt tzdb: unexpected input");
    }
  }
}

static void __parse_leap_seconds(vector<leap_second>& __leap_seconds, istream&& __input) {
  // The file stores dates since 1 January 1900, 00:00:00, we want
  // seconds since 1 January 1970.
  constexpr auto __offset = sys_days{1970y / January / 1} - sys_days{1900y / January / 1};

  while (true) {
    switch (__input.peek()) {
    case istream::traits_type::eof():
      return;

    case ' ':
    case '\t':
    case '\n':
      __input.get();
      continue;

    case '#':
      chrono::__skip_line(__input);
      continue;
    }

    sys_seconds __date = sys_seconds{seconds{chrono::__parse_integral(__input, false)}} - __offset;
    chrono::__skip_mandatory_whitespace(__input);
    seconds __value{chrono::__parse_integral(__input, false)};
    chrono::__skip_line(__input);

    __leap_seconds.emplace_back(leap_second::__constructor_tag{}, __date, __value);
  }
}

void __init_tzdb(tzdb& __tzdb, __tz::__rules_storage_type& __rules) {
  filesystem::path __root = chrono::__libcpp_tzdb_directory();
  ifstream __tzdata{__root / "tzdata.zi"};

  __tzdb.version = chrono::__parse_version(__tzdata);
  chrono::__parse_tzdata(__tzdb, __rules, __tzdata);
  std::ranges::sort(__tzdb.zones);
  std::ranges::sort(__tzdb.links);
  std::ranges::sort(__rules, {}, [](const auto& p) { return p.first; });

  // There are two files with the leap second information
  // - leapseconds as specified by zic
  // - leap-seconds.list the source data
  // The latter is much easier to parse, it seems Howard shares that
  // opinion.
  chrono::__parse_leap_seconds(__tzdb.leap_seconds, ifstream{__root / "leap-seconds.list"});
  // The Standard requires the leap seconds to be sorted. The file
  // leap-seconds.list usually provides them in sorted order, but that is not
  // guaranteed so we ensure it here.
  std::ranges::sort(__tzdb.leap_seconds);
}

//===----------------------------------------------------------------------===//
//                           Public API
//===----------------------------------------------------------------------===//

_LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI tzdb_list& get_tzdb_list() {
  static tzdb_list __result{new tzdb_list::__impl()};
  return __result;
}

_LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI const tzdb& reload_tzdb() {
  if (chrono::remote_version() == chrono::get_tzdb().version)
    return chrono::get_tzdb();

  return chrono::get_tzdb_list().__implementation().__load();
}

_LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI string remote_version() {
  filesystem::path __root = chrono::__libcpp_tzdb_directory();
  ifstream __tzdata{__root / "tzdata.zi"};
  return chrono::__parse_version(__tzdata);
}

} // namespace chrono

_LIBCPP_END_NAMESPACE_STD