return std::pair<time_t, long>{sec, subsec};
}
+/*
+ * Helper classes for picking an intermediate duration type to use
+ * when doing conversions to/from durations where neither the numerator nor
+ * denominator are 1.
+ */
+template <typename T, bool IsFloatingPoint, bool IsSigned>
+struct IntermediateTimeRep {};
+template <typename T, bool IsSigned>
+struct IntermediateTimeRep<T, true, IsSigned> {
+ using type = T;
+};
+template <typename T>
+struct IntermediateTimeRep<T, false, true> {
+ using type = intmax_t;
+};
+template <typename T>
+struct IntermediateTimeRep<T, false, false> {
+ using type = uintmax_t;
+};
+// For IntermediateDuration we always use 1 as the numerator, and the original
+// Period denominator. This ensures that we do not lose precision when
+// performing the conversion.
+template <typename Rep, typename Period>
+using IntermediateDuration = std::chrono::duration<
+ typename IntermediateTimeRep<
+ Rep,
+ std::is_floating_point<Rep>::value,
+ std::is_signed<Rep>::value>::type,
+ std::ratio<1, Period::den>>;
+
/**
* Convert a std::chrono::duration to a pair of (seconds, subseconds)
*
static_assert(
SubsecondRatio::num == 1, "subsecond numerator should always be 1");
- // TODO: We need to implement an overflow-checking tryTo() function for
- // duration-to-duration casts for the code above to work.
- //
- // For now this is unimplemented, and we just have a static_assert that
- // will always fail if someone tries to instantiate this. Unusual duration
- // types should be extremely rare, and I'm not aware of any code at the
- // moment that actually needs this.
- static_assert(
- Period::num == 1,
- "conversion from unusual duration types is not implemented yet");
- (void)duration;
- return makeUnexpected(ConversionCode::SUCCESS);
+ // Perform this conversion by first converting to a duration where the
+ // numerator is 1, then convert to the output type.
+ using IntermediateType = IntermediateDuration<Rep, Period>;
+ using IntermediateRep = typename IntermediateType::rep;
+
+ // Check to see if we would have overflow converting to the intermediate
+ // type.
+ constexpr auto maxInput =
+ std::numeric_limits<IntermediateRep>::max() / Period::num;
+ if (duration.count() > maxInput) {
+ return makeUnexpected(ConversionCode::POSITIVE_OVERFLOW);
+ }
+ constexpr auto minInput =
+ std::numeric_limits<IntermediateRep>::min() / Period::num;
+ if (duration.count() < minInput) {
+ return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
+ }
+ auto intermediate =
+ IntermediateType{static_cast<IntermediateRep>(duration.count()) *
+ static_cast<IntermediateRep>(Period::num)};
+
+ return durationToPosixTime<SubsecondRatio>(intermediate);
}
/**
static_assert(
SubsecondRatio::num == 1, "subsecond numerator should always be 1");
- // TODO: We need to implement an overflow-checking tryTo() function for
- // duration-to-duration casts for the code above to work.
+ // Cast through an intermediate type with subsecond granularity.
+ // Note that this could fail due to overflow during the initial conversion
+ // even if the result is representable in the output POSIX-style types.
//
- // For now this is unimplemented, and we just have a static_assert that
- // will always fail if someone tries to instantiate this. Unusual duration
- // types should be extremely rare, and I'm not aware of any code at the
- // moment that actually needs this.
- static_assert(
- Tgt::period::num == 1,
- "conversion to unusual duration types is not implemented yet");
- (void)seconds;
- (void)subseconds;
- return makeUnexpected(ConversionCode::SUCCESS);
+ // Note that for integer type conversions going through this intermediate
+ // type can result in slight imprecision due to truncating the intermediate
+ // calculation to an integer.
+ using IntermediateType =
+ IntermediateDuration<typename Tgt::rep, typename Tgt::period>;
+ auto intermediate = posixTimeToDuration<SubsecondRatio>(
+ seconds, subseconds, IntermediateType{});
+ if (intermediate.hasError()) {
+ return makeUnexpected(intermediate.error());
+ }
+ // Now convert back to the target duration. Use tryTo() to confirm that the
+ // result fits in the target representation type.
+ return tryTo<typename Tgt::rep>(
+ intermediate.value().count() / Tgt::period::num)
+ .then([](typename Tgt::rep tgt) { return Tgt{tgt}; });
}
template <
ts.tv_nsec = 0;
auto doubleMinutes = to<duration<double, std::ratio<60>>>(ts);
EXPECT_EQ(1.5, doubleMinutes.count());
+
+ // Test with unusual durations where neither the numerator nor denominator
+ // are 1.
+ using five_sevenths = std::chrono::duration<int64_t, std::ratio<5, 7>>;
+ ts.tv_sec = 1;
+ ts.tv_nsec = 0;
+ EXPECT_EQ(1, to<five_sevenths>(ts).count());
+ ts.tv_sec = 1;
+ ts.tv_nsec = 428571500;
+ EXPECT_EQ(2, to<five_sevenths>(ts).count());
+
+ using thirteen_thirds = std::chrono::duration<double, std::ratio<13, 3>>;
+ ts.tv_sec = 39;
+ ts.tv_nsec = 0;
+ EXPECT_NEAR(9.0, to<thirteen_thirds>(ts).count(), 0.000000001);
+ ts.tv_sec = 1;
+ ts.tv_nsec = 0;
+ EXPECT_NEAR(0.230769230, to<thirteen_thirds>(ts).count(), 0.000000001);
}
TEST(Conv, timespecToStdChronoOverflow) {
EXPECT_EQ(
std::numeric_limits<decltype(ts.tv_sec)>::max() / 3600,
to<hours_u64>(ts).count());
+
+ // Test overflow with an unusual duration where neither the numerator nor
+ // denominator are 1.
+ using unusual_time = std::chrono::duration<int16_t, std::ratio<13, 3>>;
+ ts.tv_sec = 141994;
+ ts.tv_nsec = 666666666;
+ EXPECT_EQ(32767, to<unusual_time>(ts).count());
+ ts.tv_nsec = 666666667;
+ EXPECT_THROW(to<unusual_time>(ts), std::range_error);
+
+ ts.tv_sec = -141998;
+ ts.tv_nsec = 999999999;
+ EXPECT_EQ(-32768, to<unusual_time>(ts).count());
+ ts.tv_sec = -141999;
+ ts.tv_nsec = 0;
+ EXPECT_THROW(to<unusual_time>(ts), std::range_error);
}
TEST(Conv, timevalToStdChrono) {
ts = to<struct timespec>(createTimePoint<system_clock>(123ns));
EXPECT_EQ(0, ts.tv_sec);
EXPECT_EQ(123, ts.tv_nsec);
+
+ // Test with some unusual durations where neither the numerator nor
+ // denominator are 1.
+ using five_sevenths = std::chrono::duration<int64_t, std::ratio<5, 7>>;
+ ts = to<struct timespec>(five_sevenths(7));
+ EXPECT_EQ(5, ts.tv_sec);
+ EXPECT_EQ(0, ts.tv_nsec);
+ ts = to<struct timespec>(five_sevenths(19));
+ EXPECT_EQ(13, ts.tv_sec);
+ EXPECT_EQ(571428571, ts.tv_nsec);
+ using seven_fifths = std::chrono::duration<int64_t, std::ratio<7, 5>>;
+ ts = to<struct timespec>(seven_fifths(5));
+ EXPECT_EQ(7, ts.tv_sec);
+ EXPECT_EQ(0, ts.tv_nsec);
}
TEST(Conv, stdChronoToTimespecOverflow) {
EXPECT_EQ(ts.tv_nsec, 0);
EXPECT_THROW(
to<struct timespec>(hours_i64(2562047788015216LL)), std::range_error);
+
+ // Test overflows from an unusual duration where neither the numerator nor
+ // denominator are 1.
+ using three_halves = std::chrono::duration<uint64_t, std::ratio<3, 2>>;
+ EXPECT_THROW(
+ to<struct timespec>(three_halves(6148914691236517206ULL)),
+ std::range_error);
}
// Test for overflow.