[gcc(refs/users/mikael/heads/pr121185_v01)] libstdc++: Format %a/%A/%b/%h/%B/%p without using locale::classic [PR110739]

Mikael Morin mikael@gcc.gnu.org
Thu Jul 24 07:57:45 GMT 2025


https://gcc.gnu.org/g:d3f9782b9efcdd500b3b4560811aca4e1e38d3ea

commit d3f9782b9efcdd500b3b4560811aca4e1e38d3ea
Author: Tomasz Kamiński <tkaminsk@redhat.com>
Date:   Fri Jun 27 15:04:19 2025 +0200

    libstdc++: Format %a/%A/%b/%h/%B/%p without using locale::classic [PR110739]
    
    With changes r16-2063-g8ad5968a8dcb47 the _M_a_A, _M_b_B and _M_p functions
    are called only if the locale is equal to the locale::classic(), for which
    the behavior is know. This patch changes they implementation, so instead of
    reffering to __timepunct facet members, they use hardcoded list of English
    weekday, months names. Only one list is needed, as in case of locale::classic()
    abbreviated name corresponds to first tree letters of the full name.
    
    For _M_p, _M_r we use a new _M_fill_ampm helper, that fills provided buffer
    with "AM"/"PM" depending on the hours value.
    
    In _M_S we no longer guard querying of numpuct facet, with check that requires
    potentially equally expensive construction of locale::classic. We also mark
    localized path as unlikely.
    
    The _M_locale method is no longer used in __formatter_chrono, and thus was
    moved to __formatter_duration.
    
            PR libstdc++/110739
    
    libstdc++-v3/ChangeLog:
    
            * include/bits/chrono_io.h (__formatter_chrono::_S_weekdays)
            (__formatter_chrono::_S_months, __formatter_chrono::_S_fill_ampm):
            Define.
            (__formatter_chrono::_M_format_to): Do not pass context parameter
            to functions listed below.
            (__formatter_chrono::_M_a_A, __formatter_chrono::_M_b_B): Implement
            using harcoded list of names, and remove format context parameter.
            (__formatter_chrono::_M_p, __formatter_chrono::_M_r): Implement
            using _S_fill_ampm.
            (__formatter_chrono::_M_c): Removed format context parameter.
            (__formatter_chrono::_M_subsecs): Call __ctx.locale() directly,
            instead of _M_locale and do not compare with locale::classic().
            Add [[unlikely]] attributes.
            (__formatter_chrono::_M_locale): Move to __formatter_duration.
            (__formatter_duration::_M_locale): Moved from __formatter_chrono.

Diff:
---
 libstdc++-v3/include/bits/chrono_io.h | 154 +++++++++++++++++++---------------
 1 file changed, 87 insertions(+), 67 deletions(-)

diff --git a/libstdc++-v3/include/bits/chrono_io.h b/libstdc++-v3/include/bits/chrono_io.h
index 75ee7e818b2f..fe3c91267497 100644
--- a/libstdc++-v3/include/bits/chrono_io.h
+++ b/libstdc++-v3/include/bits/chrono_io.h
@@ -881,16 +881,32 @@ namespace __format
       _S_empty_fs()
       { return _Runtime_format_string<_CharT>(_S_empty_spec); }
 
-      // Return the formatting locale.
-      template<typename _FormatContext>
-	std::locale
-	_M_locale(_FormatContext& __fc) const
-	{
-	  if (!_M_spec._M_localized)
-	    return std::locale::classic();
-	  else
-	    return __fc.locale();
-	}
+      static constexpr const _CharT* _S_weekdays[]
+      {
+	_GLIBCXX_WIDEN("Sunday"),
+	_GLIBCXX_WIDEN("Monday"),
+	_GLIBCXX_WIDEN("Tuesday"),
+	_GLIBCXX_WIDEN("Wednesday"),
+	_GLIBCXX_WIDEN("Thursday"),
+	_GLIBCXX_WIDEN("Friday"),
+	_GLIBCXX_WIDEN("Saturday"),
+      };
+
+      static constexpr const _CharT* _S_months[]
+      {
+	_GLIBCXX_WIDEN("January"),
+	_GLIBCXX_WIDEN("February"),
+	_GLIBCXX_WIDEN("March"),
+	_GLIBCXX_WIDEN("April"),
+	_GLIBCXX_WIDEN("May"),
+	_GLIBCXX_WIDEN("June"),
+	_GLIBCXX_WIDEN("July"),
+	_GLIBCXX_WIDEN("August"),
+	_GLIBCXX_WIDEN("September"),
+	_GLIBCXX_WIDEN("October"),
+	_GLIBCXX_WIDEN("November"),
+	_GLIBCXX_WIDEN("December"),
+      };
 
     private:
       template<typename _OutIter>
@@ -1051,15 +1067,15 @@ namespace __format
 		  break;
 		case 'a':
 		case 'A':
-		  __out = _M_a_A(__t._M_weekday, std::move(__out), __fc, __c == 'A');
+		  __out = _M_a_A(__t._M_weekday, std::move(__out), __c == 'A');
 		  break;
 		case 'b':
 		case 'h':
 		case 'B':
-		  __out = _M_b_B(__t._M_month, std::move(__out), __fc, __c == 'B');
+		  __out = _M_b_B(__t._M_month, std::move(__out), __c == 'B');
 		  break;
 		case 'c':
-		  __out = _M_c(__t, std::move(__out), __fc);
+		  __out = _M_c(__t, std::move(__out));
 		  break;
 		case 'C':
 		case 'y':
@@ -1095,7 +1111,7 @@ namespace __format
 		  __out = _M_M(__t._M_minutes, __print_sign());
 		  break;
 		case 'p':
-		  __out = _M_p(__t._M_hours, std::move(__out), __fc);
+		  __out = _M_p(__t._M_hours, std::move(__out));
 		  break;
 		case 'q':
 		  __out = _M_q(__t._M_unit_suffix, std::move(__out));
@@ -1104,7 +1120,7 @@ namespace __format
 		  __out = _M_Q(__t, __print_sign(), __fc);
 		  break;
 		case 'r':
-		  __out = _M_r(__t, __print_sign(), __fc);
+		  __out = _M_r(__t, __print_sign());
 		  break;
 		case 'R':
 		case 'X':
@@ -1183,10 +1199,9 @@ namespace __format
 	  return std::move(__out);
 	}
 
-      template<typename _OutIter, typename _FormatContext>
+      template<typename _OutIter>
 	_OutIter
-	_M_a_A(chrono::weekday __wd, _OutIter __out,
-	       _FormatContext& __ctx, bool __full) const
+	_M_a_A(chrono::weekday __wd, _OutIter __out, bool __full) const
 	{
 	  // %a Locale's abbreviated weekday name.
 	  // %A Locale's full weekday name.
@@ -1199,21 +1214,15 @@ namespace __format
 			__string_view(_GLIBCXX_WIDEN(" is not a valid weekday")));
 	    }
 
-	  locale __loc = _M_locale(__ctx);
-	  const auto& __tp = use_facet<__timepunct<_CharT>>(__loc);
-	  const _CharT* __days[7];
-	  if (__full)
-	    __tp._M_days(__days);
-	  else
-	    __tp._M_days_abbreviated(__days);
-	  __string_view __str(__days[__wd.c_encoding()]);
-	  return _M_write(std::move(__out), __loc, __str);
+	  __string_view __str = _S_weekdays[__wd.c_encoding()];
+	  if (!__full)
+	    __str = __str.substr(0, 3);
+	  return __format::__write(std::move(__out), __str);
 	}
 
-      template<typename _OutIter, typename _FormatContext>
+      template<typename _OutIter>
 	_OutIter
-	_M_b_B(chrono::month __m, _OutIter __out,
-	       _FormatContext& __ctx, bool __full) const
+	_M_b_B(chrono::month __m, _OutIter __out, bool __full) const
 	{
 	  // %b Locale's abbreviated month name.
 	  // %B Locale's full month name.
@@ -1226,28 +1235,22 @@ namespace __format
 			__string_view(_GLIBCXX_WIDEN(" is not a valid month")));
 	    }
 
-	  locale __loc = _M_locale(__ctx);
-	  const auto& __tp = use_facet<__timepunct<_CharT>>(__loc);
-	  const _CharT* __months[12];
-	  if (__full)
-	    __tp._M_months(__months);
-	  else
-	    __tp._M_months_abbreviated(__months);
-	  __string_view __str(__months[(unsigned)__m - 1]);
-	  return _M_write(std::move(__out), __loc, __str);
+	  __string_view __str = _S_months[(unsigned)__m - 1];
+	  if (!__full)
+	    __str = __str.substr(0, 3);
+	  return __format::__write(std::move(__out), __str);
 	}
 
-      template<typename _OutIter, typename _FormatContext>
+      template<typename _OutIter>
 	_OutIter
-	_M_c(const _ChronoData<_CharT>& __t, _OutIter __out,
-	     _FormatContext& __ctx) const
+	_M_c(const _ChronoData<_CharT>& __t, _OutIter __out) const
 	{
 	  // %c  Locale's date and time representation, for C-locale: %a %b %e %T %Y
 	  // %Ec Locale's alternate date and time representation, for C-locale same as above
 
-	  __out = _M_a_A(__t._M_weekday, std::move(__out), __ctx, false);
+	  __out = _M_a_A(__t._M_weekday, std::move(__out), false);
 	  *__out = _S_space;
-	  __out = _M_b_B(__t._M_month, std::move(++__out), __ctx, false);
+	  __out = _M_b_B(__t._M_month, std::move(++__out), false);
 	  *__out = _S_space;
 	  __out = _M_d_e(__t._M_day, std::move(++__out), 'e');
 	  *__out = _S_space;
@@ -1489,20 +1492,15 @@ namespace __format
 	  return __format::__write(std::move(__out), _S_two_digits(__i));
 	}
 
-      template<typename _OutIter, typename _FormatContext>
+      template<typename _OutIter>
 	_OutIter
-	_M_p(chrono::hours __h, _OutIter __out, _FormatContext& __ctx) const
+	_M_p(chrono::hours __h, _OutIter __out) const
 	{
 	  // %p The locale's equivalent of the AM/PM designations.
-	  auto __hi =  __h.count();
-	  if (__hi >= 24) [[unlikely]]
-	    __hi %= 24;
-
-	  locale __loc = _M_locale(__ctx);
-	  const auto& __tp = use_facet<__timepunct<_CharT>>(__loc);
-	  const _CharT* __ampm[2];
-	  __tp._M_am_pm(__ampm);
-	  return _M_write(std::move(__out), __loc, __ampm[__hi >= 12]);
+
+	  _CharT __buf[2];
+	  _S_fill_ampm(__buf, __h);
+	  return __format::__write(std::move(__out), __string_view(__buf, 2));
 	}
 
       template<typename _OutIter>
@@ -1522,27 +1520,25 @@ namespace __format
 	  return std::vformat_to(std::move(__out), _S_empty_spec, __t._M_ereps);
 	}
 
-      template<typename _OutIter, typename _FormatContext>
+      template<typename _OutIter>
 	_OutIter
-	_M_r(const _ChronoData<_CharT>& __t, _OutIter __out,
-	     _FormatContext& __ctx) const
+	_M_r(const _ChronoData<_CharT>& __t, _OutIter __out) const
 	{
 	  // %r Locale's 12-hour clock time, for C-locale: %I:%M:%S %p
 	  auto __hi = __t._M_hours.count() % 12;
 	  if (__hi == 0)
 	    __hi = 12;
 
-	  _CharT __buf[9];
+	  _CharT __buf[11];
 	  __buf[2] = _S_colon;
 	  __buf[5] = _S_colon;
 	  __buf[8] = _S_space;
 	  _S_fill_two_digits(__buf, __hi);
 	  _S_fill_two_digits(__buf + 3, __t._M_minutes.count());
 	  _S_fill_two_digits(__buf + 6, __t._M_seconds.count());
+	  _S_fill_ampm(__buf + 9, __t._M_hours);
 
-	  __string_view __sv(__buf, 9);
-	  __out = __format::__write(std::move(__out), __sv);
-	  return _M_p(__t._M_hours, std::move(__out), __ctx);
+	  return __format::__write(std::move(__out), __string_view(__buf, 11));
 	}
 
       template<typename _OutIter>
@@ -1606,9 +1602,9 @@ namespace __format
 	    return __out;
 
 	  _CharT __dot = _S_dot;
-	  if (_M_spec._M_localized)
-	    if (auto __loc = __ctx.locale(); __loc != locale::classic())
+	  if (_M_spec._M_localized) [[unlikely]]
 	    {
+	      auto __loc = __ctx.locale();
 	      const auto& __np = use_facet<numpunct<_CharT>>(__loc);
 	      __dot = __np.decimal_point();
 	    }
@@ -1618,8 +1614,8 @@ namespace __format
 	  if (_M_spec._M_floating_point_rep)
 	    {
 	      _Str_sink<_CharT> __sink;
-	      if (_M_spec._M_localized && _M_spec._M_custom_rep)
-		 std::vformat_to(__sink.out(), _M_locale(__ctx),
+	      if (_M_spec._M_localized && _M_spec._M_custom_rep) [[unlikely]]
+		 std::vformat_to(__sink.out(), __ctx.locale(),
 				 _GLIBCXX_WIDEN("{1:0.{2}Lf}"), __t._M_ereps);
 	      else
 		 std::vformat_to(__sink.out(),
@@ -1778,6 +1774,20 @@ namespace __format
 	__buf[1] = __sv[1];
       }
 
+      // Fills __buf[0] and __buf[1] with "AM", "PM" depending on __h.
+      [[__gnu__::__always_inline__]]
+      static void
+      _S_fill_ampm(_CharT* __buf, chrono::hours __h)
+      {
+	auto __hi =  __h.count();
+	if (__hi >= 24) [[unlikely]]
+	  __hi %= 24;
+
+	constexpr const _CharT* __apm = _GLIBCXX_WIDEN("APM");
+	__buf[0] = __apm[__hi >= 12];
+	__buf[1] = __apm[2];
+      }
+
       // Returns decimal representation of __n.
       // Returned string_view may point to __buf.
       [[__gnu__::__always_inline__]]
@@ -1879,7 +1889,6 @@ namespace __format
 	};
 
       using __formatter_chrono<_CharT>::__formatter_chrono;
-      using __formatter_chrono<_CharT>::_M_locale;
       using __formatter_chrono<_CharT>::_M_spec;
 
       template<typename _Duration, typename _ParseContext>
@@ -1909,6 +1918,17 @@ namespace __format
 	  return __res;
 	}
 
+      // Return the formatting locale.
+      template<typename _FormatContext>
+	std::locale
+	_M_locale(_FormatContext& __fc) const
+	{
+	  if (!_M_spec._M_localized)
+	    return std::locale::classic();
+	  else
+	    return __fc.locale();
+	}
+
       // Format duration for empty chrono-specs, e.g. "{}" (C++20 [time.format] p6).
       template<typename _Rep, typename _Period, typename _FormatContext>
 	typename _FormatContext::iterator


More information about the Libstdc++-cvs mailing list