1 // Formatting library for C++ - chrono support
3 // Copyright (c) 2012 - present, Victor Zverovich
4 // All rights reserved.
6 // For the license information refer to format.h.
21 // Prevents expansion of a preceding token as a function-style macro.
22 // Usage: f FMT_NOMACRO()
26 inline null
<> localtime_r
FMT_NOMACRO(...) { return null
<>(); }
27 inline null
<> localtime_s(...) { return null
<>(); }
28 inline null
<> gmtime_r(...) { return null
<>(); }
29 inline null
<> gmtime_s(...) { return null
<>(); }
30 } // namespace internal
32 // Thread-safe replacement for std::localtime
33 inline std::tm
localtime(std::time_t time
) {
38 dispatcher(std::time_t t
) : time_(t
) {}
41 using namespace fmt::internal
;
42 return handle(localtime_r(&time_
, &tm_
));
45 bool handle(std::tm
* tm
) { return tm
!= FMT_NULL
; }
47 bool handle(internal::null
<>) {
48 using namespace fmt::internal
;
49 return fallback(localtime_s(&tm_
, &time_
));
52 bool fallback(int res
) { return res
== 0; }
55 bool fallback(internal::null
<>) {
56 using namespace fmt::internal
;
57 std::tm
* tm
= std::localtime(&time_
);
59 return tm
!= FMT_NULL
;
64 // Too big time values may be unsupported.
65 if (!lt
.run()) FMT_THROW(format_error("time_t value out of range"));
69 // Thread-safe replacement for std::gmtime
70 inline std::tm
gmtime(std::time_t time
) {
75 dispatcher(std::time_t t
) : time_(t
) {}
78 using namespace fmt::internal
;
79 return handle(gmtime_r(&time_
, &tm_
));
82 bool handle(std::tm
* tm
) { return tm
!= FMT_NULL
; }
84 bool handle(internal::null
<>) {
85 using namespace fmt::internal
;
86 return fallback(gmtime_s(&tm_
, &time_
));
89 bool fallback(int res
) { return res
== 0; }
92 bool fallback(internal::null
<>) {
93 std::tm
* tm
= std::gmtime(&time_
);
95 return tm
!= FMT_NULL
;
100 // Too big time values may be unsupported.
101 if (!gt
.run()) FMT_THROW(format_error("time_t value out of range"));
106 inline std::size_t strftime(char* str
, std::size_t count
, const char* format
,
107 const std::tm
* time
) {
108 return std::strftime(str
, count
, format
, time
);
111 inline std::size_t strftime(wchar_t* str
, std::size_t count
,
112 const wchar_t* format
, const std::tm
* time
) {
113 return std::wcsftime(str
, count
, format
, time
);
115 } // namespace internal
117 template <typename Char
> struct formatter
<std::tm
, Char
> {
118 template <typename ParseContext
>
119 auto parse(ParseContext
& ctx
) -> decltype(ctx
.begin()) {
120 auto it
= ctx
.begin();
121 if (it
!= ctx
.end() && *it
== ':') ++it
;
123 while (end
!= ctx
.end() && *end
!= '}') ++end
;
124 tm_format
.reserve(internal::to_unsigned(end
- it
+ 1));
125 tm_format
.append(it
, end
);
126 tm_format
.push_back('\0');
130 template <typename FormatContext
>
131 auto format(const std::tm
& tm
, FormatContext
& ctx
) -> decltype(ctx
.out()) {
132 basic_memory_buffer
<Char
> buf
;
133 std::size_t start
= buf
.size();
135 std::size_t size
= buf
.capacity() - start
;
137 internal::strftime(&buf
[start
], size
, &tm_format
[0], &tm
);
139 buf
.resize(start
+ count
);
142 if (size
>= tm_format
.size() * 256) {
143 // If the buffer is 256 times larger than the format string, assume
144 // that `strftime` gives an empty result. There doesn't seem to be a
145 // better way to distinguish the two cases:
146 // https://github.com/fmtlib/fmt/issues/367
149 const std::size_t MIN_GROWTH
= 10;
150 buf
.reserve(buf
.capacity() + (size
> MIN_GROWTH
? size
: MIN_GROWTH
));
152 return std::copy(buf
.begin(), buf
.end(), ctx
.out());
155 basic_memory_buffer
<Char
> tm_format
;
159 template <typename Period
> FMT_CONSTEXPR
const char* get_units() {
162 template <> FMT_CONSTEXPR
const char* get_units
<std::atto
>() { return "as"; }
163 template <> FMT_CONSTEXPR
const char* get_units
<std::femto
>() { return "fs"; }
164 template <> FMT_CONSTEXPR
const char* get_units
<std::pico
>() { return "ps"; }
165 template <> FMT_CONSTEXPR
const char* get_units
<std::nano
>() { return "ns"; }
166 template <> FMT_CONSTEXPR
const char* get_units
<std::micro
>() { return "µs"; }
167 template <> FMT_CONSTEXPR
const char* get_units
<std::milli
>() { return "ms"; }
168 template <> FMT_CONSTEXPR
const char* get_units
<std::centi
>() { return "cs"; }
169 template <> FMT_CONSTEXPR
const char* get_units
<std::deci
>() { return "ds"; }
170 template <> FMT_CONSTEXPR
const char* get_units
<std::ratio
<1>>() { return "s"; }
171 template <> FMT_CONSTEXPR
const char* get_units
<std::deca
>() { return "das"; }
172 template <> FMT_CONSTEXPR
const char* get_units
<std::hecto
>() { return "hs"; }
173 template <> FMT_CONSTEXPR
const char* get_units
<std::kilo
>() { return "ks"; }
174 template <> FMT_CONSTEXPR
const char* get_units
<std::mega
>() { return "Ms"; }
175 template <> FMT_CONSTEXPR
const char* get_units
<std::giga
>() { return "Gs"; }
176 template <> FMT_CONSTEXPR
const char* get_units
<std::tera
>() { return "Ts"; }
177 template <> FMT_CONSTEXPR
const char* get_units
<std::peta
>() { return "Ps"; }
178 template <> FMT_CONSTEXPR
const char* get_units
<std::exa
>() { return "Es"; }
179 template <> FMT_CONSTEXPR
const char* get_units
<std::ratio
<60>>() {
182 template <> FMT_CONSTEXPR
const char* get_units
<std::ratio
<3600>>() {
186 enum class numeric_system
{
188 // Alternative numeric system, e.g. 十二 instead of 12 in ja_JP locale.
192 // Parses a put_time-like format string and invokes handler actions.
193 template <typename Char
, typename Handler
>
194 FMT_CONSTEXPR
const Char
* parse_chrono_format(const Char
* begin
,
205 if (begin
!= ptr
) handler
.on_text(begin
, ptr
);
206 ++ptr
; // consume '%'
207 if (ptr
== end
) FMT_THROW(format_error("invalid format"));
211 handler
.on_text(ptr
- 1, ptr
);
214 const char newline
[] = "\n";
215 handler
.on_text(newline
, newline
+ 1);
219 const char tab
[] = "\t";
220 handler
.on_text(tab
, tab
+ 1);
225 handler
.on_abbr_weekday();
228 handler
.on_full_weekday();
231 handler
.on_dec0_weekday(numeric_system::standard
);
234 handler
.on_dec1_weekday(numeric_system::standard
);
238 handler
.on_abbr_month();
241 handler
.on_full_month();
243 // Hour, minute, second:
245 handler
.on_24_hour(numeric_system::standard
);
248 handler
.on_12_hour(numeric_system::standard
);
251 handler
.on_minute(numeric_system::standard
);
254 handler
.on_second(numeric_system::standard
);
258 handler
.on_datetime(numeric_system::standard
);
261 handler
.on_loc_date(numeric_system::standard
);
264 handler
.on_loc_time(numeric_system::standard
);
267 handler
.on_us_date();
270 handler
.on_iso_date();
273 handler
.on_12_hour_time();
276 handler
.on_24_hour_time();
279 handler
.on_iso_time();
285 handler
.on_duration_value();
288 handler
.on_duration_unit();
291 handler
.on_utc_offset();
294 handler
.on_tz_name();
296 // Alternative representation:
298 if (ptr
== end
) FMT_THROW(format_error("invalid format"));
302 handler
.on_datetime(numeric_system::alternative
);
305 handler
.on_loc_date(numeric_system::alternative
);
308 handler
.on_loc_time(numeric_system::alternative
);
311 FMT_THROW(format_error("invalid format"));
316 if (ptr
== end
) FMT_THROW(format_error("invalid format"));
320 handler
.on_dec0_weekday(numeric_system::alternative
);
323 handler
.on_dec1_weekday(numeric_system::alternative
);
326 handler
.on_24_hour(numeric_system::alternative
);
329 handler
.on_12_hour(numeric_system::alternative
);
332 handler
.on_minute(numeric_system::alternative
);
335 handler
.on_second(numeric_system::alternative
);
338 FMT_THROW(format_error("invalid format"));
342 FMT_THROW(format_error("invalid format"));
346 if (begin
!= ptr
) handler
.on_text(begin
, ptr
);
350 struct chrono_format_checker
{
351 void report_no_date() { FMT_THROW(format_error("no date")); }
353 template <typename Char
> void on_text(const Char
*, const Char
*) {}
354 void on_abbr_weekday() { report_no_date(); }
355 void on_full_weekday() { report_no_date(); }
356 void on_dec0_weekday(numeric_system
) { report_no_date(); }
357 void on_dec1_weekday(numeric_system
) { report_no_date(); }
358 void on_abbr_month() { report_no_date(); }
359 void on_full_month() { report_no_date(); }
360 void on_24_hour(numeric_system
) {}
361 void on_12_hour(numeric_system
) {}
362 void on_minute(numeric_system
) {}
363 void on_second(numeric_system
) {}
364 void on_datetime(numeric_system
) { report_no_date(); }
365 void on_loc_date(numeric_system
) { report_no_date(); }
366 void on_loc_time(numeric_system
) { report_no_date(); }
367 void on_us_date() { report_no_date(); }
368 void on_iso_date() { report_no_date(); }
369 void on_12_hour_time() {}
370 void on_24_hour_time() {}
371 void on_iso_time() {}
373 void on_duration_value() {}
374 void on_duration_unit() {}
375 void on_utc_offset() { report_no_date(); }
376 void on_tz_name() { report_no_date(); }
379 template <typename Int
> inline int to_int(Int value
) {
380 FMT_ASSERT(value
>= (std::numeric_limits
<int>::min
)() &&
381 value
<= (std::numeric_limits
<int>::max
)(),
383 return static_cast<int>(value
);
386 template <typename Rep
, typename OutputIt
>
387 OutputIt
static format_chrono_duration_value(OutputIt out
, Rep val
,
390 return format_to(out
, std::is_floating_point
<Rep
>::value
? "{:g}" : "{}",
393 return format_to(out
, "{:.{}f}", val
, precision
);
396 template <typename Period
, typename OutputIt
>
397 static OutputIt
format_chrono_duration_unit(OutputIt out
) {
398 if (const char* unit
= get_units
<Period
>())
399 return format_to(out
, "{}", unit
);
400 else if (Period::den
== 1)
401 return format_to(out
, "[{}]s", Period::num
);
403 return format_to(out
, "[{}/{}]s", Period::num
, Period::den
);
406 template <typename FormatContext
, typename OutputIt
, typename Rep
,
408 struct chrono_formatter
{
409 FormatContext
& context
;
413 typedef std::chrono::duration
<Rep
, std::milli
> milliseconds
;
414 std::chrono::seconds s
;
417 typedef typename
FormatContext::char_type char_type
;
419 explicit chrono_formatter(FormatContext
& ctx
, OutputIt o
,
420 std::chrono::duration
<Rep
, Period
> d
)
424 s(std::chrono::duration_cast
<std::chrono::seconds
>(d
)),
425 ms(std::chrono::duration_cast
<milliseconds
>(d
- s
)) {}
427 int hour() const { return to_int((s
.count() / 3600) % 24); }
430 auto hour
= to_int((s
.count() / 3600) % 12);
431 return hour
> 0 ? hour
: 12;
434 int minute() const { return to_int((s
.count() / 60) % 60); }
435 int second() const { return to_int(s
.count() % 60); }
437 std::tm
time() const {
438 auto time
= std::tm();
439 time
.tm_hour
= hour();
440 time
.tm_min
= minute();
441 time
.tm_sec
= second();
445 void write(int value
, int width
) {
446 typedef typename int_traits
<int>::main_type main_type
;
447 main_type n
= to_unsigned(value
);
448 int num_digits
= internal::count_digits(n
);
449 if (width
> num_digits
) out
= std::fill_n(out
, width
- num_digits
, '0');
450 out
= format_decimal
<char_type
>(out
, n
, num_digits
);
453 void format_localized(const tm
& time
, const char* format
) {
454 auto locale
= context
.locale().template get
<std::locale
>();
455 auto& facet
= std::use_facet
<std::time_put
<char_type
>>(locale
);
456 std::basic_ostringstream
<char_type
> os
;
458 facet
.put(os
, os
, ' ', &time
, format
, format
+ std::strlen(format
));
460 std::copy(str
.begin(), str
.end(), out
);
463 void on_text(const char_type
* begin
, const char_type
* end
) {
464 std::copy(begin
, end
, out
);
467 // These are not implemented because durations don't have date information.
468 void on_abbr_weekday() {}
469 void on_full_weekday() {}
470 void on_dec0_weekday(numeric_system
) {}
471 void on_dec1_weekday(numeric_system
) {}
472 void on_abbr_month() {}
473 void on_full_month() {}
474 void on_datetime(numeric_system
) {}
475 void on_loc_date(numeric_system
) {}
476 void on_loc_time(numeric_system
) {}
478 void on_iso_date() {}
479 void on_utc_offset() {}
482 void on_24_hour(numeric_system ns
) {
483 if (ns
== numeric_system::standard
) return write(hour(), 2);
485 time
.tm_hour
= hour();
486 format_localized(time
, "%OH");
489 void on_12_hour(numeric_system ns
) {
490 if (ns
== numeric_system::standard
) return write(hour12(), 2);
492 time
.tm_hour
= hour();
493 format_localized(time
, "%OI");
496 void on_minute(numeric_system ns
) {
497 if (ns
== numeric_system::standard
) return write(minute(), 2);
499 time
.tm_min
= minute();
500 format_localized(time
, "%OM");
503 void on_second(numeric_system ns
) {
504 if (ns
== numeric_system::standard
) {
506 if (ms
!= std::chrono::milliseconds(0)) {
508 write(to_int(ms
.count()), 3);
513 time
.tm_sec
= second();
514 format_localized(time
, "%OS");
517 void on_12_hour_time() { format_localized(time(), "%r"); }
519 void on_24_hour_time() {
531 void on_am_pm() { format_localized(time(), "%p"); }
533 void on_duration_value() {
534 out
= format_chrono_duration_value(out
, val
, precision
);
537 void on_duration_unit() { out
= format_chrono_duration_unit
<Period
>(out
); }
539 } // namespace internal
541 template <typename Rep
, typename Period
, typename Char
>
542 struct formatter
<std::chrono::duration
<Rep
, Period
>, Char
> {
546 typedef internal::arg_ref
<Char
> arg_ref_type
;
547 arg_ref_type width_ref
;
548 arg_ref_type precision_ref
;
549 mutable basic_string_view
<Char
> format_str
;
550 typedef std::chrono::duration
<Rep
, Period
> duration
;
552 struct spec_handler
{
554 basic_parse_context
<Char
>& context
;
555 basic_string_view
<Char
> format_str
;
557 template <typename Id
> FMT_CONSTEXPR arg_ref_type
make_arg_ref(Id arg_id
) {
558 context
.check_arg_id(arg_id
);
559 return arg_ref_type(arg_id
);
562 FMT_CONSTEXPR arg_ref_type
make_arg_ref(basic_string_view
<Char
> arg_id
) {
563 context
.check_arg_id(arg_id
);
564 const auto str_val
= internal::string_view_metadata(format_str
, arg_id
);
565 return arg_ref_type(str_val
);
568 FMT_CONSTEXPR arg_ref_type
make_arg_ref(internal::auto_id
) {
569 return arg_ref_type(context
.next_arg_id());
572 void on_error(const char* msg
) { FMT_THROW(format_error(msg
)); }
573 void on_fill(Char fill
) { f
.spec
.fill_
= fill
; }
574 void on_align(alignment align
) { f
.spec
.align_
= align
; }
575 void on_width(unsigned width
) { f
.spec
.width_
= width
; }
576 void on_precision(unsigned precision
) { f
.precision
= precision
; }
577 void end_precision() {}
579 template <typename Id
> void on_dynamic_width(Id arg_id
) {
580 f
.width_ref
= make_arg_ref(arg_id
);
583 template <typename Id
> void on_dynamic_precision(Id arg_id
) {
584 f
.precision_ref
= make_arg_ref(arg_id
);
589 formatter() : spec(), precision(-1) {}
591 FMT_CONSTEXPR
auto parse(basic_parse_context
<Char
>& ctx
)
592 -> decltype(ctx
.begin()) {
593 auto begin
= ctx
.begin(), end
= ctx
.end();
594 if (begin
== end
) return begin
;
595 spec_handler handler
{*this, ctx
, format_str
};
596 begin
= internal::parse_align(begin
, end
, handler
);
597 if (begin
== end
) return begin
;
598 begin
= internal::parse_width(begin
, end
, handler
);
599 if (begin
== end
) return begin
;
601 if (std::is_floating_point
<Rep
>::value
)
602 begin
= internal::parse_precision(begin
, end
, handler
);
604 handler
.on_error("precision not allowed for this argument type");
606 end
= parse_chrono_format(begin
, end
, internal::chrono_format_checker());
608 basic_string_view
<Char
>(&*begin
, internal::to_unsigned(end
- begin
));
612 template <typename FormatContext
>
613 auto format(const duration
& d
, FormatContext
& ctx
) -> decltype(ctx
.out()) {
614 auto begin
= format_str
.begin(), end
= format_str
.end();
615 // As a possible future optimization, we could avoid extra copying if width
617 basic_memory_buffer
<Char
> buf
;
618 auto out
= std::back_inserter(buf
);
619 typedef output_range
<decltype(ctx
.out()), Char
> range
;
620 basic_writer
<range
> w(range(ctx
.out()));
621 internal::handle_dynamic_spec
<internal::width_checker
>(
622 spec
.width_
, width_ref
, ctx
, format_str
.begin());
623 internal::handle_dynamic_spec
<internal::precision_checker
>(
624 precision
, precision_ref
, ctx
, format_str
.begin());
625 if (begin
== end
|| *begin
== '}') {
626 out
= internal::format_chrono_duration_value(out
, d
.count(), precision
);
627 internal::format_chrono_duration_unit
<Period
>(out
);
629 internal::chrono_formatter
<FormatContext
, decltype(out
), Rep
, Period
> f(
631 f
.precision
= precision
;
632 parse_chrono_format(begin
, end
, f
);
634 w
.write(buf
.data(), buf
.size(), spec
);
641 #endif // FMT_CHRONO_H_