diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 1a8d8d04c2aa9..ee320f4afe6eb 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -966,13 +966,33 @@ inline void tzset_once() { } #endif -template class tm_writer { +struct tm_ss { + std::tm tm; + int subsec_wdt; + uint64_t subsec; +}; + +struct empty_tm_ss { + private: + tm_ss tm_ss_; + empty_tm_ss() { tm_ss_.subsec_wdt = 0; } + + public: + static empty_tm_ss& instance() { + static empty_tm_ss inst; + return inst; + } + const tm_ss& get_tm_ss() { return tm_ss_; } +}; + +template class tm_ss_writer { private: static constexpr int days_per_week = 7; const std::locale& loc_; const bool is_classic_; OutputIt out_; + const tm_ss& tm_ss_; const std::tm& tm_; auto tm_sec() const noexcept -> int { @@ -1135,10 +1155,18 @@ template class tm_writer { } public: - tm_writer(const std::locale& loc, OutputIt out, const std::tm& tm) + tm_ss_writer(const std::locale& loc, OutputIt out, const tm_ss& tm_ss_val) : loc_(loc), is_classic_(loc_ == get_classic_locale()), out_(out), + tm_ss_(tm_ss_val), + tm_(tm_ss_val.tm) {} + + tm_ss_writer(const std::locale& loc, OutputIt out, const std::tm& tm) + : loc_(loc), + is_classic_(loc_ == get_classic_locale()), + out_(out), + tm_ss_(empty_tm_ss::instance().get_tm_ss()), tm_(tm) {} OutputIt out() const { return out_; } @@ -1338,8 +1366,20 @@ template class tm_writer { format_localized('M', 'O'); } void on_second(numeric_system ns) { - if (is_classic_ || ns == numeric_system::standard) return write2(tm_sec()); - format_localized('S', 'O'); + if (is_classic_ || ns == numeric_system::standard) { + write2(tm_sec()); + if (tm_ss_.subsec_wdt) { + *out_++ = '.'; + const auto ss_num_digits = count_digits(tm_ss_.subsec); + if (tm_ss_.subsec_wdt > ss_num_digits) { + out_ = std::fill_n(out_, tm_ss_.subsec_wdt - ss_num_digits, '0'); + } + out_ = format_decimal(out_, tm_ss_.subsec, ss_num_digits).end; + } + } else { + // Currently no formatting of nanoseconds when a locale is set. + format_localized('S', 'O'); + } } void on_12_hour_time() { @@ -1595,7 +1635,7 @@ struct chrono_formatter { bool negative; using char_type = typename FormatContext::char_type; - using tm_writer_type = tm_writer; + using tm_writer_type = tm_ss_writer; chrono_formatter(FormatContext& ctx, OutputIt o, std::chrono::duration d) @@ -1883,7 +1923,8 @@ template struct formatter { auto time = std::tm(); time.tm_wday = static_cast(wd.c_encoding()); detail::get_locale loc(localized, ctx.locale()); - auto w = detail::tm_writer(loc, ctx.out(), time); + auto w = + detail::tm_ss_writer(loc, ctx.out(), time); w.on_abbr_weekday(); return w.out(); } @@ -2016,28 +2057,52 @@ struct formatter, this->do_parse(default_specs.begin(), default_specs.end()); } - template - auto format(std::chrono::time_point val, + template + auto format(std::chrono::time_point> + val, FormatContext& ctx) const -> decltype(ctx.out()) { - return formatter::format(localtime(val), ctx); + detail::tm_ss tm_ss_val; + tm_ss_val.tm = + localtime(std::chrono::time_point_cast(val)); + + constexpr bool reasonable_subsec_available = + Period::num == 1 && + (Period::den == 1e1 || Period::den == 1e2 || Period::den == 1e3 || + Period::den == 1e4 || Period::den == 1e5 || Period::den == 1e6 || + Period::den == 1e7 || Period::den == 1e8 || Period::den == 1e9 || + Period::den == 1e10 || Period::den == 1e11 || Period::den == 1e12 || + Period::den == 1e13 || Period::den == 1e14 || Period::den == 1e15); + + if (reasonable_subsec_available) { + const auto epoch = val.time_since_epoch(); + const auto subsecs = + std::chrono::duration_cast>( + epoch - std::chrono::duration_cast(epoch)); + + tm_ss_val.subsec = static_cast(subsecs.count()); + tm_ss_val.subsec_wdt = + detail::count_digits(static_cast(Period::den)) - 1; + } else { + tm_ss_val.subsec_wdt = 0; + } + + return formatter::format(tm_ss_val, ctx); } }; #if FMT_USE_UTC_TIME template struct formatter, - Char> : formatter { - FMT_CONSTEXPR formatter() { - basic_string_view default_specs = - detail::string_literal{}; - this->do_parse(default_specs.begin(), default_specs.end()); - } - - template - auto format(std::chrono::time_point val, + Char> + : formatter, + Char> { + template + auto format(std::chrono::time_point val, FormatContext& ctx) const -> decltype(ctx.out()) { - return formatter::format( - localtime(std::chrono::utc_clock::to_sys(val)), ctx); + return formatter< + std::chrono::time_point, + Char>::format(std::chrono::utc_clock::to_sys(val), ctx); } }; #endif @@ -2075,12 +2140,15 @@ template struct formatter { return end; } - template - auto format(const std::tm& tm, FormatContext& ctx) const + template ::value || + std::is_same::value)> + auto format(const TimeType& tm, FormatContext& ctx) const -> decltype(ctx.out()) { const auto loc_ref = ctx.locale(); detail::get_locale loc(static_cast(loc_ref), loc_ref); - auto w = detail::tm_writer(loc, ctx.out(), tm); + auto w = + detail::tm_ss_writer(loc, ctx.out(), tm); if (spec_ == spec::year_month_day) w.on_iso_date(); else if (spec_ == spec::hh_mm_ss) diff --git a/test/chrono-test.cc b/test/chrono-test.cc index bc474a40fc49e..bdf2d20f12543 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -253,7 +253,8 @@ template auto strftime_full(TimePoint tp) -> std::string { } TEST(chrono_test, time_point) { - auto t1 = std::chrono::system_clock::now(); + auto t1 = std::chrono::time_point_cast( + std::chrono::system_clock::now()); EXPECT_EQ(strftime_full(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1)); EXPECT_EQ(strftime_full(t1), fmt::format("{}", t1)); using time_point = @@ -634,9 +635,11 @@ TEST(chrono_test, cpp20_duration_subsecond_support) { // fixed precision, and print zeros even if there is no fractional part. EXPECT_EQ(fmt::format("{:%S}", std::chrono::microseconds{7000000}), "07.000000"); - EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration>(1)), + EXPECT_EQ(fmt::format("{:%S}", + std::chrono::duration>(1)), "00.333333"); - EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration>(1)), + EXPECT_EQ(fmt::format("{:%S}", + std::chrono::duration>(1)), "00.142857"); } @@ -652,3 +655,36 @@ TEST(chrono_test, utc_clock) { fmt::format("{:%Y-%m-%d %H:%M:%S}", t1_utc)); } #endif + +TEST(chrono_test, sub_sec) { + const std::chrono::time_point + t0(std::chrono::seconds(2)); + + EXPECT_EQ(fmt::format("{:%S}", t0), "02"); + + const std::chrono::time_point + t1(std::chrono::seconds(1) + std::chrono::milliseconds(12)); + + EXPECT_EQ(fmt::format("{:%S}", t1), "01.012"); + + const std::chrono::time_point + t2(+std::chrono::microseconds(1234567)); + + EXPECT_EQ(fmt::format("{:%S}", t2), "01.234567"); + + const std::chrono::time_point + t3(std::chrono::nanoseconds(123456789)); + + EXPECT_EQ(fmt::format("{:%S}", t3), "00.123456789"); + + const auto t4 = std::chrono::time_point_cast( + std::chrono::system_clock::now()); + const auto t4_sec = std::chrono::time_point_cast(t4); + + auto t4_sub_sec_part = fmt::format("{0:09}", (t4 - t4_sec).count()); + EXPECT_EQ(fmt::format("{}.{}", strftime_full(t4_sec), t4_sub_sec_part), + fmt::format("{:%Y-%m-%d %H:%M:%S}", t4)); +} \ No newline at end of file diff --git a/test/xchar-test.cc b/test/xchar-test.cc index dd45826d3a23a..608af350f4ee3 100644 --- a/test/xchar-test.cc +++ b/test/xchar-test.cc @@ -285,7 +285,8 @@ std::wstring system_wcsftime(const std::wstring& format, const std::tm* timeptr, } TEST(chrono_test_wchar, time_point) { - auto t1 = std::chrono::system_clock::now(); + auto t1 = std::chrono::time_point_cast( + std::chrono::system_clock::now()); std::vector spec_list = { L"%%", L"%n", L"%t", L"%Y", L"%EY", L"%y", L"%Oy", L"%Ey", L"%C",