// chrono::tzdb -*- C++ -*- // Copyright The GNU Toolchain Authors. // // This file is part of the GNU ISO C++ Library. This library is free // software; you can redistribute it and/or modify it under the // terms of the GNU General Public License as published by the // Free Software Foundation; either version 3, or (at your option) // any later version. // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // Under Section 7 of GPL version 3, you are granted additional // permissions described in the GCC Runtime Library Exception, version // 3.1, as published by the Free Software Foundation. // You should have received a copy of the GNU General Public License and // a copy of the GCC Runtime Library Exception along with this program; // see the files COPYING3 and COPYING.RUNTIME respectively. If not, see // . // The -Wabi warnings in this file are all for non-exported symbols. #pragma GCC diagnostic ignored "-Wabi" // In the usual dual-abi build, std::chrono::tzdb is only defined for cxx11. #define _GLIBCXX_USE_CXX11_ABI 1 #include #include // ifstream #include // istringstream #include // ranges::upper_bound, ranges::lower_bound, ranges::sort #include // atomic, atomic #include // atomic> #include // mutex #include // filesystem::read_symlink #ifndef _AIX # include // getenv #endif #if defined __GTHREADS && ATOMIC_POINTER_LOCK_FREE == 2 # define USE_ATOMIC_LIST_HEAD 1 // TODO benchmark atomic> vs mutex. # define USE_ATOMIC_SHARED_PTR 1 #else # define USE_ATOMIC_LIST_HEAD 0 # define USE_ATOMIC_SHARED_PTR 0 #endif #if USE_ATOMIC_SHARED_PTR && ! USE_ATOMIC_LIST_HEAD # error Unsupported combination #endif #if ! __cpp_constinit # if __has_cpp_attribute(clang::require_constant_initialization) # define constinit [[clang::require_constant_initialization]] # else // YOLO # define constinit # endif #endif namespace __gnu_cxx { #ifdef _AIX // Cannot override weak symbols on AIX. const char* (*zoneinfo_dir_override)() = nullptr; #else [[gnu::weak]] const char* zoneinfo_dir_override(); #if defined(__APPLE__) || defined(__hpux__) \ || (defined(__VXWORKS__) && !defined(__RTP__)) // Need a weak definition for Mach-O et al. [[gnu::weak]] const char* zoneinfo_dir_override() { #ifdef _GLIBCXX_ZONEINFO_DIR return _GLIBCXX_ZONEINFO_DIR; #else return nullptr; #endif } #endif #endif } #if ! defined _GLIBCXX_ZONEINFO_DIR && ! defined _GLIBCXX_STATIC_TZDATA # define TZDB_DISABLED [[noreturn]] void __throw_disabled() { std::__throw_runtime_error("tzdb: support for loading tzdata is disabled"); } #endif namespace std::chrono { namespace { #if ! USE_ATOMIC_SHARED_PTR #ifndef __GTHREADS // Dummy no-op mutex type for single-threaded targets. struct mutex { void lock() { } void unlock() { } }; #endif inline mutex& list_mutex() { #ifdef __GTHREAD_MUTEX_INIT constinit static mutex m; #else // Cannot use a constinit mutex, so use a local static. alignas(mutex) constinit static char buf[sizeof(mutex)]; static mutex& m = *::new(buf) mutex(); #endif return m; } #endif // ! USE_ATOMIC_SHARED_PTR struct Rule; } // The tzdb list is a singly-linked list of _Node objects, using shared_ptr // for the links. Iterators into the list share ownership of the nodes. // Each _Node contains a tzdb and a vector with the rule set. struct tzdb_list::_Node { shared_ptr<_Node> next; tzdb db; #ifndef TZDB_DISABLED vector rules; #endif // The following static members are here because making them members // of this type gives them access to the private members of time_zone // and tzdb, without needing them declared in the header. static tzdb_list _S_the_list; #if USE_ATOMIC_SHARED_PTR using head_ptr = atomic>; #else // Non-atomic, list_mutex must be locked to access it. using head_ptr = shared_ptr<_Node>; #endif // This is the owning reference to the first tzdb in the list. static head_ptr _S_head_owner; #if USE_ATOMIC_LIST_HEAD // Lock-free access to the head of the list. static atomic<_Node*> _S_head_cache; static _Node* _S_list_head(memory_order ord) noexcept { return _S_head_cache.load(ord); } static void _S_cache_list_head(_Node* new_head) noexcept { _S_head_cache = new_head; } #else static _Node* _S_list_head(memory_order) { lock_guard l(list_mutex()); return _S_head_owner.get(); } static void _S_cache_list_head(_Node*) noexcept { } #endif static const tzdb& _S_init_tzdb(); static const tzdb& _S_replace_head(shared_ptr<_Node>, shared_ptr<_Node>); static pair, bool> _S_read_leap_seconds(); }; // Implementation of the private constructor used for the singleton object. constexpr tzdb_list::tzdb_list(nullptr_t) { } // The tzdb_list singleton. This doesn't contain the actual linked list, // but it has member functions that give access to it. constinit tzdb_list tzdb_list::_Node::_S_the_list(nullptr); // Shared pointer to the first Node in the list. constinit tzdb_list::_Node::head_ptr tzdb_list::_Node::_S_head_owner{nullptr}; #if USE_ATOMIC_LIST_HEAD // Lock-free access to the first Node in the list. constinit atomic tzdb_list::_Node::_S_head_cache{nullptr}; #endif // The data structures defined in this file (Rule, on_day, at_time etc.) // are used to represent the information parsed from the tzdata.zi file // described at https://man7.org/linux/man-pages/man8/zic.8.html#FILES // N.B. Most stream extraction operations for time zones, rules etc. // assume that setting failbit will throw an exception, so individual // input operations are not always checked for success. #ifndef TZDB_DISABLED namespace { // Used for reading a possibly-quoted string from a stream. struct quoted { string& str; friend istream& operator>>(istream& in, quoted&& q) { if (ws(in).peek() == '"') in >> std::quoted(q.str); else in >> q.str; return in; } }; // 32-bit version of chrono::seconds for offsets in the range [-24h,24h]. // Care must be taken to avoid overflow when using this in arithmetic. // For example, if sys_days::rep is also 32-bit then the result of // sys_days(1850y/January/1) + sec32_t(0) will be incorrect. using sec32_t = duration; // A time relative to midnight, as measured by the indicator clock. struct at_time { sec32_t time{}; enum Indicator : unsigned char { Wall, Universal, Standard, Daylight }; Indicator indicator = Wall; static pair is_indicator(int c) noexcept { switch (c) { case 's': return {Standard, true}; case 'u': case 'g': case 'z': return {Universal, true}; case 'w': return {Wall, true}; case 'd': return {Daylight, true}; default: return {Wall, false}; } } // Checks if the next character in the stream is an indicator. // If not, indic is unchanged. Callers should set a default first. friend istream& operator>>(istream& in, Indicator& indic) { auto [val, yes] = at_time::is_indicator(in.peek()); if (yes) { in.ignore(1); indic = val; } return in; } friend istream& operator>>(istream& in, at_time& at); }; // Wrapper for chrono::month that can be extracted from an istream // as an abbreviated month name. // The month name can be any unambiguous portion of a month name, // e.g. "S" (September) or "Ja" (January), but not "Ju" (June/July). struct abbrev_month { month m; friend istream& operator>>(istream& in, abbrev_month& am); }; // The IN and ON fields of a RULE record, e.g. "March lastSunday". struct on_day { using rep = uint_least16_t; // Equivalent to Kind, chrono::month, chrono::day, chrono::weekday, // but half the size. enum Kind : rep { DayOfMonth=0, LastWeekday, LessEq, GreaterEq }; Kind kind : 2 = DayOfMonth; rep month : 4 = 0; // [1,12] rep day_of_month : 5 = 0; // [1,31] rep day_of_week : 3 = 0; // [0,6] chrono::month get_month() const noexcept { return chrono::month{month}; } chrono::day get_day() const noexcept { return chrono::day{day_of_month}; } chrono::month_day get_month_day() const noexcept { return chrono::month_day{get_month(), get_day()}; } bool ok() const noexcept { switch (kind) { case DayOfMonth: return day_of_month != 0; case LastWeekday: return day_of_week != 7 && day_of_month == 0; case LessEq: case GreaterEq: return day_of_week != 7 && day_of_month != 0; } } // Convert date like "last Sunday in June" or "Sunday <= June 30" // to a specific date in the given year. year_month_day pin(year y) const { year_month_day ymd; if (kind == LastWeekday) // Last Sunday in June { month_weekday_last mwdl{get_month(), weekday_last{weekday{day_of_week}}}; ymd = year_month_day{sys_days{y/mwdl}}; } else if (kind != DayOfMonth) // Sunday <= June 30 or Sunday >= June 30 { sys_days date{y/get_month_day()}; days diff; if (kind == LessEq) diff = -(weekday{date} - weekday{day_of_week}); else diff = weekday{day_of_week} - weekday{date}; // XXX need to handle underflow/overflow to another year? ymd = year_month_day{date + diff}; } else ymd = year_month_day{y, get_month(), get_day()}; return ymd; } friend istream& operator>>(istream&, on_day&); }; // Wrapper for chrono::year that reads a year, or one of the keywords // "minimum" or "maximum", or an unambiguous prefix of a keyword. struct minmax_year { year& y; friend istream& operator>>(istream& in, minmax_year&& y) { if (ws(in).peek() == 'm') // keywords "minimum" or "maximum" { string s; in >> s; // extract the rest of the word, but only look at s[1] if (s[1] == 'a') y.y = year::max(); else if (s[1] == 'i') y.y = year::min(); else in.setstate(ios::failbit); } else if (int num = 0; in >> num) y.y = year{num}; return in; } }; // As above for minmax_year, but also supports the keyword "only", // meaning that the TO year is the same as the FROM year. struct minmax_year2 { minmax_year to; year from; friend istream& operator>>(istream& in, minmax_year2&& y) { if (ws(in).peek() == 'o') // keyword "only" { string s; in >> s; // extract the whole keyword y.to.y = y.from; } else in >> std::move(y.to); return in; } }; // A time zone information record. // Zone NAME STDOFF RULES FORMAT [UNTIL] // Zone Asia/Amman 2:00 Jordan EE%sT 2017 Oct 27 01:00 // Will be lazily expanded into sys_info objects as needed. struct ZoneInfo { ZoneInfo() = default; ZoneInfo(sys_info&& info) : m_buf(std::move(info.abbrev)), m_expanded(true), m_save(info.save), m_offset(info.offset), m_until(info.end) { } ZoneInfo(const pair& info) : m_expanded(true), m_save(info.first.save), m_offset(info.first.offset), m_until(info.first.end) { if (info.second.size()) { m_buf = info.second; // LETTERS field m_buf += ' '; m_pos = m_buf.size(); } m_buf += info.first.abbrev; } // STDOFF: Seconds from UTC during standard time. seconds offset() const noexcept { return m_offset; } // RULES: The name of the rules that apply for this period. string_view rules() const noexcept { string_view r; if (m_pos != 0) r = {m_buf.data(), m_pos - 1u}; return r; } // FORMAT: The name of the time zone (might contain %s or %z). string_view format() const noexcept { return {m_buf.data() + m_pos, m_buf.size() - m_pos}; } // UNTIL: The time when this info no longer applies. sys_seconds until() const noexcept { return m_until; } friend istream& operator>>(istream&, ZoneInfo&); bool expanded() const noexcept { return m_expanded; } // For an expanded ZoneInfo this returns the LETTERS that apply to the // **next** sys_info after this one. string_view next_letters() const noexcept { return m_expanded ? rules() : string_view{}; } bool to(sys_info& info) const { // If this object references a named Rule then we can't populate // a sys_info yet. if (!m_expanded) return false; info.end = until(); info.offset = offset(); info.save = minutes(m_save); info.abbrev = format(); return true; } private: friend class time_zone; void set_abbrev(const string& abbrev) { // In practice, the FORMAT field never needs expanding here. if (abbrev.find_first_of("/%") != abbrev.npos) __throw_runtime_error("std::chrono::time_zone: invalid data"); m_buf = abbrev; m_pos = 0; m_expanded = true; } void set_rules_and_format(const string& rls, const string& fmt) { // Both strings are typically short enough to fit in one SSO string. // As of tzdata 2022f the maximum is 14 chars, e.g. "AU +0845/+0945". m_buf.reserve(rls.size() + 1 + fmt.size()); m_buf = rls; m_buf += ' '; m_buf += fmt; m_pos = rls.size() + 1; } string m_buf; // rules() + ' ' + format() OR letters + ' ' + format() uint_least16_t m_pos : 15 = 0; // offset of format() in m_buf uint_least16_t m_expanded : 1 = 0; duration> m_save{}; sec32_t m_offset{}; sys_seconds m_until{}; #if 0 // Consider making this class more compact, e.g. int_least64_t offset_seconds : 18; int_least64_t until_sys_seconds : 46; uint_least32_t save_minutes : 12; uint_least32_t pos : 20; string buf; // abbrev OR "rules format" #endif }; // A RULE record from the tzdata.zi timezone info file. struct Rule { // This allows on_day to reuse padding of at_time. // This keeps the size to 8 bytes and the alignment to 4 bytes. struct datetime : at_time { on_day day; }; // TODO combining name+letters into a single string (like in ZoneInfo) // would save sizeof(string) and make Rule fit in a single cacheline. // Or don't store name in the Rule, and replace vector with // vector>> i.e. map-like structure. string name; // the name of the rule set year from{}; // first year in which the rule applies year to{}; // final year in which the rule applies datetime when; // the day and time on which the rule takes effect sec32_t save{}; // amount of time to be added when the rule is in effect string letters; // variable part of TZ abbreviations when rule in effect // Combine y + this->when + offset to give a UTC timestamp. sys_seconds start_time(year y, seconds offset) const { auto time = when.time; if (when.indicator == at_time::Wall || when.indicator == at_time::Standard) time -= offset; // Convert local time to sys time. return sys_days(when.day.pin(y)) + time; } friend istream& operator>>(istream& in, Rule& rule) { string str; // Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S in >> quoted(rule.name) >> minmax_year{rule.from} >> minmax_year2{rule.to, rule.from}; if (char type; in >> type && type != '-') in.setstate(ios::failbit); in >> rule.when.day >> static_cast(rule.when); at_time save_time; save_time.indicator = at_time::Wall; // not valid for this field in >> save_time; if (save_time.indicator != at_time::Wall) { // We don't actually store the save_time.indicator, because we // assume that it's always deducable from the actual offset value. auto expected = save_time.time == 0s ? at_time::Standard : at_time::Daylight; __glibcxx_assert(save_time.indicator == expected); } rule.save = save_time.time; in >> rule.letters; if (rule.letters == "-") rule.letters.clear(); return in; } #ifdef _GLIBCXX_ASSERTIONS friend ostream& operator<<(ostream& out, const Rule& r) { out << "Rule " << r.name << ' ' << (int)r.from << ' ' << (int)r.to << ' ' << r.when.day.get_month() << ' '; switch (r.when.day.kind) { case on_day::DayOfMonth: out << (unsigned)r.when.day.get_day(); break; case on_day::LastWeekday: out << "last" << weekday(r.when.day.day_of_week); break; case on_day::LessEq: out << weekday(r.when.day.day_of_week) << " <= " << r.when.day.day_of_month; break; case on_day::GreaterEq: out << weekday(r.when.day.day_of_week) << " >= " << r.when.day.day_of_month; break; } out << ' ' << hh_mm_ss(r.when.time) << "wusd"[r.when.indicator]; out << ' ' << r.save.count(); if (!r.letters.empty()) out << ' ' << r.letters; else out << " -"; return out; } #endif }; } // namespace #endif // TZDB_DISABLED // Private constructor used by reload_tzdb() to create time_zone objects. time_zone::time_zone(unique_ptr<_Impl> __p) : _M_impl(std::move(__p)) { } time_zone::~time_zone() = default; // The opaque pimpl class stored in a time_zone object. struct time_zone::_Impl { #ifndef TZDB_DISABLED explicit _Impl(weak_ptr node) : node(std::move(node)) { } vector infos; // Definitions of the time zone's transitions. // Non-owning reference back to the tzdb that owns this time_zone. // Needed to access the list of rules for the time zones. weak_ptr node; // In the simple case, we don't actual keep count. No concurrent access // to the infos vector is possible, even if all infos are expanded. template struct RulesCounter { // Called for each rule-based ZoneInfo added to the infos vector. // Called when the time_zone::_Impl is created, so no concurrent calls. void increment() { } // Called when a rule-based ZoneInfo is expanded. // The caller must have called lock() for exclusive access to infos. void decrement() { } // Use a mutex to synchronize all access to the infos vector. mutex infos_mutex; void lock() { infos_mutex.lock(); } void unlock() { infos_mutex.unlock(); } }; #if defined __GTHREADS && __cpp_lib_atomic_wait // Atomic count of unexpanded ZoneInfo objects in the infos vector. // Concurrent access is allowed when all objects have been expanded. // Only use an atomic counter if it would not require libatomic, // because we don't want libstdc++.so to depend on libatomic. template requires _Tp::is_always_lock_free struct RulesCounter<_Tp> { _Tp counter{0}; void increment() { counter.fetch_add(1, memory_order::relaxed); } void decrement() { // The current thread holds the lock, so the counter is negative // and so we need to increment it to decrement it! // If the count reaches zero then there are no more unexpanded infos, // so notify all waiting threads that they can access the infos. // We must do this here, because unlock() is a no-op if counter==0. if (++counter == 0) counter.notify_all(); } void lock() { // If counter is zero then concurrent access is allowed, so lock() // and unlock() are no-ops and multiple threads can "lock" at once. // If counter is non-zero then the contents of the infos vector might // need to be changed, so only one thread is allowed to access it. for (auto c = counter.load(memory_order::relaxed); c != 0;) { // Setting counter to negative means this thread has the lock. if (c > 0 && counter.compare_exchange_strong(c, -c)) return; if (c < 0) { // Counter is negative, another thread already has the lock. counter.wait(c); c = counter.load(); } } } void unlock() { if (auto c = counter.load(memory_order::relaxed); c < 0) { counter.store(-c, memory_order::release); counter.notify_one(); } } }; #endif // __GTHREADS && __cpp_lib_atomic_wait #if __cpp_lib_atomic_lock_free_type_aliases RulesCounter rules_counter; #else RulesCounter rules_counter; #endif #else // TZDB_DISABLED _Impl(weak_ptr) { } struct { sys_info info; void push_back(sys_info i) { info = std::move(i); } } infos; #endif // TZDB_DISABLED }; #ifndef TZDB_DISABLED namespace { bool select_std_or_dst_abbrev(string& abbrev, minutes save) { if (size_t pos = abbrev.find('/'); pos != string::npos) { // Select one of "STD/DST" for standard or daylight. if (save == 0min) abbrev.erase(pos); else abbrev.erase(0, pos + 1); return true; } return false; } // Set the sys_info::abbrev string by expanding any placeholders. void format_abbrev_str(sys_info& info, string_view letters = {}) { if (size_t pos = info.abbrev.find("%s"); pos != string::npos) { // Expand "%s" to the variable part, given by Rule::letters. info.abbrev.replace(pos, 2, letters); } else if (size_t pos = info.abbrev.find("%z"); pos != string::npos) { // Expand "%z" to the UT offset as +/-hh, +/-hhmm, or +/-hhmmss. hh_mm_ss t(info.offset); string z(1, "+-"[t.is_negative()]); long val = t.hours().count(); if (minutes m = t.minutes(); m != m.zero()) { val *= 100; val += m.count(); if (seconds s = t.seconds(); s != s.zero()) { val *= 100; val += s.count(); } } z += std::to_string(val); info.abbrev.replace(pos, 2, z); } else select_std_or_dst_abbrev(info.abbrev, info.save); } } #endif // TZDB_DISABLED // Implementation of std::chrono::time_zone::get_info(const sys_time&) sys_info time_zone::_M_get_sys_info(sys_seconds tp) const { #ifndef TZDB_DISABLED // This gives us access to the node->rules vector, but also ensures // that the tzdb node won't get erased while we're still using it. const auto node = _M_impl->node.lock(); auto& infos = _M_impl->infos; // Prevent concurrent access to _M_impl->infos if it might need to change. lock_guard lock(_M_impl->rules_counter); // Find the transition info for the time point. auto i = ranges::upper_bound(infos, tp, ranges::less{}, &ZoneInfo::until); if (i == infos.end()) { if (infos.empty()) __throw_runtime_error("std::chrono::time_zone::get_info: invalid data"); tp = (--i)->until(); } sys_info info; if (i == infos.begin()) info.begin = sys_days(year::min()/January/1); else info.begin = i[-1].until(); if (i->to(info)) // We already know a sys_info for this time. return info; // Otherwise, we have a ZoneInfo object that describes the applicable // transitions in terms of a set of named rules that vary by year. // Replace that rules-based ZoneInfo object with a sequence of one or more // objects that map directly to a sys_info value. const ZoneInfo& ri = *i; // Find the rules named by ri.rules() auto rules = ranges::equal_range(node->rules, ri.rules(), ranges::less{}, &Rule::name); if (ranges::empty(rules)) __throw_runtime_error("std::chrono::time_zone::get_info: invalid data"); vector> new_infos; if (int n = ceil(tp - info.begin).count()) new_infos.reserve(std::min(100, n * 2)); long result_index = -1; int num_after = 4; // number of sys_info to generate past tp. // The following initial values for info.offset, info.save, and letters // are only valid if the first sys_info we are generating uses the time // zone's standard time, because daylight time would need non-zero offset. // This is true by construction, because this function always tries to // finish so that the last ZoneInfo object expanded is for daylight time. // This means that i[-1] is either an expanded ZoneInfo for a DST sys_info // or is an unexpanded (rule-based) ZoneInfo for a different rule, and // rule changes always occur between periods of standard time. info.offset = ri.offset(); info.save = 0min; // XXX The ri.until() time point should be // "interpreted using the rules in effect just before the transition" info.end = ri.until(); info.abbrev = ri.format(); string_view letters; if (i != infos.begin()) { if (i[-1].expanded()) letters = i[-1].next_letters(); // XXX else need to find Rule active before this time and use it // to know the initial offset, save, and letters. } const Rule* curr_rule = nullptr; while (info.begin < info.end && num_after > 0) { sys_seconds t = info.begin; const year_month_day date(chrono::floor(t)); const Rule* next_rule = nullptr; // Check every rule to find the next transition after t. for (const auto& rule : rules) { if (&rule == curr_rule) // don't bother checking this one again continue; if (date.year() > rule.to) // rule no longer applies at time t continue; sys_seconds rule_start; seconds offset{}; // appropriate for at_time::Universal if (rule.when.indicator == at_time::Wall) offset = info.offset; else if (rule.when.indicator == at_time::Standard) offset = ri.offset(); if (date.year() < rule.from) // rule doesn't apply yet at time t { // Find first transition for this rule: rule_start = rule.start_time(rule.from, offset); } else // rule applies in the year that contains time t { year y = date.year(); // Time the rule takes effect this year: rule_start = rule.start_time(y, offset); if (rule_start < t && rule.to > y) { // Try this rule in the following year. rule_start = rule.start_time(++y, offset); } } if (t < rule_start && rule_start < info.end) { if (rule_start - t < days(1)) // XXX shouldn't be needed! continue; // Found a closer transition than the previous info.end. info.end = rule_start; next_rule = &rule; } } format_abbrev_str(info, letters); bool merged = false; #if 0 if (!new_infos.empty()) { auto& back = new_infos.back(); if (back.offset == info.offset && back.abbrev == info.abbrev && back.save == info.save) { // This is a continuation of the previous sys_info. back.end = info.end; merged = true; } } #endif if (next_rule) letters = next_rule->letters; else letters = {}; if (!merged) new_infos.emplace_back(info, letters); if (info.begin <= tp && tp < info.end) // Found the result. result_index = new_infos.size() - 1; else if (result_index >= 0 && !merged) { // Finish on a DST sys_info if possible, so that if we resume // generating sys_info objects after this time point, save=0 // should be correct for the next sys_info. if (num_after > 1 || info.save != 0min) --num_after; } info.begin = info.end; if (next_rule) { info.end = ri.until(); info.offset = ri.offset() + next_rule->save; info.save = duration_cast(next_rule->save); info.abbrev = ri.format(); letters = next_rule->letters; curr_rule = next_rule; } } if (new_infos.empty() || result_index < 0) __throw_runtime_error("time_zone::get_info"); info = new_infos[result_index].first; if (new_infos.back().first.end < ri.until()) { // Insert the new sys_info objects but don't remove the rules_info. infos.insert(i, new_infos.begin(), new_infos.end()); } else { // Replace the rules_info at *i with the sys_info objects in new_infos. // First note the index of *i because we will invalidate i. result_index = i - infos.begin(); // Insert everything except new_infos.front() at the end of infos: i = infos.insert(infos.end(), new_infos.begin() + 1, new_infos.end()); // Then rotate those new elements into place: std::rotate(infos.begin() + result_index + 1, i, infos.end()); // Then replace the original rules_info object with new_infos.front(): infos[result_index] = ZoneInfo(new_infos.front()); // Decrement count of rule-based infos (might also release lock). _M_impl->rules_counter.decrement(); } return info; #else return _M_impl->infos.info; #endif // TZDB_DISABLED } // Implementation of std::chrono::time_zone::get_info(const local_time&) local_info time_zone::_M_get_local_info(local_seconds tp) const { local_info info{}; #ifndef TZDB_DISABLED const auto node = _M_impl->node.lock(); // Get sys_info assuming no offset between local time and UTC: info.first = _M_get_sys_info(sys_seconds(tp.time_since_epoch())); // Convert to UTC using the real offset: sys_seconds st(tp.time_since_epoch() - info.first.offset); if ((st - info.first.begin) <= days(1)) { sys_info prev = _M_get_sys_info(info.first.begin - 1s); sys_seconds prevst(tp.time_since_epoch() - prev.offset); if (st < info.first.begin) { if (prevst < info.first.begin) { // tp is a unique local time, prev is the correct sys_info. info.first = prev; } else { // The local time is nonexistent, falling within a clock change // e.g. there is no 1:30am if DST moves clock from 1am to 2am. __glibcxx_assert(prev.offset < info.first.offset); // start DST info.result = local_info::nonexistent; info.second = info.first; info.first = prev; } } else if (prevst < info.first.begin) { // The local time is ambiguous, referring to two possible UTC times // e.g. 1:30am happens twice if clock moves back from 2am to 1am. __glibcxx_assert(prev.offset > info.first.offset); // DST ended info.result = local_info::ambiguous; info.second = info.first; info.first = prev; } // else tp is a unique local time, info.first is the correct sys_info. } else if ((info.first.end - st) <= days(1)) { sys_info next = _M_get_sys_info(info.first.end); sys_seconds nextst(tp.time_since_epoch() - next.offset); if (st >= info.first.end) { if (nextst >= info.first.end) { // tp is a unique local time, next is the correct sys_info. info.first = next; } else { info.result = local_info::nonexistent; info.second = next; } } else if (nextst >= info.first.end) { info.result = local_info::ambiguous; info.second = next; } // else tp is a unique local time, info.first is the correct sys_info. } #else info.first = _M_impl->infos.info; #endif // TZDB_DISABLED return info; } #ifndef TZDB_DISABLED namespace { // If a zoneinfo directory is defined (either when the library was built, // or via the zoneinfo_dir_override function) then append filename to it. // The filename should have a leading '/' as one is not added explicitly. string zoneinfo_file(string_view filename) { string path; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Waddress" if (__gnu_cxx::zoneinfo_dir_override) { if (auto override_dir = __gnu_cxx::zoneinfo_dir_override()) path = override_dir; #pragma GCC diagnostic pop } #ifdef _GLIBCXX_ZONEINFO_DIR else path = _GLIBCXX_ZONEINFO_DIR; #endif if (!path.empty()) path.append(filename); return path; } // N.B. Leading slash as required by zoneinfo_file function. const string_view tzdata_file = "/tzdata.zi"; const string_view leaps_file = "/leapseconds"; #ifdef _GLIBCXX_STATIC_TZDATA // Static copy of tzdata.zi embedded in the library as tzdata_chars[] #include "tzdata.zi.h" #endif // An istream type that can read from a file or from a string. struct tzdata_stream : istream { // std::spanbuf not available until C++23 struct ispanbuf : streambuf { ispanbuf() : streambuf() { #ifdef _GLIBCXX_STATIC_TZDATA char* p = const_cast(tzdata_chars); this->setg(p, p, p + std::size(tzdata_chars) - 1); #endif } // N.B. seekoff and seekpos not overridden, not currently needed. }; union { filebuf fb; ispanbuf sb; }; tzdata_stream() : istream(nullptr) { if (string path = zoneinfo_file(tzdata_file); !path.empty()) { filebuf fbuf; if (fbuf.open(path, std::ios::in)) { std::construct_at(&fb, std::move(fbuf)); this->init(&fb); return; } } std::construct_at(&sb); this->init(&sb); } ~tzdata_stream() { std::destroy_at(this->rdbuf()); } // use virtual dtor bool using_static_data() const { return this->rdbuf() == &sb; } }; } #endif // TZDB_DISABLED // Return leap_second values, and a bool indicating whether the values are // current (true), or potentially out of date (false). pair, bool> tzdb_list::_Node::_S_read_leap_seconds() { // This list is valid until at least 2024-12-28 00:00:00 UTC. auto expires = sys_days{2024y/12/28}; vector leaps { (leap_second) 78796800, // 1 Jul 1972 (leap_second) 94694400, // 1 Jan 1973 (leap_second) 126230400, // 1 Jan 1974 (leap_second) 157766400, // 1 Jan 1975 (leap_second) 189302400, // 1 Jan 1976 (leap_second) 220924800, // 1 Jan 1977 (leap_second) 252460800, // 1 Jan 1978 (leap_second) 283996800, // 1 Jan 1979 (leap_second) 315532800, // 1 Jan 1980 (leap_second) 362793600, // 1 Jul 1981 (leap_second) 394329600, // 1 Jul 1982 (leap_second) 425865600, // 1 Jul 1983 (leap_second) 489024000, // 1 Jul 1985 (leap_second) 567993600, // 1 Jan 1988 (leap_second) 631152000, // 1 Jan 1990 (leap_second) 662688000, // 1 Jan 1991 (leap_second) 709948800, // 1 Jul 1992 (leap_second) 741484800, // 1 Jul 1993 (leap_second) 773020800, // 1 Jul 1994 (leap_second) 820454400, // 1 Jan 1996 (leap_second) 867715200, // 1 Jul 1997 (leap_second) 915148800, // 1 Jan 1999 (leap_second)1136073600, // 1 Jan 2006 (leap_second)1230768000, // 1 Jan 2009 (leap_second)1341100800, // 1 Jul 2012 (leap_second)1435708800, // 1 Jul 2015 (leap_second)1483228800, // 1 Jan 2017 }; #if 0 // This optimization isn't valid if the file has additional leap seconds // defined since the library was compiled, but the system clock has been // set to a time before the hardcoded expiration date. if (system_clock::now() < expires) return {std::move(leaps), true}; #endif #ifndef TZDB_DISABLED if (ifstream ls{zoneinfo_file(leaps_file)}) { auto exp_year = year_month_day(expires).year(); std::string s, w; s.reserve(80); // Avoid later reallocations. while (std::getline(ls, s)) { // Leap YEAR MONTH DAY HH:MM:SS CORR R/S if (!s.starts_with("Leap")) continue; istringstream li(std::move(s)); li.exceptions(ios::failbit); li.ignore(4); unsigned yval; if (li >> yval) { if (year y(yval); y >= exp_year) // Only process new entries. { li >> w; char m = w[0]; if (m != 'J' && m != 'D') return {std::move(leaps), false}; const int is_december = m == 'D'; year_month_day ymd{y, month(6 + 6 * is_december), day(30 + is_december)}; sys_seconds secs(sys_days(ymd) + days(1)); li >> w >> w >> m; if (m != '+' && m != '-') return {std::move(leaps), false}; seconds::rep val = secs.time_since_epoch().count(); if (m == '-') [[unlikely]] val = -(val - 1); // -ve leap second happens at 23:59:59 if (leap_second ls{val}; ls > leaps.back()) leaps.push_back(ls); } } s = std::move(li).str(); // return storage to s } return {std::move(leaps), true}; } #endif return {std::move(leaps), false}; } #ifndef TZDB_DISABLED namespace { // Read the version number from a tzdata.zi file. string remote_version(istream& zif) { char hash; string label; string version; if (zif >> hash >> label >> version) if (hash == '#' && label == "version") return version; #if 0 // Ignore these files, because we're not using them anyway. #if defined __NetBSD__ if (string ver; ifstream(zoneinfo_file("/TZDATA_VERSION")) >> ver) return ver; #elif defined __APPLE__ // The standard install on macOS has no tzdata.zi, but we can find the // version from +VERSION. if (string ver; ifstream(zoneinfo_file("/+VERSION")) >> ver) return ver; #endif #endif __throw_runtime_error("tzdb: no version found in tzdata.zi"); } } #endif // Definition of std::chrono::remote_version() string remote_version() { #ifndef TZDB_DISABLED tzdata_stream zif; return remote_version(zif); #else __throw_disabled(); #endif } // Used by chrono::reload_tzdb() to add a new node to the front of the list. const tzdb& tzdb_list::_Node::_S_replace_head(shared_ptr<_Node> curr [[maybe_unused]], shared_ptr<_Node> new_head) { _Node* new_head_ptr = new_head.get(); #if USE_ATOMIC_SHARED_PTR new_head_ptr->next = curr; while (!_S_head_owner.compare_exchange_strong(curr, new_head)) { if (curr->db.version == new_head_ptr->db.version) return curr->db; new_head_ptr->next = curr; } // XXX small window here where _S_head_cache still points to previous tzdb. #else lock_guard l(list_mutex()); if (const _Node* h = _S_head_owner.get()) { if (h->db.version == new_head_ptr->db.version) return h->db; new_head_ptr->next = _S_head_owner; } _S_head_owner = std::move(new_head); #endif _S_cache_list_head(new_head_ptr); return new_head_ptr->db; } // Called to populate the list for the first time. If reload_tzdb() fails, // it creates a tzdb that only contains the UTC and GMT time zones. const tzdb& tzdb_list::_Node::_S_init_tzdb() { #ifndef TZDB_DISABLED __try { return reload_tzdb(); } __catch (const std::exception&) #endif { auto [leaps, ok] = _S_read_leap_seconds(); using Node = tzdb_list::_Node; auto node = std::make_shared(); node->db.version = "ersatz"; node->db.leap_seconds = std::move(leaps); node->db.zones.reserve(2); node->db.links.reserve(7); time_zone zone(nullptr); time_zone_link link(nullptr); sys_info info{sys_seconds::min(), sys_seconds::max(), 0s, 0min, ""}; zone._M_impl = std::make_unique(node); zone._M_name = "Etc/UTC"; info.abbrev = "UTC"; zone._M_impl->infos.push_back(std::move(info)); link._M_target = zone._M_name; link._M_name = "UTC"; node->db.links.push_back(std::move(link)); for (auto name : {"Etc/UCT", "Etc/Universal", "Etc/Zulu"}) { link._M_target = zone._M_name; link._M_name = name; node->db.links.push_back(std::move(link)); link._M_target = zone._M_name; link._M_name = name + 4; node->db.links.push_back(std::move(link)); } node->db.zones.emplace_back(std::move(zone)); zone._M_impl = std::make_unique(node); zone._M_name = "Etc/GMT"; info.abbrev = "GMT"; zone._M_impl->infos.push_back(std::move(info)); link._M_target = zone._M_name; link._M_name = "GMT"; node->db.links.push_back(std::move(link)); for (auto name : {"Etc/GMT+0", "Etc/GMT-0", "Etc/GMT0", "Etc/Greenwich"}) { link._M_target = zone._M_name; link._M_name = name; node->db.links.push_back(std::move(link)); link._M_target = zone._M_name; link._M_name = name + 4; node->db.links.push_back(std::move(link)); } node->db.zones.emplace_back(std::move(zone)); ranges::sort(node->db.zones); ranges::sort(node->db.links); return Node::_S_replace_head(nullptr, std::move(node)); } } // There are only three ways for users to access the tzdb list. // get_tzdb_list() returns a reference to the list itself. // get_tzdb() returns a reference to the front of the list. // reload_tzdb() returns a reference to the (possibly new) front of the list. // Those are the only functions that need to check whether the list has // been populated already. // Implementation of std::chrono::get_tzdb_list() tzdb_list& get_tzdb_list() { using Node = tzdb_list::_Node; if (Node::_S_list_head(memory_order::acquire) == nullptr) [[unlikely]] Node::_S_init_tzdb(); // populates list return Node::_S_the_list; } // Implementation of std::chrono::get_tzdb() const tzdb& get_tzdb() { using Node = tzdb_list::_Node; if (auto* __p = Node::_S_list_head(memory_order::acquire)) [[likely]] return __p->db; return Node::_S_init_tzdb(); // populates list } // Implementation of std::chrono::reload_tzdb() const tzdb& reload_tzdb() { #ifndef TZDB_DISABLED using Node = tzdb_list::_Node; tzdata_stream zif; const string version = remote_version(zif); #if USE_ATOMIC_SHARED_PTR auto head = Node::_S_head_owner.load(memory_order::acquire); if (head != nullptr && head->db.version == version) return head->db; #else if (Node::_S_list_head(memory_order::relaxed) != nullptr) [[likely]] { lock_guard l(list_mutex()); const tzdb& current = Node::_S_head_owner->db; if (current.version == version) return current; } shared_ptr head; // Passed as unused arg to _S_replace_head. #endif auto [leaps, leaps_ok] = Node::_S_read_leap_seconds(); if (!leaps_ok && !zif.using_static_data()) __throw_runtime_error("tzdb: cannot parse leapseconds file"); auto node = std::make_shared(); node->db.version = std::move(version); node->db.leap_seconds = std::move(leaps); string line, type; line.reserve(80); // Maximum allowed line is 511 but much less in practice. istringstream is; is.exceptions(ios::failbit); int lineno = 0; while (std::getline(zif, line)) { ++lineno; if (line.empty()) continue; is.str(std::move(line)); is.clear(); ws(is); int c = is.eof() ? '#' : is.peek(); __try { switch (c) { case '#': break; case 'R': { // Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S is >> type; // extract the "Rule" or "R" marker Rule rule; is >> rule; node->rules.push_back(std::move(rule)); break; } case 'L': { // Link TARGET LINK-NAME is >> type; // extract the "Link" or "L" marker time_zone_link link(nullptr); is >> quoted(link._M_target) >> quoted(link._M_name); node->db.links.push_back(std::move(link)); break; } case 'Z': { // Zone NAME STDOFF RULES FORMAT [UNTIL] is >> type; // extract the "Zone" or "Z" marker time_zone tz(std::make_unique(node)); is >> quoted(tz._M_name); node->db.zones.push_back(time_zone(std::move(tz))); [[fallthrough]]; // Use default case to parse rest of line ... } default: // Continuation of the previous Zone line. { // STDOFF RULES FORMAT [UNTIL] if (type[0] != 'Z') is.setstate(ios::failbit); auto& impl = *node->db.zones.back()._M_impl; ZoneInfo& info = impl.infos.emplace_back(); is >> info; // Keep count of ZoneInfo objects that refer to named Rules. if (!info.rules().empty()) impl.rules_counter.increment(); } } } __catch (const ios::failure&) { ostringstream ss; ss << "std::chrono::reload_tzdb: parse error at line " << lineno << ": " << std::move(is).str(); __throw_runtime_error(std::move(ss).str().c_str()); } line = std::move(is).str(); // return storage to line } ranges::sort(node->db.zones, {}, &time_zone::name); ranges::sort(node->db.links, {}, &time_zone_link::name); ranges::stable_sort(node->rules, {}, &Rule::name); return Node::_S_replace_head(std::move(head), std::move(node)); #else __throw_disabled(); #endif // TZDB_DISABLED } // Any call to tzdb_list::front() or tzdb_list::begin() must follow // a call to get_tzdb_list() so the list has already been populated. // Implementation of std::chrono::tzdb_list::front(). const tzdb& tzdb_list::front() const noexcept { return _Node::_S_list_head(memory_order::seq_cst)->db; } // Implementation of std::chrono::tzdb_list::begin(). auto tzdb_list::begin() const noexcept -> const_iterator { #if USE_ATOMIC_SHARED_PTR return const_iterator{_Node::_S_head_owner.load()}; #else lock_guard l(list_mutex()); return const_iterator{_Node::_S_head_owner}; #endif } // Implementation of std::chrono::tzdb_list::erase_after(const_iterator). auto tzdb_list::erase_after(const_iterator p) -> const_iterator { if (p._M_node) [[likely]] { #if ! USE_ATOMIC_SHARED_PTR lock_guard l(list_mutex()); #endif if (auto next = p._M_node->next) [[likely]] return const_iterator{p._M_node->next = std::move(next->next)}; } // This is undefined, but let's be kind: std::__throw_logic_error("std::tzdb_list::erase_after: iterator is not " "dereferenceable"); } // Private constructor for tzdb_list::const_iterator. // Only used within this file, so can be inline. inline tzdb_list:: const_iterator::const_iterator(const shared_ptr<_Node>& __p) noexcept : _M_node(__p) { } // Implementation of std::chrono::tzdb_list::const_iterator::operator*(). auto tzdb_list::const_iterator::operator*() const noexcept -> reference { return _M_node->db; } // Implementation of std::chrono::tzdb_list::const_iterator::operator++(). auto tzdb_list::const_iterator::operator++() -> const_iterator& { auto cur = std::move(_M_node); _M_node = cur->next; return *this; } // Implementation of std::chrono::tzdb_list::const_iterator::operator++(int). auto tzdb_list::const_iterator::operator++(int) -> const_iterator { auto tmp = std::move(*this); _M_node = tmp._M_node->next; return tmp; } namespace { const time_zone* do_locate_zone(const vector& zones, const vector& links, string_view tz_name) noexcept { // Lambda mangling changed between -fabi-version=2 and -fabi-version=18 auto search = [](const Vec& v, string_view name) { auto pos = ranges::lower_bound(v, name, {}, &Vec::value_type::name); auto ptr = pos.base(); if (pos == v.end() || pos->name() != name) ptr = nullptr; return ptr; }; if (auto tz = search(zones, tz_name)) return tz; if (auto tz_l = search(links, tz_name)) return search(zones, tz_l->target()); return nullptr; } } // namespace // Implementation of std::chrono::tzdb::locate_zone(string_view). const time_zone* tzdb::locate_zone(string_view tz_name) const { if (auto tz = do_locate_zone(zones, links, tz_name)) return tz; string_view err = "tzdb: cannot locate zone: "; string str; str.reserve(err.size() + tz_name.size()); str += err; str += tz_name; __throw_runtime_error(str.c_str()); } // Implementation of std::chrono::tzdb::current_zone(). const time_zone* tzdb::current_zone() const { // TODO cache this function's result? #ifndef _AIX // Repeat the preprocessor condition used by filesystem::read_symlink, // to avoid a dependency on src/c++17/fs_ops.o if it won't work anyway. #if defined(_GLIBCXX_HAVE_READLINK) && defined(_GLIBCXX_HAVE_SYS_STAT_H) error_code ec; // This should be a symlink to e.g. /usr/share/zoneinfo/Europe/London auto path = filesystem::read_symlink("/etc/localtime", ec); if (!ec) { auto first = path.begin(), last = path.end(); if (std::distance(first, last) > 2) { --last; string name = last->string(); if (auto tz = do_locate_zone(this->zones, this->links, name)) return tz; --last; name = last->string() + '/' + name; if (auto tz = do_locate_zone(this->zones, this->links, name)) return tz; } } #endif // Otherwise, look for a file naming the time zone. string_view files[] { "/etc/timezone", // Debian derivates "/var/db/zoneinfo", // FreeBSD }; for (auto f : files) { std::ifstream tzf{string{f}}; if (std::string name; std::getline(tzf, name)) if (auto tz = do_locate_zone(this->zones, this->links, name)) return tz; } if (ifstream tzf{"/etc/sysconfig/clock"}) { string line; // Old versions of Suse use TIMEZONE. Old versions of RHEL use ZONE. const string_view keys[] = { "TIMEZONE=" , "ZONE=" }; while (std::getline(tzf, line)) for (string_view key : keys) if (line.starts_with(key)) { string_view name = line; name.remove_prefix(key.size()); if (name.size() != 0 && name.front() == '"') { name.remove_prefix(1); if (auto pos = name.find('"'); pos != name.npos) name = name.substr(0, pos); } if (auto tz = do_locate_zone(this->zones, this->links, name)) return tz; } } #else // AIX stores current zone in $TZ in /etc/environment but the value // is typically a POSIX time zone name, not IANA zone. // https://developer.ibm.com/articles/au-aix-posix/ // https://www.ibm.com/support/pages/managing-time-zone-variable-posix if (const char* env = std::getenv("TZ")) { // This will fail unless TZ contains an IANA time zone name. if (auto tz = do_locate_zone(this->zones, this->links, env)) return tz; } #endif // Default to UTC. if (auto tz = do_locate_zone(this->zones, this->links, "UTC")) return tz; __throw_runtime_error("tzdb: cannot determine current zone"); } // Implementation of std::chrono::locate_zone(string_view) // TODO define this inline in the header instead? const time_zone* locate_zone(string_view tz_name) { // Use begin() so the tzdb cannot be erased while this operation runs. return get_tzdb_list().begin()->locate_zone(tz_name); } // Implementation of std::chrono::current_zone() // TODO define this inline in the header instead? const time_zone* current_zone() { // Use begin() so the tzdb cannot be erased while this operation runs. return get_tzdb_list().begin()->current_zone(); } #ifndef TZDB_DISABLED namespace { istream& operator>>(istream& in, abbrev_month& am) { string s; in >> s; switch (s[0]) { case 'J': switch (s[1]) { case 'a': am.m = January; return in; case 'u': switch (s[2]) { case 'n': am.m = June; return in; case 'l': am.m = July; return in; } break; } break; case 'F': am.m = February; return in; case 'M': if (s[1] == 'a') [[likely]] switch (s[2]) { case 'r': am.m = March; return in; case 'y': am.m = May; return in; } break; case 'A': switch (s[1]) { case 'p': am.m = April; return in; case 'u': am.m = August; return in; } break; case 'S': am.m = September; return in; case 'O': am.m = October; return in; case 'N': am.m = November; return in; case 'D': am.m = December; return in; } in.setstate(ios::failbit); return in; } // Wrapper for chrono::weekday that can be extracted from an istream // as an abbreviated weekday name. // The weekday name can be any unambiguous portion of a weekday name, // e.g. "M" (Monday) or "Su" (Sunday), but not "T" (Tuesday/Thursday). struct abbrev_weekday { weekday wd; friend istream& operator>>(istream& in, abbrev_weekday& aw) { // Do not read a whole word from the stream, in some cases // the weekday is only part of a larger word like "Sun<=25". // Just peek at one char at a time. switch (in.peek()) { case 'M': aw.wd = Monday; break; case 'T': in.ignore(1); // Discard the 'T' switch (in.peek()) { case 'u': aw.wd = Tuesday; break; case 'h': aw.wd = Thursday; break; default: in.setstate(ios::failbit); } break; case 'W': aw.wd = Wednesday; break; case 'F': aw.wd = Friday; break; case 'S': in.ignore(1); // Discard the 'S' switch (in.peek()) { case 'a': aw.wd = Saturday; break; case 'u': aw.wd = Sunday; break; default: in.setstate(ios::failbit); } break; default: in.setstate(ios::failbit); } in.ignore(1); // Discard whichever char we just looked at. // Discard any remaining chars from weekday, e.g. "onday". string_view day_chars = "ondayesritu"; auto is_day_char = [&day_chars](int c) { return c != char_traits::eof() && day_chars.find((char)c) != day_chars.npos; }; while (is_day_char(in.peek())) in.ignore(1); return in; } }; istream& operator>>(istream& in, on_day& to) { on_day on{}; abbrev_month m{}; in >> m; on.month = static_cast(m.m); int c = ws(in).peek(); if ('0' <= c && c <= '9') { on.kind = on_day::DayOfMonth; unsigned d; in >> d; if (d <= 31) [[likely]] { on.day_of_month = d; to = on; return in; } } else if (c == 'l') // lastSunday, lastWed, ... { in.ignore(4); if (abbrev_weekday w{}; in >> w) [[likely]] { on.kind = on_day::LastWeekday; on.day_of_week = w.wd.c_encoding(); to = on; return in; } } else { abbrev_weekday w; in >> w; if (auto c = in.get(); c == '<' || c == '>') { if (in.get() == '=') { on.kind = c == '<' ? on_day::LessEq : on_day::GreaterEq; on.day_of_week = w.wd.c_encoding(); unsigned d; in >> d; if (d <= 31) [[likely]] { on.day_of_month = d; to = on; return in; } } } } in.setstate(ios::failbit); return in; } istream& operator>>(istream& in, at_time& at) { int sign = 1; if (in.peek() == '-') { in.ignore(1); if (auto [val, yes] = at_time::is_indicator(in.peek()); yes) { in.ignore(1); at.time = 0s; at.indicator = val; return in; } sign = -1; } int i; in >> i; hours h{i}; minutes m{}; seconds s{}; if (!in.eof() && in.peek() == ':') { in.ignore(1); // discard the colon. in >> i; m = minutes{i}; if (in.peek() == ':') { in.ignore(1); // discard the colon. in >> i; if (in.peek() == '.') { double frac; in >> frac; // zic(8) rounds to nearest second, rounding ties to even. s = chrono::round(duration(i + frac)); } else s = seconds{i}; } } if (in >> at.indicator) at.time = sign * (h + m + s); return in; } // Test whether the RULES field of a Zone line is a valid Rule name. inline bool is_rule_name(string_view rules) noexcept { // The NAME field of a Rule line must start with a character that is // neither an ASCII digit nor '-' nor '+'. if (('0' <= rules[0] && rules[0] <= '9') || rules[0] == '-') return false; // However, some older tzdata.zi files (e.g. in tzdata-2018e-3.el6 RPM) // used "+" as a Rule name, so we need to handle that special case. if (rules[0] == '+') return rules.size() == 1; // "+" is a rule name, "+1" is not. // Everything else is the name of a Rule. return true; } istream& operator>>(istream& in, ZoneInfo& inf) { // STDOFF RULES FORMAT [UNTIL] at_time off; string rules; string fmt; in >> off >> quoted{rules} >> fmt; inf.m_offset = off.time; if (is_rule_name(rules)) { // `rules` refers to a named Rule which describes transitions. inf.set_rules_and_format(rules, fmt); } else { if (rules == "-") { // Standard time always applies, no DST. } else { // `rules` specifies the difference from standard time, // e.g., "-2:30" at_time rules_time; istringstream in2(std::move(rules)); in2 >> rules_time; inf.m_save = duration_cast(rules_time.time); select_std_or_dst_abbrev(fmt, inf.m_save); } inf.set_abbrev(fmt); } // YEAR [MONTH [DAY [TIME]]] ios::iostate ex = in.exceptions(); in.exceptions(ios::goodbit); // Don't throw ios::failure if YEAR absent. if (int y = int(year::max()); in >> y) { abbrev_month m{January}; int d = 1; at_time t{}; in >> m >> d >> t; inf.m_until = sys_days(year(y)/m.m/day(d)) + seconds(t.time); } else inf.m_until = sys_days(year::max()/December/31); in.clear(in.rdstate() & ios::eofbit); in.exceptions(ex); if (!in.eof()) // Not actually necessary, as we're only parsing a single line: in.ignore(numeric_limits::max(), '\n'); return in; } } // namespace #endif // TZDB_DISABLED } // namespace std::chrono