[PATCH v2 2/3] libstdc++: Partial fix for interpretation of non-UTC UNTIL times in tzdata.zi [PR116110]

Tomasz Kaminski tkaminsk@redhat.com
Thu Mar 19 07:07:22 GMT 2026


On Wed, Mar 18, 2026 at 11:23 PM Jonathan Wakely <jwakely@redhat.com> wrote:

> This is a partial fix for PR 116110, so that zone changes that occur at
> a time specified as standard time will be parsed correctly. Previously
> all UNTIL times were assumed to be UTC, which is incorrect according to
> the spec. Only times with a 'u' suffix (or the equivalent 'g' or 'z')
> are UTC times. An 's' suffix means standard time (i.e. the zone's usual
> offset from UTC, without any DST adjustment) and a 'w' suffix (or no
> suffix) means wall time, so the offset and any DST adjustment. This
> commit fixes the handling of 's' suffixes, so that the ZoneInfo::m_until
> member is set correctly. It also fixes the handling of some UNTIL times
> using wall time, specifically those where the DST adjustment is known
> during parsing of the tzdata.zi file.
>
> This is not a complete fix, because as noted in PR 116110 a Zone line
> that refers to a named Rule and uses wall time cannot be determined
> while parsing tzdata.zi. We need to remember that the m_until member is
> not correct, and then adjust it later when we find the rule that applies
> at a given time point. That requires more work.
>
> Some existing tests need to be adjusted due to the fixes in this commit.
> The std/time/time_zone/get_info_sys.cc and std/time/zoned_time/1.cc
> tests are corrected to check that the changes happens at midnight local
> time, not midnight UTC as previously assumed.
>
> One of the FIXME comments in std/time/time_zone/116110.cc can be removed
> now, because the UNTIL time is correctly interpreted as midnight local
> time. The other FIXME needs to be changed to midnight at the local
> standard time, which is still wrong but we don't currently adjust it for
> the DST save time of one hour. It also still needs the incorrect +24h
> due to Bug 124513.
>
> libstdc++-v3/ChangeLog:
>
>         PR libstdc++/116110
>         * src/c++20/tzdb.cc (operator>>(istream&, ZoneInfo&)): Adjust
>         inf.m_until according to indicator suffix on AT time in UNTIL.
>         * testsuite/std/time/time_zone/116110.cc (test_kiritimati):
>         Remove FIXME now that the UNTIL time is adjusted for STDOFF.
>         (test_apia): Adjust FIXME now that UNTIL time is adusted for
>         STDOFF.
>         * testsuite/std/time/time_zone/get_info_sys.cc: Adjust expected
>         results to account for corrected logic.
>         * testsuite/std/time/zoned_time/1.cc: Likewise.
>
> ---
>
> v2: Adjust the added tests for the Pacific time zones.
>
LGTM, with small comment change.

>
> Tested x86_64-linux.
>
>  libstdc++-v3/src/c++20/tzdb.cc                | 15 +++++++++----
>  .../testsuite/std/time/time_zone/116110.cc    |  4 ++--
>  .../std/time/time_zone/get_info_sys.cc        | 22 +++++++++++--------
>  .../testsuite/std/time/zoned_time/1.cc        | 20 ++++++++++-------
>  4 files changed, 38 insertions(+), 23 deletions(-)
>
> diff --git a/libstdc++-v3/src/c++20/tzdb.cc
> b/libstdc++-v3/src/c++20/tzdb.cc
> index e26a9ee38072..82e4790b0d7f 100644
> --- a/libstdc++-v3/src/c++20/tzdb.cc
> +++ b/libstdc++-v3/src/c++20/tzdb.cc
> @@ -2289,11 +2289,18 @@ namespace std::chrono
>           at_time t{};
>           // XXX DAY should support ON format, e.g. lastSun or Sun>=8
>           in >> m >> d >> t;
> -         // XXX UNTIL field should be interpreted
> -         // "using the rules in effect just before the transition"
> -         // so might need to store as year_month_day and hh_mm_ss and only
> -         // convert to a sys_time once we know the offset in effect.
>           inf.m_until = sys_days(year(y)/m.m/day(d)) + seconds(t.time);
> +         if (t.indicator != at_time::Universal)
> +           { // UNTIL uses "the rules in effect just before the
> transition"
> +             // adjust by STDOFF
> +             inf.m_until -= seconds(inf.m_offset);
> +             if (t.indicator != at_time::Standard)
> +               {
> +                 if (inf.m_expanded) // Not a named Rule, SAVE is known
> now.
> +                   inf.m_until -= inf.m_save;
> +                 // else Named Rule, SAVE is unknown. FIXME: PR 116110
> +               }
> +           }
>         }
>        else
>         inf.m_until = sys_days(year::max()/December/31);
> diff --git a/libstdc++-v3/testsuite/std/time/time_zone/116110.cc
> b/libstdc++-v3/testsuite/std/time/time_zone/116110.cc
> index d86011cf5739..a070d3236d1a 100644
> --- a/libstdc++-v3/testsuite/std/time/time_zone/116110.cc
> +++ b/libstdc++-v3/testsuite/std/time/time_zone/116110.cc
> @@ -39,7 +39,7 @@ test_kiritimati()
>    auto* tz = locate_zone("Pacific/Kiritimati");
>    local_seconds t = local_days(1994y/December/31);
>
> -  sys_seconds ut(t.time_since_epoch() /* FIXME: should be + 10h */);
> +  sys_seconds ut(t.time_since_epoch() + 10h);
>    sys_info info;
>    info = tz->get_info(ut - 1s);
>    VERIFY( info.offset == -10h );
> @@ -65,7 +65,7 @@ test_apia()
>    auto* tz = locate_zone("Pacific/Apia");
>    local_seconds t = local_days(2011y/December/29) + 24h;
>
> -  sys_seconds ut(t.time_since_epoch() /* FIXME: should be + 10h */ - 24h
> );
> +  sys_seconds ut(t.time_since_epoch() /* FIXME: should be + 10h */ + 11h
> - 24h );
>
And note that this is because the fix is incomplete, this is exactly the
case described as
not yet addressed in the commit description.

   sys_info info;
>    info = tz->get_info(ut - 1s);
>    VERIFY( info.offset == (-11h + info.save) );
> diff --git a/libstdc++-v3/testsuite/std/time/time_zone/get_info_sys.cc
> b/libstdc++-v3/testsuite/std/time/time_zone/get_info_sys.cc
> index 769c7744017b..88f16b76ff48 100644
> --- a/libstdc++-v3/testsuite/std/time/time_zone/get_info_sys.cc
> +++ b/libstdc++-v3/testsuite/std/time/time_zone/get_info_sys.cc
> @@ -12,33 +12,37 @@ test_zurich()
>    const time_zone* const tz = locate_zone("Europe/Zurich");
>
>    {
> -    sys_days d = 1853y/July/16;
> +    sys_days d = 1853y/July/16; // On this date ...
> +    auto offset = 34min + 8s;   // ... local time is this far ahead of
> UTC,
> +    auto t = d - offset;        // so LMT to BMT transition is at this
> time.
>
> -    auto info = tz->get_info(d - 1s);
> -    VERIFY( info.offset == (34min + 8s) );
> +    auto info = tz->get_info(t - 1s);
> +    VERIFY( info.offset == offset );
>      VERIFY( info.abbrev == "LMT" );
>
> -    info = tz->get_info(d);
> +    info = tz->get_info(t);
>      VERIFY( info.offset == (29min + 46s) );
>      VERIFY( info.abbrev == "BMT" );
>
> -    info = tz->get_info(d + 1s);
> +    info = tz->get_info(t + 1s);
>      VERIFY( info.offset == (29min + 46s) );
>      VERIFY( info.abbrev == "BMT" );
>
> -    info = tz->get_info(d + 0.001s);
> +    info = tz->get_info(t + 0.001s);
>      VERIFY( info.offset == (29min + 46s) );
>      VERIFY( info.abbrev == "BMT" );
>    }
>
>    {
>      sys_days d = 1894y/June/1;
> +    auto offset = 29min + 46s;
> +    auto t = d - offset;
>
> -    auto info = tz->get_info(d - 1s);
> -    VERIFY( info.offset == (29min + 46s) );
> +    auto info = tz->get_info(t - 1s);
> +    VERIFY( info.offset == offset );
>      VERIFY( info.abbrev == "BMT" );
>
> -    info = tz->get_info(d);
> +    info = tz->get_info(t);
>      VERIFY( info.offset == 1h );
>      VERIFY( info.abbrev == "CET" );
>    }
> diff --git a/libstdc++-v3/testsuite/std/time/zoned_time/1.cc
> b/libstdc++-v3/testsuite/std/time/zoned_time/1.cc
> index 1623aca1c7a8..a77cd99dd550 100644
> --- a/libstdc++-v3/testsuite/std/time/zoned_time/1.cc
> +++ b/libstdc++-v3/testsuite/std/time/zoned_time/1.cc
> @@ -48,24 +48,26 @@ test_zurich()
>    const time_zone* const zurich = locate_zone("Europe/Zurich");
>
>    {
> -    sys_days d = 1853y/July/16;
> +    sys_days d = 1853y/July/16; // On this date ...
> +    auto offset = 34min + 8s;   // ... local time is this far ahead of
> UTC,
> +    auto t = d - offset;        // so LMT to BMT transition is at this
> time.
>
> -    auto z = zoned_seconds(zurich, sys_seconds(d) - 1s);
> +    auto z = zoned_seconds(zurich, t - 1s);
>      auto info = z.get_info();
> -    VERIFY( info.offset == (34min + 8s) );
> +    VERIFY( info.offset == offset );
>      VERIFY( info.abbrev == "LMT" );
>
> -    z = zoned_seconds(zurich, d);
> +    z = zoned_seconds(zurich, t);
>      info = z.get_info();
>      VERIFY( info.offset == (29min + 46s) );
>      VERIFY( info.abbrev == "BMT" );
>
> -    z = zoned_seconds(zurich, d + 1s);
> +    z = zoned_seconds(zurich, t + 1s);
>      info = z.get_info();
>      VERIFY( info.offset == (29min + 46s) );
>      VERIFY( info.abbrev == "BMT" );
>
> -    auto z2 = zoned_time(zurich, d + 0.001s);
> +    auto z2 = zoned_time(zurich, t + 0.001s);
>      info = z2.get_info();
>      VERIFY( info.offset == (29min + 46s) );
>      VERIFY( info.abbrev == "BMT" );
> @@ -73,13 +75,15 @@ test_zurich()
>
>    {
>      sys_days d = 1894y/June/1;
> +    auto offset = 29min + 46s;
> +    auto t = d - offset;
>
> -    auto z = zoned_seconds(zurich, sys_seconds(d) - 1s);
> +    auto z = zoned_seconds(zurich, t - 1s);
>      auto info = z.get_info();
>      VERIFY( info.offset == (29min + 46s) );
>      VERIFY( info.abbrev == "BMT" );
>
> -    z = zoned_seconds(zurich, d);
> +    z = zoned_seconds(zurich, t);
>      info = z.get_info();
>      VERIFY( info.offset == 1h );
>      VERIFY( info.abbrev == "CET" );
> --
> 2.53.0
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20260319/9c6e1b86/attachment-0001.htm>


More information about the Libstdc++ mailing list