diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 5c19f0c6..d4b44eb3 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -533,16 +533,13 @@ template from) -> To { #if FMT_SAFE_DURATION_CAST using to_rep = typename To::rep; - auto count = detail::duration_cast< - std::chrono::duration>(from) - .count(); - // The conversion truncates toward zero so check the truncated value. - // max + 1 is a power of 2 so the cast to FromRep is exact even when max - // itself is rounded up by the conversion; the comparison is false for NaN. - auto upper = static_cast(max_value()) + 1; + using float_duration = std::chrono::duration; + auto count = std::trunc(detail::duration_cast(from).count()); + // The conversion truncates, so check the truncated count against + // [min, max + 1) where max + 1 = 2^digits. NaN fails the comparison. auto lower = static_cast((std::numeric_limits::min)()); - auto truncated = std::trunc(count); - if (!(truncated >= lower && truncated < upper)) throw_duration_error(); + auto upper = std::ldexp(FromRep(1), std::numeric_limits::digits); + if (!(count >= lower && count < upper)) throw_duration_error(); return To(static_cast(count)); #else return std::chrono::duration_cast(from); diff --git a/test/chrono-test.cc b/test/chrono-test.cc index 4e8ae32e..eac86bec 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -1053,9 +1053,8 @@ TEST(chrono_test, out_of_range) { // A floating-point day count that doesn't fit in int. auto fd = std::chrono::duration(1e300); EXPECT_THROW((void)fmt::format("{:%j}", fd), fmt::format_error); - // A floating-point time point whose seconds don't fit in time_t. - auto ftp = sys_time>(fd); - EXPECT_THROW((void)fmt::format("{:%T}", ftp), fmt::format_error); + if (!FMT_SAFE_DURATION_CAST) return; + // Floating-point time points whose seconds don't fit in time_t. auto fnan = sys_time>( std::chrono::duration(std::numeric_limits::quiet_NaN())); EXPECT_THROW((void)fmt::format("{:%T}", fnan), fmt::format_error); @@ -1083,17 +1082,21 @@ TEST(chrono_test, float_to_integral_duration_cast) { max); // Any nonzero value converts to true. using bool_duration = std::chrono::duration; - EXPECT_FALSE(fmt::detail::duration_cast(fsec(0)).count()); EXPECT_TRUE(fmt::detail::duration_cast(fsec(2)).count()); - EXPECT_TRUE(fmt::detail::duration_cast(fsec(-1)).count()); - if (FMT_SAFE_DURATION_CAST) { - EXPECT_THROW( - fmt::detail::duration_cast>(fsec(-1)), - fmt::format_error); - EXPECT_THROW( - fmt::detail::duration_cast>(fsec(min - 1.0)), - fmt::format_error); - } + if (!FMT_SAFE_DURATION_CAST) return; + EXPECT_THROW( + fmt::detail::duration_cast>(fsec(-1)), + fmt::format_error); + EXPECT_THROW( + fmt::detail::duration_cast>(fsec(min - 1.0)), + fmt::format_error); + // max + 1, which is also what max rounds to in the source type. + EXPECT_THROW(fmt::detail::duration_cast>( + fsec(9223372036854775808.0)), + fmt::format_error); + EXPECT_THROW(fmt::detail::duration_cast>( + fsec(std::numeric_limits::quiet_NaN())), + fmt::format_error); } struct custom_rep {