On Tue, 6 Oct 2026 at 11:01, Jonathan Wakely <[email protected]> wrote:
>
> 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
> compare to the list of known leap seconds. Continue to reject ":60" for
> any time point 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.
> ---
>
> Tested x86_64-linux.
>
> v2: Split out the changes to only set *abbrev and *offset if the entire
> parse succeeds in to PATCH v2 2/2. So 1/2 only implements the fixes to
> properly parse and validate leap seconds.
>
>  libstdc++-v3/include/bits/chrono_io.h         | 12 ++++--
>  .../testsuite/std/time/parse/114279.cc        | 28 +++++++++----
>  .../testsuite/std/time/parse/126543.cc        | 40 +++++++++++++++++++
>  3 files changed, 70 insertions(+), 10 deletions(-)
>  create mode 100644 libstdc++-v3/testsuite/std/time/parse/126543.cc
>
> diff --git a/libstdc++-v3/include/bits/chrono_io.h 
> b/libstdc++-v3/include/bits/chrono_io.h
> index eec12357fdf7..5cb9c159e5b5 100644
> --- a/libstdc++-v3/include/bits/chrono_io.h
> +++ b/libstdc++-v3/include/bits/chrono_io.h
> @@ -3961,7 +3961,11 @@ 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);
> +         if (__p._M_is_leap_second
> +               && !chrono::get_leap_second_info(__ut).is_leap_second)

We could optimize this slightly by checking if:

  (__p._M_time - *__offset) == 23h + 59min + 60s

and only doing the get_leap_second_info call if that's true. Because
it must be true for valid leap seconds, so we avoid the lookup (and
potentially slow call into the library to read a new leapseconds file)
on input like 01:23:60 which can never be a valid leap second.

Or we could do that inside _Parser::operator() where we set
_M_is_leap_second and set failbit.

> +           __is.setstate(ios_base::failbit);
> +         else
> +           __tp = __detail::__round<_Duration>(__ut);
>         }
>        return __is;
>      }
> @@ -4769,7 +4773,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 +4806,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;
> 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>
>  #incluHde <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::istringstream ss("20161231-23:59:60.05");
> -  ss >> std::chrono::parse("%Y%m%d-%T", tp);
> +  std::string subs;
> +  if constexpr (std::ratio_less_v<typename Dur::period, std::ratio<1, 1>>)
> +    subs = ".05";
> +
> +  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();
> +}
> --
> 2.55.0
>

Reply via email to