diff options
author | swarmer <[email protected]> | 2025-10-03 20:25:26 +0300 |
---|---|---|
committer | swarmer <[email protected]> | 2025-10-03 20:50:00 +0300 |
commit | 08d7ed83f95bdff4ddd3fd1b05b16d12f6bedbb1 (patch) | |
tree | e7c89fa017ee7154ef1a16761d4e9fa4d816e785 /util | |
parent | e8e38bf51bdfebc163b2fa9310c302f4f9e1c720 (diff) |
TDuration/TInstant named constructors should return staturated value on overflow
`TDuration::Seconds(sec)` now returns `TDuration::Max()` if `sec` is greater than the largest representable value.
Previously, a silent wrap-around could produce a significantly shorter interval (even a zero one).
commit_hash:552fde08fa3b8201ede23894bb363df2d6bbd5af
Diffstat (limited to 'util')
-rw-r--r-- | util/datetime/base.h | 142 | ||||
-rw-r--r-- | util/datetime/base_ut.cpp | 44 |
2 files changed, 143 insertions, 43 deletions
diff --git a/util/datetime/base.h b/util/datetime/base.h index d613012e860..7f1df70bddd 100644 --- a/util/datetime/base.h +++ b/util/datetime/base.h @@ -93,6 +93,50 @@ TString DateToString(time_t when, long* sec = nullptr); TString YearToString(const struct tm& theTm); TString YearToString(time_t when); +namespace NDateTimeHelpers { + template <typename T> + struct TPrecisionHelper { + using THighPrecision = ui64; + }; + + template <> + struct TPrecisionHelper<float> { + using THighPrecision = double; + }; + + template <> + struct TPrecisionHelper<double> { + using THighPrecision = double; + }; + + template <ui64 b, typename T> + static constexpr ui64 MulWithSaturation(T a) = delete; + + template <ui64 b> + constexpr ui64 MulWithSaturation(ui64 a) { + constexpr ui64 maxMultiplicand = Max<ui64>() / b; +#if defined(__GNUC__) && defined(__cpp_lib_is_constant_evaluated) + if (!std::is_constant_evaluated()) { + if constexpr (std::is_same<ui64, unsigned long>::value) { + unsigned long r = 0; + return __builtin_umull_overflow(a, b, &r) ? Max<ui64>() : r; + } + if constexpr (std::is_same<ui64, unsigned long long>::value) { + unsigned long long r = 0; + return __builtin_umulll_overflow(a, b, &r) ? Max<ui64>() : r; + } + } +#endif + return a <= maxMultiplicand ? a * b : Max<ui64>(); + } + + template <ui64 b> + constexpr ui64 MulWithSaturation(double a) { + double r = a * b; + return r < 0 ? 0 : (r <= MaxFloor<ui64>() ? static_cast<ui64>(r) : Max<ui64>()); + } +} // namespace NDateTimeHelpers + template <class S> class TTimeBase { public: @@ -172,25 +216,32 @@ public: } protected: - TValue Value_; // microseconds count -}; + /* noexcept(false) as conversion from T might throw, for example FromString("abc") */ + template <typename T> + static constexpr S MilliSecondsConvertFrom(T milliseconds) noexcept(false) { + auto ms = typename NDateTimeHelpers::TPrecisionHelper<T>::THighPrecision(milliseconds); + return S::MicroSeconds(NDateTimeHelpers::MulWithSaturation<ui64(1'000)>(ms)); + } -namespace NDateTimeHelpers { + /* noexcept(false) as conversion from T might throw, for example FromString("abc") */ template <typename T> - struct TPrecisionHelper { - using THighPrecision = ui64; - }; + static constexpr S SecondsConvertFrom(T seconds) noexcept(false) { + auto s = typename NDateTimeHelpers::TPrecisionHelper<T>::THighPrecision(seconds); + if constexpr (std::is_same_v<decltype(s), double>) { + return MilliSecondsConvertFrom(s * 1000); + } + return S::MicroSeconds(NDateTimeHelpers::MulWithSaturation<ui64(1'000'000)>(s)); + } - template <> - struct TPrecisionHelper<float> { - using THighPrecision = double; - }; + static constexpr S Minutes(ui64 m) noexcept; - template <> - struct TPrecisionHelper<double> { - using THighPrecision = double; - }; -} // namespace NDateTimeHelpers + static constexpr S Hours(ui64 h) noexcept; + + static constexpr S Days(ui64 d) noexcept; + +protected: + TValue Value_; // microseconds count +}; class TDuration: public TTimeBase<TDuration> { using TBase = TTimeBase<TDuration>; @@ -267,7 +318,7 @@ public: /* noexcept(false) as conversion from T might throw, for example FromString("abc") */ template <typename T> static constexpr TDuration MilliSeconds(T ms) noexcept(false) { - return MicroSeconds((ui64)(typename NDateTimeHelpers::TPrecisionHelper<T>::THighPrecision(ms) * 1000)); + return TBase::MilliSecondsConvertFrom(std::move(ms)); } using TBase::Days; @@ -277,6 +328,12 @@ public: using TBase::Minutes; using TBase::Seconds; + /// Base class static methods: + /// + /// static constexpr TDuration Minutes(ui64 m) noexcept; + /// static constexpr TDuration Hours(ui64 h) noexcept; + /// static constexpr TDuration Days(ui64 d) noexcept; + /// DeadLineFromTimeOut inline TInstant ToDeadLine() const; constexpr TInstant ToDeadLine(TInstant now) const; @@ -292,19 +349,7 @@ public: /* noexcept(false) as conversion from T might throw, for example FromString("abc") */ template <typename T> static constexpr TDuration Seconds(T s) noexcept(false) { - return MilliSeconds(typename NDateTimeHelpers::TPrecisionHelper<T>::THighPrecision(s) * 1000); - } - - static constexpr TDuration Minutes(ui64 m) noexcept { - return Seconds(m * 60); - } - - static constexpr TDuration Hours(ui64 h) noexcept { - return Minutes(h * 60); - } - - static constexpr TDuration Days(ui64 d) noexcept { - return Hours(d * 24); + return TBase::SecondsConvertFrom(std::move(s)); } /// parses strings like 10s, 15ms, 15.05s, 20us, or just 25 (s). See parser_ut.cpp for details @@ -396,30 +441,26 @@ public: return TInstant(us); } - /// ms since epoch + /// milliseconds since epoch static constexpr TInstant MilliSeconds(ui64 ms) noexcept { - return MicroSeconds(ms * 1000); + return TBase::MilliSecondsConvertFrom(ms); } /// seconds since epoch static constexpr TInstant Seconds(ui64 s) noexcept { - return MilliSeconds(s * 1000); + return TBase::SecondsConvertFrom(s); } + /// Base class static methods: + /// /// minutes since epoch - static constexpr TInstant Minutes(ui64 m) noexcept { - return Seconds(m * 60); - } - + /// static constexpr TInstant Minutes(ui64 m) noexcept; + /// /// hours since epoch - static constexpr TInstant Hours(ui64 h) noexcept { - return Minutes(h * 60); - } - + /// static constexpr TInstant Hours(ui64 h) noexcept; + /// /// days since epoch - static constexpr TInstant Days(ui64 d) noexcept { - return Hours(d * 24); - } + /// static constexpr TInstant Days(ui64 d) noexcept; constexpr time_t TimeT() const noexcept { return (time_t)Seconds(); @@ -818,6 +859,21 @@ static inline TInstant Now() noexcept { return TInstant::Now(); } +template <class S> +constexpr S TTimeBase<S>::Minutes(ui64 m) noexcept { + return S::MicroSeconds(NDateTimeHelpers::MulWithSaturation<ui64(1'000'000) * 60>(m)); +} + +template <class S> +constexpr S TTimeBase<S>::Hours(ui64 h) noexcept { + return S::MicroSeconds(NDateTimeHelpers::MulWithSaturation<ui64(1'000'000) * 60 * 60>(h)); +} + +template <class S> +constexpr S TTimeBase<S>::Days(ui64 d) noexcept { + return S::MicroSeconds(NDateTimeHelpers::MulWithSaturation<ui64(1'000'000) * 60 * 60 * 24>(d)); +} + #ifdef _MSC_VER #pragma warning(pop) #endif // _MSC_VER diff --git a/util/datetime/base_ut.cpp b/util/datetime/base_ut.cpp index 3ac1a90a908..ed437da4e8f 100644 --- a/util/datetime/base_ut.cpp +++ b/util/datetime/base_ut.cpp @@ -413,6 +413,11 @@ Y_UNIT_TEST_SUITE(DateTimeTest) { } } + Y_UNIT_TEST(TestFromConverableType) { + std::atomic<ui64> timestamp{42}; + UNIT_ASSERT_EQUAL(TInstant::Seconds(42), TInstant::Seconds(timestamp)); + } + Y_UNIT_TEST(TestSleep) { // check does not throw Sleep(TDuration::Seconds(0)); @@ -582,6 +587,45 @@ Y_UNIT_TEST_SUITE(DateTimeTest) { static_cast<float>(::Max<ui64>()) / 1000 + 0.1})); } + template <class T> + void TestDurationCurationConstructorFunctionSaturation() { + const ui64 maxMilliseconds = ::Max<ui64>() / T::MilliSeconds(1).GetValue(); + const ui64 maxSeconds = ::Max<ui64>() / T::Seconds(1).GetValue(); + const ui64 maxMinutes = ::Max<ui64>() / T::Minutes(1).GetValue(); + const ui64 maxHours = ::Max<ui64>() / T::Hours(1).GetValue(); + const ui64 maxDays = ::Max<ui64>() / T::Days(1).GetValue(); + + auto test = [](ui64 maxValue, T (*constructor)(ui64), TStringBuf name) { + UNIT_ASSERT_VALUES_UNEQUAL_C(constructor(maxValue), T::Max(), LabeledOutput(name)); + UNIT_ASSERT_VALUES_EQUAL_C(constructor(Max<ui64>()), T::Max(), LabeledOutput(name)); + for (ui64 v = maxValue + 1; v != Max<ui64>(); v = std::midpoint(Max<ui64>(), v)) { + UNIT_ASSERT_VALUES_EQUAL_C(constructor(v), T::Max(), LabeledOutput(name, v)); + } + for (ui64 v = maxValue; v != 0; v = std::midpoint(ui64(0), v)) { + UNIT_ASSERT_VALUES_UNEQUAL_C(constructor(v), T::Max(), LabeledOutput(name, v)); + } + }; + test(maxMilliseconds, &T::MilliSeconds, "MilliSeconds"); + test(maxSeconds, &T::Seconds, "Seconds"); + test(maxMinutes, &T::Minutes, "Minutes"); + test(maxHours, &T::Hours, "Hours"); + test(maxDays, &T::Days, "Days"); + } + + Y_UNIT_TEST(TestDurationCurationConstructorFunctionSaturation) { + UNIT_ASSERT_VALUES_EQUAL(TDuration::Seconds(-5.f), TDuration::Zero()); + UNIT_ASSERT_VALUES_EQUAL(TDuration::MilliSeconds(-5.f), TDuration::Zero()); + UNIT_ASSERT_VALUES_EQUAL(TDuration::MilliSeconds(0x1p63), TDuration::Max()); + UNIT_ASSERT_VALUES_EQUAL(TDuration::Seconds(0x1p45), TDuration::Max()); + UNIT_ASSERT_VALUES_UNEQUAL(TDuration::Seconds(0x1p44), TDuration::Max()); + + TestDurationCurationConstructorFunctionSaturation<TDuration>(); + } + + Y_UNIT_TEST(TestInstantCurationConstructorFunctionSaturation) { + TestDurationCurationConstructorFunctionSaturation<TInstant>(); + } + Y_UNIT_TEST(TestTDurationCompareWithStdChronoDuration) { UNIT_ASSERT(TDuration::Zero() == 0ms); UNIT_ASSERT(TDuration::Seconds(42) == 42s); |