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();
+}

Reply via email to