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