Add initial C++20 subsecond printing support for std::chrono::duration
This commit is contained in:
parent
7463c83205
commit
7a6dc3abbe
@ -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() {
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user