Reuse precision modifier in second specification

This commit is contained in:
Stepan Ponomarev 2022-10-19 12:00:30 +03:00
parent 08f60074b6
commit 852e039b2a
2 changed files with 71 additions and 111 deletions

View File

@ -593,27 +593,6 @@ template <typename Period> FMT_CONSTEXPR inline const char* get_units() {
return nullptr;
}
template <typename Char>
struct non_dynamic_precision_handler {
int precision = -1;
void on_error(const char* msg) { FMT_THROW(format_error(msg)); }
FMT_CONSTEXPR void on_precision(int _precision) {
precision = _precision;
}
template <typename Id> FMT_CONSTEXPR void on_dynamic_precision(Id /*_precision*/) {
on_error("dynamic precision is not allowed here");
}
FMT_CONSTEXPR void end_precision() {}
};
template <size_t N>
FMT_CONSTEXPR void check_allowed_precision(int precision, const int (&allowed_precisions)[N]) {
if (std::find(std::begin(allowed_precisions), std::end(allowed_precisions), precision) == std::end(allowed_precisions)) {
FMT_THROW(format_error("invalid precision"));
}
}
enum class numeric_system {
standard,
// Alternative numeric system, e.g. 十二 instead of 12 in ja_JP locale.
@ -651,17 +630,6 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin,
handler.on_text(tab, tab + 1);
break;
}
case '.': {
non_dynamic_precision_handler<Char> prec_handler;
ptr = detail::parse_precision(ptr-1, end, prec_handler);
c = *ptr++;
if (c == 'S') {
handler.on_second_with_fractions(numeric_system::standard, prec_handler.precision);
} else {
FMT_THROW(format_error("specifier doesn't have precision modifier"));
}
break;
}
// Year:
case 'Y':
handler.on_year(numeric_system::standard);
@ -885,7 +853,6 @@ template <typename Derived> struct null_chrono_spec_handler {
FMT_CONSTEXPR void on_12_hour(numeric_system) { unsupported(); }
FMT_CONSTEXPR void on_minute(numeric_system) { unsupported(); }
FMT_CONSTEXPR void on_second(numeric_system) { unsupported(); }
FMT_CONSTEXPR void on_second_with_fractions(numeric_system, int) { unsupported(); }
FMT_CONSTEXPR void on_datetime(numeric_system) { unsupported(); }
FMT_CONSTEXPR void on_loc_date(numeric_system) { unsupported(); }
FMT_CONSTEXPR void on_loc_time(numeric_system) { unsupported(); }
@ -1402,10 +1369,12 @@ template <typename OutputIt, typename Char> class tm_writer {
// These apply to chrono durations but not tm.
void on_duration_value() {}
void on_duration_unit() {}
void on_second_with_fractions(numeric_system, int) {}
};
struct chrono_format_checker : null_chrono_spec_handler<chrono_format_checker> {
bool is_floating_point = false;
bool has_precision = false;
FMT_NORETURN void unsupported() { FMT_THROW(format_error("no date")); }
template <typename Char>
@ -1414,15 +1383,15 @@ struct chrono_format_checker : null_chrono_spec_handler<chrono_format_checker> {
FMT_CONSTEXPR void on_12_hour(numeric_system) {}
FMT_CONSTEXPR void on_minute(numeric_system) {}
FMT_CONSTEXPR void on_second(numeric_system) {}
FMT_CONSTEXPR void on_second_with_fractions(numeric_system, int precision) {
constexpr int allowed_precisions[6] = {3, 6, 9, 12, 15, 18};
check_allowed_precision(precision, allowed_precisions);
}
FMT_CONSTEXPR void on_12_hour_time() {}
FMT_CONSTEXPR void on_24_hour_time() {}
FMT_CONSTEXPR void on_iso_time() {}
FMT_CONSTEXPR void on_am_pm() {}
FMT_CONSTEXPR void on_duration_value() {}
FMT_CONSTEXPR void on_duration_value() const {
if (has_precision && !is_floating_point) {
FMT_THROW(format_error("precision not allowed for this argument type"));
}
}
FMT_CONSTEXPR void on_duration_unit() {}
};
@ -1732,6 +1701,43 @@ struct chrono_formatter {
}
}
template <typename Duration> void write_fractional_seconds(Duration d, int precision) {
constexpr auto num_fractional_digits =
count_fractional_digits<Duration::period::num,
Duration::period::den>::value;
using subsecond_precision = std::chrono::duration<
typename std::common_type<typename Duration::rep,
std::chrono::seconds::rep>::type,
std::ratio<1, detail::pow10(num_fractional_digits)>>;
if (precision > 0) {
*out++ = '.';
auto fractional =
detail::abs(d) - std::chrono::duration_cast<std::chrono::seconds>(d);
auto subseconds =
std::chrono::treat_as_floating_point<
typename subsecond_precision::rep>::value
? fractional.count()
: std::chrono::duration_cast<subsecond_precision>(fractional)
.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);
int zeroes = std::min(num_fractional_digits - num_digits, precision);
if (num_fractional_digits > num_digits)
out = std::fill_n(out, zeroes, '0');
int remaining = precision - (zeroes > 0 ? zeroes : 0);
if (remaining < num_digits) {
n /= detail::pow10(num_digits - remaining);
out = format_decimal<char_type>(out, n, remaining).end;
return;
}
out = format_decimal<char_type>(out, n, num_digits).end;
remaining -= num_digits;
out = std::fill_n(out, remaining, '0');
}
}
void write_nan() { std::copy_n("nan", 3, out); }
void write_pinf() { std::copy_n("inf", 3, out); }
void write_ninf() { std::copy_n("-inf", 4, out); }
@ -1809,8 +1815,10 @@ struct chrono_formatter {
if (ns == numeric_system::standard) {
if (std::is_floating_point<rep>::value) {
constexpr auto num_fractional_digits =
count_fractional_digits<Period::num, Period::den>::value;
auto num_fractional_digits =
precision >= 0
? precision
: count_fractional_digits<Period::num, Period::den>::value;
auto buf = memory_buffer();
format_to(std::back_inserter(buf), runtime("{:.{}f}"),
std::fmod(val * static_cast<rep>(Period::num) /
@ -1822,7 +1830,11 @@ struct chrono_formatter {
out = std::copy(buf.begin(), buf.end(), out);
} else {
write(second(), 2);
write_fractional_seconds(std::chrono::duration<rep, Period>(val));
if (precision >= 0) {
write_fractional_seconds(std::chrono::duration<rep, Period>(val), precision);
} else {
write_fractional_seconds(std::chrono::duration<rep, Period>(val));
}
}
return;
}
@ -1831,55 +1843,6 @@ struct chrono_formatter {
format_tm(time, &tm_writer_type::on_second, ns);
}
template <typename Ratio>
void write_seconds_with_fractions(numeric_system ns) {
if (handle_nan_inf()) return;
if (ns == numeric_system::standard) {
if (std::is_floating_point<rep>::value) {
constexpr auto num_fractional_digits =
count_fractional_digits<Ratio::num, Ratio::den>::value;
auto buf = memory_buffer();
format_to(std::back_inserter(buf), runtime("{:.{}f}"),
std::fmod(val * static_cast<rep>(Period::num) /
static_cast<rep>(Period::den),
static_cast<rep>(60)),
num_fractional_digits);
if (negative) *out++ = '-';
if (buf.size() < 2 || buf[1] == '.') *out++ = '0';
out = std::copy(buf.begin(), buf.end(), out);
} else {
write(second(), 2);
const auto dur = std::chrono::duration<rep, Period>{val};
std::chrono::seconds const sec = std::chrono::duration_cast<std::chrono::seconds>(dur);
write_fractional_seconds(std::chrono::duration_cast<std::chrono::duration<rep, Ratio>>(dur - sec));
}
return;
}
auto time = tm();
time.tm_sec = to_nonnegative_int(second(), 60);
format_tm(time, &tm_writer_type::on_second, ns);
}
void on_second_with_fractions(numeric_system ns, int fractional_digits) {
switch (fractional_digits) {
case 3:
return this->write_seconds_with_fractions<std::milli>(ns);
case 6:
return this->write_seconds_with_fractions<std::micro>(ns);
case 9:
return this->write_seconds_with_fractions<std::nano>(ns);
case 12:
return this->write_seconds_with_fractions<std::pico>(ns);
case 15:
return this->write_seconds_with_fractions<std::femto>(ns);
case 18:
return this->write_seconds_with_fractions<std::atto>(ns);
default:
return;
}
}
void on_12_hour_time() {
if (handle_nan_inf()) return;
format_tm(time(), &tm_writer_type::on_12_hour_time);
@ -2032,18 +1995,17 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
if (begin == end) return {begin, begin};
begin = detail::parse_width(begin, end, handler);
if (begin == end) return {begin, begin};
auto checker = detail::chrono_format_checker();
checker.is_floating_point = std::is_floating_point<Rep>::value;
if (*begin == '.') {
if (std::is_floating_point<Rep>::value)
begin = detail::parse_precision(begin, end, handler);
else
handler.on_error("precision not allowed for this argument type");
checker.has_precision = true;
begin = detail::parse_precision(begin, end, handler);
}
if (begin != end && *begin == 'L') {
++begin;
localized = true;
}
end = detail::parse_chrono_format(begin, end,
detail::chrono_format_checker());
end = detail::parse_chrono_format(begin, end, checker);
return {begin, end};
}

View File

@ -465,7 +465,7 @@ TEST(chrono_test, format_default_fp) {
TEST(chrono_test, format_precision) {
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");
EXPECT_EQ("1ms", fmt::format("{:.0}", dms(1.234)));
EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234)));
@ -611,24 +611,22 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
"-13.420148734");
EXPECT_EQ(fmt::format("{:%S}", std::chrono::milliseconds{1234}), "01.234");
// Check subsecond presision modifier.
EXPECT_EQ(fmt::format("{:%.6S}", std::chrono::nanoseconds{1234}), "00.000001");
EXPECT_EQ(fmt::format("{:%.18S}", std::chrono::nanoseconds{1234}), "00.000001234000000000");
EXPECT_EQ(fmt::format("{:%.3S}", std::chrono::microseconds{1234}), "00.001");
EXPECT_EQ(fmt::format("{:%.9S}", std::chrono::microseconds{1234}), "00.001234000");
EXPECT_EQ(fmt::format("{:%.6S}", std::chrono::milliseconds{1234}), "01.234000");
EXPECT_EQ(fmt::format("{:%.6S}", std::chrono::milliseconds{-1234}), "-01.234000");
EXPECT_EQ(fmt::format("{:%.3S}", std::chrono::seconds{1234}), "34.000");
EXPECT_EQ(fmt::format("{:%.9S}", std::chrono::minutes{1234}), "00.000000000");
EXPECT_EQ(fmt::format("{:%.3S}", std::chrono::hours{1234}), "00.000");
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%.5S}"), std::chrono::microseconds{1234}), fmt::format_error,
"invalid precision");
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}.
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);
EXPECT_EQ(fmt::format("{:%H:%M:%.6S}", dur), "01:00:01.234000");
EXPECT_EQ(fmt::format("{:.6%H:%M:%S}", dur), "01:00:01.234000");
}
using nanoseconds_dbl = std::chrono::duration<double, std::nano>;
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{-123456789}), "-00.123456789");
@ -642,7 +640,7 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
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);
EXPECT_EQ(fmt::format("{:%H:%M:%.3S}", dur), "-00:01:39.123");
EXPECT_EQ(fmt::format("{:.3%H:%M:%S}", dur), "-00:01:39.123");
}
// Check that durations with precision greater than std::chrono::seconds have
// fixed precision, and print zeros even if there is no fractional part.