Add support for subsecond printing for std::chrono::duration according to the c++20 standard
This commit is contained in:
parent
ec3b097cb9
commit
c379573725
@ -1320,19 +1320,19 @@ inline bool isfinite(T) {
|
|||||||
|
|
||||||
// Converts value to int and checks that it's in the range [0, upper).
|
// Converts value to int and checks that it's in the range [0, upper).
|
||||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||||
inline int to_nonnegative_int(T value, int upper) {
|
inline std::intmax_t to_nonnegative_int(T value, std::intmax_t upper) {
|
||||||
FMT_ASSERT(value >= 0 && to_unsigned(value) <= to_unsigned(upper),
|
FMT_ASSERT(value >= 0 && to_unsigned(value) <= to_unsigned(upper),
|
||||||
"invalid value");
|
"invalid value");
|
||||||
(void)upper;
|
(void)upper;
|
||||||
return static_cast<int>(value);
|
return static_cast<std::intmax_t>(value);
|
||||||
}
|
}
|
||||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||||
inline int to_nonnegative_int(T value, int upper) {
|
inline std::intmax_t to_nonnegative_int(T value, std::intmax_t upper) {
|
||||||
FMT_ASSERT(
|
FMT_ASSERT(
|
||||||
std::isnan(value) || (value >= 0 && value <= static_cast<T>(upper)),
|
std::isnan(value) || (value >= 0 && value <= static_cast<T>(upper)),
|
||||||
"invalid value");
|
"invalid value");
|
||||||
(void)upper;
|
(void)upper;
|
||||||
return static_cast<int>(value);
|
return static_cast<std::intmax_t>(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||||
@ -1389,16 +1389,55 @@ inline std::chrono::duration<Rep, std::milli> get_milliseconds(
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Rep, typename Period,
|
template <class Duration> class subsecond_helper {
|
||||||
FMT_ENABLE_IF(std::is_floating_point<Rep>::value)>
|
/// Returns the amount of digits according to the c++ 20 spec
|
||||||
inline std::chrono::duration<Rep, std::milli> get_milliseconds(
|
/// In the range [0, 18], if more than 18 fractional digits are required,
|
||||||
std::chrono::duration<Rep, Period> d) {
|
/// then we return 6 for microseconds precision
|
||||||
using common_type = typename std::common_type<Rep, std::intmax_t>::type;
|
static constexpr int num_digits(std::intmax_t num,
|
||||||
auto ms = mod(d.count() * static_cast<common_type>(Period::num) /
|
std::intmax_t den,
|
||||||
static_cast<common_type>(Period::den) * 1000,
|
std::uint32_t n = 0) {
|
||||||
1000);
|
return num % den == 0 ? n : (n > 18 ? 6 : num_digits(num * 10, den, n + 1));
|
||||||
return std::chrono::duration<Rep, std::milli>(static_cast<Rep>(ms));
|
}
|
||||||
}
|
|
||||||
|
static constexpr std::intmax_t pow10(std::uint32_t n) {
|
||||||
|
return n == 0 ? 1 : 10 * pow10(n - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class Rep, class Period,
|
||||||
|
FMT_ENABLE_IF(std::numeric_limits<Rep>::is_signed)>
|
||||||
|
static constexpr std::chrono::duration<Rep, Period> abs(
|
||||||
|
std::chrono::duration<Rep, Period> d) {
|
||||||
|
return d >= d.zero() ? d : -d;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class Rep, class Period,
|
||||||
|
FMT_ENABLE_IF(!std::numeric_limits<Rep>::is_signed)>
|
||||||
|
static constexpr std::chrono::duration<Rep, Period> abs(
|
||||||
|
std::chrono::duration<Rep, Period> d) {
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
static constexpr auto fractional_width =
|
||||||
|
num_digits(Duration::period::num, Duration::period::den);
|
||||||
|
|
||||||
|
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 typename precision::rep get_subseconds(
|
||||||
|
std::chrono::duration<Rep, Period> d) {
|
||||||
|
return std::chrono::treat_as_floating_point<typename precision::rep>::value
|
||||||
|
? (abs(d) - std::chrono::duration_cast<std::chrono::seconds>(d))
|
||||||
|
.count()
|
||||||
|
: std::chrono::duration_cast<precision>(
|
||||||
|
abs(d) -
|
||||||
|
std::chrono::duration_cast<std::chrono::seconds>(d))
|
||||||
|
.count();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
template <typename Char, typename Rep, typename OutputIt,
|
template <typename Char, typename Rep, typename OutputIt,
|
||||||
FMT_ENABLE_IF(std::is_integral<Rep>::value)>
|
FMT_ENABLE_IF(std::is_integral<Rep>::value)>
|
||||||
@ -1553,8 +1592,8 @@ struct chrono_formatter {
|
|||||||
void write(Rep value, int width) {
|
void write(Rep value, int width) {
|
||||||
write_sign();
|
write_sign();
|
||||||
if (isnan(value)) return write_nan();
|
if (isnan(value)) return write_nan();
|
||||||
uint32_or_64_or_128_t<int> n =
|
uint32_or_64_or_128_t<std::intmax_t> n =
|
||||||
to_unsigned(to_nonnegative_int(value, max_value<int>()));
|
to_unsigned(to_nonnegative_int(value, max_value<std::intmax_t>()));
|
||||||
int num_digits = detail::count_digits(n);
|
int num_digits = detail::count_digits(n);
|
||||||
if (width > num_digits) out = std::fill_n(out, width - num_digits, '0');
|
if (width > num_digits) out = std::fill_n(out, width - num_digits, '0');
|
||||||
out = format_decimal<char_type>(out, n, num_digits).end;
|
out = format_decimal<char_type>(out, n, num_digits).end;
|
||||||
@ -1645,10 +1684,12 @@ struct chrono_formatter {
|
|||||||
#else
|
#else
|
||||||
auto tmpval = std::chrono::duration<Rep, Period>(val);
|
auto tmpval = std::chrono::duration<Rep, Period>(val);
|
||||||
#endif
|
#endif
|
||||||
auto ms = get_milliseconds(tmpval);
|
using subsec_helper = detail::subsecond_helper<duration_Rep>;
|
||||||
if (ms != std::chrono::milliseconds(0)) {
|
// Could use c++ 17 if constexpr
|
||||||
|
if (std::ratio_less<typename subsec_helper::precision::period,
|
||||||
|
std::chrono::seconds::period>::value) {
|
||||||
*out++ = '.';
|
*out++ = '.';
|
||||||
write(ms.count(), 3);
|
write(subsec_helper::get_subseconds(tmpval), subsec_helper::fractional_width);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1678,7 +1719,7 @@ struct chrono_formatter {
|
|||||||
on_24_hour_time();
|
on_24_hour_time();
|
||||||
*out++ = ':';
|
*out++ = ':';
|
||||||
if (handle_nan_inf()) return;
|
if (handle_nan_inf()) return;
|
||||||
write(second(), 2);
|
on_second(numeric_system::standard);
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_am_pm() {
|
void on_am_pm() {
|
||||||
|
|||||||
@ -544,8 +544,8 @@ TEST(chrono_test, negative_durations) {
|
|||||||
|
|
||||||
TEST(chrono_test, special_durations) {
|
TEST(chrono_test, special_durations) {
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(
|
||||||
"40.",
|
"40",
|
||||||
fmt::format("{:%S}", std::chrono::duration<double>(1e20)).substr(0, 3));
|
fmt::format("{:%S}", std::chrono::duration<double>(1e20)).substr(0, 2));
|
||||||
auto nan = std::numeric_limits<double>::quiet_NaN();
|
auto nan = std::numeric_limits<double>::quiet_NaN();
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(
|
||||||
"nan nan nan nan nan:nan nan",
|
"nan nan nan nan nan:nan nan",
|
||||||
@ -585,4 +585,44 @@ 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{673231113420148734}),
|
||||||
|
"00.673231113420148734");
|
||||||
|
EXPECT_EQ(fmt::format("{:%S}", attoseconds{-673231113420148734}),
|
||||||
|
"-00.673231113420148734");
|
||||||
|
EXPECT_EQ(fmt::format("{:%S}", std::chrono::nanoseconds{13420148734}),
|
||||||
|
"13.420148734");
|
||||||
|
EXPECT_EQ(fmt::format("{:%S}", std::chrono::nanoseconds{-13420148734}),
|
||||||
|
"-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{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);
|
||||||
|
}
|
||||||
|
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");
|
||||||
|
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{99123000000}), "39.123000000");
|
||||||
|
{
|
||||||
|
// 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 have
|
||||||
|
// fixed precision and empty zeros
|
||||||
|
EXPECT_EQ(fmt::format("{:%S}", std::chrono::microseconds{7000000}),
|
||||||
|
"07.000000");
|
||||||
|
}
|
||||||
|
|
||||||
#endif // FMT_STATIC_THOUSANDS_SEPARATOR
|
#endif // FMT_STATIC_THOUSANDS_SEPARATOR
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user