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
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)
{
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
{
// 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();
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
}
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{};
#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
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.
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
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;
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 tapproximate system time at, and
+ // collect 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*
}
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
// Find the transition info for the time point.
auto i = ranges::upper_bound(infos, tp, ranges::less{}, &ZoneInfo::until);
+ // Perform the comparison on save adjusted until values (if needed)
+ // Assume that applying the save will not change relative order of
+ // 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();
+ }
if (i->to(info)) // We already know a sys_info for this time.
return info;
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;
// 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();
if (rules == "-")
{
// Standard time always applies, no DST.
+ inf.m_save = minutes(0);
}
else
{
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);
--- /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* } }
+
+// 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" );
+}