diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index c8bcbb71..d4ee7526 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -19,6 +19,32 @@ FMT_BEGIN_NAMESPACE namespace internal { +template FMT_CONSTEXPR const char* get_units() { + return FMT_NULL; +} +template <> FMT_CONSTEXPR const char* get_units() { return "as"; } +template <> FMT_CONSTEXPR const char* get_units() { return "fs"; } +template <> FMT_CONSTEXPR const char* get_units() { return "ps"; } +template <> FMT_CONSTEXPR const char* get_units() { return "ns"; } +template <> FMT_CONSTEXPR const char* get_units() { return "µs"; } +template <> FMT_CONSTEXPR const char* get_units() { return "ms"; } +template <> FMT_CONSTEXPR const char* get_units() { return "cs"; } +template <> FMT_CONSTEXPR const char* get_units() { return "ds"; } +template <> FMT_CONSTEXPR const char* get_units>() { return "s"; } +template <> FMT_CONSTEXPR const char* get_units() { return "das"; } +template <> FMT_CONSTEXPR const char* get_units() { return "hs"; } +template <> FMT_CONSTEXPR const char* get_units() { return "ks"; } +template <> FMT_CONSTEXPR const char* get_units() { return "Ms"; } +template <> FMT_CONSTEXPR const char* get_units() { return "Gs"; } +template <> FMT_CONSTEXPR const char* get_units() { return "Ts"; } +template <> FMT_CONSTEXPR const char* get_units() { return "Ps"; } +template <> FMT_CONSTEXPR const char* get_units() { return "Es"; } +template <> FMT_CONSTEXPR const char* get_units>() { + return "m"; +} +template <> FMT_CONSTEXPR const char* get_units>() { + return "h"; +} enum class numeric_system { standard, @@ -118,6 +144,12 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, case 'p': handler.on_am_pm(); break; + case 'Q': + handler.on_duration_value(); + break; + case 'q': + handler.on_duration_unit(); + break; case 'z': handler.on_utc_offset(); break; @@ -201,6 +233,8 @@ struct chrono_format_checker { void on_24_hour_time() {} void on_iso_time() {} void on_am_pm() {} + void on_duration_value() {} + void on_duration_unit() {} void on_utc_offset() { report_no_date(); } void on_tz_name() { report_no_date(); } }; @@ -212,16 +246,45 @@ template inline int to_int(Int value) { return static_cast(value); } -template struct chrono_formatter { +template +OutputIt static format_chrono_duration_value(OutputIt out, Rep val, + int precision) { + if (precision < 0) + return format_to(out, "{}", val); + else + return format_to(out, "{:.{}f}", val, precision); +} + +template +static OutputIt format_chrono_duration_unit(OutputIt out) { + if (const char* unit = get_units()) + return format_to(out, "{}", unit); + else if (Period::den == 1) + return format_to(out, "[{}]s", Period::num); + else + return format_to(out, "[{}/{}]s", Period::num, Period::den); +} + +template +struct chrono_formatter { FormatContext& context; OutputIt out; + int precision; + Rep val; + typedef std::chrono::duration milliseconds; std::chrono::seconds s; - std::chrono::milliseconds ms; + milliseconds ms; typedef typename FormatContext::char_type char_type; - explicit chrono_formatter(FormatContext& ctx, OutputIt o) - : context(ctx), out(o) {} + explicit chrono_formatter(FormatContext& ctx, OutputIt o, + std::chrono::duration d) + : context(ctx), + out(o), + val(d.count()), + s(std::chrono::duration_cast(d)), + ms(std::chrono::duration_cast(d - s)) {} int hour() const { return to_int((s.count() / 3600) % 24); } @@ -328,36 +391,15 @@ template struct chrono_formatter { } void on_am_pm() { format_localized(time(), "%p"); } + + void on_duration_value() { + out = format_chrono_duration_value(out, val, precision); + } + + void on_duration_unit() { out = format_chrono_duration_unit(out); } }; } // namespace internal -template FMT_CONSTEXPR const char* get_units() { - return FMT_NULL; -} -template <> FMT_CONSTEXPR const char* get_units() { return "as"; } -template <> FMT_CONSTEXPR const char* get_units() { return "fs"; } -template <> FMT_CONSTEXPR const char* get_units() { return "ps"; } -template <> FMT_CONSTEXPR const char* get_units() { return "ns"; } -template <> FMT_CONSTEXPR const char* get_units() { return "µs"; } -template <> FMT_CONSTEXPR const char* get_units() { return "ms"; } -template <> FMT_CONSTEXPR const char* get_units() { return "cs"; } -template <> FMT_CONSTEXPR const char* get_units() { return "ds"; } -template <> FMT_CONSTEXPR const char* get_units>() { return "s"; } -template <> FMT_CONSTEXPR const char* get_units() { return "das"; } -template <> FMT_CONSTEXPR const char* get_units() { return "hs"; } -template <> FMT_CONSTEXPR const char* get_units() { return "ks"; } -template <> FMT_CONSTEXPR const char* get_units() { return "Ms"; } -template <> FMT_CONSTEXPR const char* get_units() { return "Gs"; } -template <> FMT_CONSTEXPR const char* get_units() { return "Ts"; } -template <> FMT_CONSTEXPR const char* get_units() { return "Ps"; } -template <> FMT_CONSTEXPR const char* get_units() { return "Es"; } -template <> FMT_CONSTEXPR const char* get_units>() { - return "m"; -} -template <> FMT_CONSTEXPR const char* get_units>() { - return "h"; -} - template struct formatter, Char> { private: @@ -405,22 +447,6 @@ struct formatter, Char> { } }; - template OutputIt format_value(OutputIt out, Rep val) { - if (precision < 0) - return format_to(out, "{}", val); - else - return format_to(out, "{:.{}f}", val, precision); - } - - template static void format_unit(OutputIt out) { - if (const char* unit = get_units()) - format_to(out, "{}", unit); - else if (Period::den == 1) - format_to(out, "[{}]s", Period::num); - else - format_to(out, "[{}/{}]s", Period::num, Period::den); - } - public: formatter() : spec(), precision(-1) {} @@ -456,15 +482,15 @@ struct formatter, Char> { basic_writer w(range(ctx.out())); internal::handle_dynamic_spec(spec.width_, width_ref, ctx); + internal::handle_dynamic_spec( + precision, precision_ref, ctx); if (begin == end || *begin == '}') { - internal::handle_dynamic_spec( - precision, precision_ref, ctx); - out = format_value(out, d.count()); - format_unit(out); + out = internal::format_chrono_duration_value(out, d.count(), precision); + internal::format_chrono_duration_unit(out); } else { - internal::chrono_formatter f(ctx, out); - f.s = std::chrono::duration_cast(d); - f.ms = std::chrono::duration_cast(d - f.s); + internal::chrono_formatter f( + ctx, out, d); + f.precision = precision; parse_chrono_format(begin, end, f); } w.write(buf.data(), buf.size(), spec); diff --git a/test/chrono-test.cc b/test/chrono-test.cc index b7aace4f..363fafaa 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -135,6 +135,8 @@ TEST(ChronoTest, FormatSpecs) { fmt::format("{:%H:%M:%S}", std::chrono::seconds(12345))); EXPECT_EQ("03:25", fmt::format("{:%R}", std::chrono::seconds(12345))); EXPECT_EQ("03:25:45", fmt::format("{:%T}", std::chrono::seconds(12345))); + EXPECT_EQ("12345", fmt::format("{:%Q}", std::chrono::seconds(12345))); + EXPECT_EQ("s", fmt::format("{:%q}", std::chrono::seconds(12345))); } TEST(ChronoTest, InvalidSpecs) { @@ -155,8 +157,6 @@ TEST(ChronoTest, InvalidSpecs) { EXPECT_THROW_MSG(fmt::format("{:%B}", sec), fmt::format_error, "no date"); EXPECT_THROW_MSG(fmt::format("{:%z}", sec), fmt::format_error, "no date"); EXPECT_THROW_MSG(fmt::format("{:%Z}", sec), fmt::format_error, "no date"); - EXPECT_THROW_MSG(fmt::format("{:%q}", sec), fmt::format_error, - "invalid format"); EXPECT_THROW_MSG(fmt::format("{:%Eq}", sec), fmt::format_error, "invalid format"); EXPECT_THROW_MSG(fmt::format("{:%Oq}", sec), fmt::format_error, @@ -215,7 +215,34 @@ TEST(ChronoTest, FormatFullSpecs) { EXPECT_EQ(" 1.2ms ", fmt::format("{:^{}.{}}", dms(1.234), 7, 1)); EXPECT_EQ(" 1.23ms ", fmt::format("{0:^{2}.{1}}", dms(1.234), 2, 8)); EXPECT_EQ("=1.234ms=", fmt::format("{:=^{}.{}}", dms(1.234), 9, 3)); - EXPECT_EQ("*1.2340ms*", fmt::format("{:*^10.4}", dms(1.234), 10, 4)); + EXPECT_EQ("*1.2340ms*", fmt::format("{:*^10.4}", dms(1.234))); +} + +TEST(ChronoTest, FormatSimpleQq) { + typedef std::chrono::duration fs; + EXPECT_EQ("1.234 s", fmt::format("{:%Q %q}", fs(1.234))); + typedef std::chrono::duration fms; + EXPECT_EQ("1.234 ms", fmt::format("{:%Q %q}", fms(1.234))); + typedef std::chrono::duration ds; + EXPECT_EQ("1.234 s", fmt::format("{:%Q %q}", ds(1.234))); + EXPECT_EQ("1.234 ms", fmt::format("{:%Q %q}", dms(1.234))); +} + +TEST(ChronoTest, FormatPrecisionQq) { + EXPECT_THROW_MSG(fmt::format("{:.2%Q %q}", std::chrono::seconds(42)), + fmt::format_error, + "precision not allowed for this argument type"); + EXPECT_EQ("1.2 ms", fmt::format("{:.1%Q %q}", dms(1.234))); + EXPECT_EQ("1.23 ms", fmt::format("{:.{}%Q %q}", dms(1.234), 2)); +} + +TEST(ChronoTest, FormatFullSpecsQq) { + EXPECT_EQ("1.2 ms ", fmt::format("{:7.1%Q %q}", dms(1.234))); + EXPECT_EQ(" 1.23 ms", fmt::format("{:>8.{}%Q %q}", dms(1.234), 2)); + EXPECT_EQ(" 1.2 ms ", fmt::format("{:^{}.{}%Q %q}", dms(1.234), 8, 1)); + EXPECT_EQ(" 1.23 ms ", fmt::format("{0:^{2}.{1}%Q %q}", dms(1.234), 2, 9)); + EXPECT_EQ("=1.234 ms=", fmt::format("{:=^{}.{}%Q %q}", dms(1.234), 10, 3)); + EXPECT_EQ("*1.2340 ms*", fmt::format("{:*^11.4%Q %q}", dms(1.234))); } #endif // FMT_STATIC_THOUSANDS_SEPARATOR