Add precision modifier for seconds in chrono format (#3148)
Co-authored-by: Stepan Ponomarev <stepan.ponomarev@itiviti.com>
This commit is contained in:
parent
66d71a1b35
commit
795ed8abf5
@ -1027,8 +1027,7 @@ struct count_fractional_digits<Num, Den, N, false> {
|
|||||||
// Format subseconds which are given as an integer type with an appropriate
|
// Format subseconds which are given as an integer type with an appropriate
|
||||||
// number of digits.
|
// number of digits.
|
||||||
template <typename Char, typename OutputIt, typename Duration>
|
template <typename Char, typename OutputIt, typename Duration>
|
||||||
void write_fractional_seconds(OutputIt& out, Duration d) {
|
void write_fractional_seconds(OutputIt& out, Duration d, int precision = -1) {
|
||||||
FMT_ASSERT(!std::is_floating_point<typename Duration::rep>::value, "");
|
|
||||||
constexpr auto num_fractional_digits =
|
constexpr auto num_fractional_digits =
|
||||||
count_fractional_digits<Duration::period::num,
|
count_fractional_digits<Duration::period::num,
|
||||||
Duration::period::den>::value;
|
Duration::period::den>::value;
|
||||||
@ -1037,23 +1036,39 @@ void write_fractional_seconds(OutputIt& out, Duration d) {
|
|||||||
typename std::common_type<typename Duration::rep,
|
typename std::common_type<typename Duration::rep,
|
||||||
std::chrono::seconds::rep>::type,
|
std::chrono::seconds::rep>::type,
|
||||||
std::ratio<1, detail::pow10(num_fractional_digits)>>;
|
std::ratio<1, detail::pow10(num_fractional_digits)>>;
|
||||||
if (std::ratio_less<typename subsecond_precision::period,
|
|
||||||
std::chrono::seconds::period>::value) {
|
const auto fractional =
|
||||||
|
detail::abs(d) - std::chrono::duration_cast<std::chrono::seconds>(d);
|
||||||
|
const auto subseconds =
|
||||||
|
std::chrono::treat_as_floating_point<
|
||||||
|
typename subsecond_precision::rep>::value
|
||||||
|
? fractional.count()
|
||||||
|
: std::chrono::duration_cast<subsecond_precision>(fractional).count();
|
||||||
|
auto n = static_cast<uint32_or_64_or_128_t<long long>>(subseconds);
|
||||||
|
const int num_digits = detail::count_digits(n);
|
||||||
|
|
||||||
|
int leading_zeroes = std::max(0, num_fractional_digits - num_digits);
|
||||||
|
if (precision < 0) {
|
||||||
|
FMT_ASSERT(!std::is_floating_point<typename Duration::rep>::value, "");
|
||||||
|
if (std::ratio_less<typename subsecond_precision::period,
|
||||||
|
std::chrono::seconds::period>::value) {
|
||||||
|
*out++ = '.';
|
||||||
|
out = std::fill_n(out, leading_zeroes, '0');
|
||||||
|
out = format_decimal<Char>(out, n, num_digits).end;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
*out++ = '.';
|
*out++ = '.';
|
||||||
auto fractional =
|
leading_zeroes = std::min(leading_zeroes, precision);
|
||||||
detail::abs(d) - std::chrono::duration_cast<std::chrono::seconds>(d);
|
out = std::fill_n(out, leading_zeroes, '0');
|
||||||
auto subseconds =
|
int remaining = precision - leading_zeroes;
|
||||||
std::chrono::treat_as_floating_point<
|
if (remaining < num_digits) {
|
||||||
typename subsecond_precision::rep>::value
|
n /= to_unsigned(detail::pow10(to_unsigned(num_digits - remaining)));
|
||||||
? fractional.count()
|
out = format_decimal<Char>(out, n, remaining).end;
|
||||||
: std::chrono::duration_cast<subsecond_precision>(fractional)
|
return;
|
||||||
.count();
|
}
|
||||||
uint32_or_64_or_128_t<long long> n =
|
|
||||||
to_unsigned(to_nonnegative_int(subseconds, max_value<long long>()));
|
|
||||||
int num_digits = detail::count_digits(n);
|
|
||||||
if (num_fractional_digits > num_digits)
|
|
||||||
out = std::fill_n(out, num_fractional_digits - num_digits, '0');
|
|
||||||
out = format_decimal<Char>(out, n, num_digits).end;
|
out = format_decimal<Char>(out, n, num_digits).end;
|
||||||
|
remaining -= num_digits;
|
||||||
|
out = std::fill_n(out, remaining, '0');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1061,25 +1076,26 @@ void write_fractional_seconds(OutputIt& out, Duration d) {
|
|||||||
// number of digits. We cannot pass the Duration here, as we explicitly need to
|
// number of digits. We cannot pass the Duration here, as we explicitly need to
|
||||||
// pass the Rep value in the chrono_formatter.
|
// pass the Rep value in the chrono_formatter.
|
||||||
template <typename Duration>
|
template <typename Duration>
|
||||||
void write_floating_seconds(memory_buffer& buf, Duration duration) {
|
void write_floating_seconds(memory_buffer& buf, Duration duration,
|
||||||
FMT_ASSERT(std::is_floating_point<typename Duration::rep>::value, "");
|
int num_fractional_digits = -1) {
|
||||||
auto num_fractional_digits =
|
using rep = typename Duration::rep;
|
||||||
count_fractional_digits<Duration::period::num,
|
FMT_ASSERT(std::is_floating_point<rep>::value, "");
|
||||||
Duration::period::den>::value;
|
|
||||||
// For non-integer values, we ensure at least 6 digits to get microsecond
|
|
||||||
// precision.
|
|
||||||
auto val = duration.count();
|
|
||||||
if (num_fractional_digits < 6 &&
|
|
||||||
static_cast<typename Duration::rep>(std::round(val)) != val)
|
|
||||||
num_fractional_digits = 6;
|
|
||||||
|
|
||||||
format_to(
|
auto val = duration.count();
|
||||||
std::back_inserter(buf), runtime("{:.{}f}"),
|
|
||||||
std::fmod(val *
|
if (num_fractional_digits < 0) {
|
||||||
static_cast<typename Duration::rep>(Duration::period::num) /
|
num_fractional_digits =
|
||||||
static_cast<typename Duration::rep>(Duration::period::den),
|
count_fractional_digits<Duration::period::num,
|
||||||
static_cast<typename Duration::rep>(60)),
|
Duration::period::den>::value;
|
||||||
num_fractional_digits);
|
if (num_fractional_digits < 6 && static_cast<rep>(std::round(val)) != val)
|
||||||
|
num_fractional_digits = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
format_to(std::back_inserter(buf), runtime("{:.{}f}"),
|
||||||
|
std::fmod(val * static_cast<rep>(Duration::period::num) /
|
||||||
|
static_cast<rep>(Duration::period::den),
|
||||||
|
static_cast<rep>(60)),
|
||||||
|
num_fractional_digits);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename OutputIt, typename Char,
|
template <typename OutputIt, typename Char,
|
||||||
@ -1521,6 +1537,8 @@ class tm_writer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct chrono_format_checker : null_chrono_spec_handler<chrono_format_checker> {
|
struct chrono_format_checker : null_chrono_spec_handler<chrono_format_checker> {
|
||||||
|
bool has_precision_integral = false;
|
||||||
|
|
||||||
FMT_NORETURN void unsupported() { FMT_THROW(format_error("no date")); }
|
FMT_NORETURN void unsupported() { FMT_THROW(format_error("no date")); }
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
@ -1533,7 +1551,11 @@ struct chrono_format_checker : null_chrono_spec_handler<chrono_format_checker> {
|
|||||||
FMT_CONSTEXPR void on_24_hour_time() {}
|
FMT_CONSTEXPR void on_24_hour_time() {}
|
||||||
FMT_CONSTEXPR void on_iso_time() {}
|
FMT_CONSTEXPR void on_iso_time() {}
|
||||||
FMT_CONSTEXPR void on_am_pm() {}
|
FMT_CONSTEXPR void on_am_pm() {}
|
||||||
FMT_CONSTEXPR void on_duration_value() {}
|
FMT_CONSTEXPR void on_duration_value() const {
|
||||||
|
if (has_precision_integral) {
|
||||||
|
FMT_THROW(format_error("precision not allowed for this argument type"));
|
||||||
|
}
|
||||||
|
}
|
||||||
FMT_CONSTEXPR void on_duration_unit() {}
|
FMT_CONSTEXPR void on_duration_unit() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1834,14 +1856,15 @@ struct chrono_formatter {
|
|||||||
if (ns == numeric_system::standard) {
|
if (ns == numeric_system::standard) {
|
||||||
if (std::is_floating_point<rep>::value) {
|
if (std::is_floating_point<rep>::value) {
|
||||||
auto buf = memory_buffer();
|
auto buf = memory_buffer();
|
||||||
write_floating_seconds(buf, std::chrono::duration<rep, Period>(val));
|
write_floating_seconds(buf, std::chrono::duration<rep, Period>(val),
|
||||||
|
precision);
|
||||||
if (negative) *out++ = '-';
|
if (negative) *out++ = '-';
|
||||||
if (buf.size() < 2 || buf[1] == '.') *out++ = '0';
|
if (buf.size() < 2 || buf[1] == '.') *out++ = '0';
|
||||||
out = std::copy(buf.begin(), buf.end(), out);
|
out = std::copy(buf.begin(), buf.end(), out);
|
||||||
} else {
|
} else {
|
||||||
write(second(), 2);
|
write(second(), 2);
|
||||||
write_fractional_seconds<char_type>(
|
write_fractional_seconds<char_type>(
|
||||||
out, std::chrono::duration<rep, Period>(val));
|
out, std::chrono::duration<rep, Period>(val), precision);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2002,18 +2025,16 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
|||||||
if (begin == end) return {begin, begin};
|
if (begin == end) return {begin, begin};
|
||||||
begin = detail::parse_width(begin, end, handler);
|
begin = detail::parse_width(begin, end, handler);
|
||||||
if (begin == end) return {begin, begin};
|
if (begin == end) return {begin, begin};
|
||||||
|
auto checker = detail::chrono_format_checker();
|
||||||
if (*begin == '.') {
|
if (*begin == '.') {
|
||||||
if (std::is_floating_point<Rep>::value)
|
checker.has_precision_integral = !std::is_floating_point<Rep>::value;
|
||||||
begin = detail::parse_precision(begin, end, handler);
|
begin = detail::parse_precision(begin, end, handler);
|
||||||
else
|
|
||||||
handler.on_error("precision not allowed for this argument type");
|
|
||||||
}
|
}
|
||||||
if (begin != end && *begin == 'L') {
|
if (begin != end && *begin == 'L') {
|
||||||
++begin;
|
++begin;
|
||||||
localized = true;
|
localized = true;
|
||||||
}
|
}
|
||||||
end = detail::parse_chrono_format(begin, end,
|
end = detail::parse_chrono_format(begin, end, checker);
|
||||||
detail::chrono_format_checker());
|
|
||||||
return {begin, end};
|
return {begin, end};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -466,7 +466,7 @@ TEST(chrono_test, format_default_fp) {
|
|||||||
|
|
||||||
TEST(chrono_test, format_precision) {
|
TEST(chrono_test, format_precision) {
|
||||||
EXPECT_THROW_MSG(
|
EXPECT_THROW_MSG(
|
||||||
(void)fmt::format(runtime("{:.2}"), std::chrono::seconds(42)),
|
(void)fmt::format(runtime("{:.2%Q}"), std::chrono::seconds(42)),
|
||||||
fmt::format_error, "precision not allowed for this argument type");
|
fmt::format_error, "precision not allowed for this argument type");
|
||||||
EXPECT_EQ("1ms", fmt::format("{:.0}", dms(1.234)));
|
EXPECT_EQ("1ms", fmt::format("{:.0}", dms(1.234)));
|
||||||
EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234)));
|
EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234)));
|
||||||
@ -611,12 +611,28 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
|
|||||||
EXPECT_EQ(fmt::format("{:%S}", std::chrono::nanoseconds{-13420148734}),
|
EXPECT_EQ(fmt::format("{:%S}", std::chrono::nanoseconds{-13420148734}),
|
||||||
"-13.420148734");
|
"-13.420148734");
|
||||||
EXPECT_EQ(fmt::format("{:%S}", std::chrono::milliseconds{1234}), "01.234");
|
EXPECT_EQ(fmt::format("{:%S}", std::chrono::milliseconds{1234}), "01.234");
|
||||||
|
// Check subsecond presision modifier.
|
||||||
|
EXPECT_EQ(fmt::format("{:.6%S}", std::chrono::nanoseconds{1234}),
|
||||||
|
"00.000001");
|
||||||
|
EXPECT_EQ(fmt::format("{:.18%S}", std::chrono::nanoseconds{1234}),
|
||||||
|
"00.000001234000000000");
|
||||||
|
EXPECT_EQ(fmt::format("{:.{}%S}", std::chrono::nanoseconds{1234}, 6),
|
||||||
|
"00.000001");
|
||||||
|
EXPECT_EQ(fmt::format("{:.6%S}", std::chrono::milliseconds{1234}),
|
||||||
|
"01.234000");
|
||||||
|
EXPECT_EQ(fmt::format("{:.6%S}", std::chrono::milliseconds{-1234}),
|
||||||
|
"-01.234000");
|
||||||
|
EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::seconds{1234}), "34.000");
|
||||||
|
EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::hours{1234}), "00.000");
|
||||||
|
EXPECT_EQ(fmt::format("{:.5%S}", dms(1.234)), "00.00123");
|
||||||
|
EXPECT_EQ(fmt::format("{:.8%S}", dms(1.234)), "00.00123400");
|
||||||
{
|
{
|
||||||
// Check that {:%H:%M:%S} is equivalent to {:%T}.
|
// Check that {:%H:%M:%S} is equivalent to {:%T}.
|
||||||
auto dur = std::chrono::milliseconds{3601234};
|
auto dur = std::chrono::milliseconds{3601234};
|
||||||
auto formatted_dur = fmt::format("{:%T}", dur);
|
auto formatted_dur = fmt::format("{:%T}", dur);
|
||||||
EXPECT_EQ(formatted_dur, "01:00:01.234");
|
EXPECT_EQ(formatted_dur, "01:00:01.234");
|
||||||
EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur);
|
EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur);
|
||||||
|
EXPECT_EQ(fmt::format("{:.6%H:%M:%S}", dur), "01:00:01.234000");
|
||||||
}
|
}
|
||||||
using nanoseconds_dbl = std::chrono::duration<double, std::nano>;
|
using nanoseconds_dbl = std::chrono::duration<double, std::nano>;
|
||||||
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{-123456789}), "-00.123456789");
|
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{-123456789}), "-00.123456789");
|
||||||
@ -630,6 +646,7 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
|
|||||||
auto formatted_dur = fmt::format("{:%T}", dur);
|
auto formatted_dur = fmt::format("{:%T}", dur);
|
||||||
EXPECT_EQ(formatted_dur, "-00:01:39.123456789");
|
EXPECT_EQ(formatted_dur, "-00:01:39.123456789");
|
||||||
EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur);
|
EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur);
|
||||||
|
EXPECT_EQ(fmt::format("{:.3%H:%M:%S}", dur), "-00:01:39.123");
|
||||||
}
|
}
|
||||||
// Check that durations with precision greater than std::chrono::seconds have
|
// Check that durations with precision greater than std::chrono::seconds have
|
||||||
// fixed precision, and print zeros even if there is no fractional part.
|
// fixed precision, and print zeros even if there is no fractional part.
|
||||||
|
Loading…
Reference in New Issue
Block a user