On Fri, Jul 18, 2025 at 3:04 PM Jonathan Wakely <jwak...@redhat.com> wrote:

> On 18/07/25 14:01 +0200, Tomasz Kamiński wrote:
> >Previously for localized output, if _M_debug option was send, the
> _M_check_ok
> >completed succesfully and _M_locale_fmt was called for months/weekdays
> that
> >are !ok().
> >
> >This patch lifts debug checks from each conversion function into
> _M_check_ok,
> >that in case of !ok() values return a string_view containing the kind of
> >calendar data, to be included after "is not a valid" string. The localized
> >output (_M_locale_fmt) is not used if string is non-empty.
> >The emitting of this message is now handled in _M_format_to, further
> reducing
> >each specifier function.
> >
> >To handle weekday (%a,%A) and month (%b,%B), _M_check_ok now accepts a
> >mutable reference to conversion specifier, and updates it to corresponding
> >numeric value (%w, %m). Extra care needs to be taken to handle a month(0)
> >that needs to be printed as single digit in debug format.
> >
> >Finally, the _M_time_point is replaced with _M_needs_ok_check member, that
> >indicates if input contains any user-suplied values that are checked for
> >being ok() and these values are referenced in chrono-specs.
> >
> >       PR libstdc++/PR121154
> >
> >libstdc++-v3/ChangeLog:
> >
> >       * include/bits/chrono_io.h _ChronoSpec::_M_time_point: Remove.
> >       (_ChronoSpec::_M_needs_ok_check): Define
> >       (__formatter_chrono::_M_parse): Set _M_needs_ok_check.
> >       (__formatter_chrono::_M_check_ok): Check values also for debug
> mode,
> >       and return __string_view.
> >       (__formatter_chrono::_M_format_to): Handle results of _M_check_ok.
> >       (__formatter_chrono::_M_wi, __formatter_chrono::_M_a_A)
> >       (__formatter_chrono::_M_b_B, __formatter_chrono::_M_C_y_Y)
> >       (__formatter_chrono::_M_d_e, __formatter_chrono::_M_F):
> >       Removed handling of _M_debug.
> >       (__formatter_chrono::__M_m): Print zero unpadded in _M_debug mode.
> >       (__formatter_duration::_S_spec_for): Remove _M_time_point refernce.
> >       (__formatter_duration::_M_parse): Override _M_needs_ok_check.
> >       * testsuite/std/time/month/io.cc: Test for localized !ok() values.
> >       * testsuite/std/time/weekday/io.cc: Test for localized !ok()
> values.
> >---
> >v3 just fixes some typos in comments and commit message.
> >
> > libstdc++-v3/include/bits/chrono_io.h         | 139 +++++++++++-------
> > libstdc++-v3/testsuite/std/time/month/io.cc   |   7 +
> > libstdc++-v3/testsuite/std/time/weekday/io.cc |   2 +
> > 3 files changed, 96 insertions(+), 52 deletions(-)
> >
> >diff --git a/libstdc++-v3/include/bits/chrono_io.h
> b/libstdc++-v3/include/bits/chrono_io.h
> >index fe3c9126749..95e0d489cf4 100644
> >--- a/libstdc++-v3/include/bits/chrono_io.h
> >+++ b/libstdc++-v3/include/bits/chrono_io.h
> >@@ -280,8 +280,9 @@ 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 time_point.
> >-      unsigned _M_time_point : 1;
> >+      // Indicates if parts that are checked for ok are comming directly
> from input,
>
> "are comming directly from input" -> "come directly from the input"
>
> or "the source" instead of "the input" ? that's what a comment below
> says.
>
Used "the input", I think that is clearer.

>
> OK for trunk with that changed - thanks.
>
>
> >+      // instead of being computed.
> >+      unsigned _M_needs_ok_check : 1;
> >       // Indicates that duration should be treated as floating point.
> >       unsigned _M_floating_point_rep : 1;
> >       // Indicate that duration uses user-defined representation.
> >@@ -570,7 +571,15 @@ namespace __format
> >
> >         _ChronoSpec<_CharT> __spec = __def;
> >
> >-        auto __finalize = [this, &__spec] {
> >+        auto __finalize = [this, &__spec, &__def] {
> >+          using enum _ChronoParts;
> >+            _ChronoParts __checked
> >+            = __spec._M_debug ? _YearMonthDay|_IndexedWeekday
> >+                              : _Month|_Weekday;
> >+          // n.b. for calendar types __def._M_needed contains only parts
> >+          // copied from source, remaining ones are computed, and thus ok
> >+          __spec._M_needs_ok_check
> >+            = __spec._M_needs(__def._M_needed & __checked);
> >           _M_spec = __spec;
> >         };
> >
> >@@ -821,10 +830,10 @@ namespace __format
> >           __throw_format_error("chrono format error: unescaped '%' in "
> >                                "chrono-specs");
> >
> >-        _M_spec = __spec;
> >-        _M_spec._M_chrono_specs
> >-               = __string_view(__chrono_specs, __first - __chrono_specs);
> >+        __spec._M_chrono_specs
> >+          = __string_view(__chrono_specs, __first - __chrono_specs);
> >
> >+        __finalize();
> >         return __first;
> >       }
> >
> >@@ -972,30 +981,71 @@ namespace __format
> >         return __out;
> >       }
> >
> >-      void
> >-      _M_check_ok(const _ChronoData<_CharT>& __t, _CharT __conv) const
> >+      __string_view
> >+      _M_check_ok(const _ChronoData<_CharT>& __t, _CharT& __conv) const
> >       {
> >-      // n.b. for time point all date parts are computed, so
> >-      // they are always ok.
> >-      if (_M_spec._M_time_point || _M_spec._M_debug)
> >-        return;
> >+      if (!_M_spec._M_debug)
> >+        {
> >+          switch (__conv)
> >+          {
> >+          case 'a':
> >+          case 'A':
> >+            if (!__t._M_weekday.ok()) [[unlikely]]
> >+              __throw_format_error("format error: invalid weekday");
> >+            break;
> >+          case 'b':
> >+          case 'h':
> >+          case 'B':
> >+            if (!__t._M_month.ok()) [[unlikely]]
> >+              __throw_format_error("format error: invalid month");
> >+            break;
> >+          default:
> >+            break;
> >+          }
> >+          return __string_view();
> >+        }
> >
> >       switch (__conv)
> >       {
> >+      // %\0 is extension for handling weekday index
> >+      case '\0':
> >+        if (__t._M_weekday_index < 1 || __t._M_weekday_index > 5)
> [[unlikely]]
> >+          return _GLIBCXX_WIDEN("index");
> >+        break;
> >       case 'a':
> >       case 'A':
> >         if (!__t._M_weekday.ok()) [[unlikely]]
> >-          __throw_format_error("format error: invalid weekday");
> >-        return;
> >+          {
> >+            __conv = 'w'; // print as decimal number
> >+            return _GLIBCXX_WIDEN("weekday");
> >+          }
> >+        break;
> >       case 'b':
> >       case 'h':
> >       case 'B':
> >         if (!__t._M_month.ok()) [[unlikely]]
> >-          __throw_format_error("format error: invalid month");
> >-        return;
> >+          {
> >+            __conv = 'm'; // print as decimal number
> >+            return _GLIBCXX_WIDEN("month");
> >+          }
> >+        break;
> >+      case 'd':
> >+      case 'e':
> >+        if (!__t._M_day.ok()) [[unlikely]]
> >+          return _GLIBCXX_WIDEN("day");
> >+        break;
> >+      case 'F':
> >+        if (!(__t._M_year/__t._M_month/__t._M_day).ok()) [[unlikely]]
> >+          return _GLIBCXX_WIDEN("date");
> >+        break;
> >+      case 'Y':
> >+        if (!__t._M_year.ok()) [[unlikely]]
> >+          return _GLIBCXX_WIDEN("year");
> >+        break;
> >       default:
> >-        return;
> >+        break;
> >       }
> >+      return __string_view();
> >       }
> >
> >       template<typename _OutIter, typename _FormatContext>
> >@@ -1054,9 +1104,12 @@ namespace __format
> >         do
> >           {
> >             _CharT __c = *__first++;
> >-            _M_check_ok(__t, __c);
> >+            __string_view __invalid;
> >+            if (_M_spec._M_needs_ok_check)
> >+              __invalid = _M_check_ok(__t, __c);
> >
> >-            if (__use_locale_fmt && _S_localized_spec(__c, __mod))
> [[unlikely]]
> >+            if (__invalid.empty() &&__use_locale_fmt
> >+                  && _S_localized_spec(__c, __mod)) [[unlikely]]
> >               __out = _M_locale_fmt(std::move(__out), __fc.locale(),
> >                                     __tm, __c, __mod);
> >             else switch (__c)
> >@@ -1164,6 +1217,14 @@ namespace __format
> >                 __first = __last;
> >                 break;
> >               }
> >+
> >+            if (!__invalid.empty())
> >+              {
> >+                constexpr __string_view __pref = _GLIBCXX_WIDEN(" is not
> a valid ");
> >+                __out = __format::__write(std::move(__out), __pref);
> >+                __out = __format::__write(std::move(__out), __invalid);
> >+              }
> >+
> >             __mod = _CharT();
> >             // Scan for next '%' and write out everything before it.
> >             __string_view __str(__first, __last - __first);
> >@@ -1193,9 +1254,6 @@ namespace __format
> >         // %\0 Extension to format weekday index, used only by empty
> format spec
> >         _CharT __buf[3];
> >         __out = __format::__write(std::move(__out), _S_str_d1(__buf,
> __wi));
> >-        if (_M_spec._M_debug && (__wi < 1 || __wi > 5))
> >-          __out = __format::__write(std::move(__out),
> >-                    __string_view(_GLIBCXX_WIDEN(" is not a valid
> index")));
> >         return std::move(__out);
> >       }
> >
> >@@ -1205,15 +1263,6 @@ namespace __format
> >       {
> >         // %a Locale's abbreviated weekday name.
> >         // %A Locale's full weekday name.
> >-        if (_M_spec._M_debug && !__wd.ok())
> >-          {
> >-            _CharT __buf[3];
> >-            __out = __format::__write(std::move(__out),
> >-                                      _S_str_d1(__buf,
> __wd.c_encoding()));
> >-            return __format::__write(std::move(__out),
> >-                      __string_view(_GLIBCXX_WIDEN(" is not a valid
> weekday")));
> >-          }
> >-
> >         __string_view __str = _S_weekdays[__wd.c_encoding()];
> >         if (!__full)
> >           __str = __str.substr(0, 3);
> >@@ -1226,15 +1275,6 @@ namespace __format
> >       {
> >         // %b Locale's abbreviated month name.
> >         // %B Locale's full month name.
> >-        if (_M_spec._M_debug && !__m.ok())
> >-          {
> >-            _CharT __buf[3];
> >-            __out = __format::__write(std::move(__out),
> >-                                      _S_str_d1(__buf, (unsigned)__m));
> >-            return __format::__write(std::move(__out),
> >-                      __string_view(_GLIBCXX_WIDEN(" is not a valid
> month")));
> >-          }
> >-
> >         __string_view __str = _S_months[(unsigned)__m - 1];
> >         if (!__full)
> >           __str = __str.substr(0, 3);
> >@@ -1303,10 +1343,6 @@ namespace __format
> >               }
> >             __out = __format::__write(std::move(__out), __sv);
> >           }
> >-
> >-        if (_M_spec._M_debug && __conv == 'Y' && !__y.ok()) [[unlikely]]
> >-          __out = __format::__write(std::move(__out),
> >-                    __string_view(_GLIBCXX_WIDEN(" is not a valid
> year")));
> >         return __out;
> >       }
> >
> >@@ -1365,9 +1401,6 @@ namespace __format
> >           }
> >
> >         __out = __format::__write(std::move(__out), __sv);
> >-        if (_M_spec._M_debug && !__d.ok()) [[unlikely]]
> >-          __out = __format::__write(std::move(__out),
> >-                    __string_view(_GLIBCXX_WIDEN(" is not a valid
> day")));
> >         return std::move(__out);
> >       }
> >
> >@@ -1404,9 +1437,6 @@ namespace __format
> >             __out = __format::__write(std::move(__out), __sv);
> >           }
> >
> >-        if (_M_spec._M_debug &&
> !(__t._M_year/__t._M_month/__t._M_day).ok())
> >-          __out = __format::__write(std::move(__out),
> >-                    __string_view(_GLIBCXX_WIDEN(" is not a valid
> date")));
> >         return std::move(__out);
> >       }
> >
> >@@ -1474,8 +1504,10 @@ namespace __format
> >       {
> >         // %m  month as a decimal number.
> >         // %Om Locale's alternative representation.
> >-
> >         auto __i = (unsigned)__m;
> >+        if (__i == 0 && _M_spec._M_debug) [[unlikely]]
> >+          // 0 should not be padded to two digits
> >+          return __format::__write(std::move(__out), _S_digit(0));
> >
> >         _CharT __buf[3];
> >         return __format::__write(std::move(__out), _S_str_d2(__buf,
> __i));
> >@@ -1852,7 +1884,6 @@ namespace __format
> >         using enum _ChronoParts;
> >
> >         _ChronoSpec<_CharT> __res{};
> >-        __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;
> >@@ -1901,6 +1932,10 @@ namespace __format
> >
> >         auto __res
> >           = __formatter_chrono<_CharT>::_M_parse(__pc, __parts, __def);
> >+        // n.b. durations do not contain date parts, and for time point
> all
> >+        // date parts are computed, and they are always ok.
> >+        _M_spec._M_needs_ok_check = false;
> >+
> >         // check for custom floating point durations, if digits of output
> >         // will contain subseconds, then formatters must support
> specifying
> >         // precision.
> >diff --git a/libstdc++-v3/testsuite/std/time/month/io.cc
> b/libstdc++-v3/testsuite/std/time/month/io.cc
> >index 99ec0737305..edfa196b71c 100644
> >--- a/libstdc++-v3/testsuite/std/time/month/io.cc
> >+++ b/libstdc++-v3/testsuite/std/time/month/io.cc
> >@@ -24,6 +24,9 @@ test_ostream()
> >   ss.imbue(std::locale(ISO_8859(15,fr_FR)));
> >   ss << month(1);
> >   VERIFY( ss.str() == "janv." );
> >+  ss.str("");
> >+  ss << month(0) << '|' << month(13);
> >+  VERIFY( ss.str() == "0 is not a valid month|13 is not a valid month" );
> > }
> >
> > void
> >@@ -66,6 +69,10 @@ test_format()
> >   VERIFY( s == "Jan" );
> >   s = std::format(loc_fr, "{:L%b}", month(1));
> >   VERIFY( s == "janv." );
> >+  s = std::format(loc_fr, "{:L}", month(0));
> >+  VERIFY( s == "0 is not a valid month" );
> >+  s = std::format(loc_fr, "{:L}", month(13));
> >+  VERIFY( s == "13 is not a valid month" );
> >
> >   std::string_view specs = "aAbBcCdDeFgGhHIjmMpqQrRSTuUVwWxXyYzZ";
> >   std::string_view my_specs = "bBhm";
> >diff --git a/libstdc++-v3/testsuite/std/time/weekday/io.cc
> b/libstdc++-v3/testsuite/std/time/weekday/io.cc
> >index a56cdaef88c..90a9bcbbb62 100644
> >--- a/libstdc++-v3/testsuite/std/time/weekday/io.cc
> >+++ b/libstdc++-v3/testsuite/std/time/weekday/io.cc
> >@@ -69,6 +69,8 @@ test_format()
> >   VERIFY( s == "Mon" );
> >   s = std::format(loc_fr, "{:L%a}", weekday(1));
> >   VERIFY( s == "lun." );
> >+  s = std::format(loc_fr, "{:L}", weekday(25));
> >+  VERIFY( s == "25 is not a valid weekday" );
> >
> >   std::string_view specs = "aAbBcCdDeFgGhHIjmMpqQrRSTuUVwWxXyYzZ";
> >   std::string_view my_specs = "aAuw";
> >--
> >2.49.0
> >
> >
>
>

Reply via email to