Skip to content

Commit

Permalink
Implement precision for floating-point durations.
Browse files Browse the repository at this point in the history
The formatting syntax follows p1361r0, augmented by a precision field as proposed in #1004.

Signed-off-by: Daniela Engert <[email protected]>
  • Loading branch information
DanielaE committed Jan 19, 2019
1 parent fdd8e33 commit 0aaffc7
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 27 deletions.
51 changes: 40 additions & 11 deletions include/fmt/chrono.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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()) {
Expand All @@ -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>{})
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));
Expand All @@ -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();
}
Expand Down
39 changes: 23 additions & 16 deletions include/fmt/format.h
Original file line number Diff line number Diff line change
Expand Up @@ -1936,6 +1936,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>
Expand Down Expand Up @@ -1979,22 +2001,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.
Expand Down
29 changes: 29 additions & 0 deletions test/chrono-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 0aaffc7

Please sign in to comment.