mirror of
https://github.com/fmtlib/fmt.git
synced 2024-11-19 11:14:41 +00:00
Implement precision for floating-point durations.
The formatting syntax follows p1361r0, augmented by a precision field as proposed in #1004. Signed-off-by: Daniela Engert <dani@ngrt.de>
This commit is contained in:
parent
7cdb1e5e40
commit
9f70b034e1
@ -362,8 +362,10 @@ template <typename Rep, typename Period, typename Char>
|
||||
struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||
private:
|
||||
align_spec spec;
|
||||
int precision;
|
||||
typedef internal::arg_ref<Char> arg_ref_type;
|
||||
arg_ref_type width_ref;
|
||||
arg_ref_type precision_ref;
|
||||
mutable basic_string_view<Char> format_str;
|
||||
typedef std::chrono::duration<Rep, Period> duration;
|
||||
|
||||
@ -391,14 +393,36 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||
void on_fill(Char fill) { f.spec.fill_ = fill; }
|
||||
void on_align(alignment align) { f.spec.align_ = align; }
|
||||
void on_width(unsigned width) { f.spec.width_ = width; }
|
||||
void on_precision(unsigned precision) { f.precision = precision; }
|
||||
void end_precision() {}
|
||||
|
||||
template <typename Id> void on_dynamic_width(Id arg_id) {
|
||||
f.width_ref = make_arg_ref(arg_id);
|
||||
}
|
||||
|
||||
template <typename Id> void on_dynamic_precision(Id arg_id) {
|
||||
f.precision_ref = make_arg_ref(arg_id);
|
||||
}
|
||||
};
|
||||
|
||||
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() {}
|
||||
formatter() : spec(), precision(-1) {}
|
||||
|
||||
FMT_CONSTEXPR auto parse(basic_parse_context<Char>& ctx)
|
||||
-> decltype(ctx.begin()) {
|
||||
@ -408,6 +432,13 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||
begin = internal::parse_align(begin, end, handler);
|
||||
if (begin == end) return begin;
|
||||
begin = internal::parse_width(begin, end, handler);
|
||||
if (begin == end) return begin;
|
||||
if (*begin == '.') {
|
||||
if (std::is_floating_point<Rep>::value)
|
||||
begin = internal::parse_precision(begin, end, handler);
|
||||
else
|
||||
handler.on_error("precision not allowed for this argument type");
|
||||
}
|
||||
end = parse_chrono_format(begin, end, internal::chrono_format_checker());
|
||||
format_str =
|
||||
basic_string_view<Char>(&*begin, internal::to_unsigned(end - begin));
|
||||
@ -419,25 +450,23 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||
auto begin = format_str.begin(), end = format_str.end();
|
||||
// As a possible future optimization, we could avoid extra copying if width
|
||||
// is not specified.
|
||||
memory_buffer buf;
|
||||
basic_memory_buffer<Char> buf;
|
||||
auto out = std::back_inserter(buf);
|
||||
typedef output_range<decltype(ctx.out()), Char> range;
|
||||
basic_writer<range> w(range(ctx.out()));
|
||||
internal::handle_dynamic_spec<internal::width_checker>(spec.width_,
|
||||
width_ref, ctx);
|
||||
if (begin == end || *begin == '}') {
|
||||
if (const char* unit = get_units<Period>())
|
||||
format_to(buf, "{}{}", d.count(), unit);
|
||||
else if (Period::den == 1)
|
||||
format_to(buf, "{}[{}]s", d.count(), Period::num);
|
||||
else
|
||||
format_to(buf, "{}[{}/{}]s", d.count(), Period::num, Period::den);
|
||||
internal::handle_dynamic_spec<internal::precision_checker>(
|
||||
precision, precision_ref, ctx);
|
||||
out = format_value(out, d.count());
|
||||
format_unit(out);
|
||||
} else {
|
||||
auto out = std::back_inserter(buf);
|
||||
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);
|
||||
parse_chrono_format(begin, end, f);
|
||||
}
|
||||
internal::handle_dynamic_spec<internal::width_checker>(spec.width_,
|
||||
width_ref, ctx);
|
||||
w.write(buf.data(), buf.size(), spec);
|
||||
return w.out();
|
||||
}
|
||||
|
@ -1935,6 +1935,28 @@ FMT_CONSTEXPR const Char* parse_width(const Char* begin, const Char* end,
|
||||
return begin;
|
||||
}
|
||||
|
||||
template <typename Char, typename Handler>
|
||||
FMT_CONSTEXPR const Char* parse_precision(const Char* begin, const Char* end,
|
||||
Handler&& handler) {
|
||||
++begin;
|
||||
auto c = begin != end ? *begin : 0;
|
||||
if ('0' <= c && c <= '9') {
|
||||
handler.on_precision(parse_nonnegative_int(begin, end, handler));
|
||||
} else if (c == '{') {
|
||||
++begin;
|
||||
if (begin != end) {
|
||||
begin =
|
||||
parse_arg_id(begin, end, precision_adapter<Handler, Char>(handler));
|
||||
}
|
||||
if (begin == end || *begin++ != '}')
|
||||
return handler.on_error("invalid format string"), begin;
|
||||
} else {
|
||||
return handler.on_error("missing precision specifier"), begin;
|
||||
}
|
||||
handler.end_precision();
|
||||
return begin;
|
||||
}
|
||||
|
||||
// Parses standard format specifiers and sends notifications about parsed
|
||||
// components to handler.
|
||||
template <typename Char, typename SpecHandler>
|
||||
@ -1978,22 +2000,7 @@ FMT_CONSTEXPR const Char* parse_format_specs(const Char* begin, const Char* end,
|
||||
|
||||
// Parse precision.
|
||||
if (*begin == '.') {
|
||||
++begin;
|
||||
auto c = begin != end ? *begin : 0;
|
||||
if ('0' <= c && c <= '9') {
|
||||
handler.on_precision(parse_nonnegative_int(begin, end, handler));
|
||||
} else if (c == '{') {
|
||||
++begin;
|
||||
if (begin != end) {
|
||||
begin = parse_arg_id(begin, end,
|
||||
precision_adapter<SpecHandler, Char>(handler));
|
||||
}
|
||||
if (begin == end || *begin++ != '}')
|
||||
return handler.on_error("invalid format string"), begin;
|
||||
} else {
|
||||
return handler.on_error("missing precision specifier"), begin;
|
||||
}
|
||||
handler.end_precision();
|
||||
begin = parse_precision(begin, end, handler);
|
||||
}
|
||||
|
||||
// Parse type.
|
||||
|
@ -189,4 +189,33 @@ TEST(ChronoTest, Locale) {
|
||||
EXPECT_TIME("%p", time, sec);
|
||||
}
|
||||
|
||||
typedef std::chrono::duration<double, std::milli> dms;
|
||||
|
||||
TEST(ChronoTest, FormatDefaultFP) {
|
||||
typedef std::chrono::duration<float> fs;
|
||||
EXPECT_EQ("1.234s", fmt::format("{}", fs(1.234)));
|
||||
typedef std::chrono::duration<float, std::milli> fms;
|
||||
EXPECT_EQ("1.234ms", fmt::format("{}", fms(1.234)));
|
||||
typedef std::chrono::duration<double> ds;
|
||||
EXPECT_EQ("1.234s", fmt::format("{}", ds(1.234)));
|
||||
EXPECT_EQ("1.234ms", fmt::format("{}", dms(1.234)));
|
||||
}
|
||||
|
||||
TEST(ChronoTest, FormatPrecision) {
|
||||
EXPECT_THROW_MSG(fmt::format("{:.2}", std::chrono::seconds(42)),
|
||||
fmt::format_error,
|
||||
"precision not allowed for this argument type");
|
||||
EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234)));
|
||||
EXPECT_EQ("1.23ms", fmt::format("{:.{}}", dms(1.234), 2));
|
||||
}
|
||||
|
||||
TEST(ChronoTest, FormatFullSpecs) {
|
||||
EXPECT_EQ("1.2ms ", fmt::format("{:6.1}", dms(1.234)));
|
||||
EXPECT_EQ(" 1.23ms", fmt::format("{:>8.{}}", dms(1.234), 2));
|
||||
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));
|
||||
}
|
||||
|
||||
#endif // FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
|
Loading…
Reference in New Issue
Block a user