bool
calc_save(span<const Rule> all_rules) noexcept;
+ // save value at the transition boundary, usable if expanded()
+ // is true or after calc_save() was called.
+ seconds
+ save() const noexcept { return m_save; }
+
friend istream& operator>>(istream&, ZoneInfo&);
bool
// 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.
+ // or is an unexpanded (rule-based) ZoneInfo for a different rule.
info.offset = ri.offset();
info.save = 0min;
info.end = ri.until();
letters = first_std->letters;
}
+ // For transitions, that leads to backward jump in the local time,
+ // and window of duplicated local time, the rule transition occurring
+ // during that window are considered to apply immediately at the boundary.
+ // This window is [info.begin, info.begin + merge_window].
+ seconds merge_window(0);
+ if (i != infos.begin())
+ {
+ const auto prev_offset = i[-1].offset() + i[-1].save();
+ if (prev_offset > info.offset)
+ merge_window = prev_offset - info.offset;
+ }
+
const Rule* curr_rule = nullptr;
while (info.begin < info.end && num_after > 0)
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)
+ // Transitions occuring in the backward jump time window occuring
+ // on zone transitions should be folded into zone change.
+ if (info.end - t <= merge_window)
+ info.begin = t;
+ else
{
- // Finish before a STD 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 || !next_rule || next_rule->save == 0s)
- --num_after;
+ 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)
+ {
+ // Finish before a STD 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 || !next_rule || next_rule->save == 0s)
+ --num_after;
+ }
+
+ info.begin = info.end;
}
+ merge_window = seconds(0);
- info.begin = info.end;
if (next_rule)
{
info.end = ri.until();
std::ofstream("tzdata.zi") << R"(# version test_negative_cascade
R Fr 1945 o - Apr 2 2 -3 M
R Fr 1945 o - Sep 16 0 0 -
-Z Test/Negative 0 - X 1945 Sep 16 1u
+Z Test/Negative -2 - X 1945 Sep 16 1u
1 Fr CE%sT
)";
// Test the firing of Sep 16 rule
auto at_sep_rule = tz->get_info(sys_seconds{
sys_days(1945y/September/16) + 2h});
- // The transition_window < 1d condition triggers, and
- // transition is ignored.
- // VERIFY( at_sep_rule.offset == 1h );
- // VERIFY( at_sep_rule.save == 0h );
- // VERIFY( at_sep_rule.abbrev == "CET" );
+ VERIFY( at_sep_rule.offset == 1h );
+ VERIFY( at_sep_rule.save == 0h );
+ VERIFY( at_sep_rule.abbrev == "CET" );
}
void
VERIFY( override_used ); // If this fails then XFAIL for the target.
VERIFY( db.version == "test_next_year" );
- // Pacific/Auckland requires both PR124854 and PR116110 to work
- // correctly. TODO test it once implemented.
- // The UT version uses 1945-12-31 13:00:00 UT after
- // the rule application change.
- auto* utz = locate_zone("Pacific/AucklandUT");
+ // The time zone change happens at 1945-12-31 12:00:00 UT, as
+ // total offset is 11:30 + 0:30
+ auto* tz = locate_zone("Pacific/Auckland");
// Before the change
+ auto before_boundary
+ = tz->get_info(sys_seconds{sys_days(1945y/December/31) + 12h - 1s});
+ VERIFY( before_boundary.offset == 12h );
+ VERIFY( before_boundary.save == 30min );
+ VERIFY( before_boundary.abbrev == "NZST" );
+
+ // The Jan 1 rule is immediatelly in effect
+ auto at_boundary
+ = tz->get_info(sys_seconds{sys_days(1945y/December/31) + 12h});
+ VERIFY( at_boundary.offset == 12h );
+ VERIFY( at_boundary.save == 0h );
+ VERIFY( at_boundary.abbrev == "NZST" );
+
+ auto after_boundary
+ = tz->get_info(sys_seconds{sys_days(1945y/December/31) + 13h});
+ VERIFY( after_boundary.offset == 12h );
+ VERIFY( after_boundary.save == 0h );
+
+ // The UT version uses 1945-12-31 13:00:00 UT after the rule
+ // Jan 1 1946 rule application.
+ auto* utz = locate_zone("Pacific/AucklandUT");
+
+ // Before the Jan 1 rule application
auto before_utboundary
= utz->get_info(sys_seconds{sys_days(1945y/December/31) + 11h});
VERIFY( before_utboundary.offset == 12h );
--- /dev/null
+// { 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* } }
+
+// When two adjacent Zone lines differ in total offset and the new line's
+// rule set has a rule firing within |jump| of the boundary (where jump
+// is a backward local-time jump), zic.c's writezone folds that rule
+// into the boundary, so the new line begins with the post-rule save.
+//
+// Mirrors America/Argentina/Buenos_Aires around 1999-10-03.
+
+#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;
+
+ // stdoff jumps from -3 to -4 at the same instant a save=1 rule fires.
+ // In the new (-4) frame the rule fires 1 hour after the boundary at
+ // UT 03:00, so the merge folds the rule into the boundary and the
+ // new line begins at offset=-3, save=1 (abbrev "-03").
+ std::ofstream("tzdata.zi") << R"(# version test_zone_merge
+R T 1999 o - O 3 0 1 -
+R T 2000 o - Mar 3 0 0 -
+Z Test/BA -3 - %z 1999 O 3
+ -4 T %z 2000 Mar 3
+ -3 - %z
+)";
+
+ const auto& db = reload_tzdb();
+ VERIFY( override_used );
+ VERIFY( db.version == "test_zone_merge" );
+
+ auto* tz = locate_zone("Test/BA");
+
+ // The boundary is the wall UNTIL "1999 O 3" (default time 00:00)
+ // interpreted in the prior (-3) frame, i.e. UT 03:00 1999-10-03.
+ sys_seconds boundary{sys_days(1999y/October/3) + 3h};
+
+ auto before = tz->get_info(boundary - 1s);
+ VERIFY( before.offset == -3h );
+ VERIFY( before.save == 0min );
+ VERIFY( before.abbrev == "-03" );
+
+ // The new line's first sys_info already has save=1 from the merge,
+ // total offset -3h, abbrev "-03".
+ auto at_boundary = tz->get_info(boundary);
+ VERIFY( at_boundary.offset == -3h );
+ VERIFY( at_boundary.save == 60min );
+ VERIFY( at_boundary.abbrev == "-03" );
+
+ auto plus_30min = tz->get_info(boundary + 30min);
+ VERIFY( plus_30min.offset == -3h );
+ VERIFY( plus_30min.save == 60min );
+ VERIFY( plus_30min.abbrev == "-03" );
+
+ // Sanity: well after the boundary, still in the merged sys_info
+ // until the Mar 3 2000 transition.
+ auto winter = tz->get_info(sys_days(2000y/January/15));
+ VERIFY( winter.offset == -3h );
+ VERIFY( winter.save == 60min );
+ VERIFY( winter.abbrev == "-03" );
+
+ // After Mar 3 2000: line 2 ends, line 3 begins. No DST rule fires
+ // at this boundary, so total offset reverts to -3h with save=0.
+ auto spring = tz->get_info(sys_days(2000y/April/15));
+ VERIFY( spring.offset == -3h );
+ VERIFY( spring.save == 0min );
+ VERIFY( spring.abbrev == "-03" );
+}