https://gcc.gnu.org/g:dbf94a19f29ae4733fd788d295ab3bc2a8652258

commit r16-4074-gdbf94a19f29ae4733fd788d295ab3bc2a8652258
Author: Tomasz Kamiński <tkami...@redhat.com>
Date:   Tue Sep 23 13:56:42 2025 +0200

    libstdc++: Reflect operator<< constraints in formatter for local_time.
    
    The r16-3996-gdc78d691c5e5f7 commit (resolution of LWG4257) constrained the
    operator<< for local_time, but didn't update the corresponding formatter. 
This
    meant it didn't conditionally support formatting with an empty format spec 
("{}"),
    which is defined in terms of operator<<.
    
    This patch addresses that by initializing __defSpec for the local_time
    formatter in the same manner as it's done for sys_time. This functionality 
is
    extracted to the _S_spec_for_tp function of __formatter_duration. As 
formatting
    of local_time is defined and constrained in terms of operator<< for 
sys_time,
    we can check the viability of the ostream operator for sys_time in both 
cases.
    
    As default _M_chrono_spec may now be empty for local_time, the parse method
    now checks if it was supplied in the format string, similarly to sys_time. 
The
    condition for performing runtime check is expressed directly by checking if 
a
    empty default is provided. This avoids the need to access the value of
    __stream_insertable outside of the __defSpec computation.
    
    As a note, despite their similar behavior, formatters sys_time and 
local_time
    cannot be easily defined in terms of each other, as sys_time provides time 
zone
    information while local_time does not.
    
    libstdc++-v3/ChangeLog:
    
            * include/bits/chrono_io.h (__formatter_duration::_S_spec_for_tp):
            Extracted from defition of formatter<sys_time>::__defSpec.
            (formatter<chrono::sys_time<_Duration>, _CharT>::parse): Simplify
            condition in if contexpr.
            (formatter<chrono::sys_time<_Duration>, 
_CharT>::__stream_insertable):
            Remove.
            (formatter<chrono::sys_time<_Duration>, _CharT>::__defSpec)
            (formatter<chrono::local_time<_Duration>, _CharT>::__defSpec):
            Compute using __formatter_duration::_S_spec_for_tp.
            (forrmatter<chrono::sys_time<_Duration>, _CharT>::parse): Check if
            parse _M_chrono_spec
            * testsuite/std/time/format/empty_spec.cc: Extend tests for floating
            point and other non-streamable durations (years).
    
    Reviewed-by: Jonathan Wakely <jwak...@redhat.com>
    Signed-off-by: Tomasz Kamiński <tkami...@redhat.com>

Diff:
---
 libstdc++-v3/include/bits/chrono_io.h              | 59 ++++++++++++----------
 .../testsuite/std/time/format/empty_spec.cc        | 35 ++++++++-----
 2 files changed, 54 insertions(+), 40 deletions(-)

diff --git a/libstdc++-v3/include/bits/chrono_io.h 
b/libstdc++-v3/include/bits/chrono_io.h
index 0a6a3a5ce5a2..79a44d128b12 100644
--- a/libstdc++-v3/include/bits/chrono_io.h
+++ b/libstdc++-v3/include/bits/chrono_io.h
@@ -1929,6 +1929,24 @@ namespace __format
          return __res;
        };
 
+      template<typename _Duration>
+       static consteval
+       _ChronoSpec<_CharT>
+       _S_spec_for_tp()
+       {
+         using enum _ChronoParts;
+         // streaming of local_time is defined in terms of sys_time
+         constexpr bool __stream_insertable =
+           requires (basic_ostream<_CharT>& __os, chrono::sys_time<_Duration> 
__t)
+             { __os << __t; };
+         if constexpr (!__stream_insertable)
+           return _S_spec_for<_Duration>(_None);
+         else if constexpr (is_convertible_v<_Duration, chrono::days>)
+           return _S_spec_for<_Duration>(_Date);
+         else
+           return _S_spec_for<_Duration>(_DateTime);
+       }
+
       using __formatter_chrono<_CharT>::__formatter_chrono;
       using __formatter_chrono<_CharT>::_M_spec;
 
@@ -2912,12 +2930,12 @@ namespace __format
       parse(basic_format_parse_context<_CharT>& __pc)
       {
        using enum __format::_ChronoParts;
-       auto __next
+       auto __res
          = _M_f.template _M_parse<_Duration>(__pc, _ZonedDateTime, __defSpec);
-       if constexpr (!__stream_insertable)
+       if constexpr (__defSpec._M_chrono_specs.empty())
          if (_M_f._M_spec._M_chrono_specs.empty())
            __format::__invalid_chrono_spec(); // chrono-specs can't be empty
-       return __next;
+       return __res;
      }
 
      template<typename _Out>
@@ -2935,21 +2953,8 @@ namespace __format
        }
 
     private:
-      static constexpr bool __stream_insertable
-       = requires (basic_ostream<_CharT>& __os,
-                   chrono::sys_time<_Duration> __t) { __os << __t; };
-
-      static constexpr __format::_ChronoSpec<_CharT> __defSpec = []
-       {
-         using enum __format::_ChronoParts;
-         __format::_ChronoParts __needed = _DateTime;
-         if constexpr (!__stream_insertable)
-           __needed = _None;
-         else if constexpr (is_convertible_v<_Duration, chrono::days>)
-           __needed = _Date;
-         return __format::__formatter_duration<_CharT>::
-                  template _S_spec_for<_Duration>(__needed);
-       }();
+      static constexpr __format::_ChronoSpec<_CharT> __defSpec =
+       __format::__formatter_duration<_CharT>::template 
_S_spec_for_tp<_Duration>();
 
       __format::__formatter_duration<_CharT> _M_f{__defSpec};
     };
@@ -3110,7 +3115,12 @@ namespace __format
       parse(basic_format_parse_context<_CharT>& __pc)
       {
        using enum __format::_ChronoParts;
-       return _M_f.template _M_parse<_Duration>(__pc, _DateTime, __defSpec);
+       auto __res
+         = _M_f.template _M_parse<_Duration>(__pc, _DateTime, __defSpec);
+       if constexpr (__defSpec._M_chrono_specs.empty())
+         if (_M_f._M_spec._M_chrono_specs.empty())
+           __format::__invalid_chrono_spec(); // chrono-specs can't be empty
+       return __res;
       }
 
       template<typename _Out>
@@ -3126,15 +3136,8 @@ namespace __format
        }
 
     private:
-      static constexpr __format::_ChronoSpec<_CharT> __defSpec = []
-       {
-         using enum __format::_ChronoParts;
-         __format::_ChronoParts __needed = _DateTime;
-         if constexpr (is_convertible_v<_Duration, chrono::days>)
-           __needed = _Date;
-         return __format::__formatter_duration<_CharT>::
-                  template _S_spec_for<_Duration>(__needed);
-       }();
+      static constexpr __format::_ChronoSpec<_CharT> __defSpec =
+       __format::__formatter_duration<_CharT>::template 
_S_spec_for_tp<_Duration>();
 
       __format::__formatter_duration<_CharT> _M_f{__defSpec};
     };
diff --git a/libstdc++-v3/testsuite/std/time/format/empty_spec.cc 
b/libstdc++-v3/testsuite/std/time/format/empty_spec.cc
index ef1b19d688c7..a20c074018e8 100644
--- a/libstdc++-v3/testsuite/std/time/format/empty_spec.cc
+++ b/libstdc++-v3/testsuite/std/time/format/empty_spec.cc
@@ -653,15 +653,15 @@ wall_cast(const local_time<Dur2>& tp)
 using decadays = duration<days::rep, std::ratio_multiply<std::deca, 
days::period>>;
 using kilodays = duration<days::rep, std::ratio_multiply<std::kilo, 
days::period>>;
 
-template<typename CharT, typename Clock>
+template<typename CharT, typename Clock, bool CustomizedOstream>
 void
-test_time_point(bool daysAsTime)
+test_time_point()
 {
   std::basic_string<CharT> res;
 
   const auto lt = local_days(2024y/March/22) + 13h + 24min + 54s + 111222333ns;
-  auto strip_time = [daysAsTime](std::basic_string_view<CharT> sv)
-  { return daysAsTime ? sv : sv.substr(0, 10); };
+  auto strip_time = [](std::basic_string_view<CharT> sv)
+  { return CustomizedOstream ? sv.substr(0, 10) : sv; };
 
   verify( wall_cast<Clock, nanoseconds>(lt),
          WIDEN("2024-03-22 13:24:54.111222333") );
@@ -681,6 +681,19 @@ test_time_point(bool daysAsTime)
          strip_time(WIDEN("2024-03-18 00:00:00")) );
   verify( wall_cast<Clock, kilodays>(lt),
          strip_time(WIDEN("2022-01-08 00:00:00")) );
+
+  if constexpr (!CustomizedOstream)
+  {
+    verify( wall_cast<Clock, duration<double>>(lt),
+           WIDEN("2024-03-22 13:24:54") );
+    verify( wall_cast<Clock, years>(lt),
+           WIDEN("2024-01-01 02:16:48") );
+  }
+  else
+  {
+    test_no_empty_spec<CharT, time_point<Clock, duration<double>>>();
+    test_no_empty_spec<CharT, time_point<Clock, years>>();
+  }
 }
 
 template<typename CharT>
@@ -776,20 +789,18 @@ template<typename CharT>
 void
 test_time_points()
 {
-  test_time_point<CharT, local_t>(false);
-  test_time_point<CharT, system_clock>(false);
-  test_time_point<CharT, utc_clock>(true);
-  test_time_point<CharT, tai_clock>(true);
-  test_time_point<CharT, gps_clock>(true);
-  test_time_point<CharT, file_clock>(true);
+  test_time_point<CharT, local_t, true>();
+  test_time_point<CharT, system_clock, true>();
+  test_time_point<CharT, utc_clock, false>();
+  test_time_point<CharT, tai_clock, false>();
+  test_time_point<CharT, gps_clock, false>();
+  test_time_point<CharT, file_clock, false>();
   test_leap_second<CharT>();
 #if _GLIBCXX_USE_CXX11_ABI || !_GLIBCXX_USE_DUAL_ABI
   test_zoned_time<CharT>();
 #endif
   test_local_time_format<CharT>();
 
-  test_no_empty_spec<CharT, sys_time<years>>();
-  test_no_empty_spec<CharT, sys_time<duration<float>>>();
 }
 
 #if _GLIBCXX_USE_CXX11_ABI || !_GLIBCXX_USE_DUAL_ABI

Reply via email to