use std::cmp::{Ord, Ordering, PartialOrd};
use std::fmt;
use std::ops::{Add, AddAssign, Sub, SubAssign};
use std::time::Duration;
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Instant(pub(crate) u64);
impl Instant {
pub fn now() -> Instant {
crate::get_now()
}
pub fn recent() -> Instant {
crate::get_recent()
}
pub fn duration_since(&self, earlier: Instant) -> Duration {
self.checked_duration_since(earlier).unwrap_or_default()
}
pub fn checked_duration_since(&self, earlier: Instant) -> Option<Duration> {
self.0.checked_sub(earlier.0).map(Duration::from_nanos)
}
pub fn saturating_duration_since(&self, earlier: Instant) -> Duration {
self.checked_duration_since(earlier).unwrap_or_default()
}
pub fn elapsed(&self) -> Duration {
Instant::now() - *self
}
pub fn checked_add(&self, duration: Duration) -> Option<Instant> {
let total_nanos = u64::try_from(duration.as_nanos()).ok()?;
self.0.checked_add(total_nanos).map(Instant)
}
pub fn checked_sub(&self, duration: Duration) -> Option<Instant> {
let total_nanos = u64::try_from(duration.as_nanos()).ok()?;
self.0.checked_sub(total_nanos).map(Instant)
}
}
impl Add<Duration> for Instant {
type Output = Instant;
fn add(self, other: Duration) -> Instant {
self.checked_add(other)
.expect("overflow when adding duration to instant")
}
}
impl AddAssign<Duration> for Instant {
fn add_assign(&mut self, other: Duration) {
self.0 = self.0 + other.as_nanos() as u64;
}
}
impl Sub<Duration> for Instant {
type Output = Instant;
fn sub(self, other: Duration) -> Instant {
self.checked_sub(other)
.expect("overflow when subtracting duration from instant")
}
}
impl SubAssign<Duration> for Instant {
fn sub_assign(&mut self, other: Duration) {
self.0 = self.0 - other.as_nanos() as u64;
}
}
impl Sub<Instant> for Instant {
type Output = Duration;
fn sub(self, other: Instant) -> Duration {
self.duration_since(other)
}
}
impl PartialOrd for Instant {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Instant {
fn cmp(&self, other: &Self) -> Ordering {
self.0.cmp(&other.0)
}
}
impl fmt::Debug for Instant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
#[cfg(feature = "prost")]
impl Into<prost_types::Timestamp> for Instant {
fn into(self) -> prost_types::Timestamp {
let dur = Duration::from_nanos(self.0);
let secs = if dur.as_secs() > i64::MAX as u64 {
i64::MAX
} else {
dur.as_secs() as i64
};
let nsecs = if dur.subsec_nanos() > i32::MAX as u32 {
i32::MAX
} else {
dur.subsec_nanos() as i32
};
prost_types::Timestamp {
seconds: secs,
nanos: nsecs,
}
}
}
#[cfg(test)]
mod tests {
use once_cell::sync::Lazy;
use super::Instant;
use crate::{with_clock, Clock};
use std::time::Duration;
use std::{sync::Mutex, thread};
static RECENT_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
#[test]
#[cfg_attr(
all(target_arch = "wasm32", target_os = "unknown"),
ignore = "WASM thread cannot sleep"
)]
fn test_now() {
let t0 = Instant::now();
thread::sleep(Duration::from_millis(15));
let t1 = Instant::now();
assert!(t0.0 > 0);
assert!(t1.0 > 0);
let result = t1 - t0;
let threshold = Duration::from_millis(14);
assert!(result > threshold);
}
#[test]
#[cfg_attr(
all(target_arch = "wasm32", target_os = "unknown"),
ignore = "WASM thread cannot sleep"
)]
fn test_recent() {
let _guard = RECENT_LOCK.lock().unwrap();
crate::set_recent(Instant(0));
let t0 = Instant::recent();
thread::sleep(Duration::from_millis(15));
let t1 = Instant::recent();
assert!(t0.0 > 0);
assert!(t1.0 > 0);
let result = t1 - t0;
let threshold = Duration::from_millis(14);
assert!(
result > threshold,
"t1 should be greater than t0 by at least 14ms, was only {:?} (t0: {}, t1: {})",
result,
t0.0,
t1.0
);
crate::set_recent(Instant(1));
let t2 = Instant::recent();
thread::sleep(Duration::from_millis(15));
let t3 = Instant::recent();
assert_eq!(t2, t3);
}
#[test]
#[cfg_attr(
all(target_arch = "wasm32", target_os = "unknown"),
wasm_bindgen_test::wasm_bindgen_test
)]
fn test_mocking() {
let _guard = RECENT_LOCK.lock().unwrap();
crate::set_recent(Instant(0));
let (clock, mock) = Clock::mock();
with_clock(&clock, move || {
let t0 = Instant::now();
mock.increment(42);
let t1 = Instant::now();
assert_eq!(t0.0, 0);
assert_eq!(t1.0, 42);
let t2 = Instant::recent();
mock.increment(420);
let t3 = Instant::recent();
assert_eq!(t2.0, 42);
assert_eq!(t3.0, 462);
crate::set_recent(Instant(1440));
let t4 = Instant::recent();
assert_eq!(t4.0, 1440);
})
}
#[test]
#[cfg_attr(
all(target_arch = "wasm32", target_os = "unknown"),
wasm_bindgen_test::wasm_bindgen_test
)]
fn checked_arithmetic_u64_overflow() {
fn nanos_to_dur(total_nanos: u128) -> Duration {
let nanos_per_sec = Duration::from_secs(1).as_nanos();
let secs = total_nanos / nanos_per_sec;
let nanos = total_nanos % nanos_per_sec;
let dur = Duration::new(secs as _, nanos as _);
assert_eq!(dur.as_nanos(), total_nanos);
dur
}
let dur = nanos_to_dur(1 << 64);
let now = Instant::now();
let behind = now.checked_sub(dur);
let ahead = now.checked_add(dur);
assert_ne!(Duration::ZERO, dur);
assert_ne!(Some(now), behind);
assert_ne!(Some(now), ahead);
}
}