From f5fe84923820a09a768da27ec703026150f747f7 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Wed, 28 Nov 2018 09:23:23 -0800 Subject: [PATCH] Specialize formatter for chrono durations --- include/fmt/time.h | 98 +++++++++++++++++++++++++++++++++++++++++++--- test/time-test.cc | 7 ++++ 2 files changed, 99 insertions(+), 6 deletions(-) diff --git a/include/fmt/time.h b/include/fmt/time.h index 96b07225..732d45c0 100644 --- a/include/fmt/time.h +++ b/include/fmt/time.h @@ -11,6 +11,13 @@ #include "format.h" #include +#ifndef FMT_USE_CHRONO +# define FMT_USE_CHRONO 0 +#endif +#if FMT_USE_CHRONO +# include +#endif + FMT_BEGIN_NAMESPACE // Prevents expansion of a preceding token as a function-style macro. @@ -24,11 +31,9 @@ inline null<> gmtime_r(...) { return null<>(); } inline null<> gmtime_s(...) { return null<>(); } // Parses a put_time-like format string and invokes handler actions. -template -FMT_CONSTEXPR void parse_datetime_format( - basic_string_view format_str, Handler &&handler) { - auto begin = format_str.data(); - auto end = begin + format_str.size(); +template +FMT_CONSTEXPR const Char *parse_chrono_format( + const Char *begin, const Char *end, Handler &&handler) { auto ptr = begin; while (ptr != end) { auto c = *ptr; @@ -65,13 +70,94 @@ FMT_CONSTEXPR void parse_datetime_format( case 'B': handler.on_full_month(); break; + // Hour, minute, second: + case 'S': + handler.on_second(); + break; // TODO: parse more format specifiers } } if (begin != ptr) handler.on_text(begin, ptr); + return ptr; } -} // namespace internal + +struct chrono_format_checker { + template + void on_text(const Char *, const Char *) {} + void on_abbr_weekday() {} + void on_full_weekday() {} + void on_dec0_weekday() {} + void on_dec1_weekday() {} + void on_abbr_month() {} + void on_full_month() {} + void on_second() {} +}; +} // namespace internal + +#if FMT_USE_CHRONO +namespace internal { + +template +struct chrono_formatter { + OutputIt out; + std::chrono::seconds s; + std::chrono::milliseconds ms; + + explicit chrono_formatter(OutputIt o) : out(o) {} + + template + void write(Int value, int width) { + typedef typename int_traits::main_type main_type; + main_type n = value; + auto num_digits = internal::count_digits(n); + if (width > num_digits) + out = std::fill_n(out, width - num_digits, '0'); + out = format_decimal(out, n, num_digits); + } + + void on_text(const Char *, const Char *) {} + void on_abbr_weekday() {} + void on_full_weekday() {} + void on_dec0_weekday() {} + void on_dec1_weekday() {} + void on_abbr_month() {} + void on_full_month() {} + + void on_second() { + write(s.count(), 2); + if (ms != std::chrono::milliseconds()) { + *out++ = '.'; + write(ms.count(), 3); + } + } +}; +} // namespace internal + +template +struct formatter, Char> { + mutable basic_string_view format_str; + using Duration = std::chrono::duration; + + FMT_CONSTEXPR auto parse(basic_parse_context &ctx) + -> decltype(ctx.begin()) { + auto begin = ctx.begin(), end = ctx.end(); + end = parse_chrono_format(begin, end, internal::chrono_format_checker()); + format_str = basic_string_view(&*begin, end - begin); + return end; + } + + template + auto format(const Duration &d, FormatContext &ctx) + -> decltype(ctx.out()) { + internal::chrono_formatter f(ctx.out()); + f.s = std::chrono::duration_cast(d); + f.ms = std::chrono::duration_cast(d - f.s); + parse_chrono_format(format_str.begin(), format_str.end(), f); + return f.out; + } +}; +#endif // FMT_USE_CHRONO // Thread-safe replacement for std::localtime inline std::tm localtime(std::time_t time) { diff --git a/test/time-test.cc b/test/time-test.cc index 30957404..9f092880 100644 --- a/test/time-test.cc +++ b/test/time-test.cc @@ -57,3 +57,10 @@ TEST(TimeTest, GMTime) { std::tm tm = *std::gmtime(&t); EXPECT_TRUE(EqualTime(tm, fmt::gmtime(t))); } + +#if FMT_USE_CHRONO +TEST(TimeTest, Chrono) { + EXPECT_EQ("42", fmt::format("{:%S}", std::chrono::seconds(42))); + EXPECT_EQ("01.234", fmt::format("{:%S}", std::chrono::milliseconds(1234))); +} +#endif