Implement 'chrono' formatting specifiers '%Q' and '%q'

Howard Hinnant's 'date' library recently gained these two new formatting specifiers. This implementation in {fmt} includes support for 'std::chrono::duration' specializations with floating-point representation types and user-definable precision.

Signed-off-by: Daniela Engert <dani@ngrt.de>
This commit is contained in:
Daniela Engert 2019-01-25 16:27:35 +01:00 committed by Victor Zverovich
parent 06c005b7b0
commit 0700612249
2 changed files with 110 additions and 57 deletions

View File

@ -19,6 +19,32 @@
FMT_BEGIN_NAMESPACE
namespace internal {
template <typename Period> FMT_CONSTEXPR const char* get_units() {
return FMT_NULL;
}
template <> FMT_CONSTEXPR const char* get_units<std::atto>() { return "as"; }
template <> FMT_CONSTEXPR const char* get_units<std::femto>() { return "fs"; }
template <> FMT_CONSTEXPR const char* get_units<std::pico>() { return "ps"; }
template <> FMT_CONSTEXPR const char* get_units<std::nano>() { return "ns"; }
template <> FMT_CONSTEXPR const char* get_units<std::micro>() { return "µs"; }
template <> FMT_CONSTEXPR const char* get_units<std::milli>() { return "ms"; }
template <> FMT_CONSTEXPR const char* get_units<std::centi>() { return "cs"; }
template <> FMT_CONSTEXPR const char* get_units<std::deci>() { return "ds"; }
template <> FMT_CONSTEXPR const char* get_units<std::ratio<1>>() { return "s"; }
template <> FMT_CONSTEXPR const char* get_units<std::deca>() { return "das"; }
template <> FMT_CONSTEXPR const char* get_units<std::hecto>() { return "hs"; }
template <> FMT_CONSTEXPR const char* get_units<std::kilo>() { return "ks"; }
template <> FMT_CONSTEXPR const char* get_units<std::mega>() { return "Ms"; }
template <> FMT_CONSTEXPR const char* get_units<std::giga>() { return "Gs"; }
template <> FMT_CONSTEXPR const char* get_units<std::tera>() { return "Ts"; }
template <> FMT_CONSTEXPR const char* get_units<std::peta>() { return "Ps"; }
template <> FMT_CONSTEXPR const char* get_units<std::exa>() { return "Es"; }
template <> FMT_CONSTEXPR const char* get_units<std::ratio<60>>() {
return "m";
}
template <> FMT_CONSTEXPR const char* get_units<std::ratio<3600>>() {
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 <typename Int> inline int to_int(Int value) {
return static_cast<int>(value);
}
template <typename FormatContext, typename OutputIt> struct chrono_formatter {
template <typename Rep, typename OutputIt>
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 <typename Period, typename OutputIt>
static OutputIt format_chrono_duration_unit(OutputIt out) {
if (const char* unit = get_units<Period>())
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 <typename FormatContext, typename OutputIt, typename Rep,
typename Period>
struct chrono_formatter {
FormatContext& context;
OutputIt out;
int precision;
Rep val;
typedef std::chrono::duration<Rep, std::milli> 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<Rep, Period> d)
: context(ctx),
out(o),
val(d.count()),
s(std::chrono::duration_cast<std::chrono::seconds>(d)),
ms(std::chrono::duration_cast<milliseconds>(d - s)) {}
int hour() const { return to_int((s.count() / 3600) % 24); }
@ -328,36 +391,15 @@ template <typename FormatContext, typename OutputIt> 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<Period>(out); }
};
} // namespace internal
template <typename Period> FMT_CONSTEXPR const char* get_units() {
return FMT_NULL;
}
template <> FMT_CONSTEXPR const char* get_units<std::atto>() { return "as"; }
template <> FMT_CONSTEXPR const char* get_units<std::femto>() { return "fs"; }
template <> FMT_CONSTEXPR const char* get_units<std::pico>() { return "ps"; }
template <> FMT_CONSTEXPR const char* get_units<std::nano>() { return "ns"; }
template <> FMT_CONSTEXPR const char* get_units<std::micro>() { return "µs"; }
template <> FMT_CONSTEXPR const char* get_units<std::milli>() { return "ms"; }
template <> FMT_CONSTEXPR const char* get_units<std::centi>() { return "cs"; }
template <> FMT_CONSTEXPR const char* get_units<std::deci>() { return "ds"; }
template <> FMT_CONSTEXPR const char* get_units<std::ratio<1>>() { return "s"; }
template <> FMT_CONSTEXPR const char* get_units<std::deca>() { return "das"; }
template <> FMT_CONSTEXPR const char* get_units<std::hecto>() { return "hs"; }
template <> FMT_CONSTEXPR const char* get_units<std::kilo>() { return "ks"; }
template <> FMT_CONSTEXPR const char* get_units<std::mega>() { return "Ms"; }
template <> FMT_CONSTEXPR const char* get_units<std::giga>() { return "Gs"; }
template <> FMT_CONSTEXPR const char* get_units<std::tera>() { return "Ts"; }
template <> FMT_CONSTEXPR const char* get_units<std::peta>() { return "Ps"; }
template <> FMT_CONSTEXPR const char* get_units<std::exa>() { return "Es"; }
template <> FMT_CONSTEXPR const char* get_units<std::ratio<60>>() {
return "m";
}
template <> FMT_CONSTEXPR const char* get_units<std::ratio<3600>>() {
return "h";
}
template <typename Rep, typename Period, typename Char>
struct formatter<std::chrono::duration<Rep, Period>, Char> {
private:
@ -405,22 +447,6 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
}
};
template <typename OutputIt> OutputIt format_value(OutputIt out, Rep val) {
if (precision < 0)
return format_to(out, "{}", val);
else
return format_to(out, "{:.{}f}", val, precision);
}
template <typename OutputIt> static void format_unit(OutputIt out) {
if (const char* unit = get_units<Period>())
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<std::chrono::duration<Rep, Period>, Char> {
basic_writer<range> w(range(ctx.out()));
internal::handle_dynamic_spec<internal::width_checker>(spec.width_,
width_ref, ctx);
if (begin == end || *begin == '}') {
internal::handle_dynamic_spec<internal::precision_checker>(
precision, precision_ref, ctx);
out = format_value(out, d.count());
format_unit(out);
if (begin == end || *begin == '}') {
out = internal::format_chrono_duration_value(out, d.count(), precision);
internal::format_chrono_duration_unit<Period>(out);
} else {
internal::chrono_formatter<FormatContext, decltype(out)> f(ctx, out);
f.s = std::chrono::duration_cast<std::chrono::seconds>(d);
f.ms = std::chrono::duration_cast<std::chrono::milliseconds>(d - f.s);
internal::chrono_formatter<FormatContext, decltype(out), Rep, Period> f(
ctx, out, d);
f.precision = precision;
parse_chrono_format(begin, end, f);
}
w.write(buf.data(), buf.size(), spec);

View File

@ -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<float> fs;
EXPECT_EQ("1.234 s", fmt::format("{:%Q %q}", fs(1.234)));
typedef std::chrono::duration<float, std::milli> fms;
EXPECT_EQ("1.234 ms", fmt::format("{:%Q %q}", fms(1.234)));
typedef std::chrono::duration<double> 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