On Tue, 17 Mar 2026 at 13:18, Tomasz Kaminski <[email protected]> wrote:
>
>
>
> On Mon, Mar 16, 2026 at 11:55 PM Jonathan Wakely <[email protected]> wrote:
>>
>> The Australia/Broken_Hill example in PR libstdc++/116110 demonstrates a
>> bug in the time zone code. The current code gives incorrect results when
>> a zone changes from one named Rule to another during DST, e.g. the
>> Broken_Hill time zone uses:
>>
>> Zone Australia/Broken_Hill 9:25:48 - LMT 1895 Feb
>> 10 - AEST 1896 Aug 23
>> 9 - ACST 1899 May
>> 9:30 AU AC%sT 1971
>> 9:30 AN AC%sT 2000
>> 9:30 AS AC%sT
>>
>> So the AN Rules take effect on 2000 Jan 1 which is during DST (which
>> runs from October to March).
>
> Now, I understand the problem: the end of AS 2000 Jan 1, which according
> to the application is:
> R AS 1987 2007 - O lastSu 2s 1 D
> However, we seem to be searching the wrong rule list because we are using
> the "AN" rules to find previous value of DST, not the AS. This still gives us
> expected save value, as I assume the rule switch is not meant to cause abrupt
> changes to the actual wall time, and the changes in wall clock happen
> accordingly
> to DST.
Right.
>
> I.e. we make assumption that at the time of the rule change, the save value
> is same according to new and old rules.
>
> So I think the code is technically incorrect, but practically correct. To be
> exact,
> we would need look use the rule set from previous iterator in that case.
I think we're handling this case wrong, but with the patch we give the
right result for this time zone.
The zic(8) man page says:
A zone or continuation line L with a named rule set starts with standard
time by default: that is, any of L's timestamps preceding L's earliest
rule use the rule in effect after L's first transition into standard
time.
I think that means that we should not actually change from the AN rule
to the AS rule on Jan 1st 2001, we should continue using the AN rule
until the first transition to standard time of the AS rule.
I'll see if that's reasonable to implement, because there could be
cases where what this patch does is wrong e.g. if the AN and AS rules
used different formats, say AC%sT and AC%sT2, then we should not
change from ACDT to ACDT2 on Jan 1, but should continue using ACDT
until the end of DST, then change to ACT, then change to ACDT2 in
October 2001. It doesn't matter in this case because both rules use
the same offset and the same format, so it's not detectable that we
change to a new rule on Jan 1.
>>
>>
>> The fix for this is to update info.offset and info.save when we find an
>> active rule, instead of only updating the 'letters' variable.
>>
>> libstdc++-v3/ChangeLog:
>>
>> PR libstdc++/116110
>> * src/c++20/tzdb.cc (time_zone::_M_get_sys_info): Update
>> info.offset and info.save to values from the active rule.
>> * testsuite/std/time/time_zone/116110.cc: New test.
>> ---
>>
>> Tested x86_64-linux.
>
> Add the explanation of the assumption that save value at rule change is same
> as with previous rules, and we are OK. The comment before ri.offset() would
> benefit from update.
>>
>>
>> libstdc++-v3/src/c++20/tzdb.cc | 6 ++++-
>> .../testsuite/std/time/time_zone/116110.cc | 22 +++++++++++++++++++
>> 2 files changed, 27 insertions(+), 1 deletion(-)
>> create mode 100644 libstdc++-v3/testsuite/std/time/time_zone/116110.cc
>>
>> diff --git a/libstdc++-v3/src/c++20/tzdb.cc b/libstdc++-v3/src/c++20/tzdb.cc
>> index 53441880ae6e..e26a9ee38072 100644
>> --- a/libstdc++-v3/src/c++20/tzdb.cc
>> +++ b/libstdc++-v3/src/c++20/tzdb.cc
>> @@ -917,7 +917,11 @@ namespace std::chrono
>
>
>
>
> This change is inside if and can be completely omitted:
> string_view letters;
> if (i != infos.begin() && i[-1].expanded())
> letters = i[-1].next_letters();
>
> if (letters.empty())
> {
> And I wondered if caching the next_letters is causing any trouble
> here (i.e. we should have also handle saving in this situation), however
> The code below ensures that list of expanded zones always ends with the
> save == 0, so it is correct.
>
>
>
>>
>> }
>>
>> if (active_rule)
>> - letters = active_rule->letters;
>> + {
>> + info.offset = ri.offset() + active_rule->save;
>> + info.save = chrono::duration_cast<minutes>(active_rule->save);
>> + letters = active_rule->letters;
>> + }
>> else if (first_std)
>> letters = first_std->letters;
>> }
>> diff --git a/libstdc++-v3/testsuite/std/time/time_zone/116110.cc
>> b/libstdc++-v3/testsuite/std/time/time_zone/116110.cc
>> new file mode 100644
>> index 000000000000..b52e83b293cc
>> --- /dev/null
>> +++ b/libstdc++-v3/testsuite/std/time/time_zone/116110.cc
>> @@ -0,0 +1,22 @@
>> +// { dg-do run { target c++20 } }
>> +// { dg-require-effective-target tzdb }
>> +// { dg-require-effective-target cxx11_abi }
>> +
>> +#include <chrono>
>> +#include <testsuite_hooks.h>
>> +
>> +void
>> +test_broken_hill()
>> +{
>> + using namespace std::chrono;
>> + auto* tz = locate_zone("Australia/Broken_Hill");
>>
>> + auto info = tz->get_info(sys_days(2000y/February/29d) + 23h + 23min +
>> 23s);
>> + VERIFY( info.offset == 630min );
>> + VERIFY( info.save == 60min );
>> + VERIFY( info.abbrev == "ACDT" );
>> +}
>> +
>> +int main()
>> +{
>> + test_broken_hill();
>> +}
>> --
>> 2.53.0
>>