On Tue, 23 Sep 2025 at 14:31 +0200, Tomasz KamiĆski wrote:
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
non-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).
---
Testing on x86_64-linux. OK for trunk?
Thanks, OK with one whitespace fix (see below).
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 0a6a3a5ce5a..b1b83da8386 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 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())
There are two leading spaces before the Tab here.
I use this in my git config to highlight whitespace errors in diffs:
[diff]
wsErrorHighlight = old,new
+ __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 ef1b19d688c..a20c074018e 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
--
2.51.0