| //===----------------------------------------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| // For information see https://libcxx.llvm.org/DesignDocs/TimeZone.html |
| |
| #include <algorithm> |
| #include <chrono> |
| #include <filesystem> |
| #include <fstream> |
| #include <stdexcept> |
| #include <string> |
| |
| #include "include/tzdb/time_zone_private.h" |
| #include "include/tzdb/types_private.h" |
| #include "include/tzdb/tzdb_list_private.h" |
| #include "include/tzdb/tzdb_private.h" |
| |
| // Contains a parser for the IANA time zone data files. |
| // |
| // These files can be found at https://data.iana.org/time-zones/ and are in the |
| // public domain. Information regarding the input can be found at |
| // https://data.iana.org/time-zones/tz-how-to.html and |
| // https://man7.org/linux/man-pages/man8/zic.8.html. |
| // |
| // As indicated at https://howardhinnant.github.io/date/tz.html#Installation |
| // For Windows another file seems to be required |
| // https://raw.githubusercontent.com/unicode-org/cldr/master/common/supplemental/windowsZones.xml |
| // This file seems to contain the mapping of Windows time zone name to IANA |
| // time zone names. |
| // |
| // However this article mentions another way to do the mapping on Windows |
| // https://devblogs.microsoft.com/oldnewthing/20210527-00/?p=105255 |
| // This requires Windows 10 Version 1903, which was released in May of 2019 |
| // and considered end of life in December 2020 |
| // https://learn.microsoft.com/en-us/lifecycle/announcements/windows-10-1903-end-of-servicing |
| // |
| // TODO TZDB Implement the Windows mapping in tzdb::current_zone |
| |
| _LIBCPP_BEGIN_NAMESPACE_STD |
| |
| namespace chrono { |
| |
| // This function is weak so it can be overriden in the tests. The |
| // declaration is in the test header test/support/test_tzdb.h |
| _LIBCPP_WEAK string_view __libcpp_tzdb_directory() { |
| #if defined(__linux__) |
| return "/usr/share/zoneinfo/"; |
| #else |
| # error "unknown path to the IANA Time Zone Database" |
| #endif |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Details |
| //===----------------------------------------------------------------------===// |
| |
| [[nodiscard]] static bool __is_whitespace(int __c) { return __c == ' ' || __c == '\t'; } |
| |
| static void __skip_optional_whitespace(istream& __input) { |
| while (chrono::__is_whitespace(__input.peek())) |
| __input.get(); |
| } |
| |
| static void __skip_mandatory_whitespace(istream& __input) { |
| if (!chrono::__is_whitespace(__input.get())) |
| std::__throw_runtime_error("corrupt tzdb: expected whitespace"); |
| |
| chrono::__skip_optional_whitespace(__input); |
| } |
| |
| [[nodiscard]] static bool __is_eol(int __c) { return __c == '\n' || __c == std::char_traits<char>::eof(); } |
| |
| static void __skip_line(istream& __input) { |
| while (!chrono::__is_eol(__input.peek())) { |
| __input.get(); |
| } |
| __input.get(); |
| } |
| |
| static void __skip(istream& __input, char __suffix) { |
| if (std::tolower(__input.peek()) == __suffix) |
| __input.get(); |
| } |
| |
| static void __skip(istream& __input, string_view __suffix) { |
| for (auto __c : __suffix) |
| if (std::tolower(__input.peek()) == __c) |
| __input.get(); |
| } |
| |
| static void __matches(istream& __input, char __expected) { |
| if (std::tolower(__input.get()) != __expected) |
| std::__throw_runtime_error((string("corrupt tzdb: expected character '") + __expected + '\'').c_str()); |
| } |
| |
| static void __matches(istream& __input, string_view __expected) { |
| for (auto __c : __expected) |
| if (std::tolower(__input.get()) != __c) |
| std::__throw_runtime_error((string("corrupt tzdb: expected string '") + string(__expected) + '\'').c_str()); |
| } |
| |
| [[nodiscard]] static string __parse_string(istream& __input) { |
| string __result; |
| while (true) { |
| int __c = __input.get(); |
| switch (__c) { |
| case ' ': |
| case '\t': |
| case '\n': |
| __input.unget(); |
| [[fallthrough]]; |
| case istream::traits_type::eof(): |
| if (__result.empty()) |
| std::__throw_runtime_error("corrupt tzdb: expected a string"); |
| |
| return __result; |
| |
| default: |
| __result.push_back(__c); |
| } |
| } |
| } |
| |
| [[nodiscard]] static int64_t __parse_integral(istream& __input, bool __leading_zero_allowed) { |
| int64_t __result = __input.get(); |
| if (__leading_zero_allowed) { |
| if (__result < '0' || __result > '9') |
| std::__throw_runtime_error("corrupt tzdb: expected a digit"); |
| } else { |
| if (__result < '1' || __result > '9') |
| std::__throw_runtime_error("corrupt tzdb: expected a non-zero digit"); |
| } |
| __result -= '0'; |
| while (true) { |
| if (__input.peek() < '0' || __input.peek() > '9') |
| return __result; |
| |
| // In order to avoid possible overflows we limit the accepted range. |
| // Most values parsed are expected to be very small: |
| // - 8784 hours in a year |
| // - 31 days in a month |
| // - year no real maximum, these values are expected to be less than |
| // the range of the year type. |
| // |
| // However the leapseconds use a seconds after epoch value. Using an |
| // int would run into an overflow in 2038. By using a 64-bit value |
| // the range is large enough for the bilions of years. Limiting that |
| // range slightly to make the code easier is not an issue. |
| if (__result > (std::numeric_limits<int64_t>::max() / 16)) |
| std::__throw_runtime_error("corrupt tzdb: integral too large"); |
| |
| __result *= 10; |
| __result += __input.get() - '0'; |
| } |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Calendar |
| //===----------------------------------------------------------------------===// |
| |
| [[nodiscard]] static day __parse_day(istream& __input) { |
| unsigned __result = chrono::__parse_integral(__input, false); |
| if (__result > 31) |
| std::__throw_runtime_error("corrupt tzdb day: value too large"); |
| return day{__result}; |
| } |
| |
| [[nodiscard]] static weekday __parse_weekday(istream& __input) { |
| // TZDB allows the shortest unique name. |
| switch (std::tolower(__input.get())) { |
| case 'f': |
| chrono::__skip(__input, "riday"); |
| return Friday; |
| |
| case 'm': |
| chrono::__skip(__input, "onday"); |
| return Monday; |
| |
| case 's': |
| switch (std::tolower(__input.get())) { |
| case 'a': |
| chrono::__skip(__input, "turday"); |
| return Saturday; |
| |
| case 'u': |
| chrono::__skip(__input, "nday"); |
| return Sunday; |
| } |
| break; |
| |
| case 't': |
| switch (std::tolower(__input.get())) { |
| case 'h': |
| chrono::__skip(__input, "ursday"); |
| return Thursday; |
| |
| case 'u': |
| chrono::__skip(__input, "esday"); |
| return Tuesday; |
| } |
| break; |
| case 'w': |
| chrono::__skip(__input, "ednesday"); |
| return Wednesday; |
| } |
| |
| std::__throw_runtime_error("corrupt tzdb weekday: invalid name"); |
| } |
| |
| [[nodiscard]] static month __parse_month(istream& __input) { |
| // TZDB allows the shortest unique name. |
| switch (std::tolower(__input.get())) { |
| case 'a': |
| switch (std::tolower(__input.get())) { |
| case 'p': |
| chrono::__skip(__input, "ril"); |
| return April; |
| |
| case 'u': |
| chrono::__skip(__input, "gust"); |
| return August; |
| } |
| break; |
| |
| case 'd': |
| chrono::__skip(__input, "ecember"); |
| return December; |
| |
| case 'f': |
| chrono::__skip(__input, "ebruary"); |
| return February; |
| |
| case 'j': |
| switch (std::tolower(__input.get())) { |
| case 'a': |
| chrono::__skip(__input, "nuary"); |
| return January; |
| |
| case 'u': |
| switch (std::tolower(__input.get())) { |
| case 'n': |
| chrono::__skip(__input, 'e'); |
| return June; |
| |
| case 'l': |
| chrono::__skip(__input, 'y'); |
| return July; |
| } |
| } |
| break; |
| |
| case 'm': |
| if (std::tolower(__input.get()) == 'a') |
| switch (std::tolower(__input.get())) { |
| case 'y': |
| return May; |
| |
| case 'r': |
| chrono::__skip(__input, "ch"); |
| return March; |
| } |
| break; |
| |
| case 'n': |
| chrono::__skip(__input, "ovember"); |
| return November; |
| |
| case 'o': |
| chrono::__skip(__input, "ctober"); |
| return October; |
| |
| case 's': |
| chrono::__skip(__input, "eptember"); |
| return September; |
| } |
| std::__throw_runtime_error("corrupt tzdb month: invalid name"); |
| } |
| |
| [[nodiscard]] static year __parse_year_value(istream& __input) { |
| bool __negative = __input.peek() == '-'; |
| if (__negative) [[unlikely]] |
| __input.get(); |
| |
| int64_t __result = __parse_integral(__input, true); |
| if (__result > static_cast<int>(year::max())) { |
| if (__negative) |
| std::__throw_runtime_error("corrupt tzdb year: year is less than the minimum"); |
| |
| std::__throw_runtime_error("corrupt tzdb year: year is greater than the maximum"); |
| } |
| |
| return year{static_cast<int>(__negative ? -__result : __result)}; |
| } |
| |
| [[nodiscard]] static year __parse_year(istream& __input) { |
| if (std::tolower(__input.peek()) != 'm') [[likely]] |
| return chrono::__parse_year_value(__input); |
| |
| __input.get(); |
| switch (std::tolower(__input.peek())) { |
| case 'i': |
| __input.get(); |
| chrono::__skip(__input, 'n'); |
| [[fallthrough]]; |
| |
| case ' ': |
| // The m is minimum, even when that is ambiguous. |
| return year::min(); |
| |
| case 'a': |
| __input.get(); |
| chrono::__skip(__input, 'x'); |
| return year::max(); |
| } |
| |
| std::__throw_runtime_error("corrupt tzdb year: expected 'min' or 'max'"); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // TZDB fields |
| //===----------------------------------------------------------------------===// |
| |
| [[nodiscard]] static year __parse_to(istream& __input, year __only) { |
| if (std::tolower(__input.peek()) != 'o') |
| return chrono::__parse_year(__input); |
| |
| __input.get(); |
| chrono::__skip(__input, "nly"); |
| return __only; |
| } |
| |
| [[nodiscard]] static __tz::__constrained_weekday::__comparison_t __parse_comparison(istream& __input) { |
| switch (__input.get()) { |
| case '>': |
| chrono::__matches(__input, '='); |
| return __tz::__constrained_weekday::__ge; |
| |
| case '<': |
| chrono::__matches(__input, '='); |
| return __tz::__constrained_weekday::__le; |
| } |
| std::__throw_runtime_error("corrupt tzdb on: expected '>=' or '<='"); |
| } |
| |
| [[nodiscard]] static __tz::__on __parse_on(istream& __input) { |
| if (std::isdigit(__input.peek())) |
| return chrono::__parse_day(__input); |
| |
| if (std::tolower(__input.peek()) == 'l') { |
| chrono::__matches(__input, "last"); |
| return weekday_last(chrono::__parse_weekday(__input)); |
| } |
| |
| return __tz::__constrained_weekday{ |
| chrono::__parse_weekday(__input), chrono::__parse_comparison(__input), chrono::__parse_day(__input)}; |
| } |
| |
| [[nodiscard]] static seconds __parse_duration(istream& __input) { |
| seconds __result{0}; |
| int __c = __input.peek(); |
| bool __negative = __c == '-'; |
| if (__negative) { |
| __input.get(); |
| // Negative is either a negative value or a single -. |
| // The latter means 0 and the parsing is complete. |
| if (!std::isdigit(__input.peek())) |
| return __result; |
| } |
| |
| __result += hours(__parse_integral(__input, true)); |
| if (__input.peek() != ':') |
| return __negative ? -__result : __result; |
| |
| __input.get(); |
| __result += minutes(__parse_integral(__input, true)); |
| if (__input.peek() != ':') |
| return __negative ? -__result : __result; |
| |
| __input.get(); |
| __result += seconds(__parse_integral(__input, true)); |
| if (__input.peek() != '.') |
| return __negative ? -__result : __result; |
| |
| __input.get(); |
| (void)__parse_integral(__input, true); // Truncate the digits. |
| |
| return __negative ? -__result : __result; |
| } |
| |
| [[nodiscard]] static __tz::__clock __parse_clock(istream& __input) { |
| switch (__input.get()) { // case sensitive |
| case 'w': |
| return __tz::__clock::__local; |
| case 's': |
| return __tz::__clock::__standard; |
| |
| case 'u': |
| case 'g': |
| case 'z': |
| return __tz::__clock::__universal; |
| } |
| |
| __input.unget(); |
| return __tz::__clock::__local; |
| } |
| |
| [[nodiscard]] static bool __parse_dst(istream& __input, seconds __offset) { |
| switch (__input.get()) { // case sensitive |
| case 's': |
| return false; |
| |
| case 'd': |
| return true; |
| } |
| |
| __input.unget(); |
| return __offset != 0s; |
| } |
| |
| [[nodiscard]] static __tz::__at __parse_at(istream& __input) { |
| return {__parse_duration(__input), __parse_clock(__input)}; |
| } |
| |
| [[nodiscard]] static __tz::__save __parse_save(istream& __input) { |
| seconds __time = chrono::__parse_duration(__input); |
| return {__time, chrono::__parse_dst(__input, __time)}; |
| } |
| |
| [[nodiscard]] static string __parse_letters(istream& __input) { |
| string __result = __parse_string(__input); |
| // Canonicalize "-" to "" since they are equivalent in the specification. |
| return __result != "-" ? __result : ""; |
| } |
| |
| [[nodiscard]] static __tz::__continuation::__rules_t __parse_rules(istream& __input) { |
| int __c = __input.peek(); |
| // A single - is not a SAVE but a special case. |
| if (__c == '-') { |
| __input.get(); |
| if (chrono::__is_whitespace(__input.peek())) |
| return monostate{}; |
| __input.unget(); |
| return chrono::__parse_save(__input); |
| } |
| |
| if (std::isdigit(__c) || __c == '+') |
| return chrono::__parse_save(__input); |
| |
| return chrono::__parse_string(__input); |
| } |
| |
| [[nodiscard]] static __tz::__continuation __parse_continuation(__tz::__rules_storage_type& __rules, istream& __input) { |
| __tz::__continuation __result; |
| |
| __result.__rule_database_ = std::addressof(__rules); |
| |
| // Note STDOFF is specified as |
| // This field has the same format as the AT and SAVE fields of rule lines; |
| // These fields have different suffix letters, these letters seem |
| // not to be used so do not allow any of them. |
| |
| __result.__stdoff = chrono::__parse_duration(__input); |
| chrono::__skip_mandatory_whitespace(__input); |
| __result.__rules = chrono::__parse_rules(__input); |
| chrono::__skip_mandatory_whitespace(__input); |
| __result.__format = chrono::__parse_string(__input); |
| chrono::__skip_optional_whitespace(__input); |
| |
| if (chrono::__is_eol(__input.peek())) |
| return __result; |
| __result.__year = chrono::__parse_year(__input); |
| chrono::__skip_optional_whitespace(__input); |
| |
| if (chrono::__is_eol(__input.peek())) |
| return __result; |
| __result.__in = chrono::__parse_month(__input); |
| chrono::__skip_optional_whitespace(__input); |
| |
| if (chrono::__is_eol(__input.peek())) |
| return __result; |
| __result.__on = chrono::__parse_on(__input); |
| chrono::__skip_optional_whitespace(__input); |
| |
| if (chrono::__is_eol(__input.peek())) |
| return __result; |
| __result.__at = __parse_at(__input); |
| |
| return __result; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Time Zone Database entries |
| //===----------------------------------------------------------------------===// |
| |
| static string __parse_version(istream& __input) { |
| // The first line in tzdata.zi contains |
| // # version YYYYw |
| // The parser expects this pattern |
| // #\s*version\s*\(.*) |
| // This part is not documented. |
| chrono::__matches(__input, '#'); |
| chrono::__skip_optional_whitespace(__input); |
| chrono::__matches(__input, "version"); |
| chrono::__skip_mandatory_whitespace(__input); |
| return chrono::__parse_string(__input); |
| } |
| |
| [[nodiscard]] |
| static __tz::__rule& __create_entry(__tz::__rules_storage_type& __rules, const string& __name) { |
| auto __result = [&]() -> __tz::__rule& { |
| auto& __rule = __rules.emplace_back(__name, vector<__tz::__rule>{}); |
| return __rule.second.emplace_back(); |
| }; |
| |
| if (__rules.empty()) |
| return __result(); |
| |
| // Typically rules are in contiguous order in the database. |
| // But there are exceptions, some rules are interleaved. |
| if (__rules.back().first == __name) |
| return __rules.back().second.emplace_back(); |
| |
| if (auto __it = ranges::find(__rules, __name, [](const auto& __r) { return __r.first; }); |
| __it != ranges::end(__rules)) |
| return __it->second.emplace_back(); |
| |
| return __result(); |
| } |
| |
| static void __parse_rule(tzdb& __tzdb, __tz::__rules_storage_type& __rules, istream& __input) { |
| chrono::__skip_mandatory_whitespace(__input); |
| string __name = chrono::__parse_string(__input); |
| |
| __tz::__rule& __rule = __create_entry(__rules, __name); |
| |
| chrono::__skip_mandatory_whitespace(__input); |
| __rule.__from = chrono::__parse_year(__input); |
| chrono::__skip_mandatory_whitespace(__input); |
| __rule.__to = chrono::__parse_to(__input, __rule.__from); |
| chrono::__skip_mandatory_whitespace(__input); |
| chrono::__matches(__input, '-'); |
| chrono::__skip_mandatory_whitespace(__input); |
| __rule.__in = chrono::__parse_month(__input); |
| chrono::__skip_mandatory_whitespace(__input); |
| __rule.__on = chrono::__parse_on(__input); |
| chrono::__skip_mandatory_whitespace(__input); |
| __rule.__at = __parse_at(__input); |
| chrono::__skip_mandatory_whitespace(__input); |
| __rule.__save = __parse_save(__input); |
| chrono::__skip_mandatory_whitespace(__input); |
| __rule.__letters = chrono::__parse_letters(__input); |
| chrono::__skip_line(__input); |
| } |
| |
| static void __parse_zone(tzdb& __tzdb, __tz::__rules_storage_type& __rules, istream& __input) { |
| chrono::__skip_mandatory_whitespace(__input); |
| auto __p = std::make_unique<time_zone::__impl>(chrono::__parse_string(__input), __rules); |
| vector<__tz::__continuation>& __continuations = __p->__continuations(); |
| chrono::__skip_mandatory_whitespace(__input); |
| |
| do { |
| // The first line must be valid, continuations are optional. |
| __continuations.emplace_back(__parse_continuation(__rules, __input)); |
| chrono::__skip_line(__input); |
| chrono::__skip_optional_whitespace(__input); |
| } while (std::isdigit(__input.peek()) || __input.peek() == '-'); |
| |
| __tzdb.zones.emplace_back(time_zone::__create(std::move(__p))); |
| } |
| |
| static void __parse_link(tzdb& __tzdb, istream& __input) { |
| chrono::__skip_mandatory_whitespace(__input); |
| string __target = chrono::__parse_string(__input); |
| chrono::__skip_mandatory_whitespace(__input); |
| string __name = chrono::__parse_string(__input); |
| chrono::__skip_line(__input); |
| |
| __tzdb.links.emplace_back(std::__private_constructor_tag{}, std::move(__name), std::move(__target)); |
| } |
| |
| static void __parse_tzdata(tzdb& __db, __tz::__rules_storage_type& __rules, istream& __input) { |
| while (true) { |
| int __c = std::tolower(__input.get()); |
| |
| switch (__c) { |
| case istream::traits_type::eof(): |
| return; |
| |
| case ' ': |
| case '\t': |
| case '\n': |
| break; |
| |
| case '#': |
| chrono::__skip_line(__input); |
| break; |
| |
| case 'r': |
| chrono::__skip(__input, "ule"); |
| chrono::__parse_rule(__db, __rules, __input); |
| break; |
| |
| case 'z': |
| chrono::__skip(__input, "one"); |
| chrono::__parse_zone(__db, __rules, __input); |
| break; |
| |
| case 'l': |
| chrono::__skip(__input, "ink"); |
| chrono::__parse_link(__db, __input); |
| break; |
| |
| default: |
| std::__throw_runtime_error("corrupt tzdb: unexpected input"); |
| } |
| } |
| } |
| |
| static void __parse_leap_seconds(vector<leap_second>& __leap_seconds, istream&& __input) { |
| // The file stores dates since 1 January 1900, 00:00:00, we want |
| // seconds since 1 January 1970. |
| constexpr auto __offset = sys_days{1970y / January / 1} - sys_days{1900y / January / 1}; |
| |
| while (true) { |
| switch (__input.peek()) { |
| case istream::traits_type::eof(): |
| return; |
| |
| case ' ': |
| case '\t': |
| case '\n': |
| __input.get(); |
| continue; |
| |
| case '#': |
| chrono::__skip_line(__input); |
| continue; |
| } |
| |
| sys_seconds __date = sys_seconds{seconds{chrono::__parse_integral(__input, false)}} - __offset; |
| chrono::__skip_mandatory_whitespace(__input); |
| seconds __value{chrono::__parse_integral(__input, false)}; |
| chrono::__skip_line(__input); |
| |
| __leap_seconds.emplace_back(std::__private_constructor_tag{}, __date, __value); |
| } |
| } |
| |
| void __init_tzdb(tzdb& __tzdb, __tz::__rules_storage_type& __rules) { |
| filesystem::path __root = chrono::__libcpp_tzdb_directory(); |
| ifstream __tzdata{__root / "tzdata.zi"}; |
| |
| __tzdb.version = chrono::__parse_version(__tzdata); |
| chrono::__parse_tzdata(__tzdb, __rules, __tzdata); |
| std::ranges::sort(__tzdb.zones); |
| std::ranges::sort(__tzdb.links); |
| std::ranges::sort(__rules, {}, [](const auto& p) { return p.first; }); |
| |
| // There are two files with the leap second information |
| // - leapseconds as specified by zic |
| // - leap-seconds.list the source data |
| // The latter is much easier to parse, it seems Howard shares that |
| // opinion. |
| chrono::__parse_leap_seconds(__tzdb.leap_seconds, ifstream{__root / "leap-seconds.list"}); |
| // The Standard requires the leap seconds to be sorted. The file |
| // leap-seconds.list usually provides them in sorted order, but that is not |
| // guaranteed so we ensure it here. |
| std::ranges::sort(__tzdb.leap_seconds); |
| } |
| |
| #ifdef _WIN32 |
| [[nodiscard]] static const time_zone* __current_zone_windows(const tzdb& tzdb) { |
| // TODO TZDB Implement this on Windows. |
| std::__throw_runtime_error("unknown time zone"); |
| } |
| #else // ifdef _WIN32 |
| [[nodiscard]] static const time_zone* __current_zone_posix(const tzdb& tzdb) { |
| // On POSIX systems there are several ways to configure the time zone. |
| // In order of priority they are: |
| // - TZ environment variable |
| // https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08 |
| // The documentation is unclear whether or not it's allowed to |
| // change time zone information. For example the TZ string |
| // MST7MDT |
| // this is an entry in tzdata.zi. The value |
| // MST |
| // is also an entry. Is it allowed to use the following? |
| // MST-3 |
| // Even when this is valid there is no time_zone record in the |
| // database. Since the library would need to return a valid pointer, |
| // this means the library needs to allocate and leak a pointer. |
| // |
| // - The time zone name is the target of the symlink /etc/localtime |
| // relative to /usr/share/zoneinfo/ |
| |
| // The algorithm is like this: |
| // - If the environment variable TZ is set and points to a valid |
| // record use this value. |
| // - Else use the name based on the `/etc/localtime` symlink. |
| |
| if (const char* __tz = getenv("TZ")) |
| if (const time_zone* __result = tzdb.__locate_zone(__tz)) |
| return __result; |
| |
| filesystem::path __path = "/etc/localtime"; |
| if (!std::filesystem::exists(__path)) |
| std::__throw_runtime_error("tzdb: the symlink '/etc/localtime' does not exist"); |
| |
| if (!std::filesystem::is_symlink(__path)) |
| std::__throw_runtime_error("tzdb: the path '/etc/localtime' is not a symlink"); |
| |
| filesystem::path __tz = filesystem::read_symlink(__path); |
| // The path may be a relative path, in that case convert it to an absolute |
| // path based on the proper initial directory. |
| if (__tz.is_relative()) |
| __tz = filesystem::canonical("/etc" / __tz); |
| |
| string __name = filesystem::relative(__tz, "/usr/share/zoneinfo/"); |
| if (const time_zone* __result = tzdb.__locate_zone(__name)) |
| return __result; |
| |
| std::__throw_runtime_error(("tzdb: the time zone '" + __name + "' is not found in the database").c_str()); |
| } |
| #endif // ifdef _WIN32 |
| |
| //===----------------------------------------------------------------------===// |
| // Public API |
| //===----------------------------------------------------------------------===// |
| |
| _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI tzdb_list& get_tzdb_list() { |
| static tzdb_list __result{new tzdb_list::__impl()}; |
| return __result; |
| } |
| |
| [[nodiscard]] _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI const time_zone* tzdb::__current_zone() const { |
| #ifdef _WIN32 |
| return chrono::__current_zone_windows(*this); |
| #else |
| return chrono::__current_zone_posix(*this); |
| #endif |
| } |
| |
| _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI const tzdb& reload_tzdb() { |
| if (chrono::remote_version() == chrono::get_tzdb().version) |
| return chrono::get_tzdb(); |
| |
| return chrono::get_tzdb_list().__implementation().__load(); |
| } |
| |
| _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI string remote_version() { |
| filesystem::path __root = chrono::__libcpp_tzdb_directory(); |
| ifstream __tzdata{__root / "tzdata.zi"}; |
| return chrono::__parse_version(__tzdata); |
| } |
| |
| } // namespace chrono |
| |
| _LIBCPP_END_NAMESPACE_STD |