https://gcc.gnu.org/g:bbf3f6dc951fcc7c0ebf41cc4e52a0657f0dc4bb
commit r15-11159-gbbf3f6dc951fcc7c0ebf41cc4e52a0657f0dc4bb Author: Jonathan Wakely <[email protected]> Date: Thu Mar 19 11:42:48 2026 +0000 libstdc++: Fix parsing of UNTIL times in tzdata.zi [PR124513] 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/116110.cc (test_apia): Remove offset of 24h now that the UNTIL time is parsed correctly. * testsuite/std/time/time_zone/124513.cc: New test. Reviewed-by: Tomasz KamiĆski <[email protected]> (cherry picked from commit fbc5d2b1ab17ba7eb919f2c77f5788645c18006a) Diff: --- libstdc++-v3/src/c++20/tzdb.cc | 20 ++-- .../testsuite/std/time/time_zone/116110.cc | 3 +- .../testsuite/std/time/time_zone/124513.cc | 111 +++++++++++++++++++++ 3 files changed, 124 insertions(+), 10 deletions(-) diff --git a/libstdc++-v3/src/c++20/tzdb.cc b/libstdc++-v3/src/c++20/tzdb.cc index 367f056b12e1..3a66c4d065a7 100644 --- a/libstdc++-v3/src/c++20/tzdb.cc +++ b/libstdc++-v3/src/c++20/tzdb.cc @@ -264,11 +264,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; } @@ -2054,10 +2057,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; @@ -2076,11 +2080,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/116110.cc b/libstdc++-v3/testsuite/std/time/time_zone/116110.cc index 0f3e09690e0e..26b9ba33c316 100644 --- a/libstdc++-v3/testsuite/std/time/time_zone/116110.cc +++ b/libstdc++-v3/testsuite/std/time/time_zone/116110.cc @@ -66,8 +66,7 @@ test_apia() local_seconds t = local_days(2011y/December/29) + 24h; // FIXME: this should be + 10h but we do not account for DST yet, so + 11h. - // The 24h is because we don't parse the "24" in the Zone line (PR 124513). - sys_seconds ut(t.time_since_epoch() + 11h - 24h ); + sys_seconds ut(t.time_since_epoch() + 11h ); 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/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(); +}
