mirror of
https://github.com/ziglang/zig.git
synced 2026-02-01 20:23:38 +00:00
See: * https://discourse.llvm.org/t/rfc-freezing-c-03-headers-in-libc/77319 * https://discourse.llvm.org/t/rfc-project-hand-in-hand-llvm-libc-libc-code-sharing/77701 We're dropping support for C++03 for Zig due to the first change; it would be insane to ship 1018 duplicate header files just for this outdated use case. As a result of the second change, I had to bring in a subset of the headers from llvm-libc since libc++ now depends on these. Hopefully we can continue to get away with not copying the entirety of llvm-libc.
794 lines
25 KiB
C++
Vendored
794 lines
25 KiB
C++
Vendored
//===----------------------------------------------------------------------===//
|
|
//
|
|
// 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 <__assert>
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <chrono>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <vector>
|
|
|
|
#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
|
|
// zig patch: change this compilation error into a runtime crash
|
|
abort();
|
|
#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) {
|
|
_LIBCPP_ASSERT_INTERNAL(!std::isalpha(__expected) || std::islower(__expected), "lowercase characters only here!");
|
|
char __c = __input.get();
|
|
if (std::tolower(__c) != __expected)
|
|
std::__throw_runtime_error(
|
|
(string("corrupt tzdb: expected character '") + __expected + "', got '" + __c + "' instead").c_str());
|
|
}
|
|
|
|
static void __matches(istream& __input, string_view __expected) {
|
|
for (auto __c : __expected) {
|
|
_LIBCPP_ASSERT_INTERNAL(!std::isalpha(__c) || std::islower(__c), "lowercase strings only here!");
|
|
char __actual = __input.get();
|
|
if (std::tolower(__actual) != __c)
|
|
std::__throw_runtime_error(
|
|
(string("corrupt tzdb: expected character '") + __c + "' from string '" + string(__expected) + "', got '" +
|
|
__actual + "' instead")
|
|
.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};
|
|
|
|
struct __entry {
|
|
sys_seconds __timestamp;
|
|
seconds __value;
|
|
};
|
|
vector<__entry> __entries;
|
|
[&] {
|
|
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);
|
|
|
|
__entries.emplace_back(__date, __value);
|
|
}
|
|
}();
|
|
// 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.
|
|
ranges::sort(__entries, {}, &__entry::__timestamp);
|
|
|
|
// The database should contain the number of seconds inserted by a leap
|
|
// second (1 or -1). So the difference between the two elements is stored.
|
|
// std::ranges::views::adjacent has not been implemented yet.
|
|
(void)ranges::adjacent_find(__entries, [&](const __entry& __first, const __entry& __second) {
|
|
__leap_seconds.emplace_back(
|
|
std::__private_constructor_tag{}, __second.__timestamp, __second.__value - __first.__value);
|
|
return false;
|
|
});
|
|
}
|
|
|
|
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);
|
|
ranges::sort(__tzdb.zones);
|
|
ranges::sort(__tzdb.links);
|
|
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"});
|
|
}
|
|
|
|
#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 (!filesystem::exists(__path))
|
|
std::__throw_runtime_error("tzdb: the symlink '/etc/localtime' does not exist");
|
|
|
|
if (!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
|