On Mon, 7 Jul 2025 at 10:49, Tomasz Kaminski <[email protected]> wrote:
>
>
>
> On Mon, Jul 7, 2025 at 11:44 AM Jonathan Wakely <[email protected]> wrote:
>>
>> On Mon, 7 Jul 2025 at 10:21, Tomasz Kamiński <[email protected]> wrote:
>> >
>> > From: XU Kailiang <[email protected]>
>> >
>> > C++ formatting locale could have a custom time_put that performs
>> > differently from the C locale, so do not use __timepunct directly,
>> > instead all of above specifiers use _M_locale_fmt.
>> >
>> > For %a/%A/%b/%h/%B, the code handling the exception is now moved
>> > to the _M_check_ok function, that is inovked before handling of the
>>
>> "invoked"
>>
>> > conversion specifier. For time_points the values of months/weekday
>> > are computed, and thus are always ok(), this information is indicated
>> > by new _M_time_point member of the _ChronoSpec.
>> >
>> > The different handling of j specifier for durations and time_points/
>> > calendar types, is now handled using only _ChronoParts, and _M_time_only
>> > _ChronoSpec is no longer needed, thus is was removed.
>>
>> I think this makes the handling for durations a bit easier to
>> understand, thanks.
>>
>> >
>> > PR libstdc++/117214
>> >
>> > libstdc++-v3/ChangeLog:
>> >
>> > * include/bits/chrono_io.h (_ChronoSpec::_M_time_only): Remove.
>> > (_ChronoSpec::_M_time_point): Define.
>> > (__formatter_chrono::_M_parse): Use __parts to determine
>> > interpretation of j.
>> > (__formatter_chrono::_M_check_ok): Define.
>> > (__formatter_chrono::_M_format_to): Invoke _M_check_ok.
>> > (__formatter_chrono::_M_a_A, __formatter_chrono::_M_b_B): Move
>> > exception throwing to _M_check_ok.
>> > (__formatter_chrono::_M_j): Use _M_needs to define interpreation.
>>
>> "interpretation"
>>
>> > (__formatter_duration::_S_spec_for): Set _M_time_point.
>> > * testsuite/std/time/format/pr117214_custom_timeput.cc: New
>> > test.
>> >
>> > Co-authored-by: Tomasz Kaminski <[email protected]>
>> > Signed-off-by: XU Kailiang <[email protected]>
>> > Signed-off-by: Tomasz Kaminski <[email protected]>
>> > ---
>> > This patchs adjust the implementation as follows:
>> > * we use _M_locale_fmt for all specifiers
>> > * %h which is alias for %b is also covered
>> >
>> > Tested on x86_64-linux localy.
>> >
>> > libstdc++-v3/include/bits/chrono_io.h | 58 ++++++++++++++-----
>> > .../time/format/pr117214_custom_timeput.cc | 37 ++++++++++++
>> > 2 files changed, 81 insertions(+), 14 deletions(-)
>> > create mode 100644
>> > libstdc++-v3/testsuite/std/time/format/pr117214_custom_timeput.cc
>> >
>> > diff --git a/libstdc++-v3/include/bits/chrono_io.h
>> > b/libstdc++-v3/include/bits/chrono_io.h
>> > index 72cd569ccd6..863b3550e4f 100644
>> > --- a/libstdc++-v3/include/bits/chrono_io.h
>> > +++ b/libstdc++-v3/include/bits/chrono_io.h
>> > @@ -280,8 +280,8 @@ namespace __format
>> > // in the format-spec, e.g. "{:L%a}" is localized and
>> > locale-specific,
>> > // but "{:L}" is only localized and "{:%a}" is only locale-specific.
>> > unsigned _M_locale_specific : 1;
>> > - // Indicates that we are handling duration.
>> > - unsigned _M_time_only : 1;
>> > + // Indicates that we are handling time_point.
>> > + unsigned _M_time_point : 1;
>> > // Indicates that duration should be treated as floating point.
>> > unsigned _M_floating_point_rep : 1;
>> > // Indicate that duration uses user-defined representation.
>> > @@ -693,8 +693,10 @@ namespace __format
>> > __allowed_mods = _Mod_O;
>> > break;
>> > case 'j':
>> > - __needed = __spec._M_time_only ? _HoursMinutesSeconds
>> > - : _DayOfYear;
>> > + __needed = __parts & _DayOfYear;
>> > + // 'j' is decimal number of days for durations
>> > + if (__needed == _None)
>> > + __needed = _HoursMinutesSeconds;
>>
>> Maybe it's because I haven't slept well, but I found the comment here
>> didn't make the logic clearer for me (why is __needed = _None a
>> duration? what does HMS have to do with days?).
>> Would this be better?
>>
>> // If we do not know day-of-year then we must have a
>> duration,
>> // which is to be formatted as decimal number of days.
>
> Yes, that makes sense. I will put that wording there.
> Given that the rest of comments are only typos, are changes otherwise OK for
> trunk?
> Or are you still reviewing it?
No, I reviewed the rest and it's OK for trunk - thanks (and thanks to
Kailiang for the original patch).
>>
>>
>>
>> > break;
>> > case 'm':
>> > __needed = _Month;
>> > @@ -919,7 +921,13 @@ namespace __format
>> > {
>> > switch (__conv)
>> > {
>> > + case 'a':
>> > + case 'A':
>> > + case 'b':
>> > + case 'B':
>> > case 'c':
>> > + case 'h':
>> > + case 'p':
>> > case 'r':
>> > case 'x':
>> > case 'X':
>> > @@ -947,6 +955,32 @@ namespace __format
>> > return __out;
>> > }
>> >
>> > + void
>> > + _M_check_ok(const _ChronoData<_CharT>& __t, _CharT __conv) const
>> > + {
>> > + // n.b. for time point all date parts are computed, so
>> > + // they are alwas ok.
>>
>> "always"
>>
>> > + if (_M_spec._M_time_point || _M_spec._M_debug)
>> > + return;
>> > +
>> > + switch (__conv)
>> > + {
>> > + case 'a':
>> > + case 'A':
>> > + if (!__t._M_weekday.ok()) [[unlikely]]
>> > + __throw_format_error("format error: invalid weekday");
>> > + return;
>> > + case 'b':
>> > + case 'h':
>> > + case 'B':
>> > + if (!__t._M_month.ok()) [[unlikely]]
>> > + __throw_format_error("format error: invalid month");
>> > + return;
>> > + default:
>> > + return;
>> > + }
>> > + }
>> > +
>> > template<typename _OutIter, typename _FormatContext>
>> > _OutIter
>> > _M_format_to(const _ChronoData<_CharT>& __t, _OutIter __out,
>> > @@ -1003,6 +1037,8 @@ namespace __format
>> > do
>> > {
>> > _CharT __c = *__first++;
>> > + _M_check_ok(__t, __c);
>> > +
>> > if (__use_locale_fmt && _S_localized_spec(__c, __mod))
>> > [[unlikely]]
>> > __out = _M_locale_fmt(std::move(__out), __fc.locale(),
>> > __tm, __c, __mod);
>> > @@ -1153,11 +1189,8 @@ namespace __format
>> > {
>> > // %a Locale's abbreviated weekday name.
>> > // %A Locale's full weekday name.
>> > - if (!__wd.ok())
>> > + if (_M_spec._M_debug && !__wd.ok())
>> > {
>> > - if (!_M_spec._M_debug)
>> > - __throw_format_error("format error: invalid weekday");
>> > -
>> > _CharT __buf[3];
>> > __out = __format::__write(std::move(__out),
>> > _S_str_d1(__buf,
>> > __wd.c_encoding()));
>> > @@ -1183,11 +1216,8 @@ namespace __format
>> > {
>> > // %b Locale's abbreviated month name.
>> > // %B Locale's full month name.
>> > - if (!__m.ok())
>> > + if (_M_spec._M_debug && !__m.ok())
>> > {
>> > - if (!_M_spec._M_debug)
>> > - __throw_format_error("format error: invalid month");
>> > -
>> > _CharT __buf[3];
>> > __out = __format::__write(std::move(__out),
>> > _S_str_d1(__buf, (unsigned)__m));
>> > @@ -1419,7 +1449,7 @@ namespace __format
>> > _OutIter
>> > _M_j(const _ChronoData<_CharT>& __t, _OutIter __out) const
>> > {
>> > - if (_M_spec._M_time_only)
>> > + if (!_M_spec._M_needs(_ChronoParts::_DayOfYear))
>> > {
>> > // Decimal number of days, without padding.
>> > auto __d = chrono::floor<chrono::days>(__t._M_hours).count();
>> > @@ -1811,7 +1841,7 @@ namespace __format
>> > using enum _ChronoParts;
>> >
>> > _ChronoSpec<_CharT> __res{};
>> > - __res._M_time_only = (__parts & _Date) == 0;
>> > + __res._M_time_point = (__parts & _DateTime) == _DateTime;
>> > __res._M_floating_point_rep =
>> > chrono::treat_as_floating_point_v<_Rep>;
>> > __res._M_custom_rep = !is_arithmetic_v<_Rep>;
>> > __res._M_prec = chrono::hh_mm_ss<_Duration>::fractional_width;
>> > diff --git
>> > a/libstdc++-v3/testsuite/std/time/format/pr117214_custom_timeput.cc
>> > b/libstdc++-v3/testsuite/std/time/format/pr117214_custom_timeput.cc
>> > new file mode 100644
>> > index 00000000000..03b94961096
>> > --- /dev/null
>> > +++ b/libstdc++-v3/testsuite/std/time/format/pr117214_custom_timeput.cc
>> > @@ -0,0 +1,37 @@
>> > +// { dg-do run { target c++20 } }
>> > +
>> > +#include <chrono>
>> > +#include <format>
>> > +#include <locale>
>> > +#include <testsuite_hooks.h>
>> > +
>> > +struct custom_time_put : std::time_put<char>
>> > +{
>> > + iter_type
>> > + do_put(iter_type out, std::ios_base& io, char_type fill, const tm* t,
>> > + char format, char modifier) const override
>> > + {
>> > + using Base = std::time_put<char>;
>> > +
>> > + switch (format) {
>> > + case 'a': case 'A': case 'b': case 'h': case 'B': case 'p':
>> > + *out++ = '[';
>> > + *out++ = format;
>> > + *out++ = ']';
>> > + }
>> > + return Base::do_put(out, io, fill, t, format, modifier);
>> > + }
>> > +};
>> > +
>> > +int main()
>> > +{
>> > + using namespace std::chrono;
>> > + std::locale loc(std::locale::classic(), new custom_time_put);
>> > +#define test(t, fmt, exp) VERIFY( std::format(loc, fmt, t) == exp )
>> > + test(Monday, "{:L%a}", "[a]Mon");
>> > + test(Monday, "{:L%A}", "[A]Monday");
>> > + test(January, "{:L%b}", "[b]Jan");
>> > + test(January, "{:L%h}", "[h]Jan");
>> > + test(January, "{:L%B}", "[B]January");
>> > + test(1h, "{:L%p}", "[p]AM");
>> > +}
>> > --
>> > 2.49.0
>> >
>>