https://gcc.gnu.org/g:3d1c848cd57e9c991b5e670fdc11d819cf999b5b
commit r17-4929-g3d1c848cd57e9c991b5e670fdc11d819cf999b5b Author: Jonathan Wakely <[email protected]> Date: Mon Aug 3 19:23:51 2026 +0100 libstdc++: Make chrono::parse support valid leap seconds [PR126543] Formatting utc_time can produce a time ending with ":60" for a leap second, so we should also be able to read that back using chrono::parse. We should reject ":60" unless the time really is a leap second, so check that the full time was 23:59:60 and do a lookup in the list of known leap seconds. Continue to reject ":60" for time points except utc_time. libstdc++-v3/ChangeLog: PR libstdc++/126543 * include/bits/chrono_io.h (from_stream(..., utc_time&, ...)): Use chrono::get_leap_second_info to check for valid leap seconds. (_Parser::operator()) <R>: Do not accept "60" for minutes. <S>: Accept "60" for seconds. * testsuite/std/time/parse/114279.cc: Use a real leap second as the test input. Check parsing whole seconds as well as fractional seconds. * testsuite/std/time/parse/126543.cc: New test. Reviewed-by: Tomasz KamiĆski <[email protected]> Diff: --- libstdc++-v3/include/bits/chrono_io.h | 16 ++++++++-- libstdc++-v3/testsuite/std/time/parse/114279.cc | 28 ++++++++++++----- libstdc++-v3/testsuite/std/time/parse/126543.cc | 40 +++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 10 deletions(-) diff --git a/libstdc++-v3/include/bits/chrono_io.h b/libstdc++-v3/include/bits/chrono_io.h index eec12357fdf7..33f6fcb9d2c6 100644 --- a/libstdc++-v3/include/bits/chrono_io.h +++ b/libstdc++-v3/include/bits/chrono_io.h @@ -3961,7 +3961,13 @@ namespace __detail // "23:59:60" to correctly produce a time within a leap second. auto __ut = utc_clock::from_sys(__p._M_sys_days) + __p._M_time - *__offset; - __tp = __detail::__round<_Duration>(__ut); + // Reject ":60" seconds unless the utc_time is a known leap second. + if (__p._M_is_leap_second + && (chrono::floor<seconds>(__p._M_time - *__offset) != hours(24) + || !chrono::get_leap_second_info(__ut).is_leap_second)) + __is.setstate(ios_base::failbit); + else + __tp = __detail::__round<_Duration>(__ut); } return __is; } @@ -4769,7 +4775,7 @@ namespace __detail __h = hours(__val); __val = __read_unsigned(2); - if (__val == -1 || __val > 60) [[unlikely]] + if (__val == -1 || __val > 59) [[unlikely]] if ((_M_need & _ChronoParts::_TimeOfDay) != 0) { __err |= ios_base::failbit; @@ -4802,10 +4808,12 @@ namespace __detail && !__is_floating) { auto __val = __read_unsigned(__num ? __num : 2); - if (0 <= __val && __val <= 59) [[likely]] + if (0 <= __val && __val <= 60) [[likely]] __s = seconds(__val); else { + // Not a valid second, but we only fail if we're + // parsing into a type that cares about that. if ((_M_need & _ChronoParts::_TimeOfDay) != 0) __err |= ios_base::failbit; break; @@ -5451,6 +5459,8 @@ namespace __detail { __ok = true; __t += __s; + // Record that we had "60" or "60.ddd" for seconds. + // Caller checks for valid leap second, if needed. _M_is_leap_second = __s >= seconds(60); } diff --git a/libstdc++-v3/testsuite/std/time/parse/114279.cc b/libstdc++-v3/testsuite/std/time/parse/114279.cc index 67e5cba23bd8..ff6f8888d9b8 100644 --- a/libstdc++-v3/testsuite/std/time/parse/114279.cc +++ b/libstdc++-v3/testsuite/std/time/parse/114279.cc @@ -4,14 +4,18 @@ #include <sstream> #include <testsuite_hooks.h> -template<class Clock> +template<class Clock, class Dur> void -test_leap_second_parsing() +do_test_leap_second_parsing() { - std::chrono::time_point<Clock, std::chrono::milliseconds> tp, tp2; + std::chrono::time_point<Clock, Dur> tp, tp2; + + std::string subs; + if constexpr (std::ratio_less_v<typename Dur::period, std::ratio<1, 1>>) + subs = ".05"; - std::istringstream ss("20161231-23:59:60.05"); - ss >> std::chrono::parse("%Y%m%d-%T", tp); + std::istringstream ss("20081231-23:59:60" + subs + " "); + ss >> std::chrono::parse("%Y%m%d-%T ", tp); if constexpr (std::is_same_v<Clock, std::chrono::local_t>) VERIFY( ss ); // We allow parsing "23:59:60" as local_time. @@ -28,20 +32,30 @@ test_leap_second_parsing() else VERIFY( !ss ); // Other clocks do not allow "HH:MM:60" + std::chrono::minutes offset(9999); ss.clear(); - ss.str("20161231-22:59:60.05 -0100"); // Same time at -1h offset. - ss >> std::chrono::parse("%Y%m%d-%T %z", tp2); + ss.str("20081231-22:59:60" + subs + " -0100"); // Same time at -1h offset. + ss >> std::chrono::parse("%Y%m%d-%T %z", tp2, offset); if constexpr (std::is_same_v<Clock, std::chrono::utc_clock>) { VERIFY( ss ); VERIFY( tp2 == tp ); + VERIFY( offset == std::chrono::minutes(-60) ); } else VERIFY( !ss ); } } +template<class Clock> +void +test_leap_second_parsing() +{ + do_test_leap_second_parsing<Clock, std::chrono::milliseconds>(); + do_test_leap_second_parsing<Clock, std::chrono::seconds>(); +} + int main() { test_leap_second_parsing<std::chrono::system_clock>(); diff --git a/libstdc++-v3/testsuite/std/time/parse/126543.cc b/libstdc++-v3/testsuite/std/time/parse/126543.cc new file mode 100644 index 000000000000..2d2f633b4b7a --- /dev/null +++ b/libstdc++-v3/testsuite/std/time/parse/126543.cc @@ -0,0 +1,40 @@ +// { dg-do run { target c++20 } } + +// Bug 126543 - chrono::parse should accept "60" seconds for a utc_time +// that is a valid leap second + +#include <chrono> +#include <sstream> +#include <testsuite_hooks.h> + +int main() +{ + std::istringstream in; + std::chrono::utc_seconds ut; + std::chrono::sys_seconds st; + + in.str("2016-12-31 24:59:59"); // invalid hour + in >> parse("%F %T", ut); + VERIFY(in.fail()); + in.clear(); + + in.str("2016-12-31 23:60:59"); // invalid minute + in >> parse("%F %T", ut); + VERIFY(in.fail()); + in.clear(); + + in.str("2016-12-31 23:59:60"); // valid leap second + in >> parse("%F %T", ut); + VERIFY(in.good()); + in.clear(); + + in.str("2016-12-31 23:58:60"); // invalid leap second + in >> parse("%F %T", ut); + VERIFY(in.fail()); + in.clear(); + + in.str("2016-12-31 23:59:60"); + in >> parse("%F %T", st); + VERIFY(in.fail()); // sys_time doesn't use leap seconds + in.clear(); +}
