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