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