Path: blob/main/system/lib/libcxx/src/experimental/tzdb.cpp
6178 views
//===----------------------------------------------------------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78// For information see https://libcxx.llvm.org/DesignDocs/TimeZone.html910#include <__assert>11#include <algorithm>12#include <cctype>13#include <chrono>14#include <filesystem>15#include <fstream>16#include <stdexcept>17#include <string>18#include <string_view>19#include <vector>2021#include "include/tzdb/time_zone_private.h"22#include "include/tzdb/types_private.h"23#include "include/tzdb/tzdb_list_private.h"24#include "include/tzdb/tzdb_private.h"2526// Contains a parser for the IANA time zone data files.27//28// These files can be found at https://data.iana.org/time-zones/ and are in the29// public domain. Information regarding the input can be found at30// https://data.iana.org/time-zones/tz-how-to.html and31// https://man7.org/linux/man-pages/man8/zic.8.html.32//33// As indicated at https://howardhinnant.github.io/date/tz.html#Installation34// For Windows another file seems to be required35// https://raw.githubusercontent.com/unicode-org/cldr/master/common/supplemental/windowsZones.xml36// This file seems to contain the mapping of Windows time zone name to IANA37// time zone names.38//39// However this article mentions another way to do the mapping on Windows40// https://devblogs.microsoft.com/oldnewthing/20210527-00/?p=10525541// This requires Windows 10 Version 1903, which was released in May of 201942// and considered end of life in December 202043// https://learn.microsoft.com/en-us/lifecycle/announcements/windows-10-1903-end-of-servicing44//45// TODO TZDB Implement the Windows mapping in tzdb::current_zone4647_LIBCPP_BEGIN_NAMESPACE_STD4849namespace chrono {5051// This function is weak so it can be overriden in the tests. The52// declaration is in the test header test/support/test_tzdb.h53_LIBCPP_WEAK string_view __libcpp_tzdb_directory() {54#if defined(__linux__)55return "/usr/share/zoneinfo/";56#else57# error "unknown path to the IANA Time Zone Database"58#endif59}6061//===----------------------------------------------------------------------===//62// Details63//===----------------------------------------------------------------------===//6465[[nodiscard]] static bool __is_whitespace(int __c) { return __c == ' ' || __c == '\t'; }6667static void __skip_optional_whitespace(istream& __input) {68while (chrono::__is_whitespace(__input.peek()))69__input.get();70}7172static void __skip_mandatory_whitespace(istream& __input) {73if (!chrono::__is_whitespace(__input.get()))74std::__throw_runtime_error("corrupt tzdb: expected whitespace");7576chrono::__skip_optional_whitespace(__input);77}7879[[nodiscard]] static bool __is_eol(int __c) { return __c == '\n' || __c == std::char_traits<char>::eof(); }8081static void __skip_line(istream& __input) {82while (!chrono::__is_eol(__input.peek())) {83__input.get();84}85__input.get();86}8788static void __skip(istream& __input, char __suffix) {89if (std::tolower(__input.peek()) == __suffix)90__input.get();91}9293static void __skip(istream& __input, string_view __suffix) {94for (auto __c : __suffix)95if (std::tolower(__input.peek()) == __c)96__input.get();97}9899static void __matches(istream& __input, char __expected) {100_LIBCPP_ASSERT_INTERNAL(!std::isalpha(__expected) || std::islower(__expected), "lowercase characters only here!");101char __c = __input.get();102if (std::tolower(__c) != __expected)103std::__throw_runtime_error(104(string("corrupt tzdb: expected character '") + __expected + "', got '" + __c + "' instead").c_str());105}106107static void __matches(istream& __input, string_view __expected) {108for (auto __c : __expected) {109_LIBCPP_ASSERT_INTERNAL(!std::isalpha(__c) || std::islower(__c), "lowercase strings only here!");110char __actual = __input.get();111if (std::tolower(__actual) != __c)112std::__throw_runtime_error(113(string("corrupt tzdb: expected character '") + __c + "' from string '" + string(__expected) + "', got '" +114__actual + "' instead")115.c_str());116}117}118119[[nodiscard]] static string __parse_string(istream& __input) {120string __result;121while (true) {122int __c = __input.get();123switch (__c) {124case ' ':125case '\t':126case '\n':127__input.unget();128[[fallthrough]];129case istream::traits_type::eof():130if (__result.empty())131std::__throw_runtime_error("corrupt tzdb: expected a string");132133return __result;134135default:136__result.push_back(__c);137}138}139}140141[[nodiscard]] static int64_t __parse_integral(istream& __input, bool __leading_zero_allowed) {142int64_t __result = __input.get();143if (__leading_zero_allowed) {144if (__result < '0' || __result > '9')145std::__throw_runtime_error("corrupt tzdb: expected a digit");146} else {147if (__result < '1' || __result > '9')148std::__throw_runtime_error("corrupt tzdb: expected a non-zero digit");149}150__result -= '0';151while (true) {152if (__input.peek() < '0' || __input.peek() > '9')153return __result;154155// In order to avoid possible overflows we limit the accepted range.156// Most values parsed are expected to be very small:157// - 8784 hours in a year158// - 31 days in a month159// - year no real maximum, these values are expected to be less than160// the range of the year type.161//162// However the leapseconds use a seconds after epoch value. Using an163// int would run into an overflow in 2038. By using a 64-bit value164// the range is large enough for the bilions of years. Limiting that165// range slightly to make the code easier is not an issue.166if (__result > (std::numeric_limits<int64_t>::max() / 16))167std::__throw_runtime_error("corrupt tzdb: integral too large");168169__result *= 10;170__result += __input.get() - '0';171}172}173174//===----------------------------------------------------------------------===//175// Calendar176//===----------------------------------------------------------------------===//177178[[nodiscard]] static day __parse_day(istream& __input) {179unsigned __result = chrono::__parse_integral(__input, false);180if (__result > 31)181std::__throw_runtime_error("corrupt tzdb day: value too large");182return day{__result};183}184185[[nodiscard]] static weekday __parse_weekday(istream& __input) {186// TZDB allows the shortest unique name.187switch (std::tolower(__input.get())) {188case 'f':189chrono::__skip(__input, "riday");190return Friday;191192case 'm':193chrono::__skip(__input, "onday");194return Monday;195196case 's':197switch (std::tolower(__input.get())) {198case 'a':199chrono::__skip(__input, "turday");200return Saturday;201202case 'u':203chrono::__skip(__input, "nday");204return Sunday;205}206break;207208case 't':209switch (std::tolower(__input.get())) {210case 'h':211chrono::__skip(__input, "ursday");212return Thursday;213214case 'u':215chrono::__skip(__input, "esday");216return Tuesday;217}218break;219case 'w':220chrono::__skip(__input, "ednesday");221return Wednesday;222}223224std::__throw_runtime_error("corrupt tzdb weekday: invalid name");225}226227[[nodiscard]] static month __parse_month(istream& __input) {228// TZDB allows the shortest unique name.229switch (std::tolower(__input.get())) {230case 'a':231switch (std::tolower(__input.get())) {232case 'p':233chrono::__skip(__input, "ril");234return April;235236case 'u':237chrono::__skip(__input, "gust");238return August;239}240break;241242case 'd':243chrono::__skip(__input, "ecember");244return December;245246case 'f':247chrono::__skip(__input, "ebruary");248return February;249250case 'j':251switch (std::tolower(__input.get())) {252case 'a':253chrono::__skip(__input, "nuary");254return January;255256case 'u':257switch (std::tolower(__input.get())) {258case 'n':259chrono::__skip(__input, 'e');260return June;261262case 'l':263chrono::__skip(__input, 'y');264return July;265}266}267break;268269case 'm':270if (std::tolower(__input.get()) == 'a')271switch (std::tolower(__input.get())) {272case 'y':273return May;274275case 'r':276chrono::__skip(__input, "ch");277return March;278}279break;280281case 'n':282chrono::__skip(__input, "ovember");283return November;284285case 'o':286chrono::__skip(__input, "ctober");287return October;288289case 's':290chrono::__skip(__input, "eptember");291return September;292}293std::__throw_runtime_error("corrupt tzdb month: invalid name");294}295296[[nodiscard]] static year __parse_year_value(istream& __input) {297bool __negative = __input.peek() == '-';298if (__negative) [[unlikely]]299__input.get();300301int64_t __result = __parse_integral(__input, true);302if (__result > static_cast<int>(year::max())) {303if (__negative)304std::__throw_runtime_error("corrupt tzdb year: year is less than the minimum");305306std::__throw_runtime_error("corrupt tzdb year: year is greater than the maximum");307}308309return year{static_cast<int>(__negative ? -__result : __result)};310}311312[[nodiscard]] static year __parse_year(istream& __input) {313if (std::tolower(__input.peek()) != 'm') [[likely]]314return chrono::__parse_year_value(__input);315316__input.get();317switch (std::tolower(__input.peek())) {318case 'i':319__input.get();320chrono::__skip(__input, 'n');321[[fallthrough]];322323case ' ':324// The m is minimum, even when that is ambiguous.325return year::min();326327case 'a':328__input.get();329chrono::__skip(__input, 'x');330return year::max();331}332333std::__throw_runtime_error("corrupt tzdb year: expected 'min' or 'max'");334}335336//===----------------------------------------------------------------------===//337// TZDB fields338//===----------------------------------------------------------------------===//339340[[nodiscard]] static year __parse_to(istream& __input, year __only) {341if (std::tolower(__input.peek()) != 'o')342return chrono::__parse_year(__input);343344__input.get();345chrono::__skip(__input, "nly");346return __only;347}348349[[nodiscard]] static __tz::__constrained_weekday::__comparison_t __parse_comparison(istream& __input) {350switch (__input.get()) {351case '>':352chrono::__matches(__input, '=');353return __tz::__constrained_weekday::__ge;354355case '<':356chrono::__matches(__input, '=');357return __tz::__constrained_weekday::__le;358}359std::__throw_runtime_error("corrupt tzdb on: expected '>=' or '<='");360}361362[[nodiscard]] static __tz::__on __parse_on(istream& __input) {363if (std::isdigit(__input.peek()))364return chrono::__parse_day(__input);365366if (std::tolower(__input.peek()) == 'l') {367chrono::__matches(__input, "last");368return weekday_last(chrono::__parse_weekday(__input));369}370371return __tz::__constrained_weekday{372chrono::__parse_weekday(__input), chrono::__parse_comparison(__input), chrono::__parse_day(__input)};373}374375[[nodiscard]] static seconds __parse_duration(istream& __input) {376seconds __result{0};377int __c = __input.peek();378bool __negative = __c == '-';379if (__negative) {380__input.get();381// Negative is either a negative value or a single -.382// The latter means 0 and the parsing is complete.383if (!std::isdigit(__input.peek()))384return __result;385}386387__result += hours(__parse_integral(__input, true));388if (__input.peek() != ':')389return __negative ? -__result : __result;390391__input.get();392__result += minutes(__parse_integral(__input, true));393if (__input.peek() != ':')394return __negative ? -__result : __result;395396__input.get();397__result += seconds(__parse_integral(__input, true));398if (__input.peek() != '.')399return __negative ? -__result : __result;400401__input.get();402(void)__parse_integral(__input, true); // Truncate the digits.403404return __negative ? -__result : __result;405}406407[[nodiscard]] static __tz::__clock __parse_clock(istream& __input) {408switch (__input.get()) { // case sensitive409case 'w':410return __tz::__clock::__local;411case 's':412return __tz::__clock::__standard;413414case 'u':415case 'g':416case 'z':417return __tz::__clock::__universal;418}419420__input.unget();421return __tz::__clock::__local;422}423424[[nodiscard]] static bool __parse_dst(istream& __input, seconds __offset) {425switch (__input.get()) { // case sensitive426case 's':427return false;428429case 'd':430return true;431}432433__input.unget();434return __offset != 0s;435}436437[[nodiscard]] static __tz::__at __parse_at(istream& __input) {438return {__parse_duration(__input), __parse_clock(__input)};439}440441[[nodiscard]] static __tz::__save __parse_save(istream& __input) {442seconds __time = chrono::__parse_duration(__input);443return {__time, chrono::__parse_dst(__input, __time)};444}445446[[nodiscard]] static string __parse_letters(istream& __input) {447string __result = __parse_string(__input);448// Canonicalize "-" to "" since they are equivalent in the specification.449return __result != "-" ? __result : "";450}451452[[nodiscard]] static __tz::__continuation::__rules_t __parse_rules(istream& __input) {453int __c = __input.peek();454// A single - is not a SAVE but a special case.455if (__c == '-') {456__input.get();457if (chrono::__is_whitespace(__input.peek()))458return monostate{};459__input.unget();460return chrono::__parse_save(__input);461}462463if (std::isdigit(__c) || __c == '+')464return chrono::__parse_save(__input);465466return chrono::__parse_string(__input);467}468469[[nodiscard]] static __tz::__continuation __parse_continuation(__tz::__rules_storage_type& __rules, istream& __input) {470__tz::__continuation __result;471472__result.__rule_database_ = std::addressof(__rules);473474// Note STDOFF is specified as475// This field has the same format as the AT and SAVE fields of rule lines;476// These fields have different suffix letters, these letters seem477// not to be used so do not allow any of them.478479__result.__stdoff = chrono::__parse_duration(__input);480chrono::__skip_mandatory_whitespace(__input);481__result.__rules = chrono::__parse_rules(__input);482chrono::__skip_mandatory_whitespace(__input);483__result.__format = chrono::__parse_string(__input);484chrono::__skip_optional_whitespace(__input);485486if (chrono::__is_eol(__input.peek()))487return __result;488__result.__year = chrono::__parse_year(__input);489chrono::__skip_optional_whitespace(__input);490491if (chrono::__is_eol(__input.peek()))492return __result;493__result.__in = chrono::__parse_month(__input);494chrono::__skip_optional_whitespace(__input);495496if (chrono::__is_eol(__input.peek()))497return __result;498__result.__on = chrono::__parse_on(__input);499chrono::__skip_optional_whitespace(__input);500501if (chrono::__is_eol(__input.peek()))502return __result;503__result.__at = __parse_at(__input);504505return __result;506}507508//===----------------------------------------------------------------------===//509// Time Zone Database entries510//===----------------------------------------------------------------------===//511512static string __parse_version(istream& __input) {513// The first line in tzdata.zi contains514// # version YYYYw515// The parser expects this pattern516// #\s*version\s*\(.*)517// This part is not documented.518chrono::__matches(__input, '#');519chrono::__skip_optional_whitespace(__input);520chrono::__matches(__input, "version");521chrono::__skip_mandatory_whitespace(__input);522return chrono::__parse_string(__input);523}524525[[nodiscard]]526static __tz::__rule& __create_entry(__tz::__rules_storage_type& __rules, const string& __name) {527auto __result = [&]() -> __tz::__rule& {528auto& __rule = __rules.emplace_back(__name, vector<__tz::__rule>{});529return __rule.second.emplace_back();530};531532if (__rules.empty())533return __result();534535// Typically rules are in contiguous order in the database.536// But there are exceptions, some rules are interleaved.537if (__rules.back().first == __name)538return __rules.back().second.emplace_back();539540if (auto __it = ranges::find(__rules, __name, [](const auto& __r) { return __r.first; });541__it != ranges::end(__rules))542return __it->second.emplace_back();543544return __result();545}546547static void __parse_rule(tzdb& __tzdb, __tz::__rules_storage_type& __rules, istream& __input) {548chrono::__skip_mandatory_whitespace(__input);549string __name = chrono::__parse_string(__input);550551__tz::__rule& __rule = __create_entry(__rules, __name);552553chrono::__skip_mandatory_whitespace(__input);554__rule.__from = chrono::__parse_year(__input);555chrono::__skip_mandatory_whitespace(__input);556__rule.__to = chrono::__parse_to(__input, __rule.__from);557chrono::__skip_mandatory_whitespace(__input);558chrono::__matches(__input, '-');559chrono::__skip_mandatory_whitespace(__input);560__rule.__in = chrono::__parse_month(__input);561chrono::__skip_mandatory_whitespace(__input);562__rule.__on = chrono::__parse_on(__input);563chrono::__skip_mandatory_whitespace(__input);564__rule.__at = __parse_at(__input);565chrono::__skip_mandatory_whitespace(__input);566__rule.__save = __parse_save(__input);567chrono::__skip_mandatory_whitespace(__input);568__rule.__letters = chrono::__parse_letters(__input);569chrono::__skip_line(__input);570}571572static void __parse_zone(tzdb& __tzdb, __tz::__rules_storage_type& __rules, istream& __input) {573chrono::__skip_mandatory_whitespace(__input);574auto __p = std::make_unique<time_zone::__impl>(chrono::__parse_string(__input), __rules);575vector<__tz::__continuation>& __continuations = __p->__continuations();576chrono::__skip_mandatory_whitespace(__input);577578do {579// The first line must be valid, continuations are optional.580__continuations.emplace_back(__parse_continuation(__rules, __input));581chrono::__skip_line(__input);582chrono::__skip_optional_whitespace(__input);583} while (std::isdigit(__input.peek()) || __input.peek() == '-');584585__tzdb.zones.emplace_back(time_zone::__create(std::move(__p)));586}587588static void __parse_link(tzdb& __tzdb, istream& __input) {589chrono::__skip_mandatory_whitespace(__input);590string __target = chrono::__parse_string(__input);591chrono::__skip_mandatory_whitespace(__input);592string __name = chrono::__parse_string(__input);593chrono::__skip_line(__input);594595__tzdb.links.emplace_back(std::__private_constructor_tag{}, std::move(__name), std::move(__target));596}597598static void __parse_tzdata(tzdb& __db, __tz::__rules_storage_type& __rules, istream& __input) {599while (true) {600int __c = std::tolower(__input.get());601602switch (__c) {603case istream::traits_type::eof():604return;605606case ' ':607case '\t':608case '\n':609break;610611case '#':612chrono::__skip_line(__input);613break;614615case 'r':616chrono::__skip(__input, "ule");617chrono::__parse_rule(__db, __rules, __input);618break;619620case 'z':621chrono::__skip(__input, "one");622chrono::__parse_zone(__db, __rules, __input);623break;624625case 'l':626chrono::__skip(__input, "ink");627chrono::__parse_link(__db, __input);628break;629630default:631std::__throw_runtime_error("corrupt tzdb: unexpected input");632}633}634}635636static void __parse_leap_seconds(vector<leap_second>& __leap_seconds, istream&& __input) {637// The file stores dates since 1 January 1900, 00:00:00, we want638// seconds since 1 January 1970.639constexpr auto __offset = sys_days{1970y / January / 1} - sys_days{1900y / January / 1};640641struct __entry {642sys_seconds __timestamp;643seconds __value;644};645vector<__entry> __entries;646[&] {647while (true) {648switch (__input.peek()) {649case istream::traits_type::eof():650return;651652case ' ':653case '\t':654case '\n':655__input.get();656continue;657658case '#':659chrono::__skip_line(__input);660continue;661}662663sys_seconds __date = sys_seconds{seconds{chrono::__parse_integral(__input, false)}} - __offset;664chrono::__skip_mandatory_whitespace(__input);665seconds __value{chrono::__parse_integral(__input, false)};666chrono::__skip_line(__input);667668__entries.emplace_back(__date, __value);669}670}();671// The Standard requires the leap seconds to be sorted. The file672// leap-seconds.list usually provides them in sorted order, but that is not673// guaranteed so we ensure it here.674ranges::sort(__entries, {}, &__entry::__timestamp);675676// The database should contain the number of seconds inserted by a leap677// second (1 or -1). So the difference between the two elements is stored.678// std::ranges::views::adjacent has not been implemented yet.679(void)ranges::adjacent_find(__entries, [&](const __entry& __first, const __entry& __second) {680__leap_seconds.emplace_back(681std::__private_constructor_tag{}, __second.__timestamp, __second.__value - __first.__value);682return false;683});684}685686void __init_tzdb(tzdb& __tzdb, __tz::__rules_storage_type& __rules) {687filesystem::path __root = chrono::__libcpp_tzdb_directory();688ifstream __tzdata{__root / "tzdata.zi"};689690__tzdb.version = chrono::__parse_version(__tzdata);691chrono::__parse_tzdata(__tzdb, __rules, __tzdata);692ranges::sort(__tzdb.zones);693ranges::sort(__tzdb.links);694ranges::sort(__rules, {}, [](const auto& p) { return p.first; });695696// There are two files with the leap second information697// - leapseconds as specified by zic698// - leap-seconds.list the source data699// The latter is much easier to parse, it seems Howard shares that700// opinion.701chrono::__parse_leap_seconds(__tzdb.leap_seconds, ifstream{__root / "leap-seconds.list"});702}703704#ifdef _WIN32705[[nodiscard]] static const time_zone* __current_zone_windows(const tzdb& tzdb) {706// TODO TZDB Implement this on Windows.707std::__throw_runtime_error("unknown time zone");708}709#else // ifdef _WIN32710711[[nodiscard]] static string __current_zone_environment() {712if (const char* __tz = std::getenv("TZ"))713return __tz;714715return {};716}717718[[nodiscard]] static string __current_zone_etc_localtime() {719filesystem::path __path = "/etc/localtime";720if (!filesystem::exists(__path) || !filesystem::is_symlink(__path))721return {};722723filesystem::path __tz = filesystem::read_symlink(__path);724// The path may be a relative path, in that case convert it to an absolute725// path based on the proper initial directory.726if (__tz.is_relative())727__tz = filesystem::canonical("/etc" / __tz);728729return filesystem::relative(__tz, "/usr/share/zoneinfo/");730}731732[[nodiscard]] static string __current_zone_etc_timezone() {733filesystem::path __path = "/etc/timezone";734if (!filesystem::exists(__path))735return {};736737ifstream __f(__path);738string __name;739std::getline(__f, __name);740return __name;741}742743[[nodiscard]] static const time_zone* __current_zone_posix(const tzdb& tzdb) {744// On POSIX systems there are several ways to configure the time zone.745// In order of priority they are:746// - TZ environment variable747// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08748// The documentation is unclear whether or not it's allowed to749// change time zone information. For example the TZ string750// MST7MDT751// this is an entry in tzdata.zi. The value752// MST753// is also an entry. Is it allowed to use the following?754// MST-3755// Even when this is valid there is no time_zone record in the756// database. Since the library would need to return a valid pointer,757// this means the library needs to allocate and leak a pointer.758//759// - The time zone name is the target of the symlink /etc/localtime760// relative to /usr/share/zoneinfo/761//762// - The file /etc/timezone. This text file contains the name of the time763// zone.764//765// On Linux systems it seems /etc/timezone is deprecated and being phased out.766// This file is used when /etc/localtime does not exist, or when it exists but767// is not a symlink. For more information and links see768// https://github.com/llvm/llvm-project/issues/105634769770string __name = chrono::__current_zone_environment();771772// Ignore invalid names in the environment.773if (!__name.empty())774if (const time_zone* __result = tzdb.__locate_zone(__name))775return __result;776777__name = chrono::__current_zone_etc_localtime();778if (__name.empty())779__name = chrono::__current_zone_etc_timezone();780781if (__name.empty())782std::__throw_runtime_error("tzdb: unable to determine the name of the current time zone");783784if (const time_zone* __result = tzdb.__locate_zone(__name))785return __result;786787std::__throw_runtime_error(("tzdb: the time zone '" + __name + "' is not found in the database").c_str());788}789#endif // ifdef _WIN32790791//===----------------------------------------------------------------------===//792// Public API793//===----------------------------------------------------------------------===//794795_LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI tzdb_list& get_tzdb_list() {796static tzdb_list __result{new tzdb_list::__impl()};797return __result;798}799800[[nodiscard]] _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI const time_zone* tzdb::__current_zone() const {801#ifdef _WIN32802return chrono::__current_zone_windows(*this);803#else804return chrono::__current_zone_posix(*this);805#endif806}807808_LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI const tzdb& reload_tzdb() {809if (chrono::remote_version() == chrono::get_tzdb().version)810return chrono::get_tzdb();811812return chrono::get_tzdb_list().__implementation().__load();813}814815_LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI string remote_version() {816filesystem::path __root = chrono::__libcpp_tzdb_directory();817ifstream __tzdata{__root / "tzdata.zi"};818return chrono::__parse_version(__tzdata);819}820821} // namespace chrono822823_LIBCPP_END_NAMESPACE_STD824825826