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