Rename rep to abs_rep in duration_formatter and simplify the tests

The old name differed from Rep only in case, which made the narrowing
fixed in #5003 easy to miss.
This commit is contained in:
Victor Zverovich 2026-11-07 15:42:47 -08:00
parent 3a52eb4f1d
commit a9e41a6df2
2 changed files with 25 additions and 28 deletions

View File

@ -1665,30 +1665,33 @@ template <typename Char, typename Rep, typename Period>
struct duration_formatter { struct duration_formatter {
using iterator = basic_appender<Char>; using iterator = basic_appender<Char>;
iterator out; iterator out;
// rep is unsigned to avoid overflow. // Integral reps are made unsigned so that negating the minimum value can't
using rep = // overflow, and at least as wide as int to leave headroom for the conversion
// to seconds.
using abs_rep =
conditional_t<std::is_integral<Rep>::value && sizeof(Rep) < sizeof(int), conditional_t<std::is_integral<Rep>::value && sizeof(Rep) < sizeof(int),
unsigned, typename make_unsigned_or_unchanged<Rep>::type>; unsigned, typename make_unsigned_or_unchanged<Rep>::type>;
rep val; abs_rep val;
int precision; int precision;
locale_ref locale; locale_ref locale;
bool localized = false; bool localized = false;
using seconds = std::chrono::duration<rep>; using seconds = std::chrono::duration<abs_rep>;
seconds s; seconds s;
using milliseconds = std::chrono::duration<rep, std::milli>; using milliseconds = std::chrono::duration<abs_rep, std::milli>;
bool negative; bool negative = false;
using tm_writer_type = tm_writer<iterator, Char>; using tm_writer_type = tm_writer<iterator, Char>;
duration_formatter(iterator o, std::chrono::duration<Rep, Period> d, duration_formatter(iterator o, std::chrono::duration<Rep, Period> d,
locale_ref loc) locale_ref loc)
: out(o), val(static_cast<rep>(d.count())), locale(loc), negative(false) { : out(o), val(static_cast<abs_rep>(d.count())), locale(loc) {
if (d.count() < 0) { if (d.count() < 0) {
val = 0 - val; val = 0 - val;
negative = true; negative = true;
} }
s = detail::duration_cast<seconds>(std::chrono::duration<rep, Period>(val)); s = detail::duration_cast<seconds>(
std::chrono::duration<abs_rep, Period>(val));
} }
// If val is NaN or infinite, writes it to out and returns true. // If val is NaN or infinite, writes it to out and returns true.
@ -1703,16 +1706,16 @@ struct duration_formatter {
return true; return true;
} }
auto days() const -> rep { return s.count() / 86400; } auto days() const -> abs_rep { return s.count() / 86400; }
auto hour() const -> rep { return mod((s.count() / 3600), 24); } auto hour() const -> abs_rep { return mod(s.count() / 3600, 24); }
auto hour12() const -> rep { auto hour12() const -> abs_rep {
auto hour = mod((s.count() / 3600), 12); auto hour = mod(s.count() / 3600, 12);
return hour < 1 ? 12 : hour; return hour < 1 ? 12 : hour;
} }
auto minute() const -> rep { return mod((s.count() / 60), 60); } auto minute() const -> abs_rep { return mod(s.count() / 60, 60); }
auto second() const -> rep { return mod(s.count(), 60); } auto second() const -> abs_rep { return mod(s.count(), 60); }
auto time() const -> std::tm { auto time() const -> std::tm {
auto time = std::tm(); auto time = std::tm();
@ -1728,7 +1731,7 @@ struct duration_formatter {
negative = false; negative = false;
} }
void write(rep value, int width, pad_type pad = pad_type::zero) { void write(abs_rep value, int width, pad_type pad = pad_type::zero) {
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<int> n =
@ -1816,9 +1819,9 @@ struct duration_formatter {
if (write_if_nonfinite()) return; if (write_if_nonfinite()) return;
if (ns == numeric_system::standard) { if (ns == numeric_system::standard) {
if (std::is_floating_point<rep>::value) { if (std::is_floating_point<abs_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<abs_rep, Period>(val),
precision); precision);
write_sign(); write_sign();
if (buf.size() < 2 || buf[1] == '.') if (buf.size() < 2 || buf[1] == '.')
@ -1827,7 +1830,7 @@ struct duration_formatter {
} else { } else {
write(second(), 2, pad); write(second(), 2, pad);
write_fractional_seconds<Char>( write_fractional_seconds<Char>(
out, std::chrono::duration<rep, Period>(val), precision); out, std::chrono::duration<abs_rep, Period>(val), precision);
} }
return; return;
} }

View File

@ -538,6 +538,10 @@ TEST(chrono_test, format_specs) {
EXPECT_EQ(fmt::format("{:%j}", days(12)), "12"); EXPECT_EQ(fmt::format("{:%j}", days(12)), "12");
EXPECT_EQ(fmt::format("{:%j}", days(12345)), "12345"); EXPECT_EQ(fmt::format("{:%j}", days(12345)), "12345");
EXPECT_EQ(fmt::format("{:%j}", std::chrono::hours(12345 * 24 + 12)), "12345"); EXPECT_EQ(fmt::format("{:%j}", std::chrono::hours(12345 * 24 + 12)), "12345");
using signed_days = std::chrono::duration<signed char, std::ratio<86400>>;
EXPECT_EQ(fmt::format("{:%j}", signed_days(-128)), "-128");
using two_days = std::chrono::duration<unsigned char, std::ratio<172800>>;
EXPECT_EQ(fmt::format("{:%j}", two_days(200)), "400");
EXPECT_EQ(fmt::format("{:%H:%M:%S}", std::chrono::seconds(12345)), EXPECT_EQ(fmt::format("{:%H:%M:%S}", std::chrono::seconds(12345)),
"03:25:45"); "03:25:45");
EXPECT_EQ(fmt::format("{:%R}", std::chrono::seconds(12345)), "03:25"); EXPECT_EQ(fmt::format("{:%R}", std::chrono::seconds(12345)), "03:25");
@ -546,16 +550,6 @@ TEST(chrono_test, format_specs) {
EXPECT_EQ(fmt::format("{:%q}", std::chrono::seconds(12345)), "s"); EXPECT_EQ(fmt::format("{:%q}", std::chrono::seconds(12345)), "s");
} }
TEST(chrono_test, format_days_with_signed_char_rep) {
using signed_days = std::chrono::duration<signed char, std::ratio<86400>>;
EXPECT_EQ(fmt::format("{:%j}", signed_days(-128)), "-128");
}
TEST(chrono_test, format_days_with_unsigned_char_rep) {
using two_days = std::chrono::duration<unsigned char, std::ratio<172800>>;
EXPECT_EQ(fmt::format("{:%j}", two_days(200)), "400");
}
TEST(chrono_test, invalid_specs) { TEST(chrono_test, invalid_specs) {
auto sec = std::chrono::seconds(0); auto sec = std::chrono::seconds(0);
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%a}"), sec), fmt::format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{:%a}"), sec), fmt::format_error,