On Thu, Jul 23, 2026 at 1:59 PM Tomasz Kaminski <[email protected]> wrote:
> > > On Wed, Jul 1, 2026 at 2:15 PM Anlai Lu <[email protected]> wrote: > >> Add __detail::__chrono_write which formats a chrono object into a >> stack buffer via std::format_to_n with _S_empty_fs(), then writes >> through __ostream_insert. A non-type template parameter _BufSize >> (default 128) allows per-type buffer tuning. Each _BufSize is the >> smallest power of two that accommodates the longest possible formatted >> output for that type, including error cases. All chrono operator<< >> overloads that previously used std::format, std::vformat, or >> basic_stringstream now use this helper. >> >> libstdc++-v3/ChangeLog: >> >> * include/bits/chrono_io.h (__formatter_chrono::_S_empty_fs): >> Make public. >> (__detail::__chrono_write): New function template. >> (operator<<): Use __chrono_write consistently for all chrono >> types. >> >> Signed-off-by: Anlai Lu <[email protected]> >> --- >> libstdc++-v3/include/bits/chrono_io.h | 244 ++++++-------------------- >> 1 file changed, 52 insertions(+), 192 deletions(-) >> >> diff --git a/libstdc++-v3/include/bits/chrono_io.h >> b/libstdc++-v3/include/bits/chrono_io.h >> index f8bb485d1..d3740a0e1 100644 >> --- a/libstdc++-v3/include/bits/chrono_io.h >> +++ b/libstdc++-v3/include/bits/chrono_io.h >> @@ -884,11 +884,13 @@ namespace __format >> static constexpr const _CharT* _S_minus_empty_spec = _S_chars + 17; >> static constexpr const _CharT* _S_empty_spec = _S_chars + 18; >> >> + public: >> [[__gnu__::__always_inline__]] >> static _Dynamic_format_string<_CharT> >> _S_empty_fs() >> { return _Dynamic_format_string<_CharT>(_S_empty_spec); } >> >> + protected: >> static constexpr const _CharT* _S_weekdays[] >> { >> _GLIBCXX_WIDEN("Sunday"), >> @@ -3607,6 +3609,30 @@ namespace __detail >> } >> } >> >> + // Format into a stack buffer via std::format_to_n with the empty >> + // chrono-spec (_S_empty_fs), then write through __ostream_insert. >> + // The empty spec lets each formatter use its __defSpec, producing >> + // output equivalent to the corresponding operator<<. >> + // _BufSize allows per-type tuning of the stack buffer size. >> + template<size_t _BufSize = 128, typename _CharT, typename _Traits, >> + typename _Arg, typename... _OptLocale> >> + inline basic_ostream<_CharT, _Traits>& >> + __chrono_write(basic_ostream<_CharT, _Traits>& __os, >> + const _Arg& __arg, const _OptLocale&... __loc) >> + { >> + static_assert(sizeof...(_OptLocale) <= 1); >> + constexpr auto __fs >> + = __format::__formatter_chrono<_CharT>::_S_empty_fs; >> + constexpr size_t __bufsize = _BufSize; >> + _CharT __buf[__bufsize]; >> + auto __res = std::format_to_n(__buf, __bufsize, __loc..., >> + __fs(), __arg); >> + if (static_cast<size_t>(__res.size) <= __bufsize) [[likely]] >> + return std::__ostream_insert(__os, __buf, __res.size); >> + auto __s = std::format(__loc..., __fs(), __arg); >> + return std::__ostream_insert(__os, __s.data(), __s.size()); >> + } >> + >> } // namespace __detail >> /// @endcond >> >> @@ -3629,14 +3655,7 @@ namespace __detail >> inline basic_ostream<_CharT, _Traits>& >> operator<<(basic_ostream<_CharT, _Traits>& __os, const day& __d) >> { >> - using _Ctx = __format::__format_context<_CharT>; >> - using _Str = basic_string_view<_CharT>; >> - _Str __s = _GLIBCXX_WIDEN("{:02d} is not a valid day"); >> - if (__d.ok()) >> - __s = __s.substr(0, 6); >> - auto __u = (unsigned)__d; >> - __os << std::vformat(__s, make_format_args<_Ctx>(__u)); >> - return __os; >> + return __detail::__chrono_write<32>(__os, __d); >> } >> >> template<typename _CharT, typename _Traits, >> @@ -3657,18 +3676,7 @@ namespace __detail >> inline basic_ostream<_CharT, _Traits>& >> operator<<(basic_ostream<_CharT, _Traits>& __os, const month& __m) >> { >> - using _Ctx = __format::__format_context<_CharT>; >> - using _Str = basic_string_view<_CharT>; >> - _Str __s = _GLIBCXX_WIDEN("{:L%b}{} is not a valid month"); >> - if (__m.ok()) >> - __os << std::vformat(__os.getloc(), __s.substr(0, 6), >> - make_format_args<_Ctx>(__m)); >> - else >> - { >> - auto __u = (unsigned)__m; >> - __os << std::vformat(__s.substr(6), >> make_format_args<_Ctx>(__u)); >> - } >> - return __os; >> + return __detail::__chrono_write(__os, __m, __os.getloc()); >> } >> >> template<typename _CharT, typename _Traits, >> @@ -3689,18 +3697,7 @@ namespace __detail >> inline basic_ostream<_CharT, _Traits>& >> operator<<(basic_ostream<_CharT, _Traits>& __os, const year& __y) >> { >> - using _Ctx = __format::__format_context<_CharT>; >> - using _Str = basic_string_view<_CharT>; >> - _Str __s = _GLIBCXX_WIDEN("-{:04d} is not a valid year"); >> - if (__y.ok()) >> - __s = __s.substr(0, 7); >> - int __i = (int)__y; >> - if (__i >= 0) [[likely]] >> - __s.remove_prefix(1); >> - else >> - __i = -__i; >> - __os << std::vformat(__s, make_format_args<_Ctx>(__i)); >> - return __os; >> + return __detail::__chrono_write<32>(__os, __y); >> } >> >> template<typename _CharT, typename _Traits, >> @@ -3721,18 +3718,7 @@ namespace __detail >> inline basic_ostream<_CharT, _Traits>& >> operator<<(basic_ostream<_CharT, _Traits>& __os, const weekday& __wd) >> { >> - using _Ctx = __format::__format_context<_CharT>; >> - using _Str = basic_string_view<_CharT>; >> - _Str __s = _GLIBCXX_WIDEN("{:L%a}{} is not a valid weekday"); >> - if (__wd.ok()) >> - __os << std::vformat(__os.getloc(), __s.substr(0, 6), >> - make_format_args<_Ctx>(__wd)); >> - else >> - { >> - auto __c = __wd.c_encoding(); >> - __os << std::vformat(__s.substr(6), >> make_format_args<_Ctx>(__c)); >> - } >> - return __os; >> + return __detail::__chrono_write(__os, __wd, __os.getloc()); >> } >> >> template<typename _CharT, typename _Traits, >> @@ -3754,23 +3740,7 @@ namespace __detail >> operator<<(basic_ostream<_CharT, _Traits>& __os, >> const weekday_indexed& __wdi) >> { >> - // The standard says to format wdi.weekday() and wdi.index() using >> - // either "{:L}[{}]" or "{:L}[{} is not a valid index]". The {:L} >> spec >> - // means to format the weekday using ostringstream, so just do >> that. >> - basic_stringstream<_CharT> __os2; >> - __os2.imbue(__os.getloc()); >> - __os2 << __wdi.weekday(); >> - const auto __i = __wdi.index(); >> - basic_string_view<_CharT> __s >> - = _GLIBCXX_WIDEN("[ is not a valid index]"); >> - __os2 << __s[0]; >> - __os2 << std::format(_GLIBCXX_WIDEN("{}"), __i); >> - if (__i >= 1 && __i <= 5) >> - __os2 << __s.back(); >> - else >> - __os2 << __s.substr(1); >> - __os << __os2.view(); >> - return __os; >> + return __detail::__chrono_write(__os, __wdi, __os.getloc()); >> } >> >> template<typename _CharT, typename _Traits> >> @@ -3778,29 +3748,14 @@ namespace __detail >> operator<<(basic_ostream<_CharT, _Traits>& __os, >> const weekday_last& __wdl) >> { >> - // As above, just write straight to a stringstream, as if by >> "{:L}[last]" >> - basic_stringstream<_CharT> __os2; >> - __os2.imbue(__os.getloc()); >> - __os2 << __wdl.weekday() << _GLIBCXX_WIDEN("[last]"); >> - __os << __os2.view(); >> - return __os; >> + return __detail::__chrono_write(__os, __wdl, __os.getloc()); >> } >> >> template<typename _CharT, typename _Traits> >> inline basic_ostream<_CharT, _Traits>& >> operator<<(basic_ostream<_CharT, _Traits>& __os, const month_day& >> __md) >> { >> - // As above, just write straight to a stringstream, as if by >> "{:L}/{}" >> - basic_stringstream<_CharT> __os2; >> - __os2.imbue(__os.getloc()); >> - __os2 << __md.month(); >> - if constexpr (is_same_v<_CharT, char>) >> - __os2 << '/'; >> - else >> - __os2 << L'/'; >> - __os2 << __md.day(); >> - __os << __os2.view(); >> - return __os; >> + return __detail::__chrono_write(__os, __md, __os.getloc()); >> } >> >> template<typename _CharT, typename _Traits, >> @@ -3824,12 +3779,7 @@ namespace __detail >> operator<<(basic_ostream<_CharT, _Traits>& __os, >> const month_day_last& __mdl) >> { >> - // As above, just write straight to a stringstream, as if by >> "{:L}/last" >> - basic_stringstream<_CharT> __os2; >> - __os2.imbue(__os.getloc()); >> - __os2 << __mdl.month() << _GLIBCXX_WIDEN("/last"); >> - __os << __os2.view(); >> - return __os; >> + return __detail::__chrono_write(__os, __mdl, __os.getloc()); >> } >> >> template<typename _CharT, typename _Traits> >> @@ -3837,17 +3787,7 @@ namespace __detail >> operator<<(basic_ostream<_CharT, _Traits>& __os, >> const month_weekday& __mwd) >> { >> - // As above, just write straight to a stringstream, as if by >> "{:L}/{:L}" >> - basic_stringstream<_CharT> __os2; >> - __os2.imbue(__os.getloc()); >> - __os2 << __mwd.month(); >> - if constexpr (is_same_v<_CharT, char>) >> - __os2 << '/'; >> - else >> - __os2 << L'/'; >> - __os2 << __mwd.weekday_indexed(); >> - __os << __os2.view(); >> - return __os; >> + return __detail::__chrono_write(__os, __mwd, __os.getloc()); >> } >> >> template<typename _CharT, typename _Traits> >> @@ -3855,34 +3795,14 @@ namespace __detail >> operator<<(basic_ostream<_CharT, _Traits>& __os, >> const month_weekday_last& __mwdl) >> { >> - // As above, just write straight to a stringstream, as if by >> "{:L}/{:L}" >> - basic_stringstream<_CharT> __os2; >> - __os2.imbue(__os.getloc()); >> - __os2 << __mwdl.month(); >> - if constexpr (is_same_v<_CharT, char>) >> - __os2 << '/'; >> - else >> - __os2 << L'/'; >> - __os2 << __mwdl.weekday_last(); >> - __os << __os2.view(); >> - return __os; >> + return __detail::__chrono_write(__os, __mwdl, __os.getloc()); >> } >> >> template<typename _CharT, typename _Traits> >> inline basic_ostream<_CharT, _Traits>& >> operator<<(basic_ostream<_CharT, _Traits>& __os, const year_month& >> __ym) >> { >> - // As above, just write straight to a stringstream, as if by >> "{}/{:L}" >> - basic_stringstream<_CharT> __os2; >> - __os2.imbue(__os.getloc()); >> - __os2 << __ym.year(); >> - if constexpr (is_same_v<_CharT, char>) >> - __os2 << '/'; >> - else >> - __os2 << L'/'; >> - __os2 << __ym.month(); >> - __os << __os2.view(); >> - return __os; >> + return __detail::__chrono_write(__os, __ym, __os.getloc()); >> } >> >> template<typename _CharT, typename _Traits, >> @@ -3906,12 +3826,7 @@ namespace __detail >> operator<<(basic_ostream<_CharT, _Traits>& __os, >> const year_month_day& __ymd) >> { >> - using _Ctx = __format::__format_context<_CharT>; >> - using _Str = basic_string_view<_CharT>; >> - _Str __s = _GLIBCXX_WIDEN("{:%F} is not a valid date"); >> - __os << std::vformat(__ymd.ok() ? __s.substr(0, 5) : __s, >> - make_format_args<_Ctx>(__ymd)); >> - return __os; >> + return __detail::__chrono_write<64>(__os, __ymd); >> } >> >> template<typename _CharT, typename _Traits, >> @@ -3936,17 +3851,7 @@ namespace __detail >> operator<<(basic_ostream<_CharT, _Traits>& __os, >> const year_month_day_last& __ymdl) >> { >> - // As above, just write straight to a stringstream, as if by >> "{}/{:L}" >> - basic_stringstream<_CharT> __os2; >> - __os2.imbue(__os.getloc()); >> - __os2 << __ymdl.year(); >> - if constexpr (is_same_v<_CharT, char>) >> - __os2 << '/'; >> - else >> - __os2 << L'/'; >> - __os2 << __ymdl.month_day_last(); >> - __os << __os2.view(); >> - return __os; >> + return __detail::__chrono_write(__os, __ymdl, __os.getloc()); >> } >> >> template<typename _CharT, typename _Traits> >> @@ -3954,19 +3859,7 @@ namespace __detail >> operator<<(basic_ostream<_CharT, _Traits>& __os, >> const year_month_weekday& __ymwd) >> { >> - // As above, just write straight to a stringstream, as if by >> - // "{}/{:L}/{:L}" >> - basic_stringstream<_CharT> __os2; >> - __os2.imbue(__os.getloc()); >> - _CharT __slash; >> - if constexpr (is_same_v<_CharT, char>) >> - __slash = '/'; >> - else >> - __slash = L'/'; >> - __os2 << __ymwd.year() << __slash << __ymwd.month() << __slash >> - << __ymwd.weekday_indexed(); >> - __os << __os2.view(); >> - return __os; >> + return __detail::__chrono_write(__os, __ymwd, __os.getloc()); >> } >> >> template<typename _CharT, typename _Traits> >> @@ -3974,19 +3867,7 @@ namespace __detail >> operator<<(basic_ostream<_CharT, _Traits>& __os, >> const year_month_weekday_last& __ymwdl) >> { >> - // As above, just write straight to a stringstream, as if by >> - // "{}/{:L}/{:L}" >> - basic_stringstream<_CharT> __os2; >> - __os2.imbue(__os.getloc()); >> - _CharT __slash; >> - if constexpr (is_same_v<_CharT, char>) >> - __slash = '/'; >> - else >> - __slash = L'/'; >> - __os2 << __ymwdl.year() << __slash << __ymwdl.month() << __slash >> - << __ymwdl.weekday_last(); >> - __os << __os2.view(); >> - return __os; >> + return __detail::__chrono_write(__os, __ymwdl, __os.getloc()); >> } >> >> template<typename _CharT, typename _Traits, typename _Duration> >> @@ -3994,7 +3875,7 @@ namespace __detail >> operator<<(basic_ostream<_CharT, _Traits>& __os, >> const hh_mm_ss<_Duration>& __hms) >> { >> - return __os << format(__os.getloc(), _GLIBCXX_WIDEN("{:L%T}"), >> __hms); >> + return __detail::__chrono_write<64>(__os, __hms, __os.getloc()); >> } >> >> #if _GLIBCXX_USE_CXX11_ABI || ! _GLIBCXX_USE_DUAL_ABI >> @@ -4003,7 +3884,7 @@ namespace __detail >> basic_ostream<_CharT, _Traits>& >> operator<<(basic_ostream<_CharT, _Traits>& __os, const sys_info& __i) >> { >> - return __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{}"), >> __i); >> + return __detail::__chrono_write(__os, __i, __os.getloc()); >> } >> >> /// Writes a local_info object to an ostream in an unspecified format. >> @@ -4011,20 +3892,7 @@ namespace __detail >> basic_ostream<_CharT, _Traits>& >> operator<<(basic_ostream<_CharT, _Traits>& __os, const local_info& >> __li) >> { >> - __os << __format::_Separators<_CharT>::_S_squares()[0]; >> - if (__li.result == local_info::unique) >> - __os << __li.first; >> - else >> - { >> - if (__li.result == local_info::nonexistent) >> - __os << _GLIBCXX_WIDEN("nonexistent"); >> - else >> - __os << _GLIBCXX_WIDEN("ambiguous"); >> - __os << _GLIBCXX_WIDEN(" local time between ") << __li.first; >> - __os << _GLIBCXX_WIDEN(" and ") << __li.second; >> - } >> - __os << __format::_Separators<_CharT>::_S_squares()[1]; >> - return __os; >> + return __detail::__chrono_write<256>(__os, __li); >> > As priting local_info involes sys_info output, we should put os.getloc() > here, > same as above. Will fix that before committing. > Further elaboration after looking into why the test did not detect this, it turned out that sys_info is not actually locale depended, as the output does not include dot (and subseconds). As explained by comment in __formatter_chrono_info: // n.b. only decimal separator is locale dependent for specifiers // used below, as sys_info uses seconds and minutes duration, the // output is locale-independent. constexpr auto* __fs = _GLIBCXX_WIDEN("[{0:%F %T},{1:%F %T},{2:%T},{3:%Q%q},{0:%Z}]"); So we can avoid pssing __os.getloc() in local_info operator<<. I will make that change and post v4 after commiting. > } >> >> template<typename _CharT, typename _Traits, typename _Duration, >> @@ -4033,8 +3901,7 @@ namespace __detail >> operator<<(basic_ostream<_CharT, _Traits>& __os, >> const zoned_time<_Duration, _TimeZonePtr>& __t) >> { >> - __os << format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T %Z}"), __t); >> - return __os; >> + return __detail::__chrono_write(__os, __t, __os.getloc()); >> } >> #endif >> >> @@ -4045,16 +3912,14 @@ namespace __detail >> operator<<(basic_ostream<_CharT, _Traits>& __os, >> const sys_time<_Duration>& __tp) >> { >> - __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"), >> __tp); >> - return __os; >> + return __detail::__chrono_write<64>(__os, __tp, __os.getloc()); >> } >> >> template<typename _CharT, typename _Traits> >> inline basic_ostream<_CharT, _Traits>& >> operator<<(basic_ostream<_CharT, _Traits>& __os, const sys_days& >> __dp) >> { >> - __os << year_month_day{__dp}; >> - return __os; >> + return __detail::__chrono_write<32>(__os, __dp); >> } >> >> template<typename _CharT, typename _Traits, typename _Duration, >> @@ -4090,8 +3955,7 @@ namespace __detail >> operator<<(basic_ostream<_CharT, _Traits>& __os, >> const utc_time<_Duration>& __t) >> { >> - __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"), >> __t); >> - return __os; >> + return __detail::__chrono_write<64>(__os, __t, __os.getloc()); >> } >> >> template<typename _CharT, typename _Traits, typename _Duration, >> @@ -4125,8 +3989,7 @@ namespace __detail >> operator<<(basic_ostream<_CharT, _Traits>& __os, >> const tai_time<_Duration>& __t) >> { >> - __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"), >> __t); >> - return __os; >> + return __detail::__chrono_write<64>(__os, __t, __os.getloc()); >> } >> >> template<typename _CharT, typename _Traits, typename _Duration, >> @@ -4164,8 +4027,7 @@ namespace __detail >> operator<<(basic_ostream<_CharT, _Traits>& __os, >> const gps_time<_Duration>& __t) >> { >> - __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"), >> __t); >> - return __os; >> + return __detail::__chrono_write<64>(__os, __t, __os.getloc()); >> } >> >> template<typename _CharT, typename _Traits, typename _Duration, >> @@ -4202,8 +4064,7 @@ namespace __detail >> operator<<(basic_ostream<_CharT, _Traits>& __os, >> const file_time<_Duration>& __t) >> { >> - __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"), >> __t); >> - return __os; >> + return __detail::__chrono_write<64>(__os, __t, __os.getloc()); >> } >> >> template<typename _CharT, typename _Traits, typename _Duration, >> @@ -4228,8 +4089,7 @@ namespace __detail >> // 4257. Stream insertion for chrono::local_time should be >> constrained >> requires requires(const sys_time<_Duration>& __st) { __os << __st; } >> { >> - __os << sys_time<_Duration>{__lt.time_since_epoch()}; >> - return __os; >> + return __detail::__chrono_write<64>(__os, __lt, __os.getloc()); >> } >> >> template<typename _CharT, typename _Traits, typename _Duration, >> -- >> 2.34.1 >> >>
