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 <__memory/addressof.h>
54# include <__type_traits/is_specialization.h>
55# include <cmath>
56# include <ctime>
57# include <limits>
58# include <locale>
59# include <sstream>
60# include <string_view>
61
62# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
63# pragma GCC system_header
64# endif
65
66_LIBCPP_BEGIN_NAMESPACE_STD
67
68# if _LIBCPP_STD_VER >= 20
69
70namespace __formatter {
71
72/// Formats a time based on a tm struct.
73///
74/// This formatter passes the formatting to time_put which uses strftime. When
75/// the value is outside the valid range it's unspecified what strftime will
76/// output. For example weekday 8 can print 1 when the day is processed modulo
77/// 7 since that handles the Sunday for 0-based weekday. It can also print 8 if
78/// 7 is handled as a special case.
79///
80/// The Standard doesn't specify what to do in this case so the result depends
81/// on the result of the underlying code.
82///
83/// \pre When the (abbreviated) weekday or month name are used, the caller
84/// validates whether the value is valid. So the caller handles that
85/// requirement of Table 97: Meaning of conversion specifiers
86/// [tab:time.format.spec].
87///
88/// When no chrono-specs are provided it uses the stream formatter.
89
90// For tiny ratios it's not possible to convert a duration to a hh_mm_ss. This
91// fails compile-time due to the limited precision of the ratio (64-bit is too
92// small). Therefore a duration uses its own conversion.
93template <class _CharT, class _Rep, class _Period>
94_LIBCPP_HIDE_FROM_ABI void
95__format_sub_seconds(basic_stringstream<_CharT>& __sstr, const chrono::duration<_Rep, _Period>& __value) {
96 __sstr << std::use_facet<numpunct<_CharT>>(__sstr.getloc()).decimal_point();
97
98 using __duration = chrono::duration<_Rep, _Period>;
99
100 auto __fraction = __value - chrono::duration_cast<chrono::seconds>(__value);
101 // Converts a negative fraction to its positive value.
102 if (__value < chrono::seconds{0} && __fraction != __duration{0})
103 __fraction += chrono::seconds{1};
104 if constexpr (chrono::treat_as_floating_point_v<_Rep>)
105 // When the floating-point value has digits itself they are ignored based
106 // on the wording in [tab:time.format.spec]
107 // If the precision of the input cannot be exactly represented with
108 // seconds, then the format is a decimal floating-point number with a
109 // fixed format and a precision matching that of the precision of the
110 // input (or to a microseconds precision if the conversion to
111 // floating-point decimal seconds cannot be made within 18 fractional
112 // digits).
113 //
114 // This matches the behaviour of MSVC STL, fmtlib interprets this
115 // differently and uses 3 decimals.
116 // https://godbolt.org/z/6dsbnW8ba
117 std::format_to(std::ostreambuf_iterator<_CharT>{__sstr},
118 _LIBCPP_STATICALLY_WIDEN(_CharT, "{:0{}.0f}"),
119 chrono::duration_cast<typename chrono::hh_mm_ss<__duration>::precision>(__fraction).count(),
120 chrono::hh_mm_ss<__duration>::fractional_width);
121 else
122 std::format_to(std::ostreambuf_iterator<_CharT>{__sstr},
123 _LIBCPP_STATICALLY_WIDEN(_CharT, "{:0{}}"),
124 chrono::duration_cast<typename chrono::hh_mm_ss<__duration>::precision>(__fraction).count(),
125 chrono::hh_mm_ss<__duration>::fractional_width);
126}
127
128template <class _CharT, __is_time_point _Tp>
129_LIBCPP_HIDE_FROM_ABI void __format_sub_seconds(basic_stringstream<_CharT>& __sstr, const _Tp& __value) {
130 __formatter::__format_sub_seconds(__sstr, __value.time_since_epoch());
131}
132
133template <class _CharT, class _Duration>
134_LIBCPP_HIDE_FROM_ABI void
135__format_sub_seconds(basic_stringstream<_CharT>& __sstr, const chrono::hh_mm_ss<_Duration>& __value) {
136 __sstr << std::use_facet<numpunct<_CharT>>(__sstr.getloc()).decimal_point();
137 if constexpr (chrono::treat_as_floating_point_v<typename _Duration::rep>)
138 std::format_to(std::ostreambuf_iterator<_CharT>{__sstr},
139 _LIBCPP_STATICALLY_WIDEN(_CharT, "{:0{}.0f}"),
140 __value.subseconds().count(),
141 __value.fractional_width);
142 else
143 std::format_to(std::ostreambuf_iterator<_CharT>{__sstr},
144 _LIBCPP_STATICALLY_WIDEN(_CharT, "{:0{}}"),
145 __value.subseconds().count(),
146 __value.fractional_width);
147}
148
149# if _LIBCPP_HAS_EXPERIMENTAL_TZDB && _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
150template <class _CharT, class _Duration, class _TimeZonePtr>
151_LIBCPP_HIDE_FROM_ABI void
152__format_sub_seconds(basic_stringstream<_CharT>& __sstr, const chrono::zoned_time<_Duration, _TimeZonePtr>& __value) {
153 __formatter::__format_sub_seconds(__sstr, __value.get_local_time().time_since_epoch());
154}
155# endif
156
157template <class _Tp>
158consteval bool __use_fraction() {
159 if constexpr (__is_time_point<_Tp>)
160 return chrono::hh_mm_ss<typename _Tp::duration>::fractional_width;
161# if _LIBCPP_HAS_EXPERIMENTAL_TZDB && _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
162 else if constexpr (__is_specialization_v<_Tp, chrono::zoned_time>)
163 return chrono::hh_mm_ss<typename _Tp::duration>::fractional_width;
164# endif
165 else if constexpr (chrono::__is_duration_v<_Tp>)
166 return chrono::hh_mm_ss<_Tp>::fractional_width;
167 else if constexpr (__is_hh_mm_ss<_Tp>)
168 return _Tp::fractional_width;
169 else
170 return false;
171}
172
173template <class _CharT>
174_LIBCPP_HIDE_FROM_ABI void __format_year(basic_stringstream<_CharT>& __sstr, int __year) {
175 if (__year < 0) {
176 __sstr << _CharT('-');
177 __year = -__year;
178 }
179
180 // TODO FMT Write an issue
181 // If the result has less than four digits it is zero-padded with 0 to two digits.
182 // is less -> has less
183 // left-padded -> zero-padded, otherwise the proper value would be 000-0.
184
185 // Note according to the wording it should be left padded, which is odd.
186 __sstr << std::format(_LIBCPP_STATICALLY_WIDEN(_CharT, "{:04}"), __year);
187}
188
189template <class _CharT>
190_LIBCPP_HIDE_FROM_ABI void __format_century(basic_stringstream<_CharT>& __sstr, int __year) {
191 // TODO FMT Write an issue
192 // [tab:time.format.spec]
193 // %C The year divided by 100 using floored division. If the result is a
194 // single decimal digit, it is prefixed with 0.
195
196 bool __negative = __year < 0;
197 int __century = (__year - (99 * __negative)) / 100; // floored division
198 __sstr << std::format(_LIBCPP_STATICALLY_WIDEN(_CharT, "{:02}"), __century);
199}
200
201// Implements the %z format specifier according to [tab:time.format.spec], where
202// '__modifier' signals %Oz or %Ez were used. (Both modifiers behave the same,
203// so there is no need to distinguish between them.)
204template <class _CharT>
205_LIBCPP_HIDE_FROM_ABI void
206__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 _LIBCPP_HIDE_FROM_ABI __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_LIBCPP_HIDE_FROM_ABI __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>
250_LIBCPP_HIDE_FROM_ABI void __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>
418_LIBCPP_HIDE_FROM_ABI constexpr 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>
468_LIBCPP_HIDE_FROM_ABI constexpr 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>
518_LIBCPP_HIDE_FROM_ABI constexpr 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>
568_LIBCPP_HIDE_FROM_ABI constexpr 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>
618_LIBCPP_HIDE_FROM_ABI auto
619__format_chrono(const _Tp& __value,
620 _FormatContext& __ctx,
621 __format_spec::__parsed_specifications<_CharT> __specs,
622 basic_string_view<_CharT> __chrono_specs) {
623 basic_stringstream<_CharT> __sstr;
624 // [time.format]/2
625 // 2.1 - the "C" locale if the L option is not present in chrono-format-spec, otherwise
626 // 2.2 - the locale passed to the formatting function if any, otherwise
627 // 2.3 - the global locale.
628 // Note that the __ctx's locale() call does 2.2 and 2.3.
629 if (__specs.__chrono_.__locale_specific_form_)
630 __sstr.imbue(__ctx.locale());
631 else
632 __sstr.imbue(locale::classic());
633
634 if (__chrono_specs.empty())
635 __sstr << __value;
636 else {
637 if constexpr (chrono::__is_duration_v<_Tp>) {
638 // A duration can be a user defined arithmetic type. Users may specialize
639 // numeric_limits, but they may not specialize is_signed.
640 if constexpr (numeric_limits<typename _Tp::rep>::is_signed) {
641 if (__value < __value.zero()) {
642 __sstr << _CharT('-');
643 __formatter::__format_chrono_using_chrono_specs(__sstr, -__value, __chrono_specs);
644 } else
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 // TODO FMT When keeping the precision it will truncate the string.
649 // Note that the behaviour what the precision does isn't specified.
650 __specs.__precision_ = -1;
651 } else {
652 // Test __weekday_name_ before __weekday_ to give a better error.
653 if (__specs.__chrono_.__weekday_name_ && !__formatter::__weekday_name_ok(__value))
654 std::__throw_format_error(s: "Formatting a weekday name needs a valid weekday");
655
656 if (__specs.__chrono_.__weekday_ && !__formatter::__weekday_ok(__value))
657 std::__throw_format_error(s: "Formatting a weekday needs a valid weekday");
658
659 if (__specs.__chrono_.__day_of_year_ && !__formatter::__date_ok(__value))
660 std::__throw_format_error(s: "Formatting a day of year needs a valid date");
661
662 if (__specs.__chrono_.__week_of_year_ && !__formatter::__date_ok(__value))
663 std::__throw_format_error(s: "Formatting a week of year needs a valid date");
664
665 if (__specs.__chrono_.__month_name_ && !__formatter::__month_name_ok(__value))
666 std::__throw_format_error(s: "Formatting a month name from an invalid month number");
667
668 if constexpr (__is_hh_mm_ss<_Tp>) {
669 // Note this is a pedantic intepretation of the Standard. A hh_mm_ss
670 // is no longer a time_of_day and can store an arbitrary number of
671 // hours. A number of hours in a 12 or 24 hour clock can't represent
672 // 24 hours or more. The functions std::chrono::make12 and
673 // std::chrono::make24 reaffirm this view point.
674 //
675 // Interestingly this will be the only output stream function that
676 // throws.
677 //
678 // TODO FMT The wording probably needs to be adapted to
679 // - The displayed hours is hh_mm_ss.hours() % 24
680 // - It should probably allow %j in the same fashion as duration.
681 // - The stream formatter should change its output when hours >= 24
682 // - Write it as not valid,
683 // - or write the number of days.
684 if (__specs.__chrono_.__hour_ && __value.hours().count() > 23)
685 std::__throw_format_error(s: "Formatting a hour needs a valid value");
686
687 if (__value.is_negative())
688 __sstr << _CharT('-');
689 }
690
691 __formatter::__format_chrono_using_chrono_specs(__sstr, __value, __chrono_specs);
692 }
693 }
694
695 return __formatter::__write_string(__sstr.view(), __ctx.out(), __specs);
696}
697
698} // namespace __formatter
699
700template <__fmt_char_type _CharT>
701struct __formatter_chrono {
702public:
703 template <class _ParseContext>
704 _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator
705 __parse(_ParseContext& __ctx, __format_spec::__fields __fields, __format_spec::__flags __flags) {
706 return __parser_.__parse(__ctx, __fields, __flags);
707 }
708
709 template <class _Tp, class _FormatContext>
710 _LIBCPP_HIDE_FROM_ABI typename _FormatContext::iterator format(const _Tp& __value, _FormatContext& __ctx) const {
711 return __formatter::__format_chrono(
712 __value, __ctx, __parser_.__parser_.__get_parsed_chrono_specifications(__ctx), __parser_.__chrono_specs_);
713 }
714
715 __format_spec::__parser_chrono<_CharT> __parser_;
716};
717
718template <class _Duration, __fmt_char_type _CharT>
719struct formatter<chrono::sys_time<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
720public:
721 using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
722
723 template <class _ParseContext>
724 _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
725 return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__clock);
726 }
727};
728
729# if _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
730# if _LIBCPP_HAS_EXPERIMENTAL_TZDB
731
732template <class _Duration, __fmt_char_type _CharT>
733struct formatter<chrono::utc_time<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
734public:
735 using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
736
737 template <class _ParseContext>
738 _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
739 return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__clock);
740 }
741};
742
743template <class _Duration, __fmt_char_type _CharT>
744struct formatter<chrono::tai_time<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
745public:
746 using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
747
748 template <class _ParseContext>
749 _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
750 return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__clock);
751 }
752};
753
754template <class _Duration, __fmt_char_type _CharT>
755struct formatter<chrono::gps_time<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
756public:
757 using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
758
759 template <class _ParseContext>
760 _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
761 return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__clock);
762 }
763};
764
765# endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB
766# endif // _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
767
768template <class _Duration, __fmt_char_type _CharT>
769struct formatter<chrono::file_time<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
770public:
771 using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
772
773 template <class _ParseContext>
774 _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
775 return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__clock);
776 }
777};
778
779template <class _Duration, __fmt_char_type _CharT>
780struct formatter<chrono::local_time<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
781public:
782 using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
783
784 template <class _ParseContext>
785 _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
786 // The flags are not __clock since there is no associated time-zone.
787 return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__date_time);
788 }
789};
790
791template <class _Rep, class _Period, __fmt_char_type _CharT>
792struct formatter<chrono::duration<_Rep, _Period>, _CharT> : public __formatter_chrono<_CharT> {
793public:
794 using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
795
796 template <class _ParseContext>
797 _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
798 // [time.format]/1
799 // Giving a precision specification in the chrono-format-spec is valid only
800 // for std::chrono::duration types where the representation type Rep is a
801 // floating-point type. For all other Rep types, an exception of type
802 // format_error is thrown if the chrono-format-spec contains a precision
803 // specification.
804 //
805 // Note this doesn't refer to chrono::treat_as_floating_point_v<_Rep>.
806 if constexpr (std::floating_point<_Rep>)
807 return _Base::__parse(__ctx, __format_spec::__fields_chrono_fractional, __format_spec::__flags::__duration);
808 else
809 return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__duration);
810 }
811};
812
813template <__fmt_char_type _CharT>
814struct formatter<chrono::day, _CharT> : public __formatter_chrono<_CharT> {
815public:
816 using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
817
818 template <class _ParseContext>
819 _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
820 return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__day);
821 }
822};
823
824template <__fmt_char_type _CharT>
825struct formatter<chrono::month, _CharT> : public __formatter_chrono<_CharT> {
826public:
827 using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
828
829 template <class _ParseContext>
830 _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
831 return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__month);
832 }
833};
834
835template <__fmt_char_type _CharT>
836struct formatter<chrono::year, _CharT> : public __formatter_chrono<_CharT> {
837public:
838 using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
839
840 template <class _ParseContext>
841 _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
842 return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__year);
843 }
844};
845
846template <__fmt_char_type _CharT>
847struct formatter<chrono::weekday, _CharT> : public __formatter_chrono<_CharT> {
848public:
849 using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
850
851 template <class _ParseContext>
852 _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
853 return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__weekday);
854 }
855};
856
857template <__fmt_char_type _CharT>
858struct formatter<chrono::weekday_indexed, _CharT> : public __formatter_chrono<_CharT> {
859public:
860 using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
861
862 template <class _ParseContext>
863 _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
864 return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__weekday);
865 }
866};
867
868template <__fmt_char_type _CharT>
869struct formatter<chrono::weekday_last, _CharT> : public __formatter_chrono<_CharT> {
870public:
871 using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
872
873 template <class _ParseContext>
874 _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
875 return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__weekday);
876 }
877};
878
879template <__fmt_char_type _CharT>
880struct formatter<chrono::month_day, _CharT> : public __formatter_chrono<_CharT> {
881public:
882 using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
883
884 template <class _ParseContext>
885 _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
886 return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__month_day);
887 }
888};
889
890template <__fmt_char_type _CharT>
891struct formatter<chrono::month_day_last, _CharT> : public __formatter_chrono<_CharT> {
892public:
893 using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
894
895 template <class _ParseContext>
896 _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
897 return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__month);
898 }
899};
900
901template <__fmt_char_type _CharT>
902struct formatter<chrono::month_weekday, _CharT> : public __formatter_chrono<_CharT> {
903public:
904 using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
905
906 template <class _ParseContext>
907 _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
908 return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__month_weekday);
909 }
910};
911
912template <__fmt_char_type _CharT>
913struct formatter<chrono::month_weekday_last, _CharT> : public __formatter_chrono<_CharT> {
914public:
915 using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
916
917 template <class _ParseContext>
918 _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
919 return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__month_weekday);
920 }
921};
922
923template <__fmt_char_type _CharT>
924struct formatter<chrono::year_month, _CharT> : public __formatter_chrono<_CharT> {
925public:
926 using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
927
928 template <class _ParseContext>
929 _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
930 return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__year_month);
931 }
932};
933
934template <__fmt_char_type _CharT>
935struct formatter<chrono::year_month_day, _CharT> : public __formatter_chrono<_CharT> {
936public:
937 using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
938
939 template <class _ParseContext>
940 _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
941 return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__date);
942 }
943};
944
945template <__fmt_char_type _CharT>
946struct formatter<chrono::year_month_day_last, _CharT> : public __formatter_chrono<_CharT> {
947public:
948 using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
949
950 template <class _ParseContext>
951 _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
952 return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__date);
953 }
954};
955
956template <__fmt_char_type _CharT>
957struct formatter<chrono::year_month_weekday, _CharT> : public __formatter_chrono<_CharT> {
958public:
959 using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
960
961 template <class _ParseContext>
962 _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
963 return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__date);
964 }
965};
966
967template <__fmt_char_type _CharT>
968struct formatter<chrono::year_month_weekday_last, _CharT> : public __formatter_chrono<_CharT> {
969public:
970 using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
971
972 template <class _ParseContext>
973 _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
974 return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__date);
975 }
976};
977
978template <class _Duration, __fmt_char_type _CharT>
979struct formatter<chrono::hh_mm_ss<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
980public:
981 using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
982
983 template <class _ParseContext>
984 _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
985 return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__time);
986 }
987};
988
989# if _LIBCPP_HAS_EXPERIMENTAL_TZDB
990template <__fmt_char_type _CharT>
991struct formatter<chrono::sys_info, _CharT> : public __formatter_chrono<_CharT> {
992public:
993 using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
994
995 template <class _ParseContext>
996 _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
997 return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__time_zone);
998 }
999};
1000
1001template <__fmt_char_type _CharT>
1002struct formatter<chrono::local_info, _CharT> : public __formatter_chrono<_CharT> {
1003public:
1004 using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
1005
1006 template <class _ParseContext>
1007 _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
1008 return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags{});
1009 }
1010};
1011# if _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
1012// Note due to how libc++'s formatters are implemented there is no need to add
1013// the exposition only local-time-format-t abstraction.
1014template <class _Duration, class _TimeZonePtr, __fmt_char_type _CharT>
1015struct formatter<chrono::zoned_time<_Duration, _TimeZonePtr>, _CharT> : public __formatter_chrono<_CharT> {
1016public:
1017 using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
1018
1019 template <class _ParseContext>
1020 _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
1021 return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__clock);
1022 }
1023};
1024# endif // _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
1025# endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB
1026
1027# endif // if _LIBCPP_STD_VER >= 20
1028
1029_LIBCPP_END_NAMESPACE_STD
1030
1031#endif // _LIBCPP_HAS_LOCALIZATION
1032
1033#endif // _LIBCPP___CHRONO_FORMATTER_H
1034