summaryrefslogtreecommitdiffstats
path: root/util
diff options
context:
space:
mode:
authorswarmer <[email protected]>2025-10-03 20:25:26 +0300
committerswarmer <[email protected]>2025-10-03 20:50:00 +0300
commit08d7ed83f95bdff4ddd3fd1b05b16d12f6bedbb1 (patch)
treee7c89fa017ee7154ef1a16761d4e9fa4d816e785 /util
parente8e38bf51bdfebc163b2fa9310c302f4f9e1c720 (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.h142
-rw-r--r--util/datetime/base_ut.cpp44
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);