On Wed, Mar 18, 2026 at 8:59 AM Tomasz Kaminski <[email protected]> wrote:

>
>
> On Wed, Mar 18, 2026 at 8:41 AM Tomasz Kaminski <[email protected]>
> wrote:
>
>>
>>
>> On Tue, Mar 17, 2026 at 6:16 PM Jonathan Wakely <[email protected]>
>> wrote:
>>
>>> 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 describes how the rule is interpreted for dates before its
>> first
>> entry, i.e. 1971 for both AN and AS:
>> R AN 1971 1985 - O lastSu 2s 1 D
>> R AN 1972 o - F 27 2s 0 S
>> R AN 1973 1981 - Mar Su>=1 2s 0 S
>> R AS 1971 1985 - O lastSu 2s 1 D
>> R AS 1986 o - O 19 2s 1 D
>> R AS 1987 2007 - O lastSu 2s 1 D
>> R AS 1972 o - F 27 2s 0 S
>> R AS 1973 1985 - Mar Su>=1 2s 0 S
>>
>>
>>> 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.
>>>
>> Given that the entries may also switch the offset, I think they meant to
>> apply
>> until the end date, for example, the Kirimiti zone that switched between
>> sides
>> o International Day Line:
>> Z Pacific/Kiritimati -10:29:20 - LMT 1901
>> -10:40 - %z 1979 O
>> -10 - %z 1994 D 31
>> 14 - %z
>> Switch happened on 23:59:59 21th December of 1994.
>>
> Switch happened on 23:59:59 30th December of 1994.
>
I found an example that illustrates that change happens immediately: Samoa
(the nation).
using the Pacific/Apia time zone, that skipped 30th Decembner of 2011:
Z Pacific/Apia 12:33:4 - LMT 1892 Jul 5
-11:26:56 - LMT 1911
-11:30 - %z 1950
-11 WS %z 2011 D 29 24
13 WS %z

The skip happened during DST (so offset is +1), and date command prints
atjusted offset immediately at the time of change:
~$ TZ="Pacific/Apia" date -d '29 Dec 2011 23:00:00'
Thu Dec 29 11:00:00 PM -10 2011
~$ TZ="Pacific/Apia" date -d '31 Dec 2011 00:00:00'
Sat Dec 31 12:00:00 AM +14 2011



>
>>
>>> 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
>>> >>
>>>
>>>

Reply via email to