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