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