Skip to content

Commit

Permalink
Fix handling of chrono durations with minimal signed rep
Browse files Browse the repository at this point in the history
  • Loading branch information
vitaut committed Jun 1, 2019
1 parent 87e4ea2 commit 78daa50
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 9 deletions.
33 changes: 25 additions & 8 deletions include/fmt/chrono.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <typename T, bool INTEGRAL = std::is_integral<T>::value>
struct make_unsigned_or_unchanged {
using type = T;
};

template <typename T> struct make_unsigned_or_unchanged<T, true> {
using type = typename std::make_unsigned<T>::type;
};

template <typename Rep, typename Period,
typename std::enable_if<std::is_integral<Rep>::value, int>::type = 0>
inline std::chrono::duration<Rep, std::milli> get_milliseconds(
Expand Down Expand Up @@ -438,21 +449,27 @@ struct chrono_formatter {
FormatContext& context;
OutputIt out;
int precision;
typedef typename std::conditional<std::is_integral<Rep>::value &&
sizeof(Rep) < sizeof(int),
int, Rep>::type rep;
// rep is unsigned to avoid overflow.
using rep = typename std::conditional<
std::is_integral<Rep>::value && sizeof(Rep) < sizeof(int), unsigned,
typename make_unsigned_or_unchanged<Rep>::type>::type;
rep val;
typedef std::chrono::duration<rep> seconds;
seconds s;
typedef std::chrono::duration<rep, std::milli> milliseconds;
bool negative;

typedef typename FormatContext::char_type char_type;

explicit chrono_formatter(FormatContext& ctx, OutputIt o,
std::chrono::duration<Rep, Period> d)
: context(ctx), out(o), val(d.count()) {
if (d.count() < 0) d = -d;
s = std::chrono::duration_cast<seconds>(d);
: context(ctx), out(o), val(d.count()), negative(false) {
if (d.count() < 0) {
val = -val;
negative = true;
}
s = std::chrono::duration_cast<seconds>(
std::chrono::duration<rep, Period>(val));
}

Rep hour() const { return mod((s.count() / 3600), 24); }
Expand All @@ -474,9 +491,9 @@ struct chrono_formatter {
}

void write_sign() {
if (val < 0) {
if (negative) {
*out++ = '-';
val = -val;
negative = false;
}
}

Expand Down
5 changes: 4 additions & 1 deletion test/chrono-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
Expand All @@ -316,6 +316,9 @@ TEST(ChronoTest, NegativeDuration) {
EXPECT_EQ("-00.127",
fmt::format("{:%S}",
std::chrono::duration<signed char, std::milli>{-127}));
auto min = std::numeric_limits<int>::min();
EXPECT_EQ(fmt::format("{}", min),
fmt::format("{:%Q}", std::chrono::duration<int>(min)));
}

TEST(ChronoTest, SpecialDurations) {
Expand Down

0 comments on commit 78daa50

Please sign in to comment.