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