Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/system/lib/libcxx/src/experimental/tzdb.cpp
6178 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 <__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
712
[[nodiscard]] static string __current_zone_environment() {
713
if (const char* __tz = std::getenv("TZ"))
714
return __tz;
715
716
return {};
717
}
718
719
[[nodiscard]] static string __current_zone_etc_localtime() {
720
filesystem::path __path = "/etc/localtime";
721
if (!filesystem::exists(__path) || !filesystem::is_symlink(__path))
722
return {};
723
724
filesystem::path __tz = filesystem::read_symlink(__path);
725
// The path may be a relative path, in that case convert it to an absolute
726
// path based on the proper initial directory.
727
if (__tz.is_relative())
728
__tz = filesystem::canonical("/etc" / __tz);
729
730
return filesystem::relative(__tz, "/usr/share/zoneinfo/");
731
}
732
733
[[nodiscard]] static string __current_zone_etc_timezone() {
734
filesystem::path __path = "/etc/timezone";
735
if (!filesystem::exists(__path))
736
return {};
737
738
ifstream __f(__path);
739
string __name;
740
std::getline(__f, __name);
741
return __name;
742
}
743
744
[[nodiscard]] static const time_zone* __current_zone_posix(const tzdb& tzdb) {
745
// On POSIX systems there are several ways to configure the time zone.
746
// In order of priority they are:
747
// - TZ environment variable
748
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08
749
// The documentation is unclear whether or not it's allowed to
750
// change time zone information. For example the TZ string
751
// MST7MDT
752
// this is an entry in tzdata.zi. The value
753
// MST
754
// is also an entry. Is it allowed to use the following?
755
// MST-3
756
// Even when this is valid there is no time_zone record in the
757
// database. Since the library would need to return a valid pointer,
758
// this means the library needs to allocate and leak a pointer.
759
//
760
// - The time zone name is the target of the symlink /etc/localtime
761
// relative to /usr/share/zoneinfo/
762
//
763
// - The file /etc/timezone. This text file contains the name of the time
764
// zone.
765
//
766
// On Linux systems it seems /etc/timezone is deprecated and being phased out.
767
// This file is used when /etc/localtime does not exist, or when it exists but
768
// is not a symlink. For more information and links see
769
// https://github.com/llvm/llvm-project/issues/105634
770
771
string __name = chrono::__current_zone_environment();
772
773
// Ignore invalid names in the environment.
774
if (!__name.empty())
775
if (const time_zone* __result = tzdb.__locate_zone(__name))
776
return __result;
777
778
__name = chrono::__current_zone_etc_localtime();
779
if (__name.empty())
780
__name = chrono::__current_zone_etc_timezone();
781
782
if (__name.empty())
783
std::__throw_runtime_error("tzdb: unable to determine the name of the current time zone");
784
785
if (const time_zone* __result = tzdb.__locate_zone(__name))
786
return __result;
787
788
std::__throw_runtime_error(("tzdb: the time zone '" + __name + "' is not found in the database").c_str());
789
}
790
#endif // ifdef _WIN32
791
792
//===----------------------------------------------------------------------===//
793
// Public API
794
//===----------------------------------------------------------------------===//
795
796
_LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI tzdb_list& get_tzdb_list() {
797
static tzdb_list __result{new tzdb_list::__impl()};
798
return __result;
799
}
800
801
[[nodiscard]] _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI const time_zone* tzdb::__current_zone() const {
802
#ifdef _WIN32
803
return chrono::__current_zone_windows(*this);
804
#else
805
return chrono::__current_zone_posix(*this);
806
#endif
807
}
808
809
_LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI const tzdb& reload_tzdb() {
810
if (chrono::remote_version() == chrono::get_tzdb().version)
811
return chrono::get_tzdb();
812
813
return chrono::get_tzdb_list().__implementation().__load();
814
}
815
816
_LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI string remote_version() {
817
filesystem::path __root = chrono::__libcpp_tzdb_directory();
818
ifstream __tzdata{__root / "tzdata.zi"};
819
return chrono::__parse_version(__tzdata);
820
}
821
822
} // namespace chrono
823
824
_LIBCPP_END_NAMESPACE_STD
825
826