[PATCH v2 1/2] libstdc++: Type-erase chrono-data for formatting [PR110739]

Tomasz Kaminski tkaminsk@redhat.com
Thu Jun 26 12:52:19 GMT 2025


On Thu, Jun 26, 2025 at 2:26 PM Jonathan Wakely <jwakely@redhat.com> wrote:

> On 26/06/25 11:39 +0200, Tomasz Kamiński wrote:
> >This patch reworks the formatting for the chrono types, such that they
> are all
> >formatted in terms of _ChronoData class, that includes all required
> fields.
> >Populating each required field is performed in formatter for specific
> type,
> >based on the chrono-spec used.
> >
> >To facilitate above, the _ChronoSpec now includes additional _M_needed
> field,
> >that represnts the chrono data that is referenced by format spec (this
> value
> >is also configured for __defSpec). This value differs from the value of
> >__parts passed to _M_parse, which does include all fields that can be
> computed
> >from input (e.g. weekday_indexed can be computed for year_month_day).
> Later
> >it is used to fill _ChronoData, in particular _M_fill_* family of
> functions,
> >to determine if given field needs to be set, and thus it's value needs to
> be
>
> "its"
>
> >computed.
> >
> >In consequence _ChronoParts enum was exteneded with additional values,
>
> "extended"
>
> >that allows more fine grained indentification:
>
> "identification"
>
> > * _TimeOfDay is separated into _HoursMinutesSeconds and _Subseconds,
> > * _TimeZone is separated into _ZoneAbbrev and _ZoneOffset,
> > * _LocalDays, _WeekdayIndex are defiend in included in _Date,
>
> "defined"
>
> > * _Duration is removed, and instead _EpochUnits and _UnitSuffix are
> >   introduced.
> >Furthermore, to avoid name conflicts _ChonoParts is now defined as enum
> class,
> >with additional operators that simplify uses.
>
> I don't love overloading operator- to mean clearing bits, but it does
> make clearing the bits very convenient. Maybe just add a comment
> before operator-(_ChronoParts x, _ChronoParts y) saying that it
> returns a copy of x with all bits from y unset.

I have added a comment. I actually think operator- makes the filling code
pretty readable, and intuitive. We have set some files, and remove it from
parts.

> That comment will be
> know that's what the function body
>
> (Which is x&(x^y) I think, right?)
>
I think both x&(x^y) and x & ~y gives the same result. I prefer the later.

>
>
> >
> >In addition to fields that can be printed using chron-spec, _ChronoData
> stores:
>
> "chrono-spec"
>
> > * Total days in wall time (_M_ldays), day of year (_M_day_of_year) -
> used by
> >   struct tm construction, and for ISO calendar computation.
> > * Total seconds in wall time (_M_lseconds) - this value may be different
> from
> >   sum of days, hours, minutes, seconds (e.g. see utc_time below).
> Included
> >   to allow future extension, like printing total minutes.
> > * Total seconds since epoch - due offset different from above. Again to
> be
> >   used with future extension (e.g. %s as proposed in P2945R1).
> > * Subseconds - count of attoseconds (10^(-18)), in addition to priting
> can
>
> "printing"
>
> >   be used to  compute fractional hours, minutes.
> >The both total seconds fielkds we use single _TotalSeconds enumerator in
>
> "fields"
>
> >_ChronoParts, that when present in combination with _EpochUnits or
> _LocalDays
> >indicates that _M_eseconds (_EpochSeconds) or _M_lseconds (_LocalSeconds)
> are
> >provided/required.
> >
> >To handle type formatting of time since epoch ('%Q'|_EpochUnits), we use
> the
> >format_args mechanism, where the result of +d.count() (see LWG4118) is
> erased
> >into make_format_args to local __arg_store, that is later referenced by
> >_M_ereps (_M_ereps.get(0)).
> >
> >To handle precision values, and in prepartion to allow user to configure
> ones,
> >we store the precision as third element of _M_ereps (_M_ereps.get(2)),
> this
> >allows duration with precision to be printed using "{0:{2}}". For
> subseconds
> >the precision is handled differently depending on the representation:
> > * for integral reps, _M_subseconds value is used to determine fractional
> value,
> >   precision is trimmed to 18 digits;
> > * for floating-points, we _M_ereps stores duration<Rep> initialized with
> only
>
> Strike "we"?
>
> >   fractional seconds, that is later formatted with precision.
> >Always using _M_subseconds fields for integral duration, means that we do
> not
> >use formattter for user-defined durations that are considered to be
> integral
> >(see empty_spec.cc file change). To avoid potentially expensive
> computation
> >of _M_subseconds, we make sure that _ChronoParts::_Subseconds is set only
> if
> >_Subseconds are needed. In particular we remove this flag for localized
> ouput
> >in _M_parse.
> >
> >Construction the _M_ereps as described above is handled by
> __formatter_duration,
>
> "Construction of the"
>
> >that is then used to format duration, hh_mm_ss and time_points
> specialization.
>
> "specializations"
>
> >This class also handles _UnitSuffix, the _M_units_suffix field is
> populated
> >either with predefined suffix (chrono::__detail::__units_suffix) or one
> produced
> >locally.
> >
> >Finally, formatters for types listed below contains type specific logic:
> > * hh_mm_ss - we do not compute total duration and seconds, unless
> explicitly
> >   requested, as such computation may overflow;
> > * utc_time - for time during leap second insertion, the _M_seconds field
> is
> >   increased to 60;
> > * __local_time_fmt - exception is thrown if zone offset (_ZoneOffset) or
> >   abbrevation (_ZoneAbbrev) is requsted, but corresponding pointer is
> null,
> >   futhermore conversion from `char` to `wchar_t` for abbreviation is
> performed
> >   if needed.
>
>
> I had other comments about the benefits of this change, and the
> unintended change that Michael noticed, but your [PATCH 0/2] covers
> the former and the latter is fixed in this v2 patch!
>
>
> >       PR libstdc++/110739
> >
> >libstdc++-v3/ChangeLog:
> >
> >       * include/bits/chrono_io.h (__format::__no_timezone_available):
> >       Removed, replaced with separate throws in formatter for
> >       __local_time_fmt
> >       (__format::_ChronoParts): Defined additional enumertors and
> >       declared as enum class.
> >       (__format::operator&(_ChronoParts, _ChronoParts))
> >       (__format::operator&=(_ChronoParts&, _ChronoParts))
> >       (__format::operator-(_ChronoParts, _ChronoParts))
> >       (__format::operator-=(_ChronoParts&, _ChronoParts))
> >       (__format::operator==(_ChronoParts, decltype(nullptr)))
> >       (_ChronoSpec::_M_time_only, _ChronoSpec::_M_floating_point_rep)
> >       (_ChronoSpec::_M_custom_rep, _ChronoSpec::_M_needed)
> >       (_ChronoSpec::_M_needs, __format::_ChronoData): Define.
> >       (__format::__formatter_chrono): Redefine to accept _ChronoData.
> >       (__formatter_chrono::_M_format_to_ostream): Moved toi
> >       __formatter_duration.
> >       (__format::__formatter_duration): Define.
> >       (__formatter_chrono_info::format): Pass value-constructed
> >       _ChronoData.
> >       (std::formatter<chrono::day, _CharT>)
> >       (std::formatter<chrono::month, _CharT>)
> >       (std::formatter<chrono::year, _CharT>)
> >       (std::formatter<chrono::weekday, _CharT>)
> >       (std::formatter<chrono::weekday_indexed, _CharT>)
> >       (std::formatter<chrono::weekday_last, _CharT>)
> >       (std::formatter<chrono::month_day, _CharT>)
> >       (std::formatter<chrono::month_day_last, _CharT>)
> >       (std::formatter<chrono::month_weekday, _CharT>)
> >       (std::formatter<chrono::month_weekday_indexed, _CharT>)
> >       (std::formatter<chrono::month_weekday_last, _CharT>)
> >       (std::formatter<chrono::year_month, _CharT>)
> >       (std::formatter<chrono::year_month_day, _CharT>)
> >       (std::formatter<chrono::year_month_day_last, _CharT>)
> >       (std::formatter<chrono::year_month_weekday, _CharT>)
> >       (std::formatter<chrono::year_month_weekday_indexed, _CharT>)
> >       (std::formatter<chrono::year_month_weekday_last, _CharT>):
> >       Construct _ChronoData in format, and configure _M_needed in
> >       _ChronoSpec.
> >       (std::formatter<chrono::duration<_Rep, _Period>, _CharT>)
> >       (std::formatter<chrono::hh_mm_ss<_Duration>, _CharT>)
> >       (std::formatter<chrono::sys_time<_Duration>, _CharT>)
> >       (std::formatter<chrono::utc_time<_Duration>, _CharT>)
> >       (std::formatter<chrono::tai_time<_Duration>, _CharT>)
> >       (std::formatter<chrono::gps_time<_Duration>, _CharT>)
> >       (std::formatter<chrono::file_time<_Duration>, _CharT>)
> >       (std::formatter<chrono::local_time<_Duration>, _CharT>)
> >       (std::formatter<chrono::_detail::__local_time_fmt<_Duration>,
> _CharT>):
> >       Reworked in terms of __formatter_duration and _ChronoData.
> >       (std::formatter<chrono::_detail::__utc_leap_second<_Duration>,
> _CharT>):
> >       Removed.
> >       (_Parser<_Duration>::operator()): Adjusted for _ChronoParts
> >       being enum class.
> >       * include/std/chrono (__detail::__utc_leap_second): Removed,
> >       replaced with simply bumping _M_seconds in _ChronoData.
> >       * testsuite/std/time/format/empty_spec.cc: Updated %S integral
> >       ouput.
> >---
> >v2:
> > * fixes _M_z that was priting minutes:seconds, instead of hours::seconds
>
> Oh good catch! I didn't notice that in v1. Did you find that by
> testing or inspection?
>
> > * use uint_least64 as type of pow10_t
> > * fix typo pointed by Michael Welsh Duggan
> > * fix formatting
> >
> > libstdc++-v3/include/bits/chrono_io.h         | 1952 ++++++++++-------
> > libstdc++-v3/include/std/chrono               |   24 -
> > .../testsuite/std/time/format/empty_spec.cc   |   16 +-
> > 3 files changed, 1163 insertions(+), 829 deletions(-)
> >
> >diff --git a/libstdc++-v3/include/bits/chrono_io.h
> b/libstdc++-v3/include/bits/chrono_io.h
> >index 4eb00f4932d..35e95906e6a 100644
> >--- a/libstdc++-v3/include/bits/chrono_io.h
> >+++ b/libstdc++-v3/include/bits/chrono_io.h
> >@@ -187,11 +187,6 @@ namespace __detail
> > /// @cond undocumented
> > namespace __format
> > {
> >-  [[noreturn,__gnu__::__always_inline__]]
> >-  inline void
> >-  __no_timezone_available()
> >-  { __throw_format_error("format error: no timezone available for %Z or
> %z"); }
> >-
> >   [[noreturn,__gnu__::__always_inline__]]
> >   inline void
> >   __not_valid_for_duration()
> >@@ -204,40 +199,103 @@ namespace __format
> >   { __throw_format_error("format error: chrono-format-spec not valid for
> "
> >                        "argument type"); }
> >
> >-  template<typename _CharT>
> >-    struct _ChronoSpec : _Spec<_CharT>
> >-    {
> >-      // Placed in tail-padding of __format::_Spec<C>.
> >-      // This indicates that a locale-dependent conversion specifier
> such as
> >-      // %a is used in the chrono-specs. This is not the same as the
> >-      // _Spec<C>::_M_localized member which indicates that "L" was
> present
> >-      // 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;
> >-
> >-      basic_string_view<_CharT> _M_chrono_specs;
> >-    };
> >-
> >   // Represents the information provided by a chrono type.
> >   // e.g. month_weekday has month and weekday but no year or time of day,
> >   // hh_mm_ss has time of day but no date, sys_time is
> time_point+timezone.
> >-  enum _ChronoParts {
> >-    _Year = 1, _Month = 2, _Day = 4, _Weekday = 8, _TimeOfDay = 16,
> >-    _TimeZone = 32,
> >-    _Date = _Year | _Month | _Day | _Weekday,
> >-    _DateTime = _Date | _TimeOfDay,
> >+  enum class _ChronoParts : unsigned short {
> >+    _None = 0, _TotalSeconds = 1u, _Subseconds = 1u << 2,
> >+
> >+    // time since epoch
> >+    _EpochUnits = 1u << 3, _UnitSuffix = 1u << 4,
> >+    _EpochSeconds = _EpochUnits | _TotalSeconds,
> >+
> >+    // local (wall) time
> >+    _LocalDays = 1u << 5,
> >+    _LocalSeconds = _LocalDays | _TotalSeconds,
> >+
> >+    _Year = 1u << 6, _Month = 1u << 7, _Day = 1u << 8,
> >+    _Weekday = 1u << 9, _WeekdayIndex = 1u << 10,  _DayOfYear = 1u << 11,
> >+    _IndexedWeekday = _Weekday | _WeekdayIndex,
> >+    _YearMonthDay = _Year | _Month | _Day,
> >+    _Date = _LocalDays | _YearMonthDay | _IndexedWeekday | _DayOfYear,
> >+
> >+    _HoursMinutesSeconds = 1u << 12,
> >+    _TimeOfDay = _HoursMinutesSeconds | _Subseconds,
> >+    _Time = _TimeOfDay | _TotalSeconds,
> >+    _EpochTime = _Time | _EpochUnits | _UnitSuffix,
> >+    _DateTime = _Date | _Time,
> >+
> >+    _ZoneAbbrev = 1u << 13, _ZoneOffset = 1u << 14,
> >+    _TimeZone = _ZoneAbbrev | _ZoneOffset,
> >     _ZonedDateTime = _DateTime | _TimeZone,
> >-    _Duration = 128 // special case
> >   };
> >
> >+  [[__gnu__::__always_inline__]]
> >+  constexpr _ChronoParts
> >+  operator&(_ChronoParts __x, _ChronoParts __y) noexcept
> >+  { return static_cast<_ChronoParts>((unsigned)__x & (unsigned)__y); }
> >+
> >+  [[__gnu__::__always_inline__]]
> >+  constexpr _ChronoParts&
> >+  operator&=(_ChronoParts& __x, _ChronoParts __y) noexcept
> >+  { return __x = __x & __y; }
> >+
> >+  [[__gnu__::__always_inline__]]
> >   constexpr _ChronoParts
> >   operator|(_ChronoParts __x, _ChronoParts __y) noexcept
> >-  { return static_cast<_ChronoParts>((int)__x | (int)__y); }
> >+  { return static_cast<_ChronoParts>((unsigned short)__x | (unsigned
> short)__y); }
> >
> >+  [[__gnu__::__always_inline__]]
> >   constexpr _ChronoParts&
> >   operator|=(_ChronoParts& __x, _ChronoParts __y) noexcept
> >   { return __x = __x | __y; }
> >
> >+  [[__gnu__::__always_inline__]]
> >+  constexpr _ChronoParts
> >+  operator-(_ChronoParts __x, _ChronoParts __y) noexcept
> >+  { return static_cast<_ChronoParts>((unsigned short)__x & ~(unsigned
> short)__y); }
> >+
> >+  [[__gnu__::__always_inline__]]
> >+  constexpr _ChronoParts&
> >+  operator-=(_ChronoParts& __x, _ChronoParts __y) noexcept
> >+  { return __x = __x - __y; }
> >+
> >+  [[__gnu__::__always_inline__]]
> >+  constexpr bool
> >+  operator==(_ChronoParts __x, decltype(nullptr)) noexcept
> >+  { return (unsigned short)__x == 0; }
> >+
> >+  template<typename _CharT>
> >+    struct _ChronoSpec : _Spec<_CharT>
> >+    {
> >+      // When _M_prec_kind is _WP_none, the _M_prec contains the default
> >+      // value of fraction digits to be used for time '%S'.
> >+
> >+      // Placed in tail-padding of __format::_Spec<C>.
> >+      // This indicates that a locale-dependent conversion specifier
> such as
> >+      // %a is used in the chrono-specs. This is not the same as the
> >+      // _Spec<C>::_M_localized member which indicates that "L" was
> present
> >+      // 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 is duration should be treated as floating point.
>
> "Indicates that"
>
> >+      unsigned _M_floating_point_rep : 1;
> >+      // Indicate that duration uses user-defined representation.
> >+      unsigned _M_custom_rep : 1;
> >+      unsigned _M_unused : 4;
> >+
> >+      // Chrono parts required by format specs
> >+      _ChronoParts _M_needed;
> >+      basic_string_view<_CharT> _M_chrono_specs;
> >+
> >+      [[__gnu__::__always_inline__]]
> >+      constexpr bool
> >+      _M_needs(_ChronoParts __parts) const
> >+      { return (_M_needed & __parts) != 0; }
> >+    };
> >+
> >   template<typename _CharT>
> >   struct _ChronoFormats
> >   {
> >@@ -340,6 +398,152 @@ namespace __format
> >     { return _S_yml().substr(3); }
> >   };
> >
> >+  template<typename _CharT>
> >+    struct _ChronoData
> >+    {
> >+      static constexpr unsigned _S_max_prec = 18;
> >+      using _Attoseconds = chrono::duration<__UINT_LEAST64_TYPE__, atto>;
> >+
> >+      using _FormatContext
> >+      = basic_format_context<_Sink_iter<_CharT>, _CharT>;
> >+      using _FormatArgs = basic_format_args<_FormatContext>;
> >+      static inline auto _S_args =
> std::make_format_args<_FormatContext>();
> >+
> >+      _ChronoData() = default;
> >+      _ChronoData(_ChronoData&&) = delete;
> >+
> >+      // time since epoch
> >+      chrono::seconds           _M_eseconds;
> >+      // n.b. due offset being seconds or corser, local and epoch
> subseconds
>
> "coarser"
>
> >+      // has the same value
> >+      _Attoseconds              _M_subseconds;
> >+      // _M_ereps.get(0) stores duration units
> >+      // _M_ereps.get(1) stores subseconds units
> >+      // _M_ereps.get(2) stores precision
> >+      _FormatArgs               _M_ereps = _S_args;
> >+      basic_string_view<_CharT> _M_unit_suffix;
> >+
> >+      // local (wall) time
> >+      chrono::local_seconds _M_lseconds;
> >+      chrono::local_days    _M_ldays;
> >+
> >+      chrono::year    _M_year;
> >+      chrono::month   _M_month;
> >+      chrono::day     _M_day;
> >+      chrono::weekday _M_weekday;
> >+      unsigned char   _M_weekday_index;
> >+      chrono::days    _M_day_of_year;
> >+
> >+      bool            _M_is_neg;
> >+      chrono::hours   _M_hours;
> >+      chrono::minutes _M_minutes;
> >+      chrono::seconds _M_seconds;
> >+
> >+      chrono::seconds           _M_zone_offset;
> >+      basic_string_view<_CharT> _M_zone_abbrev;
> >+      const char*               _M_zone_cstr = "";
> >+
> >+      template<typename _YearMonth>
> >+      [[__gnu__::__always_inline__]]
> >+      _ChronoParts
> >+      _M_fill_year_month(const _YearMonth& __ym, _ChronoParts __parts)
> >+      {
> >+        _M_year = __ym.year();
> >+        __parts -= _ChronoParts::_Year;
> >+        _M_month = __ym.month();
> >+        __parts -= _ChronoParts::_Month;
> >+        return __parts;
> >+      }
> >+
> >+      [[__gnu__::__always_inline__]]
> >+      _ChronoParts
> >+      _M_fill_day(chrono::day __d, _ChronoParts __parts)
> >+      {
> >+      _M_day = __d;
> >+      __parts -= _ChronoParts::_Day;
> >+      _M_weekday_index = ((unsigned)__d + 6u) % 7u;
> >+      __parts -= _ChronoParts::_WeekdayIndex;
> >+      return __parts;
> >+      }
> >+
> >+      [[__gnu__::__always_inline__]]
> >+      _ChronoParts
> >+      _M_fill_weekday(chrono::weekday_indexed __wi, _ChronoParts __parts)
> >+      {
> >+      _M_weekday = __wi.weekday();
> >+      __parts -= _ChronoParts::_Weekday;
> >+      _M_weekday_index = __wi.index();
> >+      __parts -= _ChronoParts::_WeekdayIndex;
> >+      return __parts;
> >+      }
> >+
> >+      [[__gnu__::__always_inline__]]
> >+      _ChronoParts
> >+      _M_fill_aux(chrono::local_days __ld, _ChronoParts __parts)
> >+      {
> >+      using namespace chrono;
> >+      if ((__parts & _ChronoParts::_Weekday) != 0)
> >+        _M_weekday = weekday(__ld);
> >+      __parts -= _ChronoParts::_Weekday;
> >+      if ((__parts & _ChronoParts::_DayOfYear) != 0)
> >+        // See "Calculating Ordinal Dates" at
> >+        //
> https://github.com/HowardHinnant/date/wiki/Examples-and-Recipes
> >+        _M_day_of_year = __ld - local_days(_M_year/January/0);
> >+      __parts -= _ChronoParts::_DayOfYear;
> >+      return __parts;
> >+      }
> >+
> >+      [[__gnu__::__always_inline__]]
> >+      _ChronoParts
> >+      _M_fill_ldays(chrono::local_days __ld, _ChronoParts __parts)
> >+      {
> >+      _M_ldays = __ld;
> >+      __parts -= _ChronoParts::_LocalDays;
> >+      return _M_fill_aux(__ld, __parts);
> >+      }
> >+
> >+      void
> >+      _M_fill_time(chrono::seconds __d)
> >+      {
> >+      chrono::hh_mm_ss<chrono::seconds> __hms(__d);
> >+      _M_hours = __hms.hours();
> >+      _M_minutes = __hms.minutes();
> >+      _M_seconds = __hms.seconds();
> >+      }
> >+
> >+      void
> >+      _M_fill_date_time(chrono::local_seconds __ls, _ChronoParts __parts)
> >+      {
> >+      _M_ldays = chrono::floor<chrono::days>(__ls);
> >+      __parts -= _ChronoParts::_LocalDays;
> >+      if ((__parts & _ChronoParts::_HoursMinutesSeconds) != 0)
> >+        _M_fill_time(_M_lseconds - _M_ldays);
> >+
> >+      if ((__parts & _ChronoParts::_Date) != 0)
> >+        {
> >+          const chrono::year_month_day __ymd(_M_ldays);
> >+          _M_fill_year_month(__ymd, __parts);
> >+          _M_fill_day(__ymd.day(), __parts);
> >+          _M_fill_aux(_M_ldays, __parts);
> >+        }
> >+      }
> >+
> >+      void
> >+      _M_fill_zone(const char* __abbrev, const wchar_t* __wabbrev)
> >+      {
> >+      if constexpr (is_same_v<_CharT, char>)
> >+        _M_zone_abbrev = __abbrev;
> >+      else
> >+        _M_zone_abbrev = __wabbrev;
> >+      _M_zone_cstr = __abbrev;
> >+      }
> >+
> >+      [[__gnu__::__always_inline__]]
> >+      void
> >+      _M_fill_utc_zone()
> >+      { _M_fill_zone("UTC", L"UTC"); }
> >+    };
> >+
> >   // TODO rename this to chrono::__formatter? or
> chrono::__detail::__formatter?
> >   template<typename _CharT>
> >     struct __formatter_chrono
> >@@ -357,7 +561,7 @@ namespace __format
> >       template<typename _ParseContext>
> >       constexpr typename _ParseContext::iterator
> >       _M_parse(_ParseContext& __pc, _ChronoParts __parts,
> >-               const _ChronoSpec<_CharT>& __def = {})
> >+               const _ChronoSpec<_CharT>& __def)
> >       {
> >         auto __first = __pc.begin();
> >         auto __last = __pc.end();
> >@@ -388,11 +592,18 @@ namespace __format
> >         if (__finished())
> >           return __first;
> >
> >-        if (__parts & _ChronoParts::_Duration)
> >+        if (*__first == '.')
> >           {
> >-            __first = __spec._M_parse_precision(__first, __last, __pc);
> >-            if (__finished())
> >-              return __first;
> >+            if ((__parts & _ChronoParts::_EpochUnits) == 0
> >+                || !__spec._M_floating_point_rep)
> >+              __throw_format_error("format error: invalid precision for
> duration");
> >+
> >+            // Precision is allowed, but value is ignored.
> >+            __first = _Spec<_CharT>()._M_parse_precision(__first,
> __last, __pc);
> >+            // Still inditate that there was user supplied precision.
>
> "indicate"
>
> >+            __spec._M_prec_kind = _WP_value;
> >+           if (__finished())
> >+             return __first;
> >           }
> >
> >         __spec._M_localized = false;
> >@@ -418,7 +629,8 @@ namespace __format
> >         // against __parts (so fail for %Y if no year in parts).
> >         // Save range in __spec._M_chrono_specs.
> >         __spec._M_debug = false;
> >-        __spec._M_locale_specific = true;
> >+        __spec._M_locale_specific = false;
> >+        __spec._M_needed = _ChronoParts::_None;
> >         __spec._M_chrono_specs = __string_view();
> >
> >         const auto __chrono_specs = __first++; // Skip leading '%'
> >@@ -428,17 +640,18 @@ namespace __format
> >
> >         _CharT __mod{};
> >         bool __conv = true;
> >-        int __needed = 0;
> >-        bool __locale_specific = false;
> >-
> >         while (__first != __last)
> >           {
> >             enum _Mods { _Mod_none, _Mod_E, _Mod_O, _Mod_E_O };
> >             _Mods __allowed_mods = _Mod_none;
> >
> >+            _ChronoParts __needed = _ChronoParts::_None;
> >+            bool __locale_specific = false;
> >+
> >             _CharT __c = *__first++;
> >             switch (__c)
> >               {
> >+              using enum _ChronoParts;
> >               case 'a':
> >               case 'A':
> >                 __needed = _Weekday;
> >@@ -451,7 +664,7 @@ namespace __format
> >                 __locale_specific = true;
> >                 break;
> >               case 'c':
> >-                __needed = _DateTime;
> >+                __needed = _Date|_HoursMinutesSeconds;
> >                 __allowed_mods = _Mod_E;
> >                 __locale_specific = true;
> >                 break;
> >@@ -466,27 +679,27 @@ namespace __format
> >                 break;
> >               case 'D':
> >               case 'F':
> >-                __needed = _Date;
> >+                __needed = _YearMonthDay;
> >                 break;
> >               case 'g':
> >               case 'G':
> >-                __needed = _Date;
> >+                __needed = _LocalDays|_Weekday;
> >                 break;
> >               case 'H':
> >               case 'I':
> >-                __needed = _TimeOfDay;
> >+                __needed = _HoursMinutesSeconds;
> >                 __allowed_mods = _Mod_O;
> >                 break;
> >               case 'j':
> >-                if (!(__parts & _Duration))
> >-                  __needed = _Date;
> >+                __needed = !__spec._M_time_only ? _DayOfYear
> >+                                                : _HoursMinutesSeconds;
>
> This would be a tiny bit simpler without the negation, just:
>
>                   __needed = __spec._M_time_only ? _HoursMinutesSeconds
>                                                  : _DayOfYear;
>
> Is there a reason to prefer !__spec._M_time_only that I'm missing?
>
No, I think I went for having normal/default meaning as the first branch.
i.e. J is day of year, and hacked for durations. Changed it.

>
> >                 break;
> >               case 'm':
> >                 __needed = _Month;
> >                 __allowed_mods = _Mod_O;
> >                 break;
> >               case 'M':
> >-                __needed = _TimeOfDay;
> >+                __needed = _HoursMinutesSeconds;
> >                 __allowed_mods = _Mod_O;
> >                 break;
> >               case 'p':
> >@@ -494,12 +707,16 @@ namespace __format
> >                 __locale_specific = true;
> >                 [[fallthrough]];
> >               case 'R':
> >+                __needed = _HoursMinutesSeconds;
> >+                break;
> >               case 'T':
> >                 __needed = _TimeOfDay;
> >                 break;
> >               case 'q':
> >+                __needed = _UnitSuffix;
> >+                break;
> >               case 'Q':
> >-                __needed = _Duration;
> >+                __needed = _EpochUnits;
> >                 break;
> >               case 'S':
> >                 __needed = _TimeOfDay;
> >@@ -513,7 +730,7 @@ namespace __format
> >               case 'U':
> >               case 'V':
> >               case 'W':
> >-                __needed = _Date;
> >+                __needed = _LocalDays|_Year|_DayOfYear|_Weekday;
> >                 __allowed_mods = _Mod_O;
> >                 break;
> >               case 'x':
> >@@ -522,7 +739,7 @@ namespace __format
> >                 __allowed_mods = _Mod_E;
> >                 break;
> >               case 'X':
> >-                __needed = _TimeOfDay;
> >+                __needed = _HoursMinutesSeconds;
> >                 __locale_specific = true;
> >                 __allowed_mods = _Mod_E;
> >                 break;
> >@@ -535,11 +752,11 @@ namespace __format
> >                 __allowed_mods = _Mod_E;
> >                 break;
> >               case 'z':
> >-                __needed = _TimeZone;
> >+                __needed = _ZoneOffset;
> >                 __allowed_mods = _Mod_E_O;
> >                 break;
> >               case 'Z':
> >-                __needed = _TimeZone;
> >+                __needed = _ZoneAbbrev;
> >                 break;
> >               case 'n':
> >               case 't':
> >@@ -567,10 +784,16 @@ namespace __format
> >               __locale_specific = true;
> >             __mod = _CharT();
> >
> >+            // localized format do not include subseconds
>
> "formats"
>
> >+            if (__locale_specific)
> >+              __needed -= _ChronoParts::_Subseconds;
> >+
> >             if ((__parts & __needed) != __needed)
> >               __throw_format_error("chrono format error: format argument
> "
> >                                    "does not contain the information "
> >                                    "required by the chrono-specs");
> >+            __spec._M_needed |= __needed;
> >+            __spec._M_locale_specific |= __locale_specific;
> >
> >             // Scan for next '%', ignoring literal-chars before it.
> >             size_t __pos = __string_view(__first, __last -
> __first).find('%');
> >@@ -596,21 +819,14 @@ namespace __format
> >         _M_spec = __spec;
> >         _M_spec._M_chrono_specs
> >                = __string_view(__chrono_specs, __first - __chrono_specs);
> >-        _M_spec._M_locale_specific = __locale_specific;
> >
> >         return __first;
> >       }
> >
> >-      // TODO this function template is instantiated for every different
> _Tp.
> >-      // Consider creating a polymorphic interface for calendar types so
> >-      // that we instantiate fewer different specializations. Similar to
> >-      // _Sink_iter for std::format. Replace each _S_year, _S_day etc.
> with
> >-      // member functions of that type.
>
> Hooray, this comment can be removed :-)
> And there's no polymorphism, the new type just contains all the
> fields.
>
> >       // pre: !_M_spec._M_chrono_specs.empty()
> >-      template<typename _Tp, typename _FormatContext>
> >+      template<typename _FormatContext>
> >       typename _FormatContext::iterator
> >-      _M_format(const _Tp& __t, _FormatContext& __fc,
> >-                bool __is_neg = false) const
> >+      _M_format(const _ChronoData<_CharT>& __t, _FormatContext& __fc)
> const
> >       {
> > #if defined _GLIBCXX_USE_NL_LANGINFO_L && __CHAR_BIT__ == 8
> >         // _GLIBCXX_RESOLVE_LIB_DEFECTS
> >@@ -626,36 +842,80 @@ namespace __format
> >                 // in the locale's encoding to UTF-8.
> >                 locale __loc = __fc.locale();
> >                 if (__loc != locale::classic())
> >-                  __fc._M_loc =  __with_encoding_conversion(__loc);
> >+                  __fc._M_loc = __with_encoding_conversion(__loc);
> >               }
> > #endif
> >-        // formatter<duration> passes the correct value of __is_neg
> >-        // for durations but for hh_mm_ss we decide it here.
> >-        if constexpr (__is_specialization_of<_Tp, chrono::hh_mm_ss>)
> >-          __is_neg = __t.is_negative();
> >
> >         const size_t __padwidth = _M_spec._M_get_width(__fc);
> >         if (__padwidth == 0)
> >-          return _M_format_to(__t, __fc.out(), __fc, __is_neg);
> >+          return _M_format_to(__t, __fc.out(), __fc);
> >
> >         using _Out = typename _FormatContext::iterator;
> >         _Padding_sink<_Out, _CharT> __sink(__fc.out(), __padwidth);
> >-        _M_format_to(__t, __sink.out(), __fc, __is_neg);
> >+        _M_format_to(__t, __sink.out(), __fc);
> >         return __sink._M_finish(_M_spec._M_align, _M_spec._M_fill);
> >       }
> >
> >-      template<typename _Tp, typename _Out, typename _FormatContext>
> >+
> >+      _ChronoSpec<_CharT> _M_spec;
> >+
> >+    protected:
> >+      static constexpr const _CharT* _S_chars
> >+      = _GLIBCXX_WIDEN("0123456789.Lf:/ +-{}");
> >+      static constexpr _CharT _S_dot = _S_chars[10];
> >+      static constexpr _CharT _S_colon = _S_chars[13];
> >+      static constexpr _CharT _S_slash = _S_chars[14];
> >+      static constexpr _CharT _S_space = _S_chars[15];
> >+      static constexpr const _CharT* _S_fp_fmt = _S_chars + 11;
> >+      static constexpr const _CharT* _S_plus_minus = _S_chars + 16;
> >+      static constexpr const _CharT* _S_minus_empty_spec = _S_chars + 17;
> >+      static constexpr const _CharT* _S_empty_spec = _S_chars + 18;
> >+
> >+      // 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();
> >+      }
> >+
> >+    private:
> >+      template<typename _OutIter>
> >+      _OutIter
> >+      _M_write(_OutIter __out, const locale& __loc, __string_view __s)
> const
> >+      {
> >+#if defined _GLIBCXX_USE_NL_LANGINFO_L && __CHAR_BIT__ == 8
> >+        __sso_string __buf;
> >+        // _GLIBCXX_RESOLVE_LIB_DEFECTS
> >+        // 3565. Handling of encodings in localized formatting
> >+        //       of chrono types is underspecified
> >+        if constexpr (is_same_v<_CharT, char>)
> >+          if constexpr (__unicode::__literal_encoding_is_utf8())
> >+            if (_M_spec._M_localized && _M_spec._M_locale_specific
> >+                  && __loc != locale::classic())
> >+              {
> >+                extern string_view
> >+                __locale_encoding_to_utf8(const locale&, string_view,
> void*);
> >+
> >+                __s = __locale_encoding_to_utf8(__loc, __s, &__buf);
> >+              }
> >+#endif
> >+        return __format::__write(std::move(__out), __s);
> >+      }
> >+
> >+      template<typename _Out, typename _FormatContext>
> >       _Out
> >-      _M_format_to(const _Tp& __t, _Out __out, _FormatContext& __fc,
> >-                   bool __is_neg) const
> >+      _M_format_to(const _ChronoData<_CharT>& __t, _Out __out,
> >+                   _FormatContext& __fc) const
> >       {
> >         auto __first = _M_spec._M_chrono_specs.begin();
> >         const auto __last = _M_spec._M_chrono_specs.end();
> >
> >-        auto __print_sign = [&__is_neg, &__out] {
> >-          if constexpr (chrono::__is_duration_v<_Tp>
> >-                          || __is_specialization_of<_Tp,
> chrono::hh_mm_ss>)
> >-            if (__is_neg)
> >+        auto __print_sign = [__is_neg = __t._M_is_neg, &__out] ()
> mutable {
> >+          if (__is_neg)
> >               {
> >                 *__out++ = _S_plus_minus[1];
> >                 __is_neg = false;
> >@@ -676,16 +936,16 @@ namespace __format
> >               {
> >               // %\0 is extension for handling weekday index
> >               case '\0':
> >-                __out = _M_wi(__t, std::move(__out), __fc);
> >+                __out = _M_wi(__t._M_weekday_index, std::move(__out),
> __fc);
> >                 break;
> >               case 'a':
> >               case 'A':
> >-                __out = _M_a_A(__t, std::move(__out), __fc, __c == 'A');
> >+                __out = _M_a_A(__t._M_weekday, std::move(__out), __fc,
> __c == 'A');
> >                 break;
> >               case 'b':
> >               case 'h':
> >               case 'B':
> >-                __out = _M_b_B(__t, std::move(__out), __fc, __c == 'B');
> >+                __out = _M_b_B(__t._M_month, std::move(__out), __fc, __c
> == 'B');
> >                 break;
> >               case 'c':
> >               case 'r':
> >@@ -696,11 +956,11 @@ namespace __format
> >               case 'C':
> >               case 'y':
> >               case 'Y':
> >-                __out = _M_C_y_Y(__t, std::move(__out), __fc, __c,
> __mod);
> >+                __out = _M_C_y_Y(__t._M_year, std::move(__out), __fc,
> __c, __mod);
> >                 break;
> >               case 'd':
> >               case 'e':
> >-                __out = _M_d_e(__t, std::move(__out), __fc, __c, __mod
> == 'O');
> >+                __out = _M_d_e(__t._M_day, std::move(__out), __fc, __c,
> __mod == 'O');
> >                 break;
> >               case 'D':
> >                 __out = _M_D(__t, std::move(__out), __fc);
> >@@ -714,33 +974,25 @@ namespace __format
> >                 break;
> >               case 'H':
> >               case 'I':
> >-                __out = _M_H_I(__t, __print_sign(), __fc, __c, __mod ==
> 'O');
> >+                __out = _M_H_I(__t._M_hours, __print_sign(), __fc, __c,
> __mod == 'O');
> >                 break;
> >               case 'j':
> >                 __out = _M_j(__t, __print_sign(), __fc);
> >                 break;
> >               case 'm':
> >-                __out = _M_m(__t, std::move(__out), __fc, __mod == 'O');
> >+                __out = _M_m(__t._M_month, std::move(__out), __fc, __mod
> == 'O');
> >                 break;
> >               case 'M':
> >-                __out = _M_M(__t, __print_sign(), __fc, __mod == 'O');
> >+                __out = _M_M(__t._M_minutes, __print_sign(), __fc, __mod
> == 'O');
> >                 break;
> >               case 'p':
> >-                __out = _M_p(__t, std::move(__out), __fc);
> >+                __out = _M_p(__t._M_hours, std::move(__out), __fc);
> >                 break;
> >               case 'q':
> >-                __out = _M_q(__t, std::move(__out), __fc);
> >+                __out = _M_q(__t._M_unit_suffix, std::move(__out), __fc);
> >                 break;
> >               case 'Q':
> >-                // %Q The duration's numeric value.
> >-                if constexpr (chrono::__is_duration_v<_Tp>)
> >-                  // _GLIBCXX_RESOLVE_LIB_DEFECTS
> >-                  // 4118. How should duration formatters format custom
> rep?
> >-                  __out = std::format_to(__print_sign(), _S_empty_spec,
> >-                                         +__t.count());
> >-                else
> >-                  __throw_format_error("chrono format error: argument is
> "
> >-                                       "not a duration");
> >+                __out = _M_Q(__t, __print_sign(), __fc);
> >                 break;
> >               case 'R':
> >               case 'T':
> >@@ -751,7 +1003,7 @@ namespace __format
> >                 break;
> >               case 'u':
> >               case 'w':
> >-                __out = _M_u_w(__t, std::move(__out), __fc, __c, __mod
> == 'O');
> >+                __out = _M_u_w(__t._M_weekday, std::move(__out), __fc,
> __c, __mod == 'O');
> >                 break;
> >               case 'U':
> >               case 'V':
> >@@ -760,10 +1012,10 @@ namespace __format
> >                                  __mod == 'O');
> >                 break;
> >               case 'z':
> >-                __out = _M_z(__t, std::move(__out), __fc, (bool)__mod);
> >+                __out = _M_z(__t._M_zone_offset, std::move(__out), __fc,
> (bool)__mod);
> >                 break;
> >               case 'Z':
> >-                __out = _M_Z(__t, std::move(__out), __fc);
> >+                __out = _M_Z(__t._M_zone_abbrev, std::move(__out), __fc);
> >                 break;
> >               case 'n':
> >                 *__out++ = __literals[0];
> >@@ -804,78 +1056,12 @@ namespace __format
> >         return std::move(__out);
> >       }
> >
> >-      // Format duration for empty chrono-specs, e.g. "{}" (C++20
> [time.format] p6).
> >-      template<typename _Rep, typename _Period, typename _FormatContext>
> >-      typename _FormatContext::iterator
> >-      _M_format_to_ostream(const chrono::duration<_Rep, _Period>& __d,
> >-                           bool __is_neg, _FormatContext& __fc) const
> >-      {
> >-         basic_ostringstream<_CharT> __os;
> >-         __os.imbue(_M_locale(__fc));
> >-
> >-         if (__is_neg) [[unlikely]]
> >-           __os << _S_plus_minus[1];
> >-         __os << __d;
> >-
> >-        auto __str = std::move(__os).str();
> >-        return __format::__write_padded_as_spec(__str, __str.size(),
> >-                                                __fc, _M_spec);
> >-      }
> >-
> >-      _ChronoSpec<_CharT> _M_spec;
> >-
> >-    private:
> >-      // 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_chars
> >-      = _GLIBCXX_WIDEN("0123456789:/ +-{}");
> >-      static constexpr _CharT _S_colon = _S_chars[10];
> >-      static constexpr _CharT _S_slash = _S_chars[11];
> >-      static constexpr _CharT _S_space = _S_chars[12];
> >-      static constexpr const _CharT* _S_plus_minus = _S_chars + 13;
> >-      static constexpr const _CharT* _S_minus_empty_spec = _S_chars + 14;
> >-      static constexpr const _CharT* _S_empty_spec = _S_chars + 15;
> >-
> >-      template<typename _OutIter>
> >-      _OutIter
> >-      _M_write(_OutIter __out, const locale& __loc, __string_view __s)
> const
> >-      {
> >-#if defined _GLIBCXX_USE_NL_LANGINFO_L && __CHAR_BIT__ == 8
> >-        __sso_string __buf;
> >-        // _GLIBCXX_RESOLVE_LIB_DEFECTS
> >-        // 3565. Handling of encodings in localized formatting
> >-        //       of chrono types is underspecified
> >-        if constexpr (is_same_v<_CharT, char>)
> >-          if constexpr (__unicode::__literal_encoding_is_utf8())
> >-            if (_M_spec._M_localized && _M_spec._M_locale_specific
> >-                  && __loc != locale::classic())
> >-              {
> >-                extern string_view
> >-                __locale_encoding_to_utf8(const locale&, string_view,
> void*);
> >-
> >-                __s = __locale_encoding_to_utf8(__loc, __s, &__buf);
> >-              }
> >-#endif
> >-        return __format::__write(std::move(__out), __s);
> >-      }
> >-
> >-      template<typename _Tp, typename _FormatContext>
> >       typename _FormatContext::iterator
> >-      _M_wi(const _Tp& __t, typename _FormatContext::iterator __out,
> >-             _FormatContext& __ctx) const
> >+      _M_wi(unsigned __wi, typename _FormatContext::iterator __out,
> >+            _FormatContext& __ctx) const
> >       {
> >         // %\0 Extension to format weekday index, used only by empty
> format spec
> >-        unsigned __wi = _S_weekday_index(__t);
> >-
> >         _CharT __buf[3];
> >         __out = __format::__write(std::move(__out), _S_str_d1(__buf,
> __wi));
> >         if (_M_spec._M_debug && (__wi < 1 || __wi > 5))
> >@@ -884,14 +1070,13 @@ namespace __format
> >         return std::move(__out);
> >       }
> >
> >-      template<typename _Tp, typename _FormatContext>
> >+      template<typename _FormatContext>
> >       typename _FormatContext::iterator
> >-      _M_a_A(const _Tp& __t, typename _FormatContext::iterator __out,
> >+      _M_a_A(chrono::weekday __wd, typename _FormatContext::iterator
> __out,
> >              _FormatContext& __ctx, bool __full) const
> >       {
> >         // %a Locale's abbreviated weekday name.
> >         // %A Locale's full weekday name.
> >-        chrono::weekday __wd = _S_weekday(__t);
> >         if (!__wd.ok())
> >           {
> >             if (!_M_spec._M_debug)
> >@@ -915,14 +1100,13 @@ namespace __format
> >         return _M_write(std::move(__out), __loc, __str);
> >       }
> >
> >-      template<typename _Tp, typename _FormatContext>
> >+      template<typename _FormatContext>
> >       typename _FormatContext::iterator
> >-      _M_b_B(const _Tp& __t, typename _FormatContext::iterator __out,
> >+      _M_b_B(chrono::month __m, typename _FormatContext::iterator __out,
> >              _FormatContext& __ctx, bool __full) const
> >       {
> >         // %b Locale's abbreviated month name.
> >         // %B Locale's full month name.
> >-        chrono::month __m = _S_month(__t);
> >         if (!__m.ok())
> >           {
> >             if (!_M_spec._M_debug)
> >@@ -946,9 +1130,9 @@ namespace __format
> >         return _M_write(std::move(__out), __loc, __str);
> >       }
> >
> >-      template<typename _Tp, typename _FormatContext>
> >+      template<typename _FormatContext>
> >       typename _FormatContext::iterator
> >-      _M_c_r_x_X(const _Tp& __t, typename _FormatContext::iterator __out,
> >+      _M_c_r_x_X(const _ChronoData<_CharT>& __t, typename
> _FormatContext::iterator __out,
> >                  _FormatContext& __ctx, _CharT __conv, _CharT __mod)
> const
> >       {
> >         // %c  Locale's date and time representation.
> >@@ -960,9 +1144,6 @@ namespace __format
> >         // %EX Locale's alternative time representation.
> >
> >         using namespace chrono;
> >-        using ::std::chrono::__detail::__utc_leap_second;
> >-        using ::std::chrono::__detail::__local_time_fmt;
> >-
> >         struct tm __tm{};
> >
> >         // Some locales use %Z in their %c format but we don't want
> strftime
> >@@ -974,53 +1155,27 @@ namespace __format
> > #ifdef _GLIBCXX_USE_STRUCT_TM_TM_ZONE
> >         // POSIX.1-2024 adds tm.tm_zone which will be used for %Z.
> >         // BSD has had tm_zone since 1987 but as char* so cast away
> const.
> >-        if constexpr (__is_time_point_v<_Tp>)
> >-          {
> >-            // One of sys_time, utc_time, or local_time.
> >-            if constexpr (!is_same_v<typename _Tp::clock, local_t>)
> >-              __tm.tm_zone = const_cast<char*>("UTC");
> >-          }
> >-        else if constexpr (__is_specialization_of<_Tp, __local_time_fmt>)
> >-          {
> >-            // local-time-format-t is used to provide time zone info for
> >-            // one of zoned_time, tai_time, gps_time, or local_time.
> >-            if (__t._M_abbrev)
> >-              __tm.tm_zone = const_cast<char*>(__t._M_abbrev->c_str());
> >-          }
> >-        else
> >-          __tm.tm_zone = const_cast<char*>("UTC");
> >+        if (__t._M_zone_cstr)
> >+          __tm.tm_zone = const_cast<char*>(__t._M_zone_cstr);
> > #endif
> >
> >-        if (__conv == 'c' || __conv == 'x')
> >-        {
> >-          auto __d = _S_days(__t); // Either sys_days or local_days.
> >-          using _TDays = decltype(__d);
> >-          const year_month_day __ymd(__d);
> >-          const auto __y = __ymd.year();
> >-
> >-          __tm.tm_year = (int)__y - 1900;
> >-          __tm.tm_yday = (__d - _TDays(__y/January/1)).count();
> >-          __tm.tm_mon = (unsigned)__ymd.month() - 1;
> >-          __tm.tm_mday = (unsigned)__ymd.day();
> >-          __tm.tm_wday = weekday(__d).c_encoding();
> >-        }
> >-
> >-        if (__conv != 'x')
> >-        {
> >-          const auto __hms = _S_hms(__t);
> >-          __tm.tm_hour = __hms.hours().count();
> >-          __tm.tm_min = __hms.minutes().count();
> >-          __tm.tm_sec = __hms.seconds().count();
> >-        }
> >+        __tm.tm_year = (int)__t._M_year - 1900;
> >+        __tm.tm_yday = __t._M_day_of_year.count();
> >+        __tm.tm_mon = (unsigned)__t._M_month - 1;
> >+        __tm.tm_mday = (unsigned)__t._M_day;
> >+        __tm.tm_wday = __t._M_weekday.c_encoding();
> >+        __tm.tm_hour = __t._M_hours.count();
> >+        __tm.tm_min = __t._M_minutes.count();
> >+        __tm.tm_sec = __t._M_seconds.count();
> >
> >         return _M_locale_fmt(std::move(__out), _M_locale(__ctx), __tm,
> >                              __conv, __mod);
> >       }
> >
> >-      template<typename _Tp, typename _FormatContext>
> >+      template<typename _FormatContext>
> >       typename _FormatContext::iterator
> >-      _M_C_y_Y(const _Tp& __t, typename _FormatContext::iterator __out,
> >-             _FormatContext& __ctx, _CharT __conv, _CharT __mod = 0)
> const
> >+      _M_C_y_Y(chrono::year __y, typename _FormatContext::iterator __out,
> >+               _FormatContext& __ctx, _CharT __conv, _CharT __mod = 0)
> const
> >       {
> >         // %C  Year divided by 100 using floored division.
> >         // %EC Locale's alternative preresentation of the century (era
> name).
> >@@ -1030,8 +1185,6 @@ namespace __format
> >         // %Y  Year as a decimal number.
> >         // %EY Locale's alternative full year representation.
> >
> >-        chrono::year __y = _S_year(__t);
> >-
> >         if (__mod && _M_spec._M_localized) [[unlikely]]
> >           if (auto __loc = __ctx.locale(); __loc != locale::classic())
> >             {
> >@@ -1080,15 +1233,14 @@ namespace __format
> >         return __out;
> >       }
> >
> >-      template<typename _Tp, typename _FormatContext>
> >+      template<typename _FormatContext>
> >       typename _FormatContext::iterator
> >-      _M_D(const _Tp& __t, typename _FormatContext::iterator __out,
> >+      _M_D(const _ChronoData<_CharT>& __t, typename
> _FormatContext::iterator __out,
> >            _FormatContext&) const
> >       {
> >-        auto __ymd = _S_date(__t);
> >-        auto __di = (unsigned)__ymd.day();
> >-        auto __mi = (unsigned)__ymd.month();
> >-        auto __yi = __builtin_abs((int)__ymd.year()) % 100;
> >+        auto __di = (unsigned)__t._M_day;
> >+        auto __mi = (unsigned)__t._M_month;
> >+        auto __yi = __builtin_abs((int)__t._M_year) % 100;
> >
> >         if (__mi >= 100 || __di >= 100) [[unlikely]]
> >           {
> >@@ -1112,9 +1264,9 @@ namespace __format
> >         return std::move(__out);
> >       }
> >
> >-      template<typename _Tp, typename _FormatContext>
> >+      template<typename _FormatContext>
> >       typename _FormatContext::iterator
> >-      _M_d_e(const _Tp& __t, typename _FormatContext::iterator __out,
> >+      _M_d_e(chrono::day __d, typename _FormatContext::iterator __out,
> >              _FormatContext& __ctx, _CharT __conv, bool __mod = false)
> const
> >       {
> >         // %d  The day of month as a decimal number.
> >@@ -1122,7 +1274,6 @@ namespace __format
> >         // %e  Day of month as decimal number, padded with space.
> >         // %Oe Locale's alternative digits.
> >
> >-        chrono::day __d = _S_day(__t);
> >         unsigned __i = (unsigned)__d;
> >
> >         if (__mod && _M_spec._M_localized) [[unlikely]]
> >@@ -1150,15 +1301,14 @@ namespace __format
> >         return std::move(__out);
> >       }
> >
> >-      template<typename _Tp, typename _FormatContext>
> >+      template<typename _FormatContext>
> >       typename _FormatContext::iterator
> >-      _M_F(const _Tp& __t, typename _FormatContext::iterator __out,
> >+      _M_F(const _ChronoData<_CharT>& __t, typename
> _FormatContext::iterator __out,
> >            _FormatContext&) const
> >       {
> >-        auto __ymd = _S_date(__t);
> >-        auto __di = (unsigned)__ymd.day();
> >-        auto __mi = (unsigned)__ymd.month();
> >-        auto __yi = (int)__ymd.year();
> >+        auto __di = (unsigned)__t._M_day;
> >+        auto __mi = (unsigned)__t._M_month;
> >+        auto __yi = (int)__t._M_year;
> >         const bool __is_neg = __yi < 0;
> >         __yi = __builtin_abs(__yi);
> >
> >@@ -1185,31 +1335,31 @@ namespace __format
> >             __out = __format::__write(std::move(__out), __sv);
> >           }
> >
> >-        if (_M_spec._M_debug && !__ymd.ok())
> >+        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);
> >       }
> >
> >-      template<typename _Tp, typename _FormatContext>
> >+      template<typename _FormatContext>
> >       typename _FormatContext::iterator
> >-      _M_g_G(const _Tp& __t, typename _FormatContext::iterator __out,
> >+      _M_g_G(const _ChronoData<_CharT>& __t, typename
> _FormatContext::iterator __out,
> >            _FormatContext& __ctx, bool __full) const
> >       {
> >         // %g last two decimal digits of the ISO week-based year.
> >         // %G ISO week-based year.
> >         using namespace chrono;
> >-        auto __d = _S_days(__t);
> >+        auto __d = __t._M_ldays;
> >         // Move to nearest Thursday:
> >-        __d -= (weekday(__d) - Monday) - days(3);
> >+        __d -= (__t._M_weekday - Monday) - days(3);
> >         // ISO week-based year is the year that contains that Thursday:
> >         year __y = year_month_day(__d).year();
> >         return _M_C_y_Y(__y, std::move(__out), __ctx, "yY"[__full]);
> >       }
> >
> >-      template<typename _Tp, typename _FormatContext>
> >+      template<typename _FormatContext>
> >       typename _FormatContext::iterator
> >-      _M_H_I(const _Tp& __t, typename _FormatContext::iterator __out,
> >+      _M_H_I(chrono::hours __h, typename _FormatContext::iterator __out,
> >              _FormatContext& __ctx, _CharT __conv, bool __mod = false)
> const
> >       {
> >         // %H  The hour (24-hour clock) as a decimal number.
> >@@ -1217,8 +1367,7 @@ namespace __format
> >         // %I  The hour (12-hour clock) as a decimal number.
> >         // %OI Locale's alternative representation.
> >
> >-        const auto __hms = _S_hms(__t);
> >-        int __i = __hms.hours().count();
> >+        int __i = __h.count();
> >
> >         if (__mod && _M_spec._M_localized) [[unlikely]]
> >           if (auto __loc = __ctx.locale(); __loc != locale::classic())
> >@@ -1241,44 +1390,30 @@ namespace __format
> >         return __format::__write(std::move(__out), _S_two_digits(__i));
> >       }
> >
> >-      template<typename _Tp, typename _FormatContext>
> >+      template<typename _FormatContext>
> >       typename _FormatContext::iterator
> >-      _M_j(const _Tp& __t, typename _FormatContext::iterator __out,
> >+      _M_j(const _ChronoData<_CharT>& __t, typename
> _FormatContext::iterator __out,
> >            _FormatContext&) const
> >       {
> >-        if constexpr (chrono::__is_duration_v<_Tp>)
> >-          {
> >-            // Decimal number of days, without padding.
> >-            unsigned __d =
> chrono::duration_cast<chrono::days>(__t).count();
> >-            return std::format_to(std::move(__out), _S_empty_spec, __d);
> >-          }
> >-        else
> >-          {
> >-            // Day of the year as a decimal number, padding with zero.
> >-            using namespace chrono;
> >-            auto __day = _S_days(__t);
> >-            auto __ymd = _S_date(__t);
> >-            days __d;
> >-            // See "Calculating Ordinal Dates" at
> >-            //
> https://github.com/HowardHinnant/date/wiki/Examples-and-Recipes
> >-            if constexpr (is_same_v<typename decltype(__day)::clock,
> local_t>)
> >-              __d = __day - local_days(__ymd.year()/January/0);
> >-            else
> >-              __d = __day - sys_days(__ymd.year()/January/0);
> >-            return std::format_to(std::move(__out),
> _GLIBCXX_WIDEN("{:03d}"),
> >-                                  __d.count());
> >-          }
> >+        if (_M_spec._M_time_only)
> >+        {
> >+          // Decimal number of days, without padding.
> >+          auto __d = chrono::floor<chrono::days>(__t._M_hours).count();
> >+          return std::format_to(std::move(__out), _S_empty_spec, __d);
> >+        }
> >+
> >+        return std::format_to(std::move(__out), _GLIBCXX_WIDEN("{:03d}"),
> >+                              __t._M_day_of_year.count());
> >       }
> >
> >-      template<typename _Tp, typename _FormatContext>
> >+      template<typename _FormatContext>
> >       typename _FormatContext::iterator
> >-      _M_m(const _Tp& __t, typename _FormatContext::iterator __out,
> >+      _M_m(chrono::month __m, typename _FormatContext::iterator __out,
> >            _FormatContext& __ctx, bool __mod) const
> >       {
> >         // %m  month as a decimal number.
> >         // %Om Locale's alternative representation.
> >
> >-        auto __m = _S_month(__t);
> >         auto __i = (unsigned)__m;
> >
> >         if (__mod && _M_spec._M_localized) [[unlikely]] // %Om
> >@@ -1294,15 +1429,14 @@ namespace __format
> >         return __format::__write(std::move(__out), _S_str_d2(__buf,
> __i));
> >       }
> >
> >-      template<typename _Tp, typename _FormatContext>
> >+      template<typename _FormatContext>
> >       typename _FormatContext::iterator
> >-      _M_M(const _Tp& __t, typename _FormatContext::iterator __out,
> >+      _M_M(chrono::minutes __m, typename _FormatContext::iterator __out,
> >            _FormatContext& __ctx, bool __mod) const
> >       {
> >         // %M  The minute as a decimal number.
> >         // %OM Locale's alternative representation.
> >
> >-        auto __m = _S_hms(__t).minutes();
> >         auto __i = __m.count();
> >
> >         if (__mod && _M_spec._M_localized) [[unlikely]] // %OM
> >@@ -1317,14 +1451,13 @@ namespace __format
> >         return __format::__write(std::move(__out), _S_two_digits(__i));
> >       }
> >
> >-      template<typename _Tp, typename _FormatContext>
> >+      template<typename _FormatContext>
> >       typename _FormatContext::iterator
> >-      _M_p(const _Tp& __t, typename _FormatContext::iterator __out,
> >+      _M_p(chrono::hours __h, typename _FormatContext::iterator __out,
> >            _FormatContext& __ctx) const
> >       {
> >         // %p The locale's equivalent of the AM/PM designations.
> >-        auto __hms = _S_hms(__t);
> >-        auto __hi = __hms.hours().count();
> >+        auto __hi =  __h.count();
> >         if (__hi >= 24) [[unlikely]]
> >           __hi %= 24;
> >
> >@@ -1335,35 +1468,34 @@ namespace __format
> >         return _M_write(std::move(__out), __loc, __ampm[__hi >= 12]);
> >       }
> >
> >-      template<typename _Tp, typename _FormatContext>
> >+      template<typename _FormatContext>
> >       typename _FormatContext::iterator
> >-      _M_q(const _Tp&, typename _FormatContext::iterator __out,
> >+      _M_q(__string_view __us, typename _FormatContext::iterator __out,
> >            _FormatContext&) const
> >       {
> >         // %q The duration's unit suffix
> >-        if constexpr (!chrono::__is_duration_v<_Tp>)
> >-          __throw_format_error("format error: argument is not a
> duration");
> >-        else
> >-          {
> >-            namespace __d = chrono::__detail;
> >-            using period = typename _Tp::period;
> >-            return __d::__fmt_units_suffix<period,
> _CharT>(std::move(__out));
> >-          }
> >+        return __format::__write(std::move(__out), __us);
> >       }
> >
> >-      // %Q handled in _M_format
> >+      template<typename _FormatContext>
> >+      typename _FormatContext::iterator
> >+      _M_Q(const _ChronoData<_CharT>& __t, typename
> _FormatContext::iterator __out,
> >+           _FormatContext& __ctx) const
> >+      {
> >+        // %Q The duration's numeric value.
> >+        return std::vformat_to(std::move(__out), _S_empty_spec,
> __t._M_ereps);
> >+      }
> >
> >-      template<typename _Tp, typename _FormatContext>
> >+      template<typename _FormatContext>
> >       typename _FormatContext::iterator
> >-      _M_R_T(const _Tp& __t, typename _FormatContext::iterator __out,
> >+      _M_R_T(const _ChronoData<_CharT>& __t, typename
> _FormatContext::iterator __out,
> >              _FormatContext& __ctx, bool __secs) const
> >       {
> >         // %R Equivalent to %H:%M
> >         // %T Equivalent to %H:%M:%S
> >-        auto __hms = _S_hms(__t);
> >-        auto __hi = __hms.hours().count();
> >+        auto __hi = __t._M_hours.count();
> >
> >-        _CharT __buf[6];
> >+        _CharT __buf[6];
> >         __buf[2] = _S_colon;
> >         __buf[5] = _S_colon;
> >         __string_view __sv(__buf, 5 + __secs);
> >@@ -1376,86 +1508,96 @@ namespace __format
> >         else
> >           _S_fill_two_digits(__buf, __hi);
> >
> >-        _S_fill_two_digits(__buf + 3, __hms.minutes().count());
> >+        _S_fill_two_digits(__buf + 3, __t._M_minutes.count());
> >         __out = __format::__write(std::move(__out), __sv);
> >         if (__secs)
> >-          __out = _M_S(__hms, std::move(__out), __ctx);
> >+          __out = _M_S(__t, std::move(__out), __ctx);
> >         return __out;
> >       }
> >
> >-      template<typename _Tp, typename _FormatContext>
> >+      template<typename _FormatContext>
> >       typename _FormatContext::iterator
> >-      _M_S(const _Tp& __t, typename _FormatContext::iterator __out,
> >+      _M_S(const _ChronoData<_CharT>& __t, typename
> _FormatContext::iterator __out,
> >            _FormatContext& __ctx, bool __mod = false) const
> >       {
> >         // %S  Seconds as a decimal number.
> >         // %OS The locale's alternative representation.
> >-        auto __hms = _S_hms(__t);
> >-        auto __s = __hms.seconds();
> >+        auto __s = __t._M_seconds;
> >
> >-        if (__mod) [[unlikely]] // %OS
> >-          {
> >-            if (_M_spec._M_localized)
> >-              if (auto __loc = __ctx.locale(); __loc !=
> locale::classic())
> >-                {
> >-                  struct tm __tm{};
> >-                  __tm.tm_sec = (int)__s.count();
> >-                  return _M_locale_fmt(std::move(__out), __loc, __tm,
> >-                                       'S', 'O');
> >-                }
> >+        if (__mod && _M_spec._M_localized) [[unlikely]] // %OS
> >+          if (auto __loc = __ctx.locale(); __loc != locale::classic())
> >+            {
> >+              struct tm __tm{};
> >+              __tm.tm_sec = (int)__s.count();
> >+              return _M_locale_fmt(std::move(__out), __loc, __tm,
> >+                                   'S', 'O');
> >+            }
> >
> >-            // %OS formats don't include subseconds, so just format that:
> >-            return __format::__write(std::move(__out),
> >-                                     _S_two_digits(__s.count()));
> >+        __out = __format::__write(std::move(__out),
> >+                                   _S_two_digits(__s.count()));
> >+
> >+        unsigned __prec = _M_spec._M_prec_kind != _WP_none
> >+                        ? _M_spec._M_get_precision(__ctx)
> >+                        : _M_spec._M_prec;
> >+
> >+        // %OS formats don't include subseconds
> >+        if (__prec == 0 || __mod)
> >+          return __out;
> >+
> >+        _CharT __dot = _S_dot;
> >+        if (_M_spec._M_localized)
> >+          if (auto __loc = __ctx.locale(); __loc != locale::classic())
> >+          {
> >+            const auto& __np = use_facet<numpunct<_CharT>>(__loc);
> >+            __dot = __np.decimal_point();
> >           }
> >+        *__out = __dot;
> >+        ++__out;
> >
> >-        if constexpr (__hms.fractional_width == 0)
> >-          __out = __format::__write(std::move(__out),
> >-                                    _S_two_digits(__s.count()));
> >-        else
> >+        if (_M_spec._M_floating_point_rep)
> >           {
> >-            locale __loc = _M_locale(__ctx);
> >-            auto __ss = __hms.subseconds();
> >-            using rep = typename decltype(__ss)::rep;
> >-            if constexpr (is_floating_point_v<rep>)
> >-              {
> >-                chrono::duration<rep> __fs = __s + __ss;
> >-                __out = std::format_to(std::move(__out), __loc,
> >-                                       _GLIBCXX_WIDEN("{:#0{}.{}Lf}"),
> >-                                       __fs.count(),
> >-                                       3 + __hms.fractional_width,
> >-                                       __hms.fractional_width);
> >-              }
> >+            _Str_sink<_CharT> __sink;
> >+            if (_M_spec._M_localized && _M_spec._M_custom_rep)
> >+               std::vformat_to(__sink.out(), _M_locale(__ctx),
> >+                               _GLIBCXX_WIDEN("{1:0.{2}Lf}"),
> __t._M_ereps);
> >             else
> >-              {
> >-                const auto& __np
> >-                  = use_facet<numpunct<_CharT>>(__loc);
> >-                __out = __format::__write(std::move(__out),
> >-                                          _S_two_digits(__s.count()));
> >-                *__out++ = __np.decimal_point();
> >-                if constexpr (is_integral_v<rep>)
> >-                  __out = std::format_to(std::move(__out),
> >-                                         _GLIBCXX_WIDEN("{:0{}}"),
> >-                                         __ss.count(),
> >-                                         __hms.fractional_width);
> >-                else
> >-                  {
> >-                    auto __str = std::format(_S_empty_spec,
> __ss.count());
> >-                    __out = std::format_to(std::move(__out),
> >-                                           _GLIBCXX_WIDEN("{:0>{}s}"),
> >-                                           __str,
> >-                                           __hms.fractional_width);
> >-                  }
> >-              }
> >+               std::vformat_to(__sink.out(),
> >+                               _GLIBCXX_WIDEN("{1:0.{2}f}"),
> __t._M_ereps);
> >+
> >+            auto __sv = __sink.view();
> >+            // Skip leading zero and dot
> >+            __sv.remove_prefix(2);
> >+            return __format::__write(std::move(__out), __sv);
> >           }
> >-        return __out;
> >+
> >+        constexpr unsigned __max_prec = _ChronoData<_CharT>::_S_max_prec;
> >+        constexpr typename _ChronoData<_CharT>::_Attoseconds::rep
> __pow10t[]
> >+        {
> >+          1u,
> >+          10u,                     100u,                     1000u,
> >+          10'000u,                 100'000u,                 1000'000u,
> >+          10'000'000u,             100'000'000u,
>  1000'000'000u,
> >+          10'000'000'000u,         100'000'000'000u,
>  1000'000'000'000u,
> >+          10'000'000'000'000u,     100'000'000'000'000u,
>  1000'000'000'000'000u,
> >+          10'000'000'000'000'000u, 100'000'000'000'000'000u,
> 1000'000'000'000'000'000u,
> >+        };
> >+
> >+        auto __subs = __t._M_subseconds.count();
> >+        if (__prec < __max_prec)
> >+          __subs /= __pow10t[__max_prec - __prec];
> >+        else if (__prec > __max_prec)
> >+          __prec = __max_prec;
> >+
> >+        using _FmtStr = _Runtime_format_string<_CharT>;
> >+        return std::format_to(__out, _FmtStr(_GLIBCXX_WIDEN("{0:0{1}}")),
> >+                              __subs, __prec);
> >       }
> >
> >       // %t handled in _M_format
> >
> >-      template<typename _Tp, typename _FormatContext>
> >+      template<typename _FormatContext>
> >       typename _FormatContext::iterator
> >-      _M_u_w(const _Tp& __t, typename _FormatContext::iterator __out,
> >+      _M_u_w(chrono::weekday __wd, typename _FormatContext::iterator
> __out,
> >              _FormatContext& __ctx, _CharT __conv, bool __mod = false)
> const
> >       {
> >         // %u  ISO weekday as a decimal number (1-7), where Monday is 1.
> >@@ -1463,8 +1605,6 @@ namespace __format
> >         // %w  Weekday as a decimal number (0-6), where Sunday is 0.
> >         // %Ow Locale's alternative numeric rep.
> >
> >-        chrono::weekday __wd = _S_weekday(__t);
> >-
> >         if (__mod && _M_spec._M_localized) [[unlikely]]
> >           if (auto __loc = __ctx.locale(); __loc != locale::classic())
> >             {
> >@@ -1480,9 +1620,9 @@ namespace __format
> >         return __format::__write(std::move(__out), _S_str_d1(__buf,
> __wdi));
> >       }
> >
> >-      template<typename _Tp, typename _FormatContext>
> >+      template<typename _FormatContext>
> >       typename _FormatContext::iterator
> >-      _M_U_V_W(const _Tp& __t, typename _FormatContext::iterator __out,
> >+      _M_U_V_W(const _ChronoData<_CharT>& __t, typename
> _FormatContext::iterator __out,
> >                _FormatContext& __ctx, _CharT __conv, bool __mod = false)
> const
> >       {
> >         // %U  Week number of the year as a decimal number, from first
> Sunday.
> >@@ -1492,126 +1632,68 @@ namespace __format
> >         // %W  Week number of the year as a decimal number, from first
> Monday.
> >         // %OW Locale's alternative numeric rep.
> >         using namespace chrono;
> >-        auto __d = _S_days(__t);
> >-        using _TDays = decltype(__d); // Either sys_days or local_days.
> >
> >         if (__mod && _M_spec._M_localized) [[unlikely]]
> >           if (auto __loc = __ctx.locale(); __loc != locale::classic())
> >             {
> >-              const year_month_day __ymd(__d);
> >-              const year __y = __ymd.year();
> >               struct tm __tm{};
> >-              __tm.tm_year = (int)__y - 1900;
> >-              __tm.tm_yday = (__d - _TDays(__y/January/1)).count();
> >-              __tm.tm_wday = weekday(__d).c_encoding();
> >+              __tm.tm_year = (int)__t._M_year - 1900;
> >+              __tm.tm_yday = __t._M_day_of_year.count();
> >+              __tm.tm_wday = __t._M_weekday.c_encoding();
> >               return _M_locale_fmt(std::move(__out), __loc, __tm,
> >                                    (char)__conv, 'O');
> >             }
> >
> >-        _TDays __first; // First day of week 1.
> >+        auto __d = __t._M_ldays;
> >+        local_days __first; // First day of week 1.
> >         if (__conv == 'V') // W01 begins on Monday before first Thursday.
> >           {
> >             // Move to nearest Thursday:
> >-            __d -= (weekday(__d) - Monday) - days(3);
> >+            __d -= (__t._M_weekday - Monday) - days(3);
> >             // ISO week of __t is number of weeks since January 1 of the
> >             // same year as that nearest Thursday.
> >-            __first = _TDays(year_month_day(__d).year()/January/1);
> >+            __first = local_days(year_month_day(__d).year()/January/1);
> >           }
> >         else
> >           {
> >-            year __y;
> >-            if constexpr (requires { __t.year(); })
> >-              __y = __t.year();
> >-            else
> >-              __y = year_month_day(__d).year();
> >             const weekday __weekstart = __conv == 'U' ? Sunday : Monday;
> >-            __first = _TDays(__y/January/__weekstart[1]);
> >+            __first = local_days(__t._M_year/January/__weekstart[1]);
> >           }
> >         auto __weeks = chrono::floor<weeks>(__d - __first);
> >         __string_view __sv = _S_two_digits(__weeks.count() + 1);
> >         return __format::__write(std::move(__out), __sv);
> >       }
> >
> >-      template<typename _Tp, typename _FormatContext>
> >+      template<typename _FormatContext>
> >       typename _FormatContext::iterator
> >-      _M_z(const _Tp& __t, typename _FormatContext::iterator __out,
> >+      _M_z(chrono::seconds __ts, typename _FormatContext::iterator __out,
> >            _FormatContext&, bool __mod = false) const
> >       {
> >-        using ::std::chrono::__detail::__utc_leap_second;
> >-        using ::std::chrono::__detail::__local_time_fmt;
> >-
> >-        auto __utc = __mod ? __string_view(_GLIBCXX_WIDEN("+00:00"), 6)
> >-                           : __string_view(_GLIBCXX_WIDEN("+0000"), 5);
> >-
> >-        if constexpr (chrono::__is_time_point_v<_Tp>)
> >+        if (__ts == 0s)
> >           {
> >-            if constexpr (is_same_v<typename _Tp::clock,
> >-                                    chrono::system_clock>)
> >-              return __format::__write(std::move(__out), __utc);
> >-          }
> >-        else if constexpr (__is_specialization_of<_Tp, __local_time_fmt>)
> >-          {
> >-            if (__t._M_offset_sec)
> >-              {
> >-                auto __sv = __utc;
> >-                basic_string<_CharT> __s;
> >-                if (*__t._M_offset_sec != 0s)
> >-                  {
> >-                    chrono:: hh_mm_ss __hms(*__t._M_offset_sec);
> >-                    __s = _S_plus_minus[__hms.is_negative()];
> >-                    __s += _S_two_digits(__hms.hours().count());
> >-                    if (__mod)
> >-                      __s += _S_colon;
> >-                    __s += _S_two_digits(__hms.minutes().count());
> >-                    __sv = __s;
> >-                  }
> >-                return __format::__write(std::move(__out), __sv);
> >-              }
> >+            __string_view __zero
> >+              = __mod ? _GLIBCXX_WIDEN("+00:00") :
> _GLIBCXX_WIDEN("+0000");
> >+            return __format::__write(std::move(__out), __zero);
> >           }
> >-        else if constexpr (__is_specialization_of<_Tp,
> __utc_leap_second>)
> >-          return __format::__write(std::move(__out), __utc);
> >
> >-        __no_timezone_available();
> >+        chrono::hh_mm_ss<chrono::seconds> __hms(__ts);
> >+        unsigned __mo = 3 + __mod;
> >+
> >+        _CharT __buf[6];
> >+        __buf[0] = _S_plus_minus[__hms.is_negative()];
> >+        __buf[3] = _S_colon;
> >+        _S_fill_two_digits(__buf + 1, __hms.hours().count());
> >+        _S_fill_two_digits(__buf + __mo, __hms.minutes().count());
> >+
> >+        __string_view __sv(__buf, __mo + 2);
> >+        return __format::__write(std::move(__out), __sv);
> >       }
> >
> >-      template<typename _Tp, typename _FormatContext>
> >+      template<typename _FormatContext>
> >       typename _FormatContext::iterator
> >-      _M_Z(const _Tp& __t, typename _FormatContext::iterator __out,
> >+      _M_Z(__string_view __abbrev, typename _FormatContext::iterator
> __out,
> >            _FormatContext& __ctx) const
> >-      {
> >-        using ::std::chrono::__detail::__utc_leap_second;
> >-        using ::std::chrono::__detail::__local_time_fmt;
> >-
> >-        __string_view __utc(_GLIBCXX_WIDEN("UTC"), 3);
> >-        if constexpr (chrono::__is_time_point_v<_Tp>)
> >-          {
> >-            if constexpr (is_same_v<typename _Tp::clock,
> >-                                    chrono::system_clock>)
> >-              return __format::__write(std::move(__out), __utc);
> >-          }
> >-        else if constexpr (__is_specialization_of<_Tp, __local_time_fmt>)
> >-          {
> >-            if (__t._M_abbrev)
> >-              {
> >-                string_view __sv = *__t._M_abbrev;
> >-                if constexpr (is_same_v<_CharT, char>)
> >-                  return __format::__write(std::move(__out), __sv);
> >-                else
> >-                  {
> >-                    // TODO use resize_and_overwrite
> >-                    basic_string<_CharT> __ws(__sv.size(), _CharT());
> >-                    auto& __ct =
> use_facet<ctype<_CharT>>(_M_locale(__ctx));
> >-                    __ct.widen(__sv.begin(), __sv.end(), __ws.data());
> >-                    __string_view __wsv = __ws;
> >-                    return __format::__write(std::move(__out), __wsv);
> >-                  }
> >-              }
> >-          }
> >-        else if constexpr (__is_specialization_of<_Tp,
> __utc_leap_second>)
> >-          return __format::__write(std::move(__out), __utc);
> >-
> >-        __no_timezone_available();
> >-      }
> >+      { return __format::__write(std::move(__out), __abbrev); }
> >
> >       // %% handled in _M_format
> >
> >@@ -1670,194 +1752,202 @@ namespace __format
> >         return _S_two_digits(__n);
> >
> >       _S_fill_two_digits(__buf.data(), __n / 10);
> >-      __buf[2] = _S_digit(__n % 10)[0];
> >+      __buf[2] = _S_chars[__n % 10];
> >       return __string_view(__buf.data(), 3);
> >       }
> >
> >-      // Accessors for the components of chrono types:
> >+      // Use the formatting locale's std::time_put facet to produce
> >+      // a locale-specific representation.
> >+      template<typename _Iter>
> >+      _Iter
> >+      _M_locale_fmt(_Iter __out, const locale& __loc, const struct tm&
> __tm,
> >+                    char __fmt, char __mod) const
> >+      {
> >+        basic_ostringstream<_CharT> __os;
> >+        __os.imbue(__loc);
> >+        const auto& __tp = use_facet<time_put<_CharT>>(__loc);
> >+        __tp.put(__os, __os, _S_space, &__tm, __fmt, __mod);
> >+        if (__os)
> >+          __out = _M_write(std::move(__out), __loc, __os.view());
> >+        return __out;
> >+      }
> >+    };
> >
> >-      // Returns a hh_mm_ss.
> >-      template<typename _Tp>
> >-      static decltype(auto)
> >-      _S_hms(const _Tp& __t)
> >+  template<typename _CharT>
> >+    struct __formatter_duration : private __formatter_chrono<_CharT>
> >+    {
> >+      template<typename _Rep, typename _Period>
> >+      constexpr static auto
> >+      _S_subseconds(const chrono::duration<_Rep, _Period>& __d)
> >       {
> >-        using ::std::chrono::__detail::__utc_leap_second;
> >-        using ::std::chrono::__detail::__local_time_fmt;
> >-
> >-        if constexpr (__is_specialization_of<_Tp, chrono::hh_mm_ss>)
> >-          return __t;
> >-        else if constexpr (__is_specialization_of<_Tp,
> __utc_leap_second>)
> >-          return __t._M_time;
> >-        else if constexpr (chrono::__is_duration_v<_Tp>)
> >-          return chrono::hh_mm_ss<_Tp>(__t);
> >-        else if constexpr (chrono::__is_time_point_v<_Tp>)
> >-          return chrono::hh_mm_ss(__t -
> chrono::floor<chrono::days>(__t));
> >-        else if constexpr (__is_specialization_of<_Tp, __local_time_fmt>)
> >-          return _S_hms(__t._M_time);
> >+        if constexpr (chrono::treat_as_floating_point_v<_Rep>)
> >+          return chrono::duration<_Rep>(__d);
> >+        else if constexpr (_Period::den == 1)
> >+          return chrono::seconds(0);
> >         else
> >-          {
> >-            __invalid_chrono_spec();
> >-            return chrono::hh_mm_ss<chrono::seconds>();
> >-          }
> >-      }
> >+         {
> >+           using _Attoseconds = _ChronoData<_CharT>::_Attoseconds;
> >+           using _CRep = common_type_t<_Rep, typename _Attoseconds::rep>;
> >+           chrono::duration<_CRep, _Period> subs(__d.count());
> >+           return chrono::duration_cast<_Attoseconds>(subs);
> >+         }
> >+       }
> >
> >-      // Returns a sys_days or local_days.
> >-      template<typename _Tp>
> >-      static auto
> >-      _S_days(const _Tp& __t)
> >+    public:
> >+      template<typename _Duration>
> >+      static consteval
> >+      _ChronoSpec<_CharT>
> >+      _S_spec_for(_ChronoParts __parts)
> >       {
> >-        using namespace chrono;
> >-        using ::std::chrono::__detail::__utc_leap_second;
> >-        using ::std::chrono::__detail::__local_time_fmt;
> >-
> >-        if constexpr (__is_time_point_v<_Tp>)
> >-          return chrono::floor<days>(__t);
> >-        else if constexpr (__is_specialization_of<_Tp,
> __utc_leap_second>)
> >-          return __t._M_date;
> >-        else if constexpr (__is_specialization_of<_Tp, __local_time_fmt>)
> >-          return chrono::floor<days>(__t._M_time);
> >-        else if constexpr (is_same_v<_Tp, year_month_day>
> >-                             || is_same_v<_Tp, year_month_day_last>
> >-                             || is_same_v<_Tp, year_month_weekday>
> >-                             || is_same_v<_Tp, year_month_weekday_last>)
> >-          return sys_days(__t);
> >-        else
> >+        using _Rep = typename _Duration::rep;
> >+        using enum _ChronoParts;
> >+
> >+        _ChronoSpec<_CharT> __res{};
> >+        __res._M_time_only = (__parts & _Date) == 0;
> >+        __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;
> >+        if ((__parts & _TimeOfDay) != 0)
> >+           __res._M_localized = __res._M_prec > 0 ||
> __res._M_floating_point_rep;
> >+
> >+        if ((__parts & _TimeOfDay) != 0)
> >+          __res._M_needed |= _TimeOfDay;
> >+        if ((__parts & _Date) != 0)
> >+          __res._M_needed |= _YearMonthDay;
> >+        if ((__parts & _ZoneAbbrev) != 0)
> >+          __res._M_needed |= _ZoneAbbrev;
> >+
> >+        switch (__parts)
> >           {
> >-            if constexpr (__is_duration_v<_Tp>)
> >-              __not_valid_for_duration();
> >-            else
> >-              __invalid_chrono_spec();
> >-            return chrono::sys_days();
> >+          case _ZonedDateTime:
> >+            __res._M_chrono_specs = _ChronoFormats<_CharT>::_S_ftz();
> >+            break;
> >+          case _DateTime:
> >+            __res._M_chrono_specs = _ChronoFormats<_CharT>::_S_ft();
> >+            break;
> >+          case _Date:
> >+            __res._M_chrono_specs = _ChronoFormats<_CharT>::_S_f();
> >+            break;
> >+          case _Time:
> >+            __res._M_chrono_specs = _ChronoFormats<_CharT>::_S_t();
> >+            break;
> >+          case _None:
> >+            break;
> >           }
> >-      }
> >+        return __res;
> >+      };
> >
> >-      // Returns a year_month_day.
> >-      template<typename _Tp>
> >-      static chrono::year_month_day
> >-      _S_date(const _Tp& __t)
> >-      {
> >-        if constexpr (is_same_v<_Tp, chrono::year_month_day>)
> >-          return __t;
> >-        else
> >-          return chrono::year_month_day(_S_days(__t));
> >-      }
> >+      using __formatter_chrono<_CharT>::__formatter_chrono;
> >+      using __formatter_chrono<_CharT>::_M_locale;
> >+      using __formatter_chrono<_CharT>::_M_spec;
> >
> >-      template<typename _Tp>
> >-      static chrono::day
> >-      _S_day(const _Tp& __t)
> >+      template<typename _Duration, typename _ParseContext>
> >+      constexpr typename _ParseContext::iterator
> >+      _M_parse(_ParseContext& __pc, _ChronoParts __parts,
> >+               const _ChronoSpec<_CharT>& __def = {})
> >       {
> >-        using namespace chrono;
> >-
> >-        if constexpr (is_same_v<_Tp, day>)
> >-          return __t;
> >-        else if constexpr (requires { __t.day(); })
> >-          return __t.day();
> >-        else
> >-          return _S_date(__t).day();
> >+        using _Rep = typename _Duration::rep;
> >+        using enum _ChronoParts;
> >+
> >+        auto __res
> >+          = __formatter_chrono<_CharT>::_M_parse(__pc, __parts, __def);
> >+        // check for custom floating point durations, if digits of output
> >+        // will contain subseconds, then formatters must support
> specifying
> >+        // precision.
> >+        if constexpr (!is_floating_point_v<_Rep>)
> >+          if constexpr (chrono::treat_as_floating_point_v<_Rep>)
> >+            if (_M_spec._M_needs(_Subseconds|_EpochUnits)
> >+                  || _M_spec._M_prec_kind != _WP_none
> >+                  || _M_spec._M_prec_value > 0)
> >+              {
> >+                constexpr const _CharT* __fs = _GLIBCXX_WIDEN("#02.5Lf");
> >+                basic_format_parse_context<_CharT> __npc(__fs);
> >+                formatter<_Rep, _CharT> __fmtter;
> >+                __fmtter.parse(__npc);
> >+              }
> >+        return __res;
> >       }
> >
> >-      template<typename _Tp>
> >-      static chrono::month
> >-      _S_month(const _Tp& __t)
> >+      // Format duration for empty chrono-specs, e.g. "{}" (C++20
> [time.format] p6).
> >+      template<typename _Rep, typename _Period, typename _FormatContext>
> >+      typename _FormatContext::iterator
> >+      _M_format_to_ostream(const chrono::duration<_Rep, _Period>& __d,
> >+                           bool __is_neg,
> >+                           _FormatContext& __fc) const
> >       {
> >-        using namespace chrono;
> >+         basic_ostringstream<_CharT> __os;
> >+         __os.imbue(this->_M_locale(__fc));
> >
> >-        if constexpr (is_same_v<_Tp, month>)
> >-          return __t;
> >-        else if constexpr (requires { __t.month(); })
> >-          return __t.month();
> >-        else
> >-          return _S_date(__t).month();
> >+         if (__is_neg) [[unlikely]]
> >+           __os << this->_S_plus_minus[1];
> >+         __os << __d;
> >+
> >+        auto __str = std::move(__os).str();
> >+        return __format::__write_padded_as_spec(__str, __str.size(),
> >+                                                __fc, _M_spec);
> >       }
> >
> >-      template<typename _Tp>
> >-      static chrono::year
> >-      _S_year(const _Tp& __t)
> >+      template<typename _Rep1, typename _Period1,
> >+             typename _Rep2, typename _Period2,
> >+             typename _FormatContext>
> >+      typename _FormatContext::iterator
> >+      _M_format_units(_ChronoData<_CharT>& __cd,
> >+                      const chrono::duration<_Rep1, _Period1>& __ed,
> >+                      const chrono::duration<_Rep2, _Period2>& __ss,
> >+                      _FormatContext& __fc) const
> >       {
> >-        using namespace chrono;
> >+        __format::_Str_sink<_CharT> __suffix_store;
> >+        constexpr auto _S_unit_suffix
> >+          = chrono::__detail::__units_suffix<_Period1, _CharT>();
> >+        if constexpr (!_S_unit_suffix.empty())
> >+          __cd._M_unit_suffix = _S_unit_suffix;
> >+        else if (_M_spec._M_needs(_ChronoParts::_UnitSuffix))
> >+          {
> >+            chrono::__detail::
> >+              __fmt_units_suffix<_Period1, _CharT>(__suffix_store.out());
> >+            __cd._M_unit_suffix = __suffix_store.view();
> >+          }
> >
> >-        if constexpr (is_same_v<_Tp, year>)
> >-          return __t;
> >-        else if constexpr (requires { __t.year(); })
> >-          return __t.year();
> >-        else
> >-          return _S_date(__t).year();
> >-      }
> >+        const auto __prec = _M_spec._M_prec_kind != _WP_none
> >+                          ? _M_spec._M_get_precision(__fc)
> >+                          : _M_spec._M_prec;
> >
> >-      template<typename _Tp>
> >-      static chrono::weekday
> >-      _S_weekday(const _Tp& __t)
> >-      {
> >-        using namespace ::std::chrono;
> >-        using ::std::chrono::__detail::__local_time_fmt;
> >-
> >-        if constexpr (is_same_v<_Tp, weekday>)
> >-          return __t;
> >-        else if constexpr (requires { __t.weekday(); })
> >-          return __t.weekday();
> >-        else if constexpr (is_same_v<_Tp, month_weekday>)
> >-          return __t.weekday_indexed().weekday();
> >-        else if constexpr (is_same_v<_Tp, month_weekday_last>)
> >-          return __t.weekday_last().weekday();
> >-        else
> >-          return weekday(_S_days(__t));
> >-      }
> >+        using _ErasedContext = typename
> _ChronoData<_CharT>::_FormatContext;
> >+        // _GLIBCXX_RESOLVE_LIB_DEFECTS
> >+        // 4118. How should duration formatters format custom rep?
> >+        auto __ereps = +__ed.count();
> >+        if (!_M_spec._M_needs(_ChronoParts::_Subseconds))
> >+        {
> >+          auto __ssreps = 0u;
> >+          auto __args_store
> >+            = std::make_format_args<_ErasedContext>(__ereps, __ssreps,
> __prec);
> >+          __cd._M_ereps = __args_store;
> >+          return this->_M_format(__cd, __fc);
> >+        }
> >
> >-      template<typename _Tp>
> >-      static unsigned
> >-      _S_weekday_index(const _Tp& __t)
> >-      {
> >-        using namespace ::std::chrono;
> >+        using _Attoseconds = _ChronoData<_CharT>::_Attoseconds;
> >+        auto __nss = _S_subseconds(__ss);
> >+        __cd._M_subseconds = chrono::duration_cast<_Attoseconds>(__nss);
> >
> >-        if constexpr (is_same_v<_Tp, weekday_indexed>)
> >-          return __t.index();
> >-        else if constexpr (requires { __t.weekday_indexed(); })
> >-          return __t.weekday_indexed().index();
> >-        else
> >-          return ((unsigned)_S_day(__t) + 6) / 7;
> >-      }
> >+        auto __ssreps = __nss.count();
> >+        auto __args_store
> >+          = std::make_format_args<_ErasedContext>(__ereps, __ssreps,
> __prec);
> >+        __cd._M_ereps = __args_store;
> >
> >-      // Remove subsecond precision from a time_point.
> >-      template<typename _Tp>
> >-      static auto
> >-      _S_floor_seconds(const _Tp& __t)
> >-      {
> >-        using chrono::__detail::__local_time_fmt;
> >-        if constexpr (chrono::__is_time_point_v<_Tp>
> >-                        || chrono::__is_duration_v<_Tp>)
> >-          {
> >-            if constexpr (_Tp::period::den != 1)
> >-              return chrono::floor<chrono::seconds>(__t);
> >-            else
> >-              return __t;
> >-          }
> >-        else if constexpr (__is_specialization_of<_Tp, chrono::hh_mm_ss>)
> >-          {
> >-            if constexpr (_Tp::fractional_width != 0)
> >-              return chrono::floor<chrono::seconds>(__t.to_duration());
> >-            else
> >-              return __t;
> >-          }
> >-        else if constexpr (__is_specialization_of<_Tp, __local_time_fmt>)
> >-          return _S_floor_seconds(__t._M_time);
> >-        else
> >-          return __t;
> >+        return this->_M_format(__cd, __fc);
> >       }
> >
> >-      // Use the formatting locale's std::time_put facet to produce
> >-      // a locale-specific representation.
> >-      template<typename _Iter>
> >-      _Iter
> >-      _M_locale_fmt(_Iter __out, const locale& __loc, const struct tm&
> __tm,
> >-                    char __fmt, char __mod) const
> >+      template<typename _Rep1, typename _Period1, typename
> _FormatContext>
> >+      // pre: __cd._M_lseconds and __cd._M_eseconds are set.
>
> Please put this comment before the template-head
>
> >+      typename _FormatContext::iterator
> >+      _M_format_time_point(_ChronoData<_CharT>& __cd,
> >+                           const chrono::duration<_Rep1, _Period1>& __ed,
> >+                           _FormatContext& __fc) const
> >       {
> >-        basic_ostringstream<_CharT> __os;
> >-        __os.imbue(__loc);
> >-        const auto& __tp = use_facet<time_put<_CharT>>(__loc);
> >-        __tp.put(__os, __os, _S_space, &__tm, __fmt, __mod);
> >-        if (__os)
> >-          __out = _M_write(std::move(__out), __loc, __os.view());
> >-        return __out;
> >+        auto __parts = _M_spec._M_needed - _ChronoParts::_TotalSeconds;
> >+        if ((__parts & _ChronoParts::_DateTime) != 0)
> >+          __cd._M_fill_date_time(__cd._M_lseconds, __parts);
> >+        return _M_format_units(__cd, __ed, __ed - __cd._M_eseconds,
> __fc);
> >       }
> >     };
> >
> >@@ -1877,7 +1967,7 @@ namespace __format
> >         // n.b. only acceptable chrono-spec for info is one containing
> >         // only whitespaces and %%, that do not depend on formatted
> object.
> >         if (!_M_f._M_spec._M_chrono_specs.empty()) [[unlikely]]
> >-          return _M_f._M_format(chrono::day(1), __fc);
> >+          return _M_f._M_format(_ChronoData<_CharT>{}, __fc);
> >
> >         const size_t __padwidth = _M_f._M_spec._M_get_width(__fc);
> >         if (__padwidth == 0)
> >@@ -1949,12 +2039,8 @@ namespace __format
> >       constexpr typename basic_format_parse_context<_CharT>::iterator
> >       parse(basic_format_parse_context<_CharT>& __pc)
> >       {
> >-      using namespace __format;
> >-      auto __it = _M_f._M_parse(__pc, _Duration|_TimeOfDay, __defSpec);
> >-      if constexpr (!is_floating_point_v<_Rep>)
> >-        if (_M_f._M_spec._M_prec_kind != __format::_WP_none)
> >-          __throw_format_error("format error: invalid precision for
> duration");
> >-      return __it;
> >+      using enum __format::_ChronoParts;
> >+      return _M_f.template _M_parse<_Duration>(__pc, _EpochTime,
> __defSpec);
> >       }
> >
> >       template<typename _Out>
> >@@ -1981,12 +2067,20 @@ namespace __format
> >       }
> >
> >     private:
> >+      using _Duration = chrono::duration<_Rep, _Period>;
> >+
> >       static constexpr __format::_ChronoSpec<_CharT> __defSpec = []
> >       {
> >-        __format::_ChronoSpec<_CharT> __res{};
> >+        using enum __format::_ChronoParts;
> >+        auto __res = __format::__formatter_duration<_CharT>::
> >+                       template _S_spec_for<_Duration>(_None);
> >         __res._M_localized = !is_integral_v<_Rep>;
> >+        // n.b. for integral format output is the same as ostream outpu
>
> "ostream output"
>
> >         if constexpr (is_integral_v<_Rep>)
> >-          __res._M_chrono_specs = _GLIBCXX_WIDEN("%Q%q");
> >+          {
> >+            __res._M_needed = _EpochUnits|_UnitSuffix;
> >+            __res._M_chrono_specs = _GLIBCXX_WIDEN("%Q%q");
> >+          }
> >         return __res;
> >       }();
> >
>
> The rest of the patch below here looked fine.
>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20250626/9d7da5db/attachment-0001.htm>


More information about the Libstdc++ mailing list