diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index f57aef38..738f1bea 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -401,6 +401,17 @@ inline T mod(T x, int y) { return std::fmod(x, y); } +// If T is an integral type, maps T to its unsigned counterpart, otherwise +// leaves it unchanged (unlike std::make_unsigned). +template ::value> +struct make_unsigned_or_unchanged { + using type = T; +}; + +template struct make_unsigned_or_unchanged { + using type = typename std::make_unsigned::type; +}; + template ::value, int>::type = 0> inline std::chrono::duration get_milliseconds( @@ -438,21 +449,27 @@ struct chrono_formatter { FormatContext& context; OutputIt out; int precision; - typedef typename std::conditional::value && - sizeof(Rep) < sizeof(int), - int, Rep>::type rep; + // rep is unsigned to avoid overflow. + using rep = typename std::conditional< + std::is_integral::value && sizeof(Rep) < sizeof(int), unsigned, + typename make_unsigned_or_unchanged::type>::type; rep val; typedef std::chrono::duration seconds; seconds s; typedef std::chrono::duration milliseconds; + bool negative; typedef typename FormatContext::char_type char_type; explicit chrono_formatter(FormatContext& ctx, OutputIt o, std::chrono::duration d) - : context(ctx), out(o), val(d.count()) { - if (d.count() < 0) d = -d; - s = std::chrono::duration_cast(d); + : context(ctx), out(o), val(d.count()), negative(false) { + if (d.count() < 0) { + val = -val; + negative = true; + } + s = std::chrono::duration_cast( + std::chrono::duration(val)); } Rep hour() const { return mod((s.count() / 3600), 24); } @@ -474,9 +491,9 @@ struct chrono_formatter { } void write_sign() { - if (val < 0) { + if (negative) { *out++ = '-'; - val = -val; + negative = false; } } diff --git a/test/chrono-test.cc b/test/chrono-test.cc index abb8cb8b..a9412c90 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -306,7 +306,7 @@ TEST(ChronoTest, InvalidColons) { fmt::format_error); } -TEST(ChronoTest, NegativeDuration) { +TEST(ChronoTest, NegativeDurations) { EXPECT_EQ("-12345", fmt::format("{:%Q}", std::chrono::seconds(-12345))); EXPECT_EQ("-03:25:45", fmt::format("{:%H:%M:%S}", std::chrono::seconds(-12345))); @@ -316,6 +316,9 @@ TEST(ChronoTest, NegativeDuration) { EXPECT_EQ("-00.127", fmt::format("{:%S}", std::chrono::duration{-127})); + auto min = std::numeric_limits::min(); + EXPECT_EQ(fmt::format("{}", min), + fmt::format("{:%Q}", std::chrono::duration(min))); } TEST(ChronoTest, SpecialDurations) {