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