xref: /freebsd/contrib/llvm-project/libcxx/include/__chrono/formatter.h (revision 700637cbb5e582861067a11aaca4d053546871d2)
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 
72 namespace __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.
95 template <class _CharT, class _Rep, class _Period>
96 _LIBCPP_HIDE_FROM_ABI void
__format_sub_seconds(basic_stringstream<_CharT> & __sstr,const chrono::duration<_Rep,_Period> & __value)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 
130 template <class _CharT, __is_time_point _Tp>
__format_sub_seconds(basic_stringstream<_CharT> & __sstr,const _Tp & __value)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 
135 template <class _CharT, class _Duration>
136 _LIBCPP_HIDE_FROM_ABI void
__format_sub_seconds(basic_stringstream<_CharT> & __sstr,const chrono::hh_mm_ss<_Duration> & __value)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
152 template <class _CharT, class _Duration, class _TimeZonePtr>
153 _LIBCPP_HIDE_FROM_ABI void
__format_sub_seconds(basic_stringstream<_CharT> & __sstr,const chrono::zoned_time<_Duration,_TimeZonePtr> & __value)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 
159 template <class _Tp>
__use_fraction()160 consteval 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 
175 template <class _CharT>
__format_year(basic_stringstream<_CharT> & __sstr,int __year)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 
191 template <class _CharT>
__format_century(basic_stringstream<_CharT> & __sstr,int __year)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.)
206 template <class _CharT>
207 _LIBCPP_HIDE_FROM_ABI void
__format_zone_offset(basic_stringstream<_CharT> & __sstr,chrono::seconds __offset,bool __modifier)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.
226 struct _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 
233 template <class _Tp>
__convert_to_time_zone(const _Tp & __value)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 
251 template <class _CharT, class _Tp>
__format_chrono_using_chrono_specs(basic_stringstream<_CharT> & __sstr,const _Tp & __value,basic_string_view<_CharT> __chrono_specs)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 
419 template <class _Tp>
__weekday_ok(const _Tp & __value)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 
469 template <class _Tp>
__weekday_name_ok(const _Tp & __value)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 
519 template <class _Tp>
__date_ok(const _Tp & __value)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 
569 template <class _Tp>
__month_name_ok(const _Tp & __value)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 
619 template <class _CharT, class _Tp, class _FormatContext>
620 _LIBCPP_HIDE_FROM_ABI auto
__format_chrono(const _Tp & __value,_FormatContext & __ctx,__format_spec::__parsed_specifications<_CharT> __specs,basic_string_view<_CharT> __chrono_specs)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 
702 template <__fmt_char_type _CharT>
703 struct __formatter_chrono {
704 public:
705   template <class _ParseContext>
706   _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator
__parse__formatter_chrono707   __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>
format__formatter_chrono712   _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 
720 template <class _Duration, __fmt_char_type _CharT>
721 struct formatter<chrono::sys_time<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
722 public:
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 
734 template <class _Duration, __fmt_char_type _CharT>
735 struct formatter<chrono::utc_time<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
736 public:
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 
745 template <class _Duration, __fmt_char_type _CharT>
746 struct formatter<chrono::tai_time<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
747 public:
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 
756 template <class _Duration, __fmt_char_type _CharT>
757 struct formatter<chrono::gps_time<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
758 public:
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 
770 template <class _Duration, __fmt_char_type _CharT>
771 struct formatter<chrono::file_time<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
772 public:
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 
781 template <class _Duration, __fmt_char_type _CharT>
782 struct formatter<chrono::local_time<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
783 public:
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 
793 template <class _Rep, class _Period, __fmt_char_type _CharT>
794 struct formatter<chrono::duration<_Rep, _Period>, _CharT> : public __formatter_chrono<_CharT> {
795 public:
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 
815 template <__fmt_char_type _CharT>
816 struct formatter<chrono::day, _CharT> : public __formatter_chrono<_CharT> {
817 public:
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 
826 template <__fmt_char_type _CharT>
827 struct formatter<chrono::month, _CharT> : public __formatter_chrono<_CharT> {
828 public:
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 
837 template <__fmt_char_type _CharT>
838 struct formatter<chrono::year, _CharT> : public __formatter_chrono<_CharT> {
839 public:
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 
848 template <__fmt_char_type _CharT>
849 struct formatter<chrono::weekday, _CharT> : public __formatter_chrono<_CharT> {
850 public:
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 
859 template <__fmt_char_type _CharT>
860 struct formatter<chrono::weekday_indexed, _CharT> : public __formatter_chrono<_CharT> {
861 public:
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 
870 template <__fmt_char_type _CharT>
871 struct formatter<chrono::weekday_last, _CharT> : public __formatter_chrono<_CharT> {
872 public:
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 
881 template <__fmt_char_type _CharT>
882 struct formatter<chrono::month_day, _CharT> : public __formatter_chrono<_CharT> {
883 public:
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 
892 template <__fmt_char_type _CharT>
893 struct formatter<chrono::month_day_last, _CharT> : public __formatter_chrono<_CharT> {
894 public:
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 
903 template <__fmt_char_type _CharT>
904 struct formatter<chrono::month_weekday, _CharT> : public __formatter_chrono<_CharT> {
905 public:
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 
914 template <__fmt_char_type _CharT>
915 struct formatter<chrono::month_weekday_last, _CharT> : public __formatter_chrono<_CharT> {
916 public:
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 
925 template <__fmt_char_type _CharT>
926 struct formatter<chrono::year_month, _CharT> : public __formatter_chrono<_CharT> {
927 public:
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 
936 template <__fmt_char_type _CharT>
937 struct formatter<chrono::year_month_day, _CharT> : public __formatter_chrono<_CharT> {
938 public:
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 
947 template <__fmt_char_type _CharT>
948 struct formatter<chrono::year_month_day_last, _CharT> : public __formatter_chrono<_CharT> {
949 public:
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 
958 template <__fmt_char_type _CharT>
959 struct formatter<chrono::year_month_weekday, _CharT> : public __formatter_chrono<_CharT> {
960 public:
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 
969 template <__fmt_char_type _CharT>
970 struct formatter<chrono::year_month_weekday_last, _CharT> : public __formatter_chrono<_CharT> {
971 public:
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 
980 template <class _Duration, __fmt_char_type _CharT>
981 struct formatter<chrono::hh_mm_ss<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
982 public:
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
992 template <__fmt_char_type _CharT>
993 struct formatter<chrono::sys_info, _CharT> : public __formatter_chrono<_CharT> {
994 public:
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 
1003 template <__fmt_char_type _CharT>
1004 struct formatter<chrono::local_info, _CharT> : public __formatter_chrono<_CharT> {
1005 public:
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.
1016 template <class _Duration, class _TimeZonePtr, __fmt_char_type _CharT>
1017 struct formatter<chrono::zoned_time<_Duration, _TimeZonePtr>, _CharT> : public __formatter_chrono<_CharT> {
1018 public:
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
1036