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