| 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(c: __input.peek())) |
| 70 | __input.get(); |
| 71 | } |
| 72 | |
| 73 | static void __skip_mandatory_whitespace(istream& __input) { |
| 74 | if (!chrono::__is_whitespace(c: __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(c: __input.peek())) { |
| 84 | __input.get(); |
| 85 | } |
| 86 | __input.get(); |
| 87 | } |
| 88 | |
| 89 | static void __skip(istream& __input, char __suffix) { |
| 90 | if (std::tolower(c: __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(c: __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(c: __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, leading_zero_allowed: 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(c: __input.get())) { |
| 189 | case 'f': |
| 190 | chrono::__skip(__input, suffix: "riday" ); |
| 191 | return Friday; |
| 192 | |
| 193 | case 'm': |
| 194 | chrono::__skip(__input, suffix: "onday" ); |
| 195 | return Monday; |
| 196 | |
| 197 | case 's': |
| 198 | switch (std::tolower(c: __input.get())) { |
| 199 | case 'a': |
| 200 | chrono::__skip(__input, suffix: "turday" ); |
| 201 | return Saturday; |
| 202 | |
| 203 | case 'u': |
| 204 | chrono::__skip(__input, suffix: "nday" ); |
| 205 | return Sunday; |
| 206 | } |
| 207 | break; |
| 208 | |
| 209 | case 't': |
| 210 | switch (std::tolower(c: __input.get())) { |
| 211 | case 'h': |
| 212 | chrono::__skip(__input, suffix: "ursday" ); |
| 213 | return Thursday; |
| 214 | |
| 215 | case 'u': |
| 216 | chrono::__skip(__input, suffix: "esday" ); |
| 217 | return Tuesday; |
| 218 | } |
| 219 | break; |
| 220 | case 'w': |
| 221 | chrono::__skip(__input, suffix: "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(c: __input.get())) { |
| 231 | case 'a': |
| 232 | switch (std::tolower(c: __input.get())) { |
| 233 | case 'p': |
| 234 | chrono::__skip(__input, suffix: "ril" ); |
| 235 | return April; |
| 236 | |
| 237 | case 'u': |
| 238 | chrono::__skip(__input, suffix: "gust" ); |
| 239 | return August; |
| 240 | } |
| 241 | break; |
| 242 | |
| 243 | case 'd': |
| 244 | chrono::__skip(__input, suffix: "ecember" ); |
| 245 | return December; |
| 246 | |
| 247 | case 'f': |
| 248 | chrono::__skip(__input, suffix: "ebruary" ); |
| 249 | return February; |
| 250 | |
| 251 | case 'j': |
| 252 | switch (std::tolower(c: __input.get())) { |
| 253 | case 'a': |
| 254 | chrono::__skip(__input, suffix: "nuary" ); |
| 255 | return January; |
| 256 | |
| 257 | case 'u': |
| 258 | switch (std::tolower(c: __input.get())) { |
| 259 | case 'n': |
| 260 | chrono::__skip(__input, suffix: 'e'); |
| 261 | return June; |
| 262 | |
| 263 | case 'l': |
| 264 | chrono::__skip(__input, suffix: 'y'); |
| 265 | return July; |
| 266 | } |
| 267 | } |
| 268 | break; |
| 269 | |
| 270 | case 'm': |
| 271 | if (std::tolower(c: __input.get()) == 'a') |
| 272 | switch (std::tolower(c: __input.get())) { |
| 273 | case 'y': |
| 274 | return May; |
| 275 | |
| 276 | case 'r': |
| 277 | chrono::__skip(__input, suffix: "ch" ); |
| 278 | return March; |
| 279 | } |
| 280 | break; |
| 281 | |
| 282 | case 'n': |
| 283 | chrono::__skip(__input, suffix: "ovember" ); |
| 284 | return November; |
| 285 | |
| 286 | case 'o': |
| 287 | chrono::__skip(__input, suffix: "ctober" ); |
| 288 | return October; |
| 289 | |
| 290 | case 's': |
| 291 | chrono::__skip(__input, suffix: "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, leading_zero_allowed: 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(c: __input.peek()) != 'm') [[likely]] |
| 315 | return chrono::__parse_year_value(__input); |
| 316 | |
| 317 | __input.get(); |
| 318 | switch (std::tolower(c: __input.peek())) { |
| 319 | case 'i': |
| 320 | __input.get(); |
| 321 | chrono::__skip(__input, suffix: '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, suffix: '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(c: __input.peek()) != 'o') |
| 343 | return chrono::__parse_year(__input); |
| 344 | |
| 345 | __input.get(); |
| 346 | chrono::__skip(__input, suffix: "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, expected: '='); |
| 354 | return __tz::__constrained_weekday::__ge; |
| 355 | |
| 356 | case '<': |
| 357 | chrono::__matches(__input, expected: '='); |
| 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(c: __input.peek())) |
| 365 | return chrono::__parse_day(__input); |
| 366 | |
| 367 | if (std::tolower(c: __input.peek()) == 'l') { |
| 368 | chrono::__matches(__input, expected: "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(c: __input.peek())) |
| 385 | return __result; |
| 386 | } |
| 387 | |
| 388 | __result += hours(__parse_integral(__input, leading_zero_allowed: true)); |
| 389 | if (__input.peek() != ':') |
| 390 | return __negative ? -__result : __result; |
| 391 | |
| 392 | __input.get(); |
| 393 | __result += minutes(__parse_integral(__input, leading_zero_allowed: true)); |
| 394 | if (__input.peek() != ':') |
| 395 | return __negative ? -__result : __result; |
| 396 | |
| 397 | __input.get(); |
| 398 | __result += seconds(__parse_integral(__input, leading_zero_allowed: true)); |
| 399 | if (__input.peek() != '.') |
| 400 | return __negative ? -__result : __result; |
| 401 | |
| 402 | __input.get(); |
| 403 | (void)__parse_integral(__input, leading_zero_allowed: 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, offset: __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(c: __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(x&: __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(c: __input.peek())) |
| 488 | return __result; |
| 489 | __result.__year = chrono::__parse_year(__input); |
| 490 | chrono::__skip_optional_whitespace(__input); |
| 491 | |
| 492 | if (chrono::__is_eol(c: __input.peek())) |
| 493 | return __result; |
| 494 | __result.__in = chrono::__parse_month(__input); |
| 495 | chrono::__skip_optional_whitespace(__input); |
| 496 | |
| 497 | if (chrono::__is_eol(c: __input.peek())) |
| 498 | return __result; |
| 499 | __result.__on = chrono::__parse_on(__input); |
| 500 | chrono::__skip_optional_whitespace(__input); |
| 501 | |
| 502 | if (chrono::__is_eol(c: __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, expected: '#'); |
| 520 | chrono::__skip_optional_whitespace(__input); |
| 521 | chrono::__matches(__input, expected: "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(args: __name, args: 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, only: __rule.__from); |
| 558 | chrono::__skip_mandatory_whitespace(__input); |
| 559 | chrono::__matches(__input, expected: '-'); |
| 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>(args: chrono::__parse_string(__input), args&: __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(args: __parse_continuation(__rules, __input)); |
| 582 | chrono::__skip_line(__input); |
| 583 | chrono::__skip_optional_whitespace(__input); |
| 584 | } while (std::isdigit(c: __input.peek()) || __input.peek() == '-'); |
| 585 | |
| 586 | __tzdb.zones.emplace_back(args: time_zone::__create(p: 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(args: std::__private_constructor_tag{}, args: std::move(__name), args: 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(c: __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, suffix: "ule" ); |
| 618 | chrono::__parse_rule(tzdb&: __db, __rules, __input); |
| 619 | break; |
| 620 | |
| 621 | case 'z': |
| 622 | chrono::__skip(__input, suffix: "one" ); |
| 623 | chrono::__parse_zone(tzdb&: __db, __rules, __input); |
| 624 | break; |
| 625 | |
| 626 | case 'l': |
| 627 | chrono::__skip(__input, suffix: "ink" ); |
| 628 | chrono::__parse_link(tzdb&: __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, leading_zero_allowed: false)}} - __offset; |
| 665 | chrono::__skip_mandatory_whitespace(__input); |
| 666 | seconds __value{chrono::__parse_integral(__input, leading_zero_allowed: false)}; |
| 667 | chrono::__skip_line(__input); |
| 668 | |
| 669 | __entries.emplace_back(args&: __date, args&: __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 | args: std::__private_constructor_tag{}, args: __second.__timestamp, args: __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(input&: __tzdata); |
| 692 | chrono::__parse_tzdata(db&: __tzdb, __rules, input&: __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(leap_seconds&: __tzdb.leap_seconds, input: 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(name: "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(p: __path) || !filesystem::is_symlink(p: __path)) |
| 722 | return {}; |
| 723 | |
| 724 | filesystem::path __tz = filesystem::read_symlink(p: __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(p: "/etc" / __tz); |
| 729 | |
| 730 | return filesystem::relative(p: __tz, base: "/usr/share/zoneinfo/" ); |
| 731 | } |
| 732 | |
| 733 | [[nodiscard]] static string __current_zone_etc_timezone() { |
| 734 | filesystem::path __path = "/etc/timezone" ; |
| 735 | if (!filesystem::exists(p: __path)) |
| 736 | return {}; |
| 737 | |
| 738 | ifstream __f(__path); |
| 739 | string __name; |
| 740 | std::getline(is&: __f, str&: __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(tzdb: *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(input&: __tzdata); |
| 820 | } |
| 821 | |
| 822 | } // namespace chrono |
| 823 | |
| 824 | _LIBCPP_END_NAMESPACE_STD |
| 825 | |