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
63namespace __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.
86template <class _CharT, class _Rep, class _Period>
87_LIBCPP_HIDE_FROM_ABI void
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
121template <class _CharT, __is_time_point _Tp>
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
126template <class _CharT, class _Duration>
127_LIBCPP_HIDE_FROM_ABI void
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)
144template <class _CharT, class _Duration, class _TimeZonePtr>
145_LIBCPP_HIDE_FROM_ABI void
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
151template <class _Tp>
152consteval 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
168template <class _CharT>
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
184template <class _CharT>
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.)
199template <class _CharT>
200_LIBCPP_HIDE_FROM_ABI void
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.
219struct _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
226template <class _Tp>
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
241template <class _CharT, class _Tp>
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(x&: __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(x&: __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(x&: __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(x: __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(x&: __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(x&: __t), std::to_address(__s), std::to_address(__it + 1));
401 break;
402 }
403 } else {
404 __sstr << *__it;
405 }
406 }
407}
408
409template <class _Tp>
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
460template <class _Tp>
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
511template <class _Tp>
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
562template <class _Tp>
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
613template <class _CharT, class _Tp, class _FormatContext>
614_LIBCPP_HIDE_FROM_ABI auto
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(s: "Formatting a weekday name needs a valid weekday");
651
652 if (__specs.__chrono_.__weekday_ && !__formatter::__weekday_ok(__value))
653 std::__throw_format_error(s: "Formatting a weekday needs a valid weekday");
654
655 if (__specs.__chrono_.__day_of_year_ && !__formatter::__date_ok(__value))
656 std::__throw_format_error(s: "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(s: "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(s: "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(s: "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
696template <__fmt_char_type _CharT>
697struct _LIBCPP_TEMPLATE_VIS __formatter_chrono {
698public:
699 template <class _ParseContext>
700 _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator
701 __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>
706 _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
714template <class _Duration, __fmt_char_type _CharT>
715struct _LIBCPP_TEMPLATE_VIS formatter<chrono::sys_time<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
716public:
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
725template <class _Duration, __fmt_char_type _CharT>
726struct _LIBCPP_TEMPLATE_VIS formatter<chrono::file_time<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
727public:
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
736template <class _Duration, __fmt_char_type _CharT>
737struct _LIBCPP_TEMPLATE_VIS formatter<chrono::local_time<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
738public:
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
748template <class _Rep, class _Period, __fmt_char_type _CharT>
749struct formatter<chrono::duration<_Rep, _Period>, _CharT> : public __formatter_chrono<_CharT> {
750public:
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
770template <__fmt_char_type _CharT>
771struct _LIBCPP_TEMPLATE_VIS formatter<chrono::day, _CharT> : public __formatter_chrono<_CharT> {
772public:
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
781template <__fmt_char_type _CharT>
782struct _LIBCPP_TEMPLATE_VIS formatter<chrono::month, _CharT> : public __formatter_chrono<_CharT> {
783public:
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
792template <__fmt_char_type _CharT>
793struct _LIBCPP_TEMPLATE_VIS formatter<chrono::year, _CharT> : public __formatter_chrono<_CharT> {
794public:
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
803template <__fmt_char_type _CharT>
804struct _LIBCPP_TEMPLATE_VIS formatter<chrono::weekday, _CharT> : public __formatter_chrono<_CharT> {
805public:
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
814template <__fmt_char_type _CharT>
815struct _LIBCPP_TEMPLATE_VIS formatter<chrono::weekday_indexed, _CharT> : public __formatter_chrono<_CharT> {
816public:
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
825template <__fmt_char_type _CharT>
826struct _LIBCPP_TEMPLATE_VIS formatter<chrono::weekday_last, _CharT> : public __formatter_chrono<_CharT> {
827public:
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
836template <__fmt_char_type _CharT>
837struct _LIBCPP_TEMPLATE_VIS formatter<chrono::month_day, _CharT> : public __formatter_chrono<_CharT> {
838public:
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
847template <__fmt_char_type _CharT>
848struct _LIBCPP_TEMPLATE_VIS formatter<chrono::month_day_last, _CharT> : public __formatter_chrono<_CharT> {
849public:
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
858template <__fmt_char_type _CharT>
859struct _LIBCPP_TEMPLATE_VIS formatter<chrono::month_weekday, _CharT> : public __formatter_chrono<_CharT> {
860public:
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
869template <__fmt_char_type _CharT>
870struct _LIBCPP_TEMPLATE_VIS formatter<chrono::month_weekday_last, _CharT> : public __formatter_chrono<_CharT> {
871public:
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
880template <__fmt_char_type _CharT>
881struct _LIBCPP_TEMPLATE_VIS formatter<chrono::year_month, _CharT> : public __formatter_chrono<_CharT> {
882public:
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
891template <__fmt_char_type _CharT>
892struct _LIBCPP_TEMPLATE_VIS formatter<chrono::year_month_day, _CharT> : public __formatter_chrono<_CharT> {
893public:
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
902template <__fmt_char_type _CharT>
903struct _LIBCPP_TEMPLATE_VIS formatter<chrono::year_month_day_last, _CharT> : public __formatter_chrono<_CharT> {
904public:
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
913template <__fmt_char_type _CharT>
914struct _LIBCPP_TEMPLATE_VIS formatter<chrono::year_month_weekday, _CharT> : public __formatter_chrono<_CharT> {
915public:
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
924template <__fmt_char_type _CharT>
925struct _LIBCPP_TEMPLATE_VIS formatter<chrono::year_month_weekday_last, _CharT> : public __formatter_chrono<_CharT> {
926public:
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
935template <class _Duration, __fmt_char_type _CharT>
936struct formatter<chrono::hh_mm_ss<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
937public:
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)
947template <__fmt_char_type _CharT>
948struct formatter<chrono::sys_info, _CharT> : public __formatter_chrono<_CharT> {
949public:
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
958template <__fmt_char_type _CharT>
959struct formatter<chrono::local_info, _CharT> : public __formatter_chrono<_CharT> {
960public:
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.
972template <class _Duration, class _TimeZonePtr, __fmt_char_type _CharT>
973struct formatter<chrono::zoned_time<_Duration, _TimeZonePtr>, _CharT> : public __formatter_chrono<_CharT> {
974public:
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