On Mon, Mar 16, 2026 at 11:52 PM Jonathan Wakely <[email protected]> wrote:

> Zone lines ending with a plain number as the time for the DST transition
> (e.g. "2026 Mar 16 2") were incorrectly parsed as changing at midnight.
> The problem was that eofbit got set after extracting the "2" from the
> stream using `in >> i`, then the `in >> at.indicator` expression
> failed and set failbit, so that the `if (in >> at.indicator)` condition
> was always false and so the assignment to at.time guarded by that
> condition was never performed. So the at.time member was always left
> equal to zero, i.e. midnight. Suffixed times such as "2s" or "2u" were
> parsed correctly.
>
> This commit fixes the operator>> overload for the Indicator enum to not
> cause failbit to be set if eofbit is already set. This means that the
> `in >> at.indicator` expression yields true when converted to bool, and
> the assignment to at.time happens. Not trying to extract an Indicator
> when eofbit is already set is correct, because it's valid for there to
> be no indicator suffix on an AT time (that just means the time should be
> interpreted as wall time).
>
> There was also a bug in the handling of a "-" value for the time, which
> is supposed to mean midnight but was not being parsed due to failing to
> skip leading whitespace. That did no harm in most cases, because the "-"
> would not be extracted from the stream but would then cause a failure to
> parse an integer number of hours, and no time would be set, causing it
> to default to midnight, which is what "-" means anyway. However, a value
> of "-u" is supposed to mean midnight UTC and "-s" is supposed to mean
> midnight standard time, and the indicators for UTC or standard were not
> being set in those cases. This fix uses std::ws to skip initial
> whitespace, and then correctly handles "-" followed by EOF.
>
> libstdc++-v3/ChangeLog:
>
>         PR libstdc++/124513
>         * src/c++20/tzdb.cc (operator>>(istream&, at_time::Indicator&)):
>         Do not peek at the next character if eofbit is already set.
>         (istream& operator>>(istream&, at_time&)): Skip whitespace
>         before the first character. Handle EOF when parsing "-" as time.
>         Do not peek for ":" or "." if eofbit already set.
>         * testsuite/std/time/time_zone/124513.cc: New test.
> ---
>
> Tested x86_64-linux.
>
This also LGTM.

>
>  libstdc++-v3/src/c++20/tzdb.cc                |  20 ++--
>  .../testsuite/std/time/time_zone/124513.cc    | 111 ++++++++++++++++++
>  2 files changed, 123 insertions(+), 8 deletions(-)
>  create mode 100644 libstdc++-v3/testsuite/std/time/time_zone/124513.cc
>
> diff --git a/libstdc++-v3/src/c++20/tzdb.cc
> b/libstdc++-v3/src/c++20/tzdb.cc
> index 82e4790b0d7f..e42ba70dee24 100644
> --- a/libstdc++-v3/src/c++20/tzdb.cc
> +++ b/libstdc++-v3/src/c++20/tzdb.cc
> @@ -272,11 +272,14 @@ namespace std::chrono
>        // If not, indic is unchanged. Callers should set a default first.
>        friend istream& operator>>(istream& in, Indicator& indic)
>        {
> -       auto [val, yes] = at_time::is_indicator(in.peek());
> -       if (yes)
> +       if (!in.eof())
>           {
> -           in.ignore(1);
> -           indic = val;
> +           auto [val, yes] = at_time::is_indicator(in.peek());
> +           if (yes)
> +             {
> +               in.ignore(1);
> +               indic = val;
> +             }
>           }
>         return in;
>        }
> @@ -2186,10 +2189,11 @@ namespace std::chrono
>      istream& operator>>(istream& in, at_time& at)
>      {
>        int sign = 1;
> -      if (in.peek() == '-')
> +      if (ws(in).peek() == '-')
>         {
>           in.ignore(1);
> -         if (auto [val, yes] = at_time::is_indicator(in.peek()); yes)
> +         if (auto [val, yes] = at_time::is_indicator(in.peek());
> +             in.eof() || yes)
>             {
>               in.ignore(1);
>               at.time = 0s;
> @@ -2208,11 +2212,11 @@ namespace std::chrono
>           in.ignore(1); // discard the colon.
>           in >> i;
>           m = minutes{i};
> -         if (in.peek() == ':')
> +         if (!in.eof() && in.peek() == ':')
>             {
>               in.ignore(1); // discard the colon.
>               in >> i;
> -             if (in.peek() == '.')
> +             if (!in.eof() && in.peek() == '.')
>                 {
>                   double frac;
>                   in >> frac;
> diff --git a/libstdc++-v3/testsuite/std/time/time_zone/124513.cc
> b/libstdc++-v3/testsuite/std/time/time_zone/124513.cc
> new file mode 100644
> index 000000000000..49d7ba8bb016
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/std/time/time_zone/124513.cc
> @@ -0,0 +1,111 @@
> +// { dg-do run { target c++20 } }
> +// { dg-require-effective-target tzdb }
> +// { dg-require-effective-target cxx11_abi }
> +// { dg-xfail-run-if "no weak override on AIX" { powerpc-ibm-aix* } }
> +
> +#include <chrono>
> +#include <fstream>
> +#include <testsuite_hooks.h>
> +
> +static bool override_used = false;
> +
> +namespace __gnu_cxx
> +{
> +  const char* zoneinfo_dir_override() {
> +    override_used = true;
> +    return "./";
> +  }
> +}
> +
> +void
> +test_mawson()
> +{
> +  using namespace std::chrono;
> +
> +  std::ofstream("tzdata.zi") << R"(# version test1
> +Z Antarctica/Mawson 0 - -00 1954 F 13
> +                    6 - %z  2009 O 18 2
> +                    5 - %z
> +)";
> +
> +  const auto& db = reload_tzdb();
> +  VERIFY( override_used ); // If this fails then XFAIL for the target.
> +  VERIFY( db.version == "test1" );
> +
> +  auto zone = locate_zone("Antarctica/Mawson");
> +  auto info = zone->get_info(sys_days(2009y/October/19));
> +  VERIFY( info.begin == sys_days(2009y/October/17) + 20h);
> +}
> +
> +void
> +test_custom()
> +{
> +  using namespace std::chrono;
> +
> +  std::ofstream("tzdata.zi") << R"(# version test2
> +Z Test/Zomg 3:00:00 - LMT 1990 Mar 1 2
> +            4       - DST 1990 Oct 1 2
> +            3       - STD 1991 Mar 1 3:30
> +            3       1 DST 1991 Oct 1 3:45:01
> +            3       - STD 1992 Mar 1 -
> +            3       1 DST 1992 Oct 1 -u
> +            3       - STD 1993 Mar 1 -s
> +            3       2 DST 1993 Oct 1 -s
> +            3       - STD
> +
> +Z Test/Zoinks 0:00  -    LMT 1990 Mar 1 2
> +              0:00  1:00 DST 1990 Oct 1 3
> +              0:00  -    STD 1991 Mar 1 1:30
> +              0:00  1:00 DST 1991 Oct 1 3:30
> +              0:00  -    STD
> +)";
> +
> +  const auto& db = reload_tzdb();
> +  VERIFY( override_used ); // If this fails then XFAIL for the target.
> +  VERIFY( db.version == "test2" );
> +
> +  const auto offset = 3h;
> +
> +  const time_zone* zone;
> +  sys_info info;
> +
> +  zone = locate_zone("Test/Zomg");
> +  info = zone->get_info(sys_days(1990y/June/1));
> +  VERIFY( info.begin == sys_days(1990y/March/1) + 2h - 3h);
> +  VERIFY( info.end   == sys_days(1990y/October/1) + 2h - 4h);
> +
> +  info = zone->get_info(sys_days(1991y/June/1));
> +  VERIFY( info.begin == sys_days(1991y/March/1) + 3h + 30min - 3h);
> +  VERIFY( info.end   == sys_days(1991y/October/1) + 3h + 45min + 1s - 4h);
> +
> +  info = zone->get_info(sys_days(1992y/June/1));
> +  VERIFY( info.begin == sys_days(1992y/March/1) - 3h);
> +  VERIFY( info.end   == sys_days(1992y/October/1));   // -u means
> midnight UTC
> +
> +  info = zone->get_info(sys_days(1992y/November/1));
> +  VERIFY( info.begin == sys_days(1992y/October/1));    // -u means
> midnight UTC
> +  VERIFY( info.end   == sys_days(1993y/March/1) - 3h); // -s means
> midnight STD
> +
> +  info = zone->get_info(sys_days(1993y/June/1));
> +  VERIFY( info.begin == sys_days(1993y/March/1) -3h);  // -s means
> midnight STD
> +  VERIFY( info.end   == sys_days(1993y/October/1) - 3h);
> +
> +
> +  zone = locate_zone("Test/Zoinks");
> +  info = zone->get_info(sys_days(1990y/June/1));
> +  VERIFY( info.begin == sys_days(1990y/March/1) + 2h);
> +  VERIFY( info.end   == sys_days(1990y/October/1) + 3h - 1h);
> +
> +  info = zone->get_info(sys_days(1991y/June/1));
> +  VERIFY( info.begin == sys_days(1991y/March/1) + 1h + 30min);
> +  VERIFY( info.end   == sys_days(1991y/October/1) + 3h + 30min - 1h);
> +
> +  info = zone->get_info(sys_days(1992y/June/1));
> +  VERIFY( info.begin == sys_days(1991y/October/1) + 3h + 30min - 1h);
> +}
> +
> +int main()
> +{
> +  test_custom();
> +  test_mawson();
> +}
> --
> 2.53.0
>
>

Reply via email to