On Tue, 30 Jun 2026 at 11:00, Tomasz Kamiński <[email protected]> wrote: > > 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 tiem 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. > > Co-authored-by: Álvaro Begué <[email protected]> > Signed-off-by: Tomasz Kamiński <[email protected]> > Signed-off-by: Álvaro Begué <[email protected]> > --- > v4 restores the expansion of the year range to previous/next year > from Álvaro v2 versions. It also adds a test for situations when > expansion is necessary (based on Pacific/Auckland). This version > is more targeted, and only expands the range for Dec/31 and Jan/1. > We do not check local time (t - stdoff) date, as save may still > move the date to previous/next year. > > Futhermore: > * we record last transition for the rules that no longer applies, > as one happening before (see test_eariel)
That should probably be test_earlier, and s/eariel/earlier/g?
