Reuse precision modifier in seconds specification

This commit is contained in:
Stepan Ponomarev 2022-09-28 13:48:09 +03:00
parent 8c19bf3f2f
commit 85a4cefd86
2 changed files with 88 additions and 11 deletions

View File

@ -1056,6 +1056,43 @@ void write_fractional_seconds(OutputIt& out, Duration d) {
} }
} }
template <typename Char, typename OutputIt, typename Duration>
void write_fractional_seconds(OutputIt& out, Duration d, int precision) {
constexpr auto num_fractional_digits =
count_fractional_digits<Duration::period::num,
Duration::period::den>::value;
using subsecond_precision = std::chrono::duration<
typename std::common_type<typename Duration::rep,
std::chrono::seconds::rep>::type,
std::ratio<1, detail::pow10(num_fractional_digits)>>;
if (precision > 0) {
*out++ = '.';
auto fractional =
detail::abs(d) - std::chrono::duration_cast<std::chrono::seconds>(d);
auto subseconds =
std::chrono::treat_as_floating_point<
typename subsecond_precision::rep>::value
? fractional.count()
: std::chrono::duration_cast<subsecond_precision>(fractional)
.count();
uint32_or_64_or_128_t<long long> n =
to_unsigned(to_nonnegative_int(subseconds, max_value<long long>()));
int num_digits = detail::count_digits(n);
int zeroes = std::min(num_fractional_digits - num_digits, precision);
if (num_fractional_digits > num_digits) out = std::fill_n(out, zeroes, '0');
int remaining = precision - (zeroes > 0 ? zeroes : 0);
if (remaining < num_digits) {
n /= to_unsigned(detail::pow10(to_unsigned(num_digits - remaining)));
out = format_decimal<Char>(out, n, remaining).end;
return;
}
out = format_decimal<Char>(out, n, num_digits).end;
remaining -= num_digits;
out = std::fill_n(out, remaining, '0');
}
}
// Format subseconds which are given as a floating point type with an appropiate // Format subseconds which are given as a floating point type with an appropiate
// number of digits. We cannot pass the Duration here, as we explicitly need to // number of digits. We cannot pass the Duration here, as we explicitly need to
// pass the Rep value in the chrono_formatter. // pass the Rep value in the chrono_formatter.
@ -1081,6 +1118,25 @@ void write_floating_seconds(memory_buffer& buf, Duration duration) {
num_fractional_digits); num_fractional_digits);
} }
template <typename Duration>
void write_floating_seconds(memory_buffer& buf, Duration duration, int precision) {
if (precision < 0) {
write_floating_seconds(buf, duration);
return;
}
FMT_ASSERT(std::is_floating_point<typename Duration::rep>::value, "");
auto val = duration.count();
format_to(
std::back_inserter(buf), runtime("{:.{}f}"),
std::fmod(val *
static_cast<typename Duration::rep>(Duration::period::num) /
static_cast<typename Duration::rep>(Duration::period::den),
static_cast<typename Duration::rep>(60)),
precision);
}
template <typename OutputIt, typename Char, template <typename OutputIt, typename Char,
typename Duration = std::chrono::seconds> typename Duration = std::chrono::seconds>
class tm_writer { class tm_writer {
@ -1518,6 +1574,9 @@ class tm_writer {
}; };
struct chrono_format_checker : null_chrono_spec_handler<chrono_format_checker> { struct chrono_format_checker : null_chrono_spec_handler<chrono_format_checker> {
bool is_floating_point = false;
bool has_precision = false;
FMT_NORETURN void unsupported() { FMT_THROW(format_error("no date")); } FMT_NORETURN void unsupported() { FMT_THROW(format_error("no date")); }
template <typename Char> template <typename Char>
@ -1530,7 +1589,11 @@ struct chrono_format_checker : null_chrono_spec_handler<chrono_format_checker> {
FMT_CONSTEXPR void on_24_hour_time() {} FMT_CONSTEXPR void on_24_hour_time() {}
FMT_CONSTEXPR void on_iso_time() {} FMT_CONSTEXPR void on_iso_time() {}
FMT_CONSTEXPR void on_am_pm() {} FMT_CONSTEXPR void on_am_pm() {}
FMT_CONSTEXPR void on_duration_value() {} FMT_CONSTEXPR void on_duration_value() const {
if (has_precision && !is_floating_point) {
FMT_THROW(format_error("precision not allowed for this argument type"));
}
}
FMT_CONSTEXPR void on_duration_unit() {} FMT_CONSTEXPR void on_duration_unit() {}
}; };
@ -1831,14 +1894,17 @@ struct chrono_formatter {
if (ns == numeric_system::standard) { if (ns == numeric_system::standard) {
if (std::is_floating_point<rep>::value) { if (std::is_floating_point<rep>::value) {
auto buf = memory_buffer(); auto buf = memory_buffer();
write_floating_seconds(buf, std::chrono::duration<rep, Period>(val)); write_floating_seconds(buf, std::chrono::duration<rep, Period>(val), precision);
if (negative) *out++ = '-'; if (negative) *out++ = '-';
if (buf.size() < 2 || buf[1] == '.') *out++ = '0'; if (buf.size() < 2 || buf[1] == '.') *out++ = '0';
out = std::copy(buf.begin(), buf.end(), out); out = std::copy(buf.begin(), buf.end(), out);
} else { } else {
write(second(), 2); write(second(), 2);
write_fractional_seconds<char_type>( if (precision >= 0) {
out, std::chrono::duration<rep, Period>(val)); write_fractional_seconds<char_type>(out, std::chrono::duration<rep, Period>(val), precision);
} else {
write_fractional_seconds<char_type>(out, std::chrono::duration<rep, Period>(val));
}
} }
return; return;
} }
@ -1999,18 +2065,17 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
if (begin == end) return {begin, begin}; if (begin == end) return {begin, begin};
begin = detail::parse_width(begin, end, handler); begin = detail::parse_width(begin, end, handler);
if (begin == end) return {begin, begin}; if (begin == end) return {begin, begin};
auto checker = detail::chrono_format_checker();
checker.is_floating_point = std::is_floating_point<Rep>::value;
if (*begin == '.') { if (*begin == '.') {
if (std::is_floating_point<Rep>::value) checker.has_precision = true;
begin = detail::parse_precision(begin, end, handler); begin = detail::parse_precision(begin, end, handler);
else
handler.on_error("precision not allowed for this argument type");
} }
if (begin != end && *begin == 'L') { if (begin != end && *begin == 'L') {
++begin; ++begin;
localized = true; localized = true;
} }
end = detail::parse_chrono_format(begin, end, end = detail::parse_chrono_format(begin, end, checker);
detail::chrono_format_checker());
return {begin, end}; return {begin, end};
} }

View File

@ -466,7 +466,7 @@ TEST(chrono_test, format_default_fp) {
TEST(chrono_test, format_precision) { TEST(chrono_test, format_precision) {
EXPECT_THROW_MSG( EXPECT_THROW_MSG(
(void)fmt::format(runtime("{:.2}"), std::chrono::seconds(42)), (void)fmt::format(runtime("{:.2%Q}"), std::chrono::seconds(42)),
fmt::format_error, "precision not allowed for this argument type"); fmt::format_error, "precision not allowed for this argument type");
EXPECT_EQ("1ms", fmt::format("{:.0}", dms(1.234))); EXPECT_EQ("1ms", fmt::format("{:.0}", dms(1.234)));
EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234))); EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234)));
@ -611,12 +611,23 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
EXPECT_EQ(fmt::format("{:%S}", std::chrono::nanoseconds{-13420148734}), EXPECT_EQ(fmt::format("{:%S}", std::chrono::nanoseconds{-13420148734}),
"-13.420148734"); "-13.420148734");
EXPECT_EQ(fmt::format("{:%S}", std::chrono::milliseconds{1234}), "01.234"); EXPECT_EQ(fmt::format("{:%S}", std::chrono::milliseconds{1234}), "01.234");
// Check subsecond presision modifier.
EXPECT_EQ(fmt::format("{:.6%S}", std::chrono::nanoseconds{1234}), "00.000001");
EXPECT_EQ(fmt::format("{:.18%S}", std::chrono::nanoseconds{1234}), "00.000001234000000000");
EXPECT_EQ(fmt::format("{:.{}%S}", std::chrono::nanoseconds{1234}, 6), "00.000001");
EXPECT_EQ(fmt::format("{:.6%S}", std::chrono::milliseconds{1234}), "01.234000");
EXPECT_EQ(fmt::format("{:.6%S}", std::chrono::milliseconds{-1234}), "-01.234000");
EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::seconds{1234}), "34.000");
EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::hours{1234}), "00.000");
EXPECT_EQ(fmt::format("{:.5%S}", dms(1.234)), "00.00123");
EXPECT_EQ(fmt::format("{:.8%S}", dms(1.234)), "00.00123400");
{ {
// Check that {:%H:%M:%S} is equivalent to {:%T}. // Check that {:%H:%M:%S} is equivalent to {:%T}.
auto dur = std::chrono::milliseconds{3601234}; auto dur = std::chrono::milliseconds{3601234};
auto formatted_dur = fmt::format("{:%T}", dur); auto formatted_dur = fmt::format("{:%T}", dur);
EXPECT_EQ(formatted_dur, "01:00:01.234"); EXPECT_EQ(formatted_dur, "01:00:01.234");
EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur); EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur);
EXPECT_EQ(fmt::format("{:.6%H:%M:%S}", dur), "01:00:01.234000");
} }
using nanoseconds_dbl = std::chrono::duration<double, std::nano>; using nanoseconds_dbl = std::chrono::duration<double, std::nano>;
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{-123456789}), "-00.123456789"); EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{-123456789}), "-00.123456789");
@ -630,6 +641,7 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
auto formatted_dur = fmt::format("{:%T}", dur); auto formatted_dur = fmt::format("{:%T}", dur);
EXPECT_EQ(formatted_dur, "-00:01:39.123456789"); EXPECT_EQ(formatted_dur, "-00:01:39.123456789");
EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur); EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur);
EXPECT_EQ(fmt::format("{:.3%H:%M:%S}", dur), "-00:01:39.123");
} }
// Check that durations with precision greater than std::chrono::seconds have // Check that durations with precision greater than std::chrono::seconds have
// fixed precision, and print zeros even if there is no fractional part. // fixed precision, and print zeros even if there is no fractional part.