xref: /freebsd/contrib/llvm-project/libcxx/src/tz.cpp (revision 5f757f3f)
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)54 static void __skip_optional_whitespace(istream& __input) {
55   while (chrono::__is_whitespace(__input.peek()))
56     __input.get();
57 }
58 
__skip_mandatory_whitespace(istream & __input)59 static 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)66 static 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)71 static 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)99 static 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()112 static 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