libstdc++: Cascade wall-time saves in lazy expansion seeding [PR124853]

When _M_get_sys_info seeds a Zone line by looking up the active rule
just before info.begin, the previous code interpreted each rule in
isolation against ri.offset() (the line's standard offset alone),
ignoring the running save accumulated by earlier rules in the same
year. For most zones this gives the right answer because the search
only matters when no rule has fired yet, but for zones whose rule
set has wall-time rules whose effective firing time depends on a
prior rule's save it produces wrong answers.

Canonical case: Europe/Paris around 1945.  France's rules

  R Fr 1945 o - Apr 2  2 2 M
  R Fr 1945 o - Sep 16 3 0 -

both use plain wall time. In Paris's stdoff=1 frame, the September
rule's at_time of 03:00 wall translates to UT Sep 16 02:00 if no
prior save is applied, but to UT Sep 16 00:00 once the running save
of 2h from the April rule is taken into account.  When seeding a
sys_info whose info.begin falls between those two values, the simple
search picks the April rule (save=2 → CEMT, total offset 3h) when
the correct answer is the September rule (save=0 → CET, total offset
1h). libstdc++ reports this as a sustained CEMT stretch where zic
and libc agree on CET.

To address above the finding algorithm, is now expanded to collect
three rule transitions around specified time t, while continuing to
ignore the save.
* curr_tran: transition happening before or at time t,
* prev_tran: transition preceding above transition,
* next_tran: transition happening after time t.

This collects sufficient information to adjust the start_time (if
Wall time is used) for curr_tran (save of prev_tran) and next_tran
(save of curr_tran). Assuming that applying save value does not
change order of transition (cascading save would be ill-defined
otherwise), after the adjustment the actual active rule is:
 * next_tran.rule: if the adjustment pushed next_tran.when to
   time before or at t, which happen for positive save (see
   test_positive),
 * prev_tran.rule: if adjustment pushed curr_tran.when to time
   after time t, which happens for negative save (see test_negative),
 * curr_tran.rule.

For the time at the start (Jan/1) or end of the year (December/31),
for each rule, in addition to transition in year of t, we check
transitions in previous or next year respectively (years in range
[first_year, last_year]). This handle rules whose firing (specified
in local time) crosses a year boundary due to a large stdoff or save.
One example is Pacific/Auckland's 1946 Jan 1 rule, in stdoff=12h,
fires at 1945-12-31 11:30 UT, see test_next_year.

The fallback "earliest STD rule" logic is preserved for the case
where no rule has fired yet, but is extracted to separate function.
This lookup is optimized, by searching the rules by name, from, and
save in that order, grouping std rules in given year together.

	PR libstdc++/124853

libstdc++-v3/ChangeLog:

	* src/c++20/tzdb.cc
	(time_zone::_M_get_sys_info): Extract code blocks to
	separate functions, and invoke them.
	(<unnamed>::find_active_rule): Modify algorithm to
	handle cascading saves.
	(<unnamed>::find_first_std): Simplified implementation
	benefiting from reordering of rules.
	(chrono::reload_tzdb): Sort rules by name, from and save.
	* testsuite/std/time/time_zone/wall_cascade.cc: New test.

Reviewed-by: Tomasz Kamiński <tkaminsk@redhat.com>
Co-authored-by: Álvaro Begué <alvaro.begue@gmail.com>
Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>
Signed-off-by: Álvaro Begué <alvaro.begue@gmail.com>
2 files changed