On Thu, Jul 16, 2026 at 2:53 PM Jonathan Wakely <[email protected]> wrote:
> On Wed, 15 Jul 2026 at 10:43 +0200, Tomasz Kamiński wrote: > >The previous patches for PR 116110 left one case unresolved: a Zone > >line with a wall-time UNTIL whose RULES field is a named rule set. > >The save value used to convert the wall UNTIL to UTC depends on which > >rule of the set was active at the UNTIL instant, but at parse time > >the rule records have not all been loaded, so the active-rule lookup > >can't yet be performed. > > > >The remaining FIXME in operator>>(istream&, ZoneInfo&) caused zones > >like Africa/Algiers (around 1977-10-21) to place their zone-line > >boundary one save-period off from the canonical zic interpretation, > >producing brief incorrect sys_info windows during DST transitions. > > > >This commit defers the save adjustment to _M_get_sys_info call, > >and applies it only for the ZoneInfo surrounding the specified > >time. This assumes that applying the save does not change relative > >order of ZoneInfo (i.e. the until members are spaced more than save). > > > >The m_expanded member is replaced with four state _M_state setting: > >Expanded, and three separte state used for rule based zones: > >* SaveKnown - m_save contains save value at time m_until > >* SavePending - m_save is defualted to zero, m_until is correct > >* UntilPending - as above, but also m_until time need to adjusted > >The parser set the UntilPending state when it sees a wall-time > >UNTIL on a named-rule line, and SaveKnown if not UNTIL date is > >specified. > > > >The fixup in _M_get_sys_info, is performed by calc_save function > >on previous/current zone. If necessary (depending on m_state) this > >function updates m_save and m_until members, and returns true > >if the m_until was shifted in the process. In such case, we redo > >the comparision of until() against tp, and move iterator to active > >zone info if necessary. The save at boundary for previous/current > >ZoneInfo is required to implement proper zone merging, so it is > >always computed. > > > >The active-rule lookup either reuses the existing find_active_rule > >overload accepting sys_seconds (if UNTIL is not affected by save), > >or newly introduced overload accepting local_seconds (local time). > >The local_seconds versions, follows the same logic for finding > >rule transitions (extracted to find_surrounding_transitions) > >surrounding the sys_time. The final active rule, is then determined > >after converting the transition times to local time, including the > >running save. > > > >The test_apia case in 116110.cc had a hardcoded `+11h` workaround > >for the unfixed bug; with this fix in place the workaround is removed > >and the value becomes the canonical `+10h`. > > > >libstdc++-v3/ChangeLog: > > > > PR libstdc++/116110 > > * src/c++20/tzdb.cc (ZoneInfo::calc_save, ZoneInfo::State): Define. > > (ZoneInfo::m_expanded): Replaced with m_state. > > (ZoneInfo::m_pos): Reduce the bit with to 14. > > (ZoneInfo::m_state): Expands m_expanded with four state enum. > > (ZoneInfo::ZoneInfo, ZoneInfo::expanded, ZoneInfo::set_abbrev): > > Replace m_expanded usage with m_state and State::Expanded. > > (Transitions, find_surrounding_transitions): Extracted from > > find_active_rule. > > (find_active_rule): Define overload for local_seconds. Implement > > both in terms of find_surrounding_transitions. > > (time_zone::_M_get_sys_info): Use ZoneInfo::calc_save to adjust > > until() on surrounding zones, and shift the current zone iterator > > (i) accordingly. > > (operator>>(istream&, ZoneInfo&)): Set m_state for UntilPending > > for UNTIL time using wall time, and SaveKnown for lines without > > end date. For clarity, set m_save to 0 if daylight is not used. > > * testsuite/std/time/time_zone/116110.cc (test_apia): Use correct > > value of the the total offset. > > testsuite/std/time/time_zone/pr116110_named.cc: New test. > > > >Co-authored-by: Álvaro Begué <[email protected]> > >Signed-off-by: Álvaro Begué <[email protected]> > >Signed-off-by: Tomasz Kamiński <[email protected]> > >--- > >v3 rewrites the implementation. The major change is the fact that > > > >save-adjustment is now applied lazily and locally to the neighbooring > >zones. This have strong assumtion about zone changes being resonably > >spaced (i.e. they remain sorted after save adjustment). After considering > >that I believe this is resonable, as we are discussing save, and not > >offset (that may jump for 24h). > > > >The original v3 also changed the code to find active rule at time of > >transition, to include transition happening exaclty at info begin. > >I have dropped it for this version of the patch, as it was not > >necessary to pass included test, and caused another failures. > >We should revisit the comparision against the zic result once all > >fixes are in place. > > > >Testing on x86_64-linux. All *time_zone* test passes. > >We should also see the performance impact before any decision to > >backport. > > This logic is surprisingly easy to follow now - very nice. > I am also happy with how this ended up. Took me multiple back and forth to get to this state, so happy to hear it actually helped. > > OK for trunk with one comment typo fix and one whitespace change > (see below). > I also checked the output against Howard's date using your program, and no new differences were introduced here. There are still some FAILs remaining. For the moment, I plan to modify the script to accept the splits, i.e. situations where libstdc++ produces two zones for some time-range, that one zone covers in the date output, if they have same parameters. > > > > > libstdc++-v3/src/c++20/tzdb.cc | 221 +++++++++++++----- > > .../testsuite/std/time/time_zone/116110.cc | 4 +- > > .../std/time/time_zone/pr116110_named.cc | 74 ++++++ > > 3 files changed, 244 insertions(+), 55 deletions(-) > > create mode 100644 > libstdc++-v3/testsuite/std/time/time_zone/pr116110_named.cc > > > >diff --git a/libstdc++-v3/src/c++20/tzdb.cc > b/libstdc++-v3/src/c++20/tzdb.cc > >index 998a363151d..09c2127b242 100644 > >--- a/libstdc++-v3/src/c++20/tzdb.cc > >+++ b/libstdc++-v3/src/c++20/tzdb.cc > >@@ -483,6 +483,8 @@ namespace std::chrono > > select_std_or_dst_abbrev(info.abbrev, info.save); > > } > > > >+ struct Rule; > >+ > > // A time zone information record. > > // Zone NAME STDOFF RULES FORMAT [UNTIL] > > // Zone Asia/Amman 2:00 Jordan EE%sT 2017 Oct 27 01:00 > >@@ -492,12 +494,12 @@ namespace std::chrono > > ZoneInfo() = default; > > > > ZoneInfo(sys_info&& info) > >- : m_buf(std::move(info.abbrev)), m_expanded(true), > m_save(info.save), > >+ : m_buf(std::move(info.abbrev)), m_state(Expanded), > m_save(info.save), > > m_offset(info.offset - seconds(info.save)), m_until(info.end) > > { } > > > > ZoneInfo(const pair<sys_info, string_view>& info) > >- : m_expanded(true), m_save(info.first.save), > >+ : m_state(Expanded), m_save(info.first.save), > > m_offset(info.first.offset - seconds(info.first.save)), > > m_until(info.first.end) > > { > >@@ -533,17 +535,23 @@ namespace std::chrono > > sys_seconds > > until() const noexcept { return m_until; } > > > >+ // For non-expanded zone (using rules) computes the value > >+ // of 'save' at the time of transitions, caches it in m_save. > >+ // Returns true if until value was changed in the process. > >+ bool > >+ calc_save(span<const Rule> all_rules) noexcept; > >+ > > friend istream& operator>>(istream&, ZoneInfo&); > > > > bool > > expanded() const noexcept > >- { return m_expanded; } > >+ { return m_state == 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{}; } > >+ { return (m_state == Expanded) ? rules() : string_view{}; } > > > > > > bool > >@@ -551,7 +559,7 @@ namespace std::chrono > > { > > // If this object references a named Rule then we can't populate > > // a sys_info yet. > >- if (!m_expanded) > >+ if (m_state != Expanded) > > return false; > > > > info.end = until(); > >@@ -565,12 +573,16 @@ namespace std::chrono > > private: > > friend class time_zone; > > > >+ enum class State : uint_least16_t > >+ { Expanded, SaveKnown, SavePending, UntilPending }; > >+ using enum State; > >+ > > void > > set_abbrev(string abbrev) > > { > > m_buf = std::move(abbrev); > > m_pos = 0; > >- m_expanded = true; > >+ m_state = Expanded; > > } > > > > void > >@@ -586,8 +598,8 @@ namespace std::chrono > > } > > > > 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; > >+ uint_least16_t m_pos : 14 = 0; // offset of format() in m_buf > >+ State m_state : 2 = SavePending; > > duration<int_least16_t, ratio<60>> m_save{}; > > sec32_t m_offset{}; > > sys_seconds m_until{}; > >@@ -695,14 +707,27 @@ namespace std::chrono > > #endif > > }; > > > >- const Rule* > >- find_active_rule(span<const Rule> rules, sys_seconds t, seconds > std_offset) > >+ struct Transitions > > { > >- struct Transition { > >+ struct Entry > >+ { > > const Rule* rule; > > sys_seconds when; > > }; > > > >+ Entry prev{nullptr, sys_seconds::min()}; > >+ Entry curr{nullptr, sys_seconds::min()}; > >+ Entry next{nullptr, sys_seconds::max()}; > >+ }; > >+ > >+ // Collect transitions of rules surrounding specified time t: > >+ // * .curr - rule active directly before or at t, > >+ // * .prev - rule transition before trans.curr > >+ // * .next - rule transition directly after t > >+ Transitions > >+ find_surrounding_transitions(span<const Rule> rules, sys_seconds t, > >+ seconds std_offset) > >+ { > > const year_month_day date(chrono::floor<days>(t)); > > // Expand the search window for a time near the end > > // of the year which can be pushed to next/previous > >@@ -716,16 +741,7 @@ namespace std::chrono > > if (date.month() == January && date.day() == day(1)) > > --first_year; > > > >- // Rule specifying start time as Wall time, should apply > >- // running 'save' accumulated by earlier rules. To handle > >- // that we firstly collect transitions surrounding specified > >- // time t, ignoring the 'save': > >- // * curr_tran - rule active directly before or at t, > >- // * prev_tran - rule transition before curr_tran > >- // * next_tran - rule transition directly after t > >- Transition prev_tran{nullptr, sys_seconds::min()}; > >- Transition curr_tran{nullptr, sys_seconds::min()}; > >- Transition next_tran{nullptr, sys_seconds::max()}; > >+ Transitions trans; > > for (const auto& rule : rules) > > { > > if (last_year < rule.from) // Rule doesn't apply yet at time t. > >@@ -761,7 +777,7 @@ namespace std::chrono > > > > if (first_year > rule.to) > > // Rule no longer applies at time t, record last transition > >- start_before = rule.start_time(rule.to, offset); > >+ start_before = rule.start_time(rule.to, offset); > > else if (first_year == last_year) > > for_year(first_year); > > else > >@@ -773,24 +789,35 @@ namespace std::chrono > > for_year(last_year); > > } > > > >- if (curr_tran.when < start_before) > >+ if (trans.curr.when < start_before) > > { > >- prev_tran = curr_tran; > >- curr_tran = {&rule, start_before}; > >+ trans.prev = trans.curr; > >+ trans.curr = {&rule, start_before}; > > } > >- else if (prev_tran.when < start_before) > >- prev_tran = {&rule, start_before}; > >+ else if (trans.prev.when < start_before) > >+ trans.prev = {&rule, start_before}; > > > >- if (start_after < next_tran.when) > >- next_tran = {&rule, start_after}; > >+ if (start_after < trans.next.when) > >+ trans.next = {&rule, start_after}; > > } > >+ return trans; > >+ } > >+ > >+ const Rule* > >+ find_active_rule(span<const Rule> rules, sys_seconds t, seconds > std_offset) > >+ { > >+ // Rule specifying start time as Wall time, should apply > >+ // running 'save' accumulated by earlier rules. To handle > >+ // that we firstly collect transitions surrounding specified > >+ // time t: > >+ auto trans = find_surrounding_transitions(rules, t, std_offset); > > > > // No rule was active at the time of t, running 'save' > > // cannot change this output, as we have no save to apply. > >- if (!curr_tran.rule) > >+ if (!trans.curr.rule) > > return nullptr; > > > >- auto cascade_save = [](const Rule* from, Transition& to) > >+ auto cascade_save = [](const Rule* from, Transitions::Entry& to) > > { > > if (!from || from->save == seconds(0)) > > return false; > >@@ -800,19 +827,49 @@ namespace std::chrono > > return true; > > }; > > > >- if (cascade_save(curr_tran.rule, next_tran)) > >- // Running save moved what we considered next_tran to time > >- // before or at t, in that case next_tran is active rule. > >- if (next_tran.when <= t) > >- return next_tran.rule; > >+ if (cascade_save(trans.curr.rule, trans.next)) > >+ // Running save moved what we considered trans.next to time > >+ // before or at t, in that case trans.next is active rule. > >+ if (trans.next.when <= t) > >+ return trans.next.rule; > > > >- if (cascade_save(prev_tran.rule, curr_tran)) > >- // Running save moved what we consider curr_tran to > >- // time after t, in that case prev_tran is active rule. > >- if (curr_tran.when > t) > >- return prev_tran.rule; > >+ if (cascade_save(trans.prev.rule, trans.curr)) > >+ // Running save moved what we consider trans.curr to > >+ // time after t, in that case trans.prev is active rule. > >+ if (trans.curr.when > t) > >+ return trans.prev.rule; > > > >- return curr_tran.rule; > >+ return trans.curr.rule; > >+ } > >+ > >+ const Rule* > >+ find_active_rule(span<const Rule> rules, local_seconds t, seconds > std_offset) > >+ { > >+ // UNTIL or START time in local time should take a 'save' from > >+ // active rule. To handle approximate system time at, and > >+ // collection transitions surrounding it: > >+ const sys_seconds at(t.time_since_epoch() - std_offset); > >+ const auto trans = find_surrounding_transitions(rules, at, > std_offset); > >+ > >+ auto to_local = [&](const Transitions::Entry& tran, const Rule* > before) > >+ { > >+ local_seconds ls(tran.when.time_since_epoch() + std_offset); > >+ if (!before || !tran.rule) > >+ return ls; > >+ if (tran.rule->when.indicator == at_time::Wall) > >+ // 'when' was converted from local time without considering > >+ // running 'save' in first place > >+ return ls; > >+ return ls + before->save; > >+ }; > >+ > >+ // Translate the transitions time to local time, taking 'save' > >+ // into consideration, and recheck active rule. > >+ if (to_local(trans.next, trans.curr.rule) <= t) > >+ return trans.next.rule; > >+ if (to_local(trans.curr, trans.prev.rule) > t) > >+ return trans.prev.rule; > >+ return trans.curr.rule; > > } > > > > const Rule* > >@@ -835,6 +892,43 @@ namespace std::chrono > > } > > return first; > > } > >+ > >+ bool > >+ ZoneInfo::calc_save(span<const Rule> all_rules) noexcept > >+ { > >+ // Expanded rule, or the save value was already computed. > >+ if (m_state < SavePending) > >+ return false; > >+ > >+ // Find the rules named by rules() > >+ span<const Rule> sel_rules > >+ = ranges::equal_range(all_rules, rules(), ranges::less{}, > &Rule::name); > >+ if (sel_rules.empty()) > >+ __throw_runtime_error("std::chrono::time_zone::get_info: invalid > data"); > >+ > >+ if (m_state == UntilPending) > >+ { > >+ // UNTIL was specified in Wall time, so it is affected by 'save'. > >+ // Convert it back to local time, and find active rule using > that. > >+ const local_seconds lu(m_until.time_since_epoch() + m_offset); > >+ if (const Rule* rule = find_active_rule(sel_rules, lu - > seconds(1), m_offset)) > >+ if (rule->save.count() != 0) > >+ { > >+ m_state = SaveKnown; > >+ m_save = duration_cast<minutes>(rule->save); > >+ m_until -= rule->save; > >+ return true; > >+ } > >+ } > >+ else > >+ // UNTIL was either specified in Universal time, or Standard > >+ // time (known total offset). m_until is corresponding sys_time > point. > >+ if (const Rule* rule = find_active_rule(sel_rules, m_until - > seconds(1), m_offset)) > >+ m_save = duration_cast<minutes>(rule->save); > >+ > >+ m_state = SaveKnown; > >+ return false; > >+ } > > } // namespace > > #endif // TZDB_DISABLED > > > >@@ -966,19 +1060,35 @@ namespace std::chrono > > // Find the transition info for the time point. > > auto i = ranges::upper_bound(infos, tp, ranges::less{}, > &ZoneInfo::until); > > > >+ // Perform the comparision on save adjusted until values (if needed) > >+ // Assumme that applying the save will not change relative order of > > "Assume" > > >+ // ZoneInfo objects. > >+ if (i != infos.begin() && i[-1].calc_save(node->rules) && > (i[-1].until() > tp)) > >+ --i; > >+ else if (i != infos.end() && i->calc_save(node->rules) && > (i->until() <= tp)) > >+ ++i; > >+ > > if (i == infos.end()) > > { > > if (infos.empty()) > > __throw_runtime_error("std::chrono::time_zone::get_info: invalid > data"); > >- tp = (--i)->until(); > >- } > >+ (--i)->calc_save(node->rules); > >+ tp = i->until(); > >+ } > >+ else // Guarantee that i->until() is correct > >+ i->calc_save(node->rules); > >+ > > > > sys_info info; > > > > if (i == infos.begin()) > > info.begin = sys_days(year::min()/January/1); > > else > >- info.begin = i[-1].until(); > >+ { > >+ ZoneInfo& prev = i[-1]; > >+ prev.calc_save(node->rules); > >+ info.begin = prev.until(); > > I think the line above is indented with spaces, but the two before it > use TAB. > > >+ } > > > > if (i->to(info)) // We already know a sys_info for this time. > > return info; > >@@ -990,10 +1100,10 @@ namespace std::chrono > > const ZoneInfo& ri = *i; > > > > // Find the rules named by ri.rules() > >- auto rules = ranges::equal_range(node->rules, ri.rules(), > >- ranges::less{}, &Rule::name); > >+ span<const Rule> rules = ranges::equal_range(node->rules, ri.rules(), > >+ ranges::less{}, > &Rule::name); > > > >- if (ranges::empty(rules)) > >+ if (rules.empty()) > > __throw_runtime_error("std::chrono::time_zone::get_info: invalid > data"); > > > > vector<pair<sys_info, string_view>> new_infos; > >@@ -1012,8 +1122,6 @@ namespace std::chrono > > // 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(); > > > >@@ -2612,6 +2720,7 @@ constinit tzdb_list::_Node::NumLeapSeconds > tzdb_list::_Node::num_leap_seconds; > > if (rules == "-") > > { > > // Standard time always applies, no DST. > >+ inf.m_save = minutes(0); > > } > > else > > { > >@@ -2645,14 +2754,20 @@ constinit tzdb_list::_Node::NumLeapSeconds > tzdb_list::_Node::num_leap_seconds; > > inf.m_until -= seconds(inf.m_offset); > > if (t.indicator != at_time::Standard) > > { > >- if (inf.m_expanded) // Not a named Rule, SAVE is known > now. > >+ if (inf.expanded()) // Not a named Rule, SAVE is known > now. > > inf.m_until -= inf.m_save; > >- // else Named Rule, SAVE is unknown. FIXME: PR 116110 > >+ else // else Named Rule, SAVE is unknown, mark as pending > >+ inf.m_state = ZoneInfo::UntilPending; > > } > > } > > } > > else > >- inf.m_until = sys_days(year::max()/December/31); > >+ { > >+ inf.m_until = sys_days(year::max()/December/31); > >+ // The line does not define UNTIL, so it is not affected by save > >+ if (!inf.expanded()) > >+ inf.m_state = ZoneInfo::SaveKnown; > >+ } > > > > in.clear(in.rdstate() & ios::eofbit); > > in.exceptions(ex); > >diff --git a/libstdc++-v3/testsuite/std/time/time_zone/116110.cc > b/libstdc++-v3/testsuite/std/time/time_zone/116110.cc > >index 26b9ba33c31..1ec01d35955 100644 > >--- a/libstdc++-v3/testsuite/std/time/time_zone/116110.cc > >+++ b/libstdc++-v3/testsuite/std/time/time_zone/116110.cc > >@@ -65,8 +65,8 @@ test_apia() > > auto* tz = locate_zone("Pacific/Apia"); > > local_seconds t = local_days(2011y/December/29) + 24h; > > > >- // FIXME: this should be + 10h but we do not account for DST yet, so + > 11h. > >- sys_seconds ut(t.time_since_epoch() + 11h ); > >+ // The wall UNTIL is interpreted in the prior offset (-11h + 1h) > >+ sys_seconds ut(t.time_since_epoch() + 10h); > > sys_info info; > > info = tz->get_info(ut - 1s); > > VERIFY( info.offset == (-11h + info.save) ); > >diff --git a/libstdc++-v3/testsuite/std/time/time_zone/pr116110_named.cc > b/libstdc++-v3/testsuite/std/time/time_zone/pr116110_named.cc > >new file mode 100644 > >index 00000000000..b3bf4eb1aa9 > >--- /dev/null > >+++ b/libstdc++-v3/testsuite/std/time/time_zone/pr116110_named.cc > >@@ -0,0 +1,74 @@ > >+// { dg-do run { target c++20 } } > >+// { dg-require-effective-target tzdb } > >+// { dg-require-effective-target cxx11_abi } > >+// { dg-xfail-run-if "no weak override on AIX" { powerpc-ibm-aix* } } > >+ > >+// Africa/Algiers 1977-10-21: a Zone line whose RULES references a > >+// named Rule and whose UNTIL is a wall-time expression. The wall > >+// UNTIL is interpreted using the SAVE value in force just before the > >+// boundary (the May-6 rule's save=1, not the Oct-21 rule's save=0 > >+// even though the Oct-21 rule fires at the same wall instant). > >+// > >+// Rule d 1977 May 6 0:00 wall save=1 > >+// Rule d 1977 Oct 21 0:00 wall save=0 > >+// Z A 0 d WE%sT 1977 O 21 > >+// 1 d CE%sT > >+ > >+#include <chrono> > >+#include <fstream> > >+#include <testsuite_hooks.h> > >+ > >+static bool override_used = false; > >+ > >+namespace __gnu_cxx > >+{ > >+ const char* zoneinfo_dir_override() { > >+ override_used = true; > >+ return "./"; > >+ } > >+} > >+ > >+int > >+main() > >+{ > >+ using namespace std::chrono; > >+ > >+ std::ofstream("tzdata.zi") << R"(# version test_pr116110_named > >+R d 1977 o - May 6 0 1 S > >+R d 1977 o - O 21 0 0 - > >+Z Test/Algiers 0 d WE%sT 1977 O 21 > >+ 1 d CE%sT > >+)"; > >+ > >+ const auto& db = reload_tzdb(); > >+ VERIFY( override_used ); > >+ VERIFY( db.version == "test_pr116110_named" ); > >+ > >+ auto* tz = locate_zone("Test/Algiers"); > >+ > >+ // Just before the boundary: still in the first Zone line under > >+ // the May-6 rule (save=1, WEST, total +1). > >+ auto pre = tz->get_info(sys_days{1977y/October/20} + 22h); > >+ VERIFY( pre.offset == 1h ); > >+ VERIFY( pre.save == 1h ); > >+ VERIFY( pre.abbrev == "WEST" ); > >+ > >+ // The boundary is Oct 20 23:00 UTC (= wall 00:00 - stdoff(0) - > save(1)). > >+ // At and after the boundary we are in the second line (CET). > >+ auto at = tz->get_info(sys_days{1977y/October/20} + 23h); > >+ VERIFY( at.offset == 1h ); // stdoff 1 + save 0 (CET, second line) > >+ VERIFY( at.save == 0min ); > >+ VERIFY( at.abbrev == "CET" ); > >+ > >+ // A second query inside the second line, well clear of the boundary. > >+ auto after = tz->get_info(sys_days{1977y/October/21} + 12h); > >+ VERIFY( after.offset == 1h ); > >+ VERIFY( after.save == 0min ); > >+ VERIFY( after.abbrev == "CET" ); > >+ > >+ // A query inside the [Oct 20 23:00, Oct 21 00:00] UTC window must be > >+ // in the second line, not in a leftover stretch from the first line. > >+ auto window = tz->get_info(sys_days{1977y/October/20} + 23h + 30min); > >+ VERIFY( window.offset == 1h ); > >+ VERIFY( window.abbrev == "CET" ); > >+} > >-- > >2.55.0 > > > > > >
