master
   1// -*- C++ -*-
   2//===----------------------------------------------------------------------===//
   3//
   4// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
   5// See https://llvm.org/LICENSE.txt for license information.
   6// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
   7//
   8//===----------------------------------------------------------------------===//
   9
  10#ifndef _LIBCPP___CHRONO_FORMATTER_H
  11#define _LIBCPP___CHRONO_FORMATTER_H
  12
  13#include <__config>
  14
  15#if _LIBCPP_HAS_LOCALIZATION
  16
  17#  include <__algorithm/ranges_copy.h>
  18#  include <__chrono/calendar.h>
  19#  include <__chrono/concepts.h>
  20#  include <__chrono/convert_to_tm.h>
  21#  include <__chrono/day.h>
  22#  include <__chrono/duration.h>
  23#  include <__chrono/file_clock.h>
  24#  include <__chrono/gps_clock.h>
  25#  include <__chrono/hh_mm_ss.h>
  26#  include <__chrono/local_info.h>
  27#  include <__chrono/month.h>
  28#  include <__chrono/month_weekday.h>
  29#  include <__chrono/monthday.h>
  30#  include <__chrono/ostream.h>
  31#  include <__chrono/parser_std_format_spec.h>
  32#  include <__chrono/statically_widen.h>
  33#  include <__chrono/sys_info.h>
  34#  include <__chrono/system_clock.h>
  35#  include <__chrono/tai_clock.h>
  36#  include <__chrono/time_point.h>
  37#  include <__chrono/utc_clock.h>
  38#  include <__chrono/weekday.h>
  39#  include <__chrono/year.h>
  40#  include <__chrono/year_month.h>
  41#  include <__chrono/year_month_day.h>
  42#  include <__chrono/year_month_weekday.h>
  43#  include <__chrono/zoned_time.h>
  44#  include <__concepts/arithmetic.h>
  45#  include <__concepts/same_as.h>
  46#  include <__format/concepts.h>
  47#  include <__format/format_error.h>
  48#  include <__format/format_functions.h>
  49#  include <__format/format_parse_context.h>
  50#  include <__format/formatter.h>
  51#  include <__format/parser_std_format_spec.h>
  52#  include <__format/write_escaped.h>
  53#  include <__iterator/istreambuf_iterator.h>
  54#  include <__iterator/ostreambuf_iterator.h>
  55#  include <__locale_dir/time.h>
  56#  include <__memory/addressof.h>
  57#  include <__type_traits/is_specialization.h>
  58#  include <cmath>
  59#  include <ctime>
  60#  include <limits>
  61#  include <sstream>
  62#  include <string_view>
  63
  64#  if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
  65#    pragma GCC system_header
  66#  endif
  67
  68_LIBCPP_BEGIN_NAMESPACE_STD
  69
  70#  if _LIBCPP_STD_VER >= 20
  71
  72namespace __formatter {
  73
  74/// Formats a time based on a tm struct.
  75///
  76/// This formatter passes the formatting to time_put which uses strftime. When
  77/// the value is outside the valid range it's unspecified what strftime will
  78/// output. For example weekday 8 can print 1 when the day is processed modulo
  79/// 7 since that handles the Sunday for 0-based weekday. It can also print 8 if
  80/// 7 is handled as a special case.
  81///
  82/// The Standard doesn't specify what to do in this case so the result depends
  83/// on the result of the underlying code.
  84///
  85/// \pre When the (abbreviated) weekday or month name are used, the caller
  86///      validates whether the value is valid. So the caller handles that
  87///      requirement of Table 97: Meaning of conversion specifiers
  88///      [tab:time.format.spec].
  89///
  90/// When no chrono-specs are provided it uses the stream formatter.
  91
  92// For tiny ratios it's not possible to convert a duration to a hh_mm_ss. This
  93// fails compile-time due to the limited precision of the ratio (64-bit is too
  94// small). Therefore a duration uses its own conversion.
  95template <class _CharT, class _Rep, class _Period>
  96_LIBCPP_HIDE_FROM_ABI void
  97__format_sub_seconds(basic_stringstream<_CharT>& __sstr, const chrono::duration<_Rep, _Period>& __value) {
  98  __sstr << std::use_facet<numpunct<_CharT>>(__sstr.getloc()).decimal_point();
  99
 100  using __duration = chrono::duration<_Rep, _Period>;
 101
 102  auto __fraction = __value - chrono::duration_cast<chrono::seconds>(__value);
 103  // Converts a negative fraction to its positive value.
 104  if (__value < chrono::seconds{0} && __fraction != __duration{0})
 105    __fraction += chrono::seconds{1};
 106  if constexpr (chrono::treat_as_floating_point_v<_Rep>)
 107    // When the floating-point value has digits itself they are ignored based
 108    // on the wording in [tab:time.format.spec]
 109    //   If the precision of the input cannot be exactly represented with
 110    //   seconds, then the format is a decimal floating-point number with a
 111    //   fixed format and a precision matching that of the precision of the
 112    //   input (or to a microseconds precision if the conversion to
 113    //   floating-point decimal seconds cannot be made within 18 fractional
 114    //   digits).
 115    //
 116    // This matches the behaviour of MSVC STL, fmtlib interprets this
 117    // differently and uses 3 decimals.
 118    // https://godbolt.org/z/6dsbnW8ba
 119    std::format_to(std::ostreambuf_iterator<_CharT>{__sstr},
 120                   _LIBCPP_STATICALLY_WIDEN(_CharT, "{:0{}.0f}"),
 121                   chrono::duration_cast<typename chrono::hh_mm_ss<__duration>::precision>(__fraction).count(),
 122                   chrono::hh_mm_ss<__duration>::fractional_width);
 123  else
 124    std::format_to(std::ostreambuf_iterator<_CharT>{__sstr},
 125                   _LIBCPP_STATICALLY_WIDEN(_CharT, "{:0{}}"),
 126                   chrono::duration_cast<typename chrono::hh_mm_ss<__duration>::precision>(__fraction).count(),
 127                   chrono::hh_mm_ss<__duration>::fractional_width);
 128}
 129
 130template <class _CharT, __is_time_point _Tp>
 131_LIBCPP_HIDE_FROM_ABI void __format_sub_seconds(basic_stringstream<_CharT>& __sstr, const _Tp& __value) {
 132  __formatter::__format_sub_seconds(__sstr, __value.time_since_epoch());
 133}
 134
 135template <class _CharT, class _Duration>
 136_LIBCPP_HIDE_FROM_ABI void
 137__format_sub_seconds(basic_stringstream<_CharT>& __sstr, const chrono::hh_mm_ss<_Duration>& __value) {
 138  __sstr << std::use_facet<numpunct<_CharT>>(__sstr.getloc()).decimal_point();
 139  if constexpr (chrono::treat_as_floating_point_v<typename _Duration::rep>)
 140    std::format_to(std::ostreambuf_iterator<_CharT>{__sstr},
 141                   _LIBCPP_STATICALLY_WIDEN(_CharT, "{:0{}.0f}"),
 142                   __value.subseconds().count(),
 143                   __value.fractional_width);
 144  else
 145    std::format_to(std::ostreambuf_iterator<_CharT>{__sstr},
 146                   _LIBCPP_STATICALLY_WIDEN(_CharT, "{:0{}}"),
 147                   __value.subseconds().count(),
 148                   __value.fractional_width);
 149}
 150
 151#    if _LIBCPP_HAS_EXPERIMENTAL_TZDB && _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
 152template <class _CharT, class _Duration, class _TimeZonePtr>
 153_LIBCPP_HIDE_FROM_ABI void
 154__format_sub_seconds(basic_stringstream<_CharT>& __sstr, const chrono::zoned_time<_Duration, _TimeZonePtr>& __value) {
 155  __formatter::__format_sub_seconds(__sstr, __value.get_local_time().time_since_epoch());
 156}
 157#    endif
 158
 159template <class _Tp>
 160consteval bool __use_fraction() {
 161  if constexpr (__is_time_point<_Tp>)
 162    return chrono::hh_mm_ss<typename _Tp::duration>::fractional_width;
 163#    if _LIBCPP_HAS_EXPERIMENTAL_TZDB && _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
 164  else if constexpr (__is_specialization_v<_Tp, chrono::zoned_time>)
 165    return chrono::hh_mm_ss<typename _Tp::duration>::fractional_width;
 166#    endif
 167  else if constexpr (chrono::__is_duration_v<_Tp>)
 168    return chrono::hh_mm_ss<_Tp>::fractional_width;
 169  else if constexpr (__is_hh_mm_ss<_Tp>)
 170    return _Tp::fractional_width;
 171  else
 172    return false;
 173}
 174
 175template <class _CharT>
 176_LIBCPP_HIDE_FROM_ABI void __format_year(basic_stringstream<_CharT>& __sstr, int __year) {
 177  if (__year < 0) {
 178    __sstr << _CharT('-');
 179    __year = -__year;
 180  }
 181
 182  // TODO FMT Write an issue
 183  //   If the result has less than four digits it is zero-padded with 0 to two digits.
 184  // is less -> has less
 185  // left-padded -> zero-padded, otherwise the proper value would be 000-0.
 186
 187  // Note according to the wording it should be left padded, which is odd.
 188  __sstr << std::format(_LIBCPP_STATICALLY_WIDEN(_CharT, "{:04}"), __year);
 189}
 190
 191template <class _CharT>
 192_LIBCPP_HIDE_FROM_ABI void __format_century(basic_stringstream<_CharT>& __sstr, int __year) {
 193  // TODO FMT Write an issue
 194  // [tab:time.format.spec]
 195  //   %C The year divided by 100 using floored division. If the result is a
 196  //   single decimal digit, it is prefixed with 0.
 197
 198  bool __negative = __year < 0;
 199  int __century   = (__year - (99 * __negative)) / 100; // floored division
 200  __sstr << std::format(_LIBCPP_STATICALLY_WIDEN(_CharT, "{:02}"), __century);
 201}
 202
 203// Implements the %z format specifier according to [tab:time.format.spec], where
 204// '__modifier' signals %Oz or %Ez were used. (Both modifiers behave the same,
 205// so there is no need to distinguish between them.)
 206template <class _CharT>
 207_LIBCPP_HIDE_FROM_ABI void
 208__format_zone_offset(basic_stringstream<_CharT>& __sstr, chrono::seconds __offset, bool __modifier) {
 209  if (__offset < 0s) {
 210    __sstr << _CharT('-');
 211    __offset = -__offset;
 212  } else {
 213    __sstr << _CharT('+');
 214  }
 215
 216  chrono::hh_mm_ss __hms{__offset};
 217  std::ostreambuf_iterator<_CharT> __out_it{__sstr};
 218  // Note HMS does not allow formatting hours > 23, but the offset is not limited to 24H.
 219  std::format_to(__out_it, _LIBCPP_STATICALLY_WIDEN(_CharT, "{:02}"), __hms.hours().count());
 220  if (__modifier)
 221    __sstr << _CharT(':');
 222  std::format_to(__out_it, _LIBCPP_STATICALLY_WIDEN(_CharT, "{:02}"), __hms.minutes().count());
 223}
 224
 225// Helper to store the time zone information needed for formatting.
 226struct _LIBCPP_HIDE_FROM_ABI __time_zone {
 227  // Typically these abbreviations are short and fit in the string's internal
 228  // buffer.
 229  string __abbrev;
 230  chrono::seconds __offset;
 231};
 232
 233template <class _Tp>
 234_LIBCPP_HIDE_FROM_ABI __time_zone __convert_to_time_zone([[maybe_unused]] const _Tp& __value) {
 235#    if _LIBCPP_HAS_EXPERIMENTAL_TZDB
 236  if constexpr (same_as<_Tp, chrono::sys_info>)
 237    return {__value.abbrev, __value.offset};
 238#      if _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
 239  else if constexpr (__is_time_point<_Tp> && requires { requires same_as<typename _Tp::clock, chrono::tai_clock>; })
 240    return {"TAI", chrono::seconds{0}};
 241  else if constexpr (__is_time_point<_Tp> && requires { requires same_as<typename _Tp::clock, chrono::gps_clock>; })
 242    return {"GPS", chrono::seconds{0}};
 243  else if constexpr (__is_specialization_v<_Tp, chrono::zoned_time>)
 244    return __formatter::__convert_to_time_zone(__value.get_info());
 245#      endif // _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
 246  else
 247#    endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB
 248    return {"UTC", chrono::seconds{0}};
 249}
 250
 251template <class _CharT, class _Tp>
 252_LIBCPP_HIDE_FROM_ABI void __format_chrono_using_chrono_specs(
 253    basic_stringstream<_CharT>& __sstr, const _Tp& __value, basic_string_view<_CharT> __chrono_specs) {
 254  tm __t              = std::__convert_to_tm<tm>(__value);
 255  __time_zone __z     = __formatter::__convert_to_time_zone(__value);
 256  const auto& __facet = std::use_facet<time_put<_CharT>>(__sstr.getloc());
 257  for (auto __it = __chrono_specs.begin(); __it != __chrono_specs.end(); ++__it) {
 258    if (*__it == _CharT('%')) {
 259      auto __s = __it;
 260      ++__it;
 261      // We only handle the types that can't be directly handled by time_put.
 262      // (as an optimization n, t, and % are also handled directly.)
 263      switch (*__it) {
 264      case _CharT('n'):
 265        __sstr << _CharT('\n');
 266        break;
 267      case _CharT('t'):
 268        __sstr << _CharT('\t');
 269        break;
 270      case _CharT('%'):
 271        __sstr << *__it;
 272        break;
 273
 274      case _CharT('C'): {
 275        // strftime's output is only defined in the range [00, 99].
 276        int __year = __t.tm_year + 1900;
 277        if (__year < 1000 || __year > 9999)
 278          __formatter::__format_century(__sstr, __year);
 279        else
 280          __facet.put(
 281              {__sstr}, __sstr, _CharT(' '), std::addressof(__t), std::to_address(__s), std::to_address(__it + 1));
 282      } break;
 283
 284      case _CharT('j'):
 285        if constexpr (chrono::__is_duration_v<_Tp>)
 286          // Converting a duration where the period has a small ratio to days
 287          // may fail to compile. This due to loss of precision in the
 288          // conversion. In order to avoid that issue convert to seconds as
 289          // an intemediate step.
 290          __sstr << chrono::duration_cast<chrono::days>(chrono::duration_cast<chrono::seconds>(__value)).count();
 291        else
 292          __facet.put(
 293              {__sstr}, __sstr, _CharT(' '), std::addressof(__t), std::to_address(__s), std::to_address(__it + 1));
 294        break;
 295
 296      case _CharT('q'):
 297        if constexpr (chrono::__is_duration_v<_Tp>) {
 298          __sstr << chrono::__units_suffix<_CharT, typename _Tp::period>();
 299          break;
 300        }
 301        __builtin_unreachable();
 302
 303      case _CharT('Q'):
 304        // TODO FMT Determine the proper ideas
 305        // - Should it honour the precision?
 306        // - Shoult it honour the locale setting for the separators?
 307        // The wording for Q doesn't use the word locale and the effect of
 308        // precision is unspecified.
 309        //
 310        // MSVC STL ignores precision but uses separator
 311        // FMT honours precision and has a bug for separator
 312        // https://godbolt.org/z/78b7sMxns
 313        if constexpr (chrono::__is_duration_v<_Tp>) {
 314          __sstr << std::format(_LIBCPP_STATICALLY_WIDEN(_CharT, "{}"), __value.count());
 315          break;
 316        }
 317        __builtin_unreachable();
 318
 319      case _CharT('S'):
 320      case _CharT('T'):
 321        __facet.put(
 322            {__sstr}, __sstr, _CharT(' '), std::addressof(__t), std::to_address(__s), std::to_address(__it + 1));
 323        if constexpr (__formatter::__use_fraction<_Tp>())
 324          __formatter::__format_sub_seconds(__sstr, __value);
 325        break;
 326
 327        // Unlike time_put and strftime the formatting library requires %Y
 328        //
 329        // [tab:time.format.spec]
 330        //   The year as a decimal number. If the result is less than four digits
 331        //   it is left-padded with 0 to four digits.
 332        //
 333        // This means years in the range (-1000, 1000) need manual formatting.
 334        // It's unclear whether %EY needs the same treatment. For example the
 335        // Japanese EY contains the era name and year. This is zero-padded to 2
 336        // digits in time_put (note that older glibc versions didn't do
 337        // padding.) However most eras won't reach 100 years, let alone 1000.
 338        // So padding to 4 digits seems unwanted for Japanese.
 339        //
 340        // The same applies to %Ex since that too depends on the era.
 341        //
 342        // %x the locale's date representation is currently doesn't handle the
 343        // zero-padding too.
 344        //
 345        // The 4 digits can be implemented better at a later time. On POSIX
 346        // systems the required information can be extracted by nl_langinfo
 347        // https://man7.org/linux/man-pages/man3/nl_langinfo.3.html
 348        //
 349        // Note since year < -1000 is expected to be rare it uses the more
 350        // expensive year routine.
 351        //
 352        // TODO FMT evaluate the comment above.
 353
 354#    if defined(__GLIBC__) || defined(_AIX) || defined(_WIN32)
 355      case _CharT('y'):
 356        // Glibc fails for negative values, AIX for positive values too.
 357        __sstr << std::format(_LIBCPP_STATICALLY_WIDEN(_CharT, "{:02}"), (std::abs(__t.tm_year + 1900)) % 100);
 358        break;
 359#    endif // defined(__GLIBC__) || defined(_AIX) || defined(_WIN32)
 360
 361      case _CharT('Y'):
 362        // Depending on the platform's libc the range of supported years is
 363        // limited. Instead of of testing all conditions use the internal
 364        // implementation unconditionally.
 365        __formatter::__format_year(__sstr, __t.tm_year + 1900);
 366        break;
 367
 368      case _CharT('F'):
 369        // Depending on the platform's libc the range of supported years is
 370        // limited. Instead of testing all conditions use the internal
 371        // implementation unconditionally.
 372        __formatter::__format_year(__sstr, __t.tm_year + 1900);
 373        __sstr << std::format(_LIBCPP_STATICALLY_WIDEN(_CharT, "-{:02}-{:02}"), __t.tm_mon + 1, __t.tm_mday);
 374        break;
 375
 376      case _CharT('z'):
 377        __formatter::__format_zone_offset(__sstr, __z.__offset, false);
 378        break;
 379
 380      case _CharT('Z'):
 381        // __abbrev is always a char so the copy may convert.
 382        ranges::copy(__z.__abbrev, std::ostreambuf_iterator<_CharT>{__sstr});
 383        break;
 384
 385      case _CharT('O'):
 386        if constexpr (__formatter::__use_fraction<_Tp>()) {
 387          // Handle OS using the normal representation for the non-fractional
 388          // part. There seems to be no locale information regarding how the
 389          // fractional part should be formatted.
 390          if (*(__it + 1) == 'S') {
 391            ++__it;
 392            __facet.put(
 393                {__sstr}, __sstr, _CharT(' '), std::addressof(__t), std::to_address(__s), std::to_address(__it + 1));
 394            __formatter::__format_sub_seconds(__sstr, __value);
 395            break;
 396          }
 397        }
 398
 399        // Oz produces the same output as Ez below.
 400        [[fallthrough]];
 401      case _CharT('E'):
 402        ++__it;
 403        if (*__it == 'z') {
 404          __formatter::__format_zone_offset(__sstr, __z.__offset, true);
 405          break;
 406        }
 407        [[fallthrough]];
 408      default:
 409        __facet.put(
 410            {__sstr}, __sstr, _CharT(' '), std::addressof(__t), std::to_address(__s), std::to_address(__it + 1));
 411        break;
 412      }
 413    } else {
 414      __sstr << *__it;
 415    }
 416  }
 417}
 418
 419template <class _Tp>
 420_LIBCPP_HIDE_FROM_ABI constexpr bool __weekday_ok(const _Tp& __value) {
 421  if constexpr (__is_time_point<_Tp>)
 422    return true;
 423  else if constexpr (same_as<_Tp, chrono::day>)
 424    return true;
 425  else if constexpr (same_as<_Tp, chrono::month>)
 426    return __value.ok();
 427  else if constexpr (same_as<_Tp, chrono::year>)
 428    return true;
 429  else if constexpr (same_as<_Tp, chrono::weekday>)
 430    return true;
 431  else if constexpr (same_as<_Tp, chrono::weekday_indexed>)
 432    return true;
 433  else if constexpr (same_as<_Tp, chrono::weekday_last>)
 434    return true;
 435  else if constexpr (same_as<_Tp, chrono::month_day>)
 436    return true;
 437  else if constexpr (same_as<_Tp, chrono::month_day_last>)
 438    return true;
 439  else if constexpr (same_as<_Tp, chrono::month_weekday>)
 440    return true;
 441  else if constexpr (same_as<_Tp, chrono::month_weekday_last>)
 442    return true;
 443  else if constexpr (same_as<_Tp, chrono::year_month>)
 444    return true;
 445  else if constexpr (same_as<_Tp, chrono::year_month_day>)
 446    return __value.ok();
 447  else if constexpr (same_as<_Tp, chrono::year_month_day_last>)
 448    return __value.ok();
 449  else if constexpr (same_as<_Tp, chrono::year_month_weekday>)
 450    return __value.weekday().ok();
 451  else if constexpr (same_as<_Tp, chrono::year_month_weekday_last>)
 452    return __value.weekday().ok();
 453  else if constexpr (__is_hh_mm_ss<_Tp>)
 454    return true;
 455#    if _LIBCPP_HAS_EXPERIMENTAL_TZDB
 456  else if constexpr (same_as<_Tp, chrono::sys_info>)
 457    return true;
 458  else if constexpr (same_as<_Tp, chrono::local_info>)
 459    return true;
 460#      if _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
 461  else if constexpr (__is_specialization_v<_Tp, chrono::zoned_time>)
 462    return true;
 463#      endif
 464#    endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB
 465  else
 466    static_assert(sizeof(_Tp) == 0, "Add the missing type specialization");
 467}
 468
 469template <class _Tp>
 470_LIBCPP_HIDE_FROM_ABI constexpr bool __weekday_name_ok(const _Tp& __value) {
 471  if constexpr (__is_time_point<_Tp>)
 472    return true;
 473  else if constexpr (same_as<_Tp, chrono::day>)
 474    return true;
 475  else if constexpr (same_as<_Tp, chrono::month>)
 476    return __value.ok();
 477  else if constexpr (same_as<_Tp, chrono::year>)
 478    return true;
 479  else if constexpr (same_as<_Tp, chrono::weekday>)
 480    return __value.ok();
 481  else if constexpr (same_as<_Tp, chrono::weekday_indexed>)
 482    return __value.weekday().ok();
 483  else if constexpr (same_as<_Tp, chrono::weekday_last>)
 484    return __value.weekday().ok();
 485  else if constexpr (same_as<_Tp, chrono::month_day>)
 486    return true;
 487  else if constexpr (same_as<_Tp, chrono::month_day_last>)
 488    return true;
 489  else if constexpr (same_as<_Tp, chrono::month_weekday>)
 490    return __value.weekday_indexed().ok();
 491  else if constexpr (same_as<_Tp, chrono::month_weekday_last>)
 492    return __value.weekday_indexed().ok();
 493  else if constexpr (same_as<_Tp, chrono::year_month>)
 494    return true;
 495  else if constexpr (same_as<_Tp, chrono::year_month_day>)
 496    return __value.ok();
 497  else if constexpr (same_as<_Tp, chrono::year_month_day_last>)
 498    return __value.ok();
 499  else if constexpr (same_as<_Tp, chrono::year_month_weekday>)
 500    return __value.weekday().ok();
 501  else if constexpr (same_as<_Tp, chrono::year_month_weekday_last>)
 502    return __value.weekday().ok();
 503  else if constexpr (__is_hh_mm_ss<_Tp>)
 504    return true;
 505#    if _LIBCPP_HAS_EXPERIMENTAL_TZDB
 506  else if constexpr (same_as<_Tp, chrono::sys_info>)
 507    return true;
 508  else if constexpr (same_as<_Tp, chrono::local_info>)
 509    return true;
 510#      if _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
 511  else if constexpr (__is_specialization_v<_Tp, chrono::zoned_time>)
 512    return true;
 513#      endif
 514#    endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB
 515  else
 516    static_assert(sizeof(_Tp) == 0, "Add the missing type specialization");
 517}
 518
 519template <class _Tp>
 520_LIBCPP_HIDE_FROM_ABI constexpr bool __date_ok(const _Tp& __value) {
 521  if constexpr (__is_time_point<_Tp>)
 522    return true;
 523  else if constexpr (same_as<_Tp, chrono::day>)
 524    return true;
 525  else if constexpr (same_as<_Tp, chrono::month>)
 526    return __value.ok();
 527  else if constexpr (same_as<_Tp, chrono::year>)
 528    return true;
 529  else if constexpr (same_as<_Tp, chrono::weekday>)
 530    return true;
 531  else if constexpr (same_as<_Tp, chrono::weekday_indexed>)
 532    return true;
 533  else if constexpr (same_as<_Tp, chrono::weekday_last>)
 534    return true;
 535  else if constexpr (same_as<_Tp, chrono::month_day>)
 536    return true;
 537  else if constexpr (same_as<_Tp, chrono::month_day_last>)
 538    return true;
 539  else if constexpr (same_as<_Tp, chrono::month_weekday>)
 540    return true;
 541  else if constexpr (same_as<_Tp, chrono::month_weekday_last>)
 542    return true;
 543  else if constexpr (same_as<_Tp, chrono::year_month>)
 544    return true;
 545  else if constexpr (same_as<_Tp, chrono::year_month_day>)
 546    return __value.ok();
 547  else if constexpr (same_as<_Tp, chrono::year_month_day_last>)
 548    return __value.ok();
 549  else if constexpr (same_as<_Tp, chrono::year_month_weekday>)
 550    return __value.ok();
 551  else if constexpr (same_as<_Tp, chrono::year_month_weekday_last>)
 552    return __value.ok();
 553  else if constexpr (__is_hh_mm_ss<_Tp>)
 554    return true;
 555#    if _LIBCPP_HAS_EXPERIMENTAL_TZDB
 556  else if constexpr (same_as<_Tp, chrono::sys_info>)
 557    return true;
 558  else if constexpr (same_as<_Tp, chrono::local_info>)
 559    return true;
 560#      if _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
 561  else if constexpr (__is_specialization_v<_Tp, chrono::zoned_time>)
 562    return true;
 563#      endif
 564#    endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB
 565  else
 566    static_assert(sizeof(_Tp) == 0, "Add the missing type specialization");
 567}
 568
 569template <class _Tp>
 570_LIBCPP_HIDE_FROM_ABI constexpr bool __month_name_ok(const _Tp& __value) {
 571  if constexpr (__is_time_point<_Tp>)
 572    return true;
 573  else if constexpr (same_as<_Tp, chrono::day>)
 574    return true;
 575  else if constexpr (same_as<_Tp, chrono::month>)
 576    return __value.ok();
 577  else if constexpr (same_as<_Tp, chrono::year>)
 578    return true;
 579  else if constexpr (same_as<_Tp, chrono::weekday>)
 580    return true;
 581  else if constexpr (same_as<_Tp, chrono::weekday_indexed>)
 582    return true;
 583  else if constexpr (same_as<_Tp, chrono::weekday_last>)
 584    return true;
 585  else if constexpr (same_as<_Tp, chrono::month_day>)
 586    return __value.month().ok();
 587  else if constexpr (same_as<_Tp, chrono::month_day_last>)
 588    return __value.month().ok();
 589  else if constexpr (same_as<_Tp, chrono::month_weekday>)
 590    return __value.month().ok();
 591  else if constexpr (same_as<_Tp, chrono::month_weekday_last>)
 592    return __value.month().ok();
 593  else if constexpr (same_as<_Tp, chrono::year_month>)
 594    return __value.month().ok();
 595  else if constexpr (same_as<_Tp, chrono::year_month_day>)
 596    return __value.month().ok();
 597  else if constexpr (same_as<_Tp, chrono::year_month_day_last>)
 598    return __value.month().ok();
 599  else if constexpr (same_as<_Tp, chrono::year_month_weekday>)
 600    return __value.month().ok();
 601  else if constexpr (same_as<_Tp, chrono::year_month_weekday_last>)
 602    return __value.month().ok();
 603  else if constexpr (__is_hh_mm_ss<_Tp>)
 604    return true;
 605#    if _LIBCPP_HAS_EXPERIMENTAL_TZDB
 606  else if constexpr (same_as<_Tp, chrono::sys_info>)
 607    return true;
 608  else if constexpr (same_as<_Tp, chrono::local_info>)
 609    return true;
 610#      if _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
 611  else if constexpr (__is_specialization_v<_Tp, chrono::zoned_time>)
 612    return true;
 613#      endif
 614#    endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB
 615  else
 616    static_assert(sizeof(_Tp) == 0, "Add the missing type specialization");
 617}
 618
 619template <class _CharT, class _Tp, class _FormatContext>
 620_LIBCPP_HIDE_FROM_ABI auto
 621__format_chrono(const _Tp& __value,
 622                _FormatContext& __ctx,
 623                __format_spec::__parsed_specifications<_CharT> __specs,
 624                basic_string_view<_CharT> __chrono_specs) {
 625  basic_stringstream<_CharT> __sstr;
 626  // [time.format]/2
 627  // 2.1 - the "C" locale if the L option is not present in chrono-format-spec, otherwise
 628  // 2.2 - the locale passed to the formatting function if any, otherwise
 629  // 2.3 - the global locale.
 630  // Note that the __ctx's locale() call does 2.2 and 2.3.
 631  if (__specs.__chrono_.__locale_specific_form_)
 632    __sstr.imbue(__ctx.locale());
 633  else
 634    __sstr.imbue(locale::classic());
 635
 636  if (__chrono_specs.empty())
 637    __sstr << __value;
 638  else {
 639    if constexpr (chrono::__is_duration_v<_Tp>) {
 640      // A duration can be a user defined arithmetic type. Users may specialize
 641      // numeric_limits, but they may not specialize is_signed.
 642      if constexpr (numeric_limits<typename _Tp::rep>::is_signed) {
 643        if (__value < __value.zero()) {
 644          __sstr << _CharT('-');
 645          __formatter::__format_chrono_using_chrono_specs(__sstr, -__value, __chrono_specs);
 646        } else
 647          __formatter::__format_chrono_using_chrono_specs(__sstr, __value, __chrono_specs);
 648      } else
 649        __formatter::__format_chrono_using_chrono_specs(__sstr, __value, __chrono_specs);
 650      // TODO FMT When keeping the precision it will truncate the string.
 651      // Note that the behaviour what the precision does isn't specified.
 652      __specs.__precision_ = -1;
 653    } else {
 654      // Test __weekday_name_ before __weekday_ to give a better error.
 655      if (__specs.__chrono_.__weekday_name_ && !__formatter::__weekday_name_ok(__value))
 656        std::__throw_format_error("Formatting a weekday name needs a valid weekday");
 657
 658      if (__specs.__chrono_.__weekday_ && !__formatter::__weekday_ok(__value))
 659        std::__throw_format_error("Formatting a weekday needs a valid weekday");
 660
 661      if (__specs.__chrono_.__day_of_year_ && !__formatter::__date_ok(__value))
 662        std::__throw_format_error("Formatting a day of year needs a valid date");
 663
 664      if (__specs.__chrono_.__week_of_year_ && !__formatter::__date_ok(__value))
 665        std::__throw_format_error("Formatting a week of year needs a valid date");
 666
 667      if (__specs.__chrono_.__month_name_ && !__formatter::__month_name_ok(__value))
 668        std::__throw_format_error("Formatting a month name from an invalid month number");
 669
 670      if constexpr (__is_hh_mm_ss<_Tp>) {
 671        // Note this is a pedantic intepretation of the Standard. A hh_mm_ss
 672        // is no longer a time_of_day and can store an arbitrary number of
 673        // hours. A number of hours in a 12 or 24 hour clock can't represent
 674        // 24 hours or more. The functions std::chrono::make12 and
 675        // std::chrono::make24 reaffirm this view point.
 676        //
 677        // Interestingly this will be the only output stream function that
 678        // throws.
 679        //
 680        // TODO FMT The wording probably needs to be adapted to
 681        // - The displayed hours is hh_mm_ss.hours() % 24
 682        // - It should probably allow %j in the same fashion as duration.
 683        // - The stream formatter should change its output when hours >= 24
 684        //   - Write it as not valid,
 685        //   - or write the number of days.
 686        if (__specs.__chrono_.__hour_ && __value.hours().count() > 23)
 687          std::__throw_format_error("Formatting a hour needs a valid value");
 688
 689        if (__value.is_negative())
 690          __sstr << _CharT('-');
 691      }
 692
 693      __formatter::__format_chrono_using_chrono_specs(__sstr, __value, __chrono_specs);
 694    }
 695  }
 696
 697  return __formatter::__write_string(__sstr.view(), __ctx.out(), __specs);
 698}
 699
 700} // namespace __formatter
 701
 702template <__fmt_char_type _CharT>
 703struct __formatter_chrono {
 704public:
 705  template <class _ParseContext>
 706  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator
 707  __parse(_ParseContext& __ctx, __format_spec::__fields __fields, __format_spec::__flags __flags) {
 708    return __parser_.__parse(__ctx, __fields, __flags);
 709  }
 710
 711  template <class _Tp, class _FormatContext>
 712  _LIBCPP_HIDE_FROM_ABI typename _FormatContext::iterator format(const _Tp& __value, _FormatContext& __ctx) const {
 713    return __formatter::__format_chrono(
 714        __value, __ctx, __parser_.__parser_.__get_parsed_chrono_specifications(__ctx), __parser_.__chrono_specs_);
 715  }
 716
 717  __format_spec::__parser_chrono<_CharT> __parser_;
 718};
 719
 720template <class _Duration, __fmt_char_type _CharT>
 721struct formatter<chrono::sys_time<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
 722public:
 723  using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
 724
 725  template <class _ParseContext>
 726  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
 727    return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__clock);
 728  }
 729};
 730
 731#    if _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
 732#      if _LIBCPP_HAS_EXPERIMENTAL_TZDB
 733
 734template <class _Duration, __fmt_char_type _CharT>
 735struct formatter<chrono::utc_time<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
 736public:
 737  using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
 738
 739  template <class _ParseContext>
 740  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
 741    return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__clock);
 742  }
 743};
 744
 745template <class _Duration, __fmt_char_type _CharT>
 746struct formatter<chrono::tai_time<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
 747public:
 748  using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
 749
 750  template <class _ParseContext>
 751  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
 752    return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__clock);
 753  }
 754};
 755
 756template <class _Duration, __fmt_char_type _CharT>
 757struct formatter<chrono::gps_time<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
 758public:
 759  using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
 760
 761  template <class _ParseContext>
 762  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
 763    return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__clock);
 764  }
 765};
 766
 767#      endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB
 768#    endif   // _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
 769
 770template <class _Duration, __fmt_char_type _CharT>
 771struct formatter<chrono::file_time<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
 772public:
 773  using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
 774
 775  template <class _ParseContext>
 776  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
 777    return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__clock);
 778  }
 779};
 780
 781template <class _Duration, __fmt_char_type _CharT>
 782struct formatter<chrono::local_time<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
 783public:
 784  using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
 785
 786  template <class _ParseContext>
 787  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
 788    // The flags are not __clock since there is no associated time-zone.
 789    return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__date_time);
 790  }
 791};
 792
 793template <class _Rep, class _Period, __fmt_char_type _CharT>
 794struct formatter<chrono::duration<_Rep, _Period>, _CharT> : public __formatter_chrono<_CharT> {
 795public:
 796  using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
 797
 798  template <class _ParseContext>
 799  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
 800    // [time.format]/1
 801    // Giving a precision specification in the chrono-format-spec is valid only
 802    // for std::chrono::duration types where the representation type Rep is a
 803    // floating-point type. For all other Rep types, an exception of type
 804    // format_error is thrown if the chrono-format-spec contains a precision
 805    // specification.
 806    //
 807    // Note this doesn't refer to chrono::treat_as_floating_point_v<_Rep>.
 808    if constexpr (std::floating_point<_Rep>)
 809      return _Base::__parse(__ctx, __format_spec::__fields_chrono_fractional, __format_spec::__flags::__duration);
 810    else
 811      return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__duration);
 812  }
 813};
 814
 815template <__fmt_char_type _CharT>
 816struct formatter<chrono::day, _CharT> : public __formatter_chrono<_CharT> {
 817public:
 818  using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
 819
 820  template <class _ParseContext>
 821  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
 822    return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__day);
 823  }
 824};
 825
 826template <__fmt_char_type _CharT>
 827struct formatter<chrono::month, _CharT> : public __formatter_chrono<_CharT> {
 828public:
 829  using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
 830
 831  template <class _ParseContext>
 832  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
 833    return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__month);
 834  }
 835};
 836
 837template <__fmt_char_type _CharT>
 838struct formatter<chrono::year, _CharT> : public __formatter_chrono<_CharT> {
 839public:
 840  using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
 841
 842  template <class _ParseContext>
 843  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
 844    return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__year);
 845  }
 846};
 847
 848template <__fmt_char_type _CharT>
 849struct formatter<chrono::weekday, _CharT> : public __formatter_chrono<_CharT> {
 850public:
 851  using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
 852
 853  template <class _ParseContext>
 854  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
 855    return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__weekday);
 856  }
 857};
 858
 859template <__fmt_char_type _CharT>
 860struct formatter<chrono::weekday_indexed, _CharT> : public __formatter_chrono<_CharT> {
 861public:
 862  using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
 863
 864  template <class _ParseContext>
 865  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
 866    return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__weekday);
 867  }
 868};
 869
 870template <__fmt_char_type _CharT>
 871struct formatter<chrono::weekday_last, _CharT> : public __formatter_chrono<_CharT> {
 872public:
 873  using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
 874
 875  template <class _ParseContext>
 876  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
 877    return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__weekday);
 878  }
 879};
 880
 881template <__fmt_char_type _CharT>
 882struct formatter<chrono::month_day, _CharT> : public __formatter_chrono<_CharT> {
 883public:
 884  using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
 885
 886  template <class _ParseContext>
 887  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
 888    return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__month_day);
 889  }
 890};
 891
 892template <__fmt_char_type _CharT>
 893struct formatter<chrono::month_day_last, _CharT> : public __formatter_chrono<_CharT> {
 894public:
 895  using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
 896
 897  template <class _ParseContext>
 898  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
 899    return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__month);
 900  }
 901};
 902
 903template <__fmt_char_type _CharT>
 904struct formatter<chrono::month_weekday, _CharT> : public __formatter_chrono<_CharT> {
 905public:
 906  using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
 907
 908  template <class _ParseContext>
 909  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
 910    return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__month_weekday);
 911  }
 912};
 913
 914template <__fmt_char_type _CharT>
 915struct formatter<chrono::month_weekday_last, _CharT> : public __formatter_chrono<_CharT> {
 916public:
 917  using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
 918
 919  template <class _ParseContext>
 920  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
 921    return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__month_weekday);
 922  }
 923};
 924
 925template <__fmt_char_type _CharT>
 926struct formatter<chrono::year_month, _CharT> : public __formatter_chrono<_CharT> {
 927public:
 928  using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
 929
 930  template <class _ParseContext>
 931  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
 932    return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__year_month);
 933  }
 934};
 935
 936template <__fmt_char_type _CharT>
 937struct formatter<chrono::year_month_day, _CharT> : public __formatter_chrono<_CharT> {
 938public:
 939  using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
 940
 941  template <class _ParseContext>
 942  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
 943    return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__date);
 944  }
 945};
 946
 947template <__fmt_char_type _CharT>
 948struct formatter<chrono::year_month_day_last, _CharT> : public __formatter_chrono<_CharT> {
 949public:
 950  using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
 951
 952  template <class _ParseContext>
 953  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
 954    return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__date);
 955  }
 956};
 957
 958template <__fmt_char_type _CharT>
 959struct formatter<chrono::year_month_weekday, _CharT> : public __formatter_chrono<_CharT> {
 960public:
 961  using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
 962
 963  template <class _ParseContext>
 964  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
 965    return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__date);
 966  }
 967};
 968
 969template <__fmt_char_type _CharT>
 970struct formatter<chrono::year_month_weekday_last, _CharT> : public __formatter_chrono<_CharT> {
 971public:
 972  using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
 973
 974  template <class _ParseContext>
 975  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
 976    return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__date);
 977  }
 978};
 979
 980template <class _Duration, __fmt_char_type _CharT>
 981struct formatter<chrono::hh_mm_ss<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
 982public:
 983  using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
 984
 985  template <class _ParseContext>
 986  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
 987    return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__time);
 988  }
 989};
 990
 991#    if _LIBCPP_HAS_EXPERIMENTAL_TZDB
 992template <__fmt_char_type _CharT>
 993struct formatter<chrono::sys_info, _CharT> : public __formatter_chrono<_CharT> {
 994public:
 995  using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
 996
 997  template <class _ParseContext>
 998  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
 999    return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__time_zone);
1000  }
1001};
1002
1003template <__fmt_char_type _CharT>
1004struct formatter<chrono::local_info, _CharT> : public __formatter_chrono<_CharT> {
1005public:
1006  using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
1007
1008  template <class _ParseContext>
1009  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
1010    return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags{});
1011  }
1012};
1013#      if _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
1014// Note due to how libc++'s formatters are implemented there is no need to add
1015// the exposition only local-time-format-t abstraction.
1016template <class _Duration, class _TimeZonePtr, __fmt_char_type _CharT>
1017struct formatter<chrono::zoned_time<_Duration, _TimeZonePtr>, _CharT> : public __formatter_chrono<_CharT> {
1018public:
1019  using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
1020
1021  template <class _ParseContext>
1022  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
1023    return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__clock);
1024  }
1025};
1026#      endif // _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
1027#    endif   // _LIBCPP_HAS_EXPERIMENTAL_TZDB
1028
1029#  endif // if _LIBCPP_STD_VER >= 20
1030
1031_LIBCPP_END_NAMESPACE_STD
1032
1033#endif // _LIBCPP_HAS_LOCALIZATION
1034
1035#endif //  _LIBCPP___CHRONO_FORMATTER_H