diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index ed7f5f16..1dbf8c25 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -593,6 +593,27 @@ 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. @@ -630,6 +651,17 @@ 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); @@ -853,6 +885,7 @@ 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(); } @@ -1369,6 +1402,7 @@ 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 { @@ -1380,6 +1414,9 @@ 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) { + check_allowed_precision(precision, {3, 6, 9, 12, 15, 18}); + } FMT_CONSTEXPR void on_12_hour_time() {} FMT_CONSTEXPR void on_24_hour_time() {} FMT_CONSTEXPR void on_iso_time() {} @@ -1793,6 +1830,55 @@ 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); diff --git a/test/chrono-test.cc b/test/chrono-test.cc index 0f2a2498..768b8e71 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -610,12 +610,25 @@ TEST(chrono_test, cpp20_duration_subsecond_support) { EXPECT_EQ(fmt::format("{:%S}", std::chrono::nanoseconds{-13420148734}), "-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"); { // 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"); } using nanoseconds_dbl = std::chrono::duration; EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{-123456789}), "-00.123456789"); @@ -629,6 +642,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"); } // Check that durations with precision greater than std::chrono::seconds have // fixed precision, and print zeros even if there is no fractional part.