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 <chrono> 12 #include <filesystem> 13 #include <fstream> 14 #include <stdexcept> 15 #include <string> 16 17 // Contains a parser for the IANA time zone data files. 18 // 19 // These files can be found at https://data.iana.org/time-zones/ and are in the 20 // public domain. Information regarding the input can be found at 21 // https://data.iana.org/time-zones/tz-how-to.html and 22 // https://man7.org/linux/man-pages/man8/zic.8.html. 23 // 24 // As indicated at https://howardhinnant.github.io/date/tz.html#Installation 25 // For Windows another file seems to be required 26 // https://raw.githubusercontent.com/unicode-org/cldr/master/common/supplemental/windowsZones.xml 27 // This file seems to contain the mapping of Windows time zone name to IANA 28 // time zone names. 29 // 30 // However this article mentions another way to do the mapping on Windows 31 // https://devblogs.microsoft.com/oldnewthing/20210527-00/?p=105255 32 // This requires Windows 10 Version 1903, which was released in May of 2019 33 // and considered end of life in December 2020 34 // https://learn.microsoft.com/en-us/lifecycle/announcements/windows-10-1903-end-of-servicing 35 // 36 // TODO TZDB Implement the Windows mapping in tzdb::current_zone 37 38 _LIBCPP_BEGIN_NAMESPACE_STD 39 40 namespace chrono { 41 42 // This function is weak so it can be overriden in the tests. The 43 // declaration is in the test header test/support/test_tzdb.h __libcpp_tzdb_directory()44_LIBCPP_WEAK string_view __libcpp_tzdb_directory() { 45 #if defined(__linux__) 46 return "/usr/share/zoneinfo/"; 47 #else 48 # error "unknown path to the IANA Time Zone Database" 49 #endif 50 } 51 __is_whitespace(int __c)52[[nodiscard]] static bool __is_whitespace(int __c) { return __c == ' ' || __c == '\t'; } 53 __skip_optional_whitespace(istream & __input)54static void __skip_optional_whitespace(istream& __input) { 55 while (chrono::__is_whitespace(__input.peek())) 56 __input.get(); 57 } 58 __skip_mandatory_whitespace(istream & __input)59static void __skip_mandatory_whitespace(istream& __input) { 60 if (!chrono::__is_whitespace(__input.get())) 61 std::__throw_runtime_error("corrupt tzdb: expected whitespace"); 62 63 chrono::__skip_optional_whitespace(__input); 64 } 65 __matches(istream & __input,char __expected)66static void __matches(istream& __input, char __expected) { 67 if (std::tolower(__input.get()) != __expected) 68 std::__throw_runtime_error((string("corrupt tzdb: expected character '") + __expected + '\'').c_str()); 69 } 70 __matches(istream & __input,string_view __expected)71static void __matches(istream& __input, string_view __expected) { 72 for (auto __c : __expected) 73 if (std::tolower(__input.get()) != __c) 74 std::__throw_runtime_error((string("corrupt tzdb: expected string '") + string(__expected) + '\'').c_str()); 75 } 76 __parse_string(istream & __input)77[[nodiscard]] static string __parse_string(istream& __input) { 78 string __result; 79 while (true) { 80 int __c = __input.get(); 81 switch (__c) { 82 case ' ': 83 case '\t': 84 case '\n': 85 __input.unget(); 86 [[fallthrough]]; 87 case istream::traits_type::eof(): 88 if (__result.empty()) 89 std::__throw_runtime_error("corrupt tzdb: expected a string"); 90 91 return __result; 92 93 default: 94 __result.push_back(__c); 95 } 96 } 97 } 98 __parse_version(istream & __input)99static string __parse_version(istream& __input) { 100 // The first line in tzdata.zi contains 101 // # version YYYYw 102 // The parser expects this pattern 103 // #\s*version\s*\(.*) 104 // This part is not documented. 105 chrono::__matches(__input, '#'); 106 chrono::__skip_optional_whitespace(__input); 107 chrono::__matches(__input, "version"); 108 chrono::__skip_mandatory_whitespace(__input); 109 return chrono::__parse_string(__input); 110 } 111 __make_tzdb()112static tzdb __make_tzdb() { 113 tzdb __result; 114 115 filesystem::path __root = chrono::__libcpp_tzdb_directory(); 116 ifstream __tzdata{__root / "tzdata.zi"}; 117 118 __result.version = chrono::__parse_version(__tzdata); 119 return __result; 120 } 121 122 //===----------------------------------------------------------------------===// 123 // Public API 124 //===----------------------------------------------------------------------===// 125 get_tzdb_list()126_LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI tzdb_list& get_tzdb_list() { 127 static tzdb_list __result{chrono::__make_tzdb()}; 128 return __result; 129 } 130 reload_tzdb()131_LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI const tzdb& reload_tzdb() { 132 if (chrono::remote_version() == chrono::get_tzdb().version) 133 return chrono::get_tzdb(); 134 135 return chrono::get_tzdb_list().__emplace_front(chrono::__make_tzdb()); 136 } 137 remote_version()138_LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI string remote_version() { 139 filesystem::path __root = chrono::__libcpp_tzdb_directory(); 140 ifstream __tzdata{__root / "tzdata.zi"}; 141 return chrono::__parse_version(__tzdata); 142 } 143 144 } // namespace chrono 145 146 _LIBCPP_END_NAMESPACE_STD 147