Skip to content

Commit

Permalink
Timestamp formatting shall print also subseconds on %S
Browse files Browse the repository at this point in the history
  • Loading branch information
patrickroocks committed Sep 28, 2022
1 parent 806a939 commit 1177880
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 27 deletions.
114 changes: 91 additions & 23 deletions include/fmt/chrono.h
Original file line number Diff line number Diff line change
Expand Up @@ -966,13 +966,33 @@ inline void tzset_once() {
}
#endif

template <typename OutputIt, typename Char> 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 <typename OutputIt, typename Char> 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 {
Expand Down Expand Up @@ -1135,10 +1155,18 @@ template <typename OutputIt, typename Char> 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_; }
Expand Down Expand Up @@ -1338,8 +1366,20 @@ template <typename OutputIt, typename Char> 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<Char>(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() {
Expand Down Expand Up @@ -1595,7 +1635,7 @@ struct chrono_formatter {
bool negative;

using char_type = typename FormatContext::char_type;
using tm_writer_type = tm_writer<OutputIt, char_type>;
using tm_writer_type = tm_ss_writer<OutputIt, char_type>;

chrono_formatter(FormatContext& ctx, OutputIt o,
std::chrono::duration<Rep, Period> d)
Expand Down Expand Up @@ -1883,7 +1923,8 @@ template <typename Char> struct formatter<weekday, Char> {
auto time = std::tm();
time.tm_wday = static_cast<int>(wd.c_encoding());
detail::get_locale loc(localized, ctx.locale());
auto w = detail::tm_writer<decltype(ctx.out()), Char>(loc, ctx.out(), time);
auto w =
detail::tm_ss_writer<decltype(ctx.out()), Char>(loc, ctx.out(), time);
w.on_abbr_weekday();
return w.out();
}
Expand Down Expand Up @@ -2016,28 +2057,52 @@ struct formatter<std::chrono::time_point<std::chrono::system_clock, Duration>,
this->do_parse(default_specs.begin(), default_specs.end());
}

template <typename FormatContext>
auto format(std::chrono::time_point<std::chrono::system_clock> val,
template <typename FormatContext, typename Rep, typename Period>
auto format(std::chrono::time_point<std::chrono::system_clock,
std::chrono::duration<Rep, Period>>
val,
FormatContext& ctx) const -> decltype(ctx.out()) {
return formatter<std::tm, Char>::format(localtime(val), ctx);
detail::tm_ss tm_ss_val;
tm_ss_val.tm =
localtime(std::chrono::time_point_cast<std::chrono::seconds>(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<std::chrono::duration<Rep, Period>>(
epoch - std::chrono::duration_cast<std::chrono::seconds>(epoch));

tm_ss_val.subsec = static_cast<uint64_t>(subsecs.count());
tm_ss_val.subsec_wdt =
detail::count_digits(static_cast<uint64_t>(Period::den)) - 1;
} else {
tm_ss_val.subsec_wdt = 0;
}

return formatter<std::tm, Char>::format(tm_ss_val, ctx);
}
};

#if FMT_USE_UTC_TIME
template <typename Char, typename Duration>
struct formatter<std::chrono::time_point<std::chrono::utc_clock, Duration>,
Char> : formatter<std::tm, Char> {
FMT_CONSTEXPR formatter() {
basic_string_view<Char> default_specs =
detail::string_literal<Char, '%', 'F', ' ', '%', 'T'>{};
this->do_parse(default_specs.begin(), default_specs.end());
}

template <typename FormatContext>
auto format(std::chrono::time_point<std::chrono::utc_clock> val,
Char>
: formatter<std::chrono::time_point<std::chrono::system_clock, Duration>,
Char> {
template <typename FormatContext, typename Duration>
auto format(std::chrono::time_point<std::chrono::utc_clock, Duration> val,
FormatContext& ctx) const -> decltype(ctx.out()) {
return formatter<std::tm, Char>::format(
localtime(std::chrono::utc_clock::to_sys(val)), ctx);
return formatter<
std::chrono::time_point<std::chrono::system_clock, Duration>,
Char>::format(std::chrono::utc_clock::to_sys(val), ctx);
}
};
#endif
Expand Down Expand Up @@ -2075,12 +2140,15 @@ template <typename Char> struct formatter<std::tm, Char> {
return end;
}

template <typename FormatContext>
auto format(const std::tm& tm, FormatContext& ctx) const
template <typename TimeType, typename FormatContext,
FMT_ENABLE_IF(std::is_same<TimeType, std::tm>::value ||
std::is_same<TimeType, detail::tm_ss>::value)>
auto format(const TimeType& tm, FormatContext& ctx) const
-> decltype(ctx.out()) {
const auto loc_ref = ctx.locale();
detail::get_locale loc(static_cast<bool>(loc_ref), loc_ref);
auto w = detail::tm_writer<decltype(ctx.out()), Char>(loc, ctx.out(), tm);
auto w =
detail::tm_ss_writer<decltype(ctx.out()), Char>(loc, ctx.out(), tm);
if (spec_ == spec::year_month_day)
w.on_iso_date();
else if (spec_ == spec::hh_mm_ss)
Expand Down
42 changes: 39 additions & 3 deletions test/chrono-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,8 @@ template <typename TimePoint> 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::seconds>(
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 =
Expand Down Expand Up @@ -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<long long, std::ratio<1, 3>>(1)),
EXPECT_EQ(fmt::format("{:%S}",
std::chrono::duration<long long, std::ratio<1, 3>>(1)),
"00.333333");
EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration<long long, std::ratio<1, 7>>(1)),
EXPECT_EQ(fmt::format("{:%S}",
std::chrono::duration<long long, std::ratio<1, 7>>(1)),
"00.142857");
}

Expand All @@ -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<std::chrono::system_clock, std::chrono::seconds>
t0(std::chrono::seconds(2));

EXPECT_EQ(fmt::format("{:%S}", t0), "02");

const std::chrono::time_point<std::chrono::system_clock,
std::chrono::milliseconds>
t1(std::chrono::seconds(1) + std::chrono::milliseconds(12));

EXPECT_EQ(fmt::format("{:%S}", t1), "01.012");

const std::chrono::time_point<std::chrono::system_clock,
std::chrono::microseconds>
t2(+std::chrono::microseconds(1234567));

EXPECT_EQ(fmt::format("{:%S}", t2), "01.234567");

const std::chrono::time_point<std::chrono::system_clock,
std::chrono::nanoseconds>
t3(std::chrono::nanoseconds(123456789));

EXPECT_EQ(fmt::format("{:%S}", t3), "00.123456789");

const auto t4 = std::chrono::time_point_cast<std::chrono::nanoseconds>(
std::chrono::system_clock::now());
const auto t4_sec = std::chrono::time_point_cast<std::chrono::seconds>(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));
}
3 changes: 2 additions & 1 deletion test/xchar-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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::seconds>(
std::chrono::system_clock::now());

std::vector<std::wstring> spec_list = {
L"%%", L"%n", L"%t", L"%Y", L"%EY", L"%y", L"%Oy", L"%Ey", L"%C",
Expand Down

0 comments on commit 1177880

Please sign in to comment.