mirror of
https://github.com/fmtlib/fmt.git
synced 2024-10-02 04:52:03 +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> {
|
struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||||
private:
|
private:
|
||||||
align_spec spec;
|
align_spec spec;
|
||||||
|
int precision;
|
||||||
typedef internal::arg_ref<Char> arg_ref_type;
|
typedef internal::arg_ref<Char> arg_ref_type;
|
||||||
arg_ref_type width_ref;
|
arg_ref_type width_ref;
|
||||||
|
arg_ref_type precision_ref;
|
||||||
mutable basic_string_view<Char> format_str;
|
mutable basic_string_view<Char> format_str;
|
||||||
typedef std::chrono::duration<Rep, Period> duration;
|
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_fill(Char fill) { f.spec.fill_ = fill; }
|
||||||
void on_align(alignment align) { f.spec.align_ = align; }
|
void on_align(alignment align) { f.spec.align_ = align; }
|
||||||
void on_width(unsigned width) { f.spec.width_ = width; }
|
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) {
|
template <typename Id> void on_dynamic_width(Id arg_id) {
|
||||||
f.width_ref = make_arg_ref(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:
|
public:
|
||||||
formatter() : spec() {}
|
formatter() : spec(), precision(-1) {}
|
||||||
|
|
||||||
FMT_CONSTEXPR auto parse(basic_parse_context<Char>& ctx)
|
FMT_CONSTEXPR auto parse(basic_parse_context<Char>& ctx)
|
||||||
-> decltype(ctx.begin()) {
|
-> decltype(ctx.begin()) {
|
||||||
@ -408,6 +432,13 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
|||||||
begin = internal::parse_align(begin, end, handler);
|
begin = internal::parse_align(begin, end, handler);
|
||||||
if (begin == end) return begin;
|
if (begin == end) return begin;
|
||||||
begin = internal::parse_width(begin, end, handler);
|
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());
|
end = parse_chrono_format(begin, end, internal::chrono_format_checker());
|
||||||
format_str =
|
format_str =
|
||||||
basic_string_view<Char>(&*begin, internal::to_unsigned(end - begin));
|
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();
|
auto begin = format_str.begin(), end = format_str.end();
|
||||||
// As a possible future optimization, we could avoid extra copying if width
|
// As a possible future optimization, we could avoid extra copying if width
|
||||||
// is not specified.
|
// 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;
|
typedef output_range<decltype(ctx.out()), Char> range;
|
||||||
basic_writer<range> w(range(ctx.out()));
|
basic_writer<range> w(range(ctx.out()));
|
||||||
|
internal::handle_dynamic_spec<internal::width_checker>(spec.width_,
|
||||||
|
width_ref, ctx);
|
||||||
if (begin == end || *begin == '}') {
|
if (begin == end || *begin == '}') {
|
||||||
if (const char* unit = get_units<Period>())
|
internal::handle_dynamic_spec<internal::precision_checker>(
|
||||||
format_to(buf, "{}{}", d.count(), unit);
|
precision, precision_ref, ctx);
|
||||||
else if (Period::den == 1)
|
out = format_value(out, d.count());
|
||||||
format_to(buf, "{}[{}]s", d.count(), Period::num);
|
format_unit(out);
|
||||||
else
|
|
||||||
format_to(buf, "{}[{}/{}]s", d.count(), Period::num, Period::den);
|
|
||||||
} else {
|
} else {
|
||||||
auto out = std::back_inserter(buf);
|
|
||||||
internal::chrono_formatter<FormatContext, decltype(out)> f(ctx, out);
|
internal::chrono_formatter<FormatContext, decltype(out)> f(ctx, out);
|
||||||
f.s = std::chrono::duration_cast<std::chrono::seconds>(d);
|
f.s = std::chrono::duration_cast<std::chrono::seconds>(d);
|
||||||
f.ms = std::chrono::duration_cast<std::chrono::milliseconds>(d - f.s);
|
f.ms = std::chrono::duration_cast<std::chrono::milliseconds>(d - f.s);
|
||||||
parse_chrono_format(begin, end, f);
|
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);
|
w.write(buf.data(), buf.size(), spec);
|
||||||
return w.out();
|
return w.out();
|
||||||
}
|
}
|
||||||
|
@ -1935,6 +1935,28 @@ FMT_CONSTEXPR const Char* parse_width(const Char* begin, const Char* end,
|
|||||||
return begin;
|
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
|
// Parses standard format specifiers and sends notifications about parsed
|
||||||
// components to handler.
|
// components to handler.
|
||||||
template <typename Char, typename SpecHandler>
|
template <typename Char, typename SpecHandler>
|
||||||
@ -1978,22 +2000,7 @@ FMT_CONSTEXPR const Char* parse_format_specs(const Char* begin, const Char* end,
|
|||||||
|
|
||||||
// Parse precision.
|
// Parse precision.
|
||||||
if (*begin == '.') {
|
if (*begin == '.') {
|
||||||
++begin;
|
begin = parse_precision(begin, end, handler);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse type.
|
// Parse type.
|
||||||
|
@ -189,4 +189,33 @@ TEST(ChronoTest, Locale) {
|
|||||||
EXPECT_TIME("%p", time, sec);
|
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
|
#endif // FMT_STATIC_THOUSANDS_SEPARATOR
|
||||||
|
Loading…
Reference in New Issue
Block a user