Add initial C++20 subsecond printing support for std::chrono::duration

This commit is contained in:
Filip Matracki 2021-10-19 19:53:32 +02:00
parent 7463c83205
commit 7a6dc3abbe
2 changed files with 102 additions and 39 deletions

View File

@ -886,39 +886,57 @@ To fmt_safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from) {
}
#endif
template <typename Rep, typename Period,
FMT_ENABLE_IF(std::is_integral<Rep>::value)>
inline std::chrono::duration<Rep, std::milli> get_milliseconds(
std::chrono::duration<Rep, Period> d) {
// this may overflow and/or the result may not fit in the
// target type.
#if FMT_SAFE_DURATION_CAST
using CommonSecondsType =
typename std::common_type<decltype(d), std::chrono::seconds>::type;
const auto d_as_common = fmt_safe_duration_cast<CommonSecondsType>(d);
const auto d_as_whole_seconds =
fmt_safe_duration_cast<std::chrono::seconds>(d_as_common);
// this conversion should be nonproblematic
const auto diff = d_as_common - d_as_whole_seconds;
const auto ms =
fmt_safe_duration_cast<std::chrono::duration<Rep, std::milli>>(diff);
return ms;
#else
auto s = std::chrono::duration_cast<std::chrono::seconds>(d);
return std::chrono::duration_cast<std::chrono::milliseconds>(d - s);
#endif
}
template <class Duration> struct subsecond_helper {
static constexpr unsigned int fractional_width = [] {
static_assert(Duration::period::num > 0 && Duration::period::den > 0,
"Numerator and denominator can't be less than 1.");
auto num = Duration::period::num;
constexpr auto den = Duration::period::den;
// Returns the number of fractional digits of num / den in the range
// [0, 18]. If it can't be represented, 6 is returned. Example:
// fractional_width(1, 8) would return 3 for 0.125.
unsigned int result = 0;
for (; num % den != 0 && result < 19; num = num % den * 10, ++result) {
}
return result == 19 ? 6 : result;
}();
template <typename Rep, typename Period,
FMT_ENABLE_IF(std::is_floating_point<Rep>::value)>
inline std::chrono::duration<Rep, std::milli> get_milliseconds(
std::chrono::duration<Rep, Period> d) {
using common_type = typename std::common_type<Rep, std::intmax_t>::type;
auto ms = mod(d.count() * static_cast<common_type>(Period::num) /
static_cast<common_type>(Period::den) * 1000,
1000);
return std::chrono::duration<Rep, std::milli>(static_cast<Rep>(ms));
}
static constexpr intmax_t pow10(const unsigned int exp) {
intmax_t result = 1;
for (unsigned int i = 0; i < exp; ++i) {
result *= 10;
}
return result;
}
template <class Rep, class Period>
static constexpr std::chrono::duration<Rep, Period> abs(
std::chrono::duration<Rep, Period> d) {
if FMT_CONSTEXPR (std::chrono::duration<Rep, Period>::min() <
std::chrono::duration<Rep, Period>::zero()) {
return d >= d.zero() ? d : -d;
} else {
return d;
}
}
using precision = std::chrono::duration<
typename std::common_type<typename Duration::rep,
std::chrono::seconds::rep>::type,
std::ratio<1, pow10(fractional_width)>>;
template <class Rep, class Period>
static constexpr precision get_subseconds(
std::chrono::duration<Rep, Period> d) {
if FMT_CONSTEXPR (std::chrono::treat_as_floating_point_v<
typename precision::rep>) {
return abs(d) - std::chrono::duration_cast<std::chrono::seconds>(d);
} else {
return std::chrono::duration_cast<precision>(
abs(d) - std::chrono::duration_cast<std::chrono::seconds>(d));
}
}
};
template <typename Char, typename Rep, typename OutputIt,
FMT_ENABLE_IF(std::is_integral<Rep>::value)>
@ -1138,14 +1156,22 @@ struct chrono_formatter {
// convert rep->Rep
using duration_rep = std::chrono::duration<rep, Period>;
using duration_Rep = std::chrono::duration<Rep, Period>;
auto tmpval = fmt_safe_duration_cast<duration_Rep>(duration_rep{val});
const auto tmpval =
fmt_safe_duration_cast<duration_Rep>(duration_rep{val});
#else
auto tmpval = std::chrono::duration<Rep, Period>(val);
const auto tmpval = std::chrono::duration<Rep, Period>(val);
#endif
auto ms = get_milliseconds(tmpval);
if (ms != std::chrono::milliseconds(0)) {
using subsec_helper = detail::subsecond_helper<duration_Rep>;
const std::uintmax_t subseconds =
subsec_helper::get_subseconds(tmpval).count();
if (subseconds > 0) {
*out++ = '.';
write(ms.count(), 3);
const auto num_digits = detail::count_digits(subseconds);
if (subsec_helper::fractional_width > num_digits) {
out = std::fill_n(out, subsec_helper::fractional_width - num_digits,
'0');
}
out = format_decimal<char_type>(out, subseconds, num_digits).end;
}
return;
}
@ -1175,7 +1201,7 @@ struct chrono_formatter {
on_24_hour_time();
*out++ = ':';
if (handle_nan_inf()) return;
write(second(), 2);
on_second(numeric_system::standard);
}
void on_am_pm() {

View File

@ -500,7 +500,7 @@ TEST(chrono_test, negative_durations) {
TEST(chrono_test, special_durations) {
EXPECT_EQ(
"40.",
"40",
fmt::format("{:%S}", std::chrono::duration<double>(1e20)).substr(0, 3));
auto nan = std::numeric_limits<double>::quiet_NaN();
EXPECT_EQ(
@ -533,4 +533,41 @@ TEST(chrono_test, weekday) {
}
}
TEST(chrono_test, cpp20_duration_subsecond_support) {
using attoseconds = std::chrono::duration<std::intmax_t, std::atto>;
// Check that 18 digits of subsecond precision are supported
EXPECT_EQ(fmt::format("{:%S}", attoseconds{673'231'113'420'148'734}),
"00.673231113420148734");
EXPECT_EQ(fmt::format("{:%S}", attoseconds{-673'231'113'420'148'734}),
"-00.673231113420148734");
EXPECT_EQ(fmt::format("{:%S}", std::chrono::nanoseconds{13'420'148'734}),
"13.420148734");
EXPECT_EQ(fmt::format("{:%S}", std::chrono::nanoseconds{-13'420'148'734}),
"-13.420148734");
EXPECT_EQ(fmt::format("{:%S}", std::chrono::milliseconds{1234}), "01.234");
{
// Check that {:%H:%M:%S} is equivalent to {:%T}
auto dur = std::chrono::milliseconds{3'601'234};
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);
}
using nanoseconds_dbl = std::chrono::duration<double, std::nano>;
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{-123456789.123456789}),
"-00.123456789");
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{9123456789.123456789}),
"09.123456789");
// Only seconds part is printed
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{99123456789}), "39.123456789");
{
// Now the hour is printed, and we also test if negative doubles work
auto dur = nanoseconds_dbl{-99123456789};
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);
}
// Check that durations with precision greater than std::chrono::seconds
// with no subsecond part print as regular seconds
EXPECT_EQ(fmt::format("{:%S}", std::chrono::microseconds{7'000'000}), "07");
}
#endif // FMT_STATIC_THOUSANDS_SEPARATOR