use std::{
cmp,
sync::LazyLock,
time::{Duration, Instant, SystemTime},
};
static MAX_SYSTEM_TIME: LazyLock<SystemTime> = LazyLock::new(find_max);
static MIN_SYSTEM_TIME: LazyLock<SystemTime> = LazyLock::new(find_min);
static MAX_INSTANT: LazyLock<Instant> = LazyLock::new(find_max);
static MIN_INSTANT: LazyLock<Instant> = LazyLock::new(find_min);
pub trait SaturatingTime: Sized + Copy {
fn anchor() -> Self;
fn max_value() -> Self;
fn min_value() -> Self;
fn checked_add(&self, duration: Duration) -> Option<Self>;
fn checked_sub(&self, duration: Duration) -> Option<Self>;
fn checked_duration_since(&self, earlier: Self) -> Option<Duration>;
}
impl SaturatingTime for SystemTime {
fn anchor() -> Self {
Self::UNIX_EPOCH
}
fn max_value() -> Self {
*MAX_SYSTEM_TIME
}
fn min_value() -> Self {
*MIN_SYSTEM_TIME
}
fn checked_add(&self, duration: Duration) -> Option<Self> {
Self::checked_add(self, duration)
}
fn checked_sub(&self, duration: Duration) -> Option<Self> {
Self::checked_sub(self, duration)
}
fn checked_duration_since(&self, earlier: Self) -> Option<Duration> {
Self::duration_since(self, earlier).ok()
}
}
impl SaturatingTime for Instant {
fn anchor() -> Self {
Self::now()
}
fn max_value() -> Self {
*MAX_INSTANT
}
fn min_value() -> Self {
*MIN_INSTANT
}
fn checked_add(&self, duration: Duration) -> Option<Self> {
Self::checked_add(self, duration)
}
fn checked_sub(&self, duration: Duration) -> Option<Self> {
Self::checked_sub(self, duration)
}
fn checked_duration_since(&self, _earlier: Self) -> Option<Duration> {
unreachable!()
}
}
fn find_max<T: SaturatingTime>() -> T {
find_limit(T::checked_add)
}
fn find_min<T: SaturatingTime>() -> T {
find_limit(T::checked_sub)
}
fn find_limit<T, F>(f: F) -> T
where
T: SaturatingTime,
F: Fn(&T, Duration) -> Option<T>,
{
const INITIAL_STEP: Duration = Duration::new(1_000_000_000_000_000_000, 0);
const ONE_NS: Duration = Duration::new(0, 1);
let mut step = INITIAL_STEP;
let mut res = T::anchor();
loop {
let next = f(&res, step);
match next {
Some(st) => {
res = st
}
None => {
if step == ONE_NS {
return res;
} else {
step = cmp::max(ONE_NS, step / 2);
}
}
}
}
}
#[cfg(test)]
mod tests {
use std::{
fmt::Debug,
ops::{Add, Sub},
};
use super::*;
fn min_max<T>()
where
T: SaturatingTime
+ PartialEq
+ Debug
+ Add<Duration, Output = T>
+ Sub<Duration, Output = T>,
{
assert_eq!(
T::max_value().checked_add(Duration::ZERO),
Some(T::max_value())
);
assert_eq!(T::max_value().checked_add(Duration::new(0, 1)), None);
assert_eq!(
T::max_value().checked_sub(Duration::ZERO),
Some(T::max_value())
);
assert_eq!(
T::max_value().checked_sub(Duration::new(0, 1)),
Some(T::max_value() - Duration::new(0, 1))
);
assert_eq!(
T::min_value().checked_sub(Duration::ZERO),
Some(T::min_value())
);
assert_eq!(T::min_value().checked_sub(Duration::new(0, 1)), None);
assert_eq!(
T::min_value().checked_add(Duration::ZERO),
Some(T::min_value())
);
assert_eq!(
T::min_value().checked_add(Duration::new(0, 1)),
Some(T::min_value() + Duration::new(0, 1))
);
}
#[test]
fn system_time_min_max() {
min_max::<SystemTime>();
}
#[test]
fn instant_min_max() {
min_max::<Instant>();
}
#[cfg(target_family = "unix")]
#[test]
fn system_time_min_max_unix() {
assert_eq!(
SystemTime::max_value(),
SystemTime::UNIX_EPOCH + Duration::new(i64::MAX as u64, 999_999_999)
);
assert_eq!(
SystemTime::min_value(),
SystemTime::UNIX_EPOCH - Duration::new(i64::MAX as u64 + 1, 0)
);
}
#[test]
#[cfg(target_family = "unix")]
fn instant_min_max_unix() {
assert_eq!(
format!("{:?}", Instant::max_value()),
format!(
"Instant {{ tv_sec: {}, tv_nsec: {} }}",
i64::MAX,
999_999_999
)
);
assert_eq!(
format!("{:?}", Instant::min_value()),
format!("Instant {{ tv_sec: {}, tv_nsec: {} }}", i64::MIN, 0)
);
}
}