From: XU Kailiang <xu2k...@outlook.com>

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
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.

        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.
        (__formatter_duration::_S_spec_for): Set _M_time_point.
        * testsuite/std/time/format/pr117214_custom_timeput.cc: New
        test.

Co-authored-by: Tomasz Kaminski <tkami...@redhat.com>
Signed-off-by: XU Kailiang <xu2k...@outlook.com>
Signed-off-by: Tomasz Kaminski <tkami...@redhat.com>
---
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;
                  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.
+       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

Reply via email to