From 852e039b2ad085f1f175df36892ad981f8a8a82c Mon Sep 17 00:00:00 2001 From: Stepan Ponomarev Date: Wed, 19 Oct 2022 12:00:30 +0300 Subject: [PATCH] Reuse precision modifier in second specification --- include/fmt/chrono.h | 156 ++++++++++++++++--------------------------- test/chrono-test.cc | 26 ++++---- 2 files changed, 71 insertions(+), 111 deletions(-) diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 79f667dd..d1f45e9d 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -593,27 +593,6 @@ template FMT_CONSTEXPR inline const char* get_units() { return nullptr; } -template -struct non_dynamic_precision_handler { - int precision = -1; - - void on_error(const char* msg) { FMT_THROW(format_error(msg)); } - FMT_CONSTEXPR void on_precision(int _precision) { - precision = _precision; - } - template FMT_CONSTEXPR void on_dynamic_precision(Id /*_precision*/) { - on_error("dynamic precision is not allowed here"); - } - FMT_CONSTEXPR void end_precision() {} -}; - -template -FMT_CONSTEXPR void check_allowed_precision(int precision, const int (&allowed_precisions)[N]) { - if (std::find(std::begin(allowed_precisions), std::end(allowed_precisions), precision) == std::end(allowed_precisions)) { - FMT_THROW(format_error("invalid precision")); - } -} - enum class numeric_system { standard, // Alternative numeric system, e.g. 十二 instead of 12 in ja_JP locale. @@ -651,17 +630,6 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, handler.on_text(tab, tab + 1); break; } - case '.': { - non_dynamic_precision_handler prec_handler; - ptr = detail::parse_precision(ptr-1, end, prec_handler); - c = *ptr++; - if (c == 'S') { - handler.on_second_with_fractions(numeric_system::standard, prec_handler.precision); - } else { - FMT_THROW(format_error("specifier doesn't have precision modifier")); - } - break; - } // Year: case 'Y': handler.on_year(numeric_system::standard); @@ -885,7 +853,6 @@ template struct null_chrono_spec_handler { FMT_CONSTEXPR void on_12_hour(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_minute(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_second(numeric_system) { unsupported(); } - FMT_CONSTEXPR void on_second_with_fractions(numeric_system, int) { unsupported(); } FMT_CONSTEXPR void on_datetime(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_loc_date(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_loc_time(numeric_system) { unsupported(); } @@ -1402,10 +1369,12 @@ template class tm_writer { // These apply to chrono durations but not tm. void on_duration_value() {} void on_duration_unit() {} - void on_second_with_fractions(numeric_system, int) {} }; struct chrono_format_checker : null_chrono_spec_handler { + bool is_floating_point = false; + bool has_precision = false; + FMT_NORETURN void unsupported() { FMT_THROW(format_error("no date")); } template @@ -1414,15 +1383,15 @@ struct chrono_format_checker : null_chrono_spec_handler { FMT_CONSTEXPR void on_12_hour(numeric_system) {} FMT_CONSTEXPR void on_minute(numeric_system) {} FMT_CONSTEXPR void on_second(numeric_system) {} - FMT_CONSTEXPR void on_second_with_fractions(numeric_system, int precision) { - constexpr int allowed_precisions[6] = {3, 6, 9, 12, 15, 18}; - check_allowed_precision(precision, allowed_precisions); - } FMT_CONSTEXPR void on_12_hour_time() {} FMT_CONSTEXPR void on_24_hour_time() {} FMT_CONSTEXPR void on_iso_time() {} 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() {} }; @@ -1732,6 +1701,43 @@ struct chrono_formatter { } } + template void write_fractional_seconds(Duration d, int precision) { + constexpr auto num_fractional_digits = + count_fractional_digits::value; + + using subsecond_precision = std::chrono::duration< + typename std::common_type::type, + std::ratio<1, detail::pow10(num_fractional_digits)>>; + if (precision > 0) { + *out++ = '.'; + auto fractional = + detail::abs(d) - std::chrono::duration_cast(d); + auto subseconds = + std::chrono::treat_as_floating_point< + typename subsecond_precision::rep>::value + ? fractional.count() + : std::chrono::duration_cast(fractional) + .count(); + uint32_or_64_or_128_t n = + to_unsigned(to_nonnegative_int(subseconds, max_value())); + 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 /= detail::pow10(num_digits - remaining); + out = format_decimal(out, n, remaining).end; + return; + } + out = format_decimal(out, n, num_digits).end; + remaining -= num_digits; + out = std::fill_n(out, remaining, '0'); + } + } + void write_nan() { std::copy_n("nan", 3, out); } void write_pinf() { std::copy_n("inf", 3, out); } void write_ninf() { std::copy_n("-inf", 4, out); } @@ -1809,8 +1815,10 @@ struct chrono_formatter { if (ns == numeric_system::standard) { if (std::is_floating_point::value) { - constexpr auto num_fractional_digits = - count_fractional_digits::value; + auto num_fractional_digits = + precision >= 0 + ? precision + : count_fractional_digits::value; auto buf = memory_buffer(); format_to(std::back_inserter(buf), runtime("{:.{}f}"), std::fmod(val * static_cast(Period::num) / @@ -1822,7 +1830,11 @@ struct chrono_formatter { out = std::copy(buf.begin(), buf.end(), out); } else { write(second(), 2); - write_fractional_seconds(std::chrono::duration(val)); + if (precision >= 0) { + write_fractional_seconds(std::chrono::duration(val), precision); + } else { + write_fractional_seconds(std::chrono::duration(val)); + } } return; } @@ -1831,55 +1843,6 @@ struct chrono_formatter { format_tm(time, &tm_writer_type::on_second, ns); } - template - void write_seconds_with_fractions(numeric_system ns) { - if (handle_nan_inf()) return; - - if (ns == numeric_system::standard) { - if (std::is_floating_point::value) { - constexpr auto num_fractional_digits = - count_fractional_digits::value; - auto buf = memory_buffer(); - format_to(std::back_inserter(buf), runtime("{:.{}f}"), - std::fmod(val * static_cast(Period::num) / - static_cast(Period::den), - static_cast(60)), - num_fractional_digits); - if (negative) *out++ = '-'; - if (buf.size() < 2 || buf[1] == '.') *out++ = '0'; - out = std::copy(buf.begin(), buf.end(), out); - } else { - write(second(), 2); - const auto dur = std::chrono::duration{val}; - std::chrono::seconds const sec = std::chrono::duration_cast(dur); - write_fractional_seconds(std::chrono::duration_cast>(dur - sec)); - } - return; - } - auto time = tm(); - time.tm_sec = to_nonnegative_int(second(), 60); - format_tm(time, &tm_writer_type::on_second, ns); - } - - void on_second_with_fractions(numeric_system ns, int fractional_digits) { - switch (fractional_digits) { - case 3: - return this->write_seconds_with_fractions(ns); - case 6: - return this->write_seconds_with_fractions(ns); - case 9: - return this->write_seconds_with_fractions(ns); - case 12: - return this->write_seconds_with_fractions(ns); - case 15: - return this->write_seconds_with_fractions(ns); - case 18: - return this->write_seconds_with_fractions(ns); - default: - return; - } - } - void on_12_hour_time() { if (handle_nan_inf()) return; format_tm(time(), &tm_writer_type::on_12_hour_time); @@ -2032,18 +1995,17 @@ struct formatter, Char> { if (begin == end) return {begin, begin}; begin = detail::parse_width(begin, end, handler); if (begin == end) return {begin, begin}; + auto checker = detail::chrono_format_checker(); + checker.is_floating_point = std::is_floating_point::value; if (*begin == '.') { - if (std::is_floating_point::value) - begin = detail::parse_precision(begin, end, handler); - else - handler.on_error("precision not allowed for this argument type"); + checker.has_precision = true; + begin = detail::parse_precision(begin, end, handler); } if (begin != end && *begin == 'L') { ++begin; localized = true; } - end = detail::parse_chrono_format(begin, end, - detail::chrono_format_checker()); + end = detail::parse_chrono_format(begin, end, checker); return {begin, end}; } diff --git a/test/chrono-test.cc b/test/chrono-test.cc index 768b8e71..3d7222f2 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -465,7 +465,7 @@ TEST(chrono_test, format_default_fp) { TEST(chrono_test, format_precision) { 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"); EXPECT_EQ("1ms", fmt::format("{:.0}", dms(1.234))); EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234))); @@ -611,24 +611,22 @@ TEST(chrono_test, cpp20_duration_subsecond_support) { "-13.420148734"); EXPECT_EQ(fmt::format("{:%S}", std::chrono::milliseconds{1234}), "01.234"); // Check subsecond presision modifier. - EXPECT_EQ(fmt::format("{:%.6S}", std::chrono::nanoseconds{1234}), "00.000001"); - EXPECT_EQ(fmt::format("{:%.18S}", std::chrono::nanoseconds{1234}), "00.000001234000000000"); - EXPECT_EQ(fmt::format("{:%.3S}", std::chrono::microseconds{1234}), "00.001"); - EXPECT_EQ(fmt::format("{:%.9S}", std::chrono::microseconds{1234}), "00.001234000"); - EXPECT_EQ(fmt::format("{:%.6S}", std::chrono::milliseconds{1234}), "01.234000"); - EXPECT_EQ(fmt::format("{:%.6S}", std::chrono::milliseconds{-1234}), "-01.234000"); - EXPECT_EQ(fmt::format("{:%.3S}", std::chrono::seconds{1234}), "34.000"); - EXPECT_EQ(fmt::format("{:%.9S}", std::chrono::minutes{1234}), "00.000000000"); - EXPECT_EQ(fmt::format("{:%.3S}", std::chrono::hours{1234}), "00.000"); - EXPECT_THROW_MSG((void)fmt::format(runtime("{:%.5S}"), std::chrono::microseconds{1234}), fmt::format_error, - "invalid precision"); + 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}. auto dur = std::chrono::milliseconds{3601234}; auto formatted_dur = fmt::format("{:%T}", dur); EXPECT_EQ(formatted_dur, "01:00:01.234"); EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur); - EXPECT_EQ(fmt::format("{:%H:%M:%.6S}", dur), "01:00:01.234000"); + EXPECT_EQ(fmt::format("{:.6%H:%M:%S}", dur), "01:00:01.234000"); } using nanoseconds_dbl = std::chrono::duration; EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{-123456789}), "-00.123456789"); @@ -642,7 +640,7 @@ TEST(chrono_test, cpp20_duration_subsecond_support) { auto formatted_dur = fmt::format("{:%T}", dur); EXPECT_EQ(formatted_dur, "-00:01:39.123456789"); EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur); - EXPECT_EQ(fmt::format("{:%H:%M:%.3S}", dur), "-00:01:39.123"); + EXPECT_EQ(fmt::format("{:.3%H:%M:%S}", dur), "-00:01:39.123"); } // Check that durations with precision greater than std::chrono::seconds have // fixed precision, and print zeros even if there is no fractional part.