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