use crate::sys::time::Instant;
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct JsInstant {
inner: std::time::Duration,
}
impl JsInstant {
#[must_use]
pub fn new(secs: u64, nanos: u32) -> Self {
let inner = std::time::Duration::new(secs, nanos);
Self::new_unchecked(inner)
}
#[must_use]
fn new_unchecked(inner: std::time::Duration) -> Self {
Self { inner }
}
#[must_use]
pub fn millis_since_epoch(&self) -> u64 {
self.inner.as_millis() as u64
}
#[must_use]
pub fn nanos_since_epoch(&self) -> u128 {
self.inner.as_nanos()
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct JsDuration {
inner: std::time::Duration,
}
impl JsDuration {
#[must_use]
pub fn from_millis(millis: u64) -> Self {
Self {
inner: std::time::Duration::from_millis(millis),
}
}
#[must_use]
pub fn as_millis(&self) -> u64 {
self.inner.as_millis() as u64
}
#[must_use]
pub fn as_secs(&self) -> u64 {
self.inner.as_secs()
}
#[must_use]
pub fn as_nanos(&self) -> u128 {
self.inner.as_nanos()
}
}
impl From<std::time::Duration> for JsDuration {
fn from(duration: std::time::Duration) -> Self {
Self { inner: duration }
}
}
impl From<JsDuration> for std::time::Duration {
fn from(duration: JsDuration) -> Self {
duration.inner
}
}
macro_rules! impl_duration_ops {
($($trait:ident $trait_fn:ident),*) => {
$(
impl std::ops::$trait for JsDuration {
type Output = JsDuration;
#[inline]
fn $trait_fn(self, rhs: JsDuration) -> Self::Output {
Self {
inner: std::ops::$trait::$trait_fn(self.inner, rhs.inner)
}
}
}
impl std::ops::$trait<JsDuration> for JsInstant {
type Output = JsInstant;
#[inline]
fn $trait_fn(self, rhs: JsDuration) -> Self::Output {
Self {
inner: std::ops::$trait::$trait_fn(self.inner, rhs.inner)
}
}
}
)*
};
}
impl_duration_ops!(Add add, Sub sub);
impl std::ops::Sub for JsInstant {
type Output = JsDuration;
#[inline]
fn sub(self, rhs: JsInstant) -> Self::Output {
JsDuration {
inner: self.inner.saturating_sub(rhs.inner),
}
}
}
pub trait Clock {
fn now(&self) -> JsInstant;
fn system_time_millis(&self) -> i64;
}
#[derive(Debug, Clone, Copy)]
pub struct StdClock {
base: Instant,
}
impl Default for StdClock {
fn default() -> Self {
Self::new()
}
}
impl StdClock {
#[must_use]
pub fn new() -> Self {
Self {
base: Instant::now(),
}
}
}
impl Clock for StdClock {
fn now(&self) -> JsInstant {
let elapsed = self.base.elapsed();
JsInstant::new_unchecked(elapsed)
}
fn system_time_millis(&self) -> i64 {
let now = crate::sys::time::SystemTime::now();
let duration = now
.duration_since(crate::sys::time::UNIX_EPOCH)
.expect("System clock is before Unix epoch");
duration.as_millis() as i64
}
}
#[derive(Debug, Clone, Default)]
pub struct FixedClock(std::cell::RefCell<u64>);
impl FixedClock {
#[must_use]
pub fn from_millis(millis: u64) -> Self {
Self(std::cell::RefCell::new(millis))
}
pub fn forward(&self, millis: u64) {
*self.0.borrow_mut() += millis;
}
}
impl Clock for FixedClock {
fn now(&self) -> JsInstant {
let millis = *self.0.borrow();
JsInstant::new_unchecked(std::time::Duration::new(
millis / 1000,
((millis % 1000) * 1_000_000) as u32,
))
}
fn system_time_millis(&self) -> i64 {
*self.0.borrow() as i64
}
}
#[test]
fn basic() {
let clock = StdClock::new();
let now = clock.now();
let _millis = now.millis_since_epoch();
let _nanos = now.nanos_since_epoch();
let duration = JsDuration::from_millis(1000);
let later = now + duration;
assert!(later > now);
let duration_small = JsDuration::from_millis(100);
let later_small = now + duration_small;
let earlier = later_small - duration_small;
assert_eq!(earlier, now);
let diff = later - now;
assert_eq!(diff.as_millis(), 1000);
let fixed = FixedClock::from_millis(0);
let now2 = fixed.now();
assert_eq!(now2.millis_since_epoch(), 0);
fixed.forward(1000);
let now3 = fixed.now();
assert_eq!(now3.millis_since_epoch(), 1000);
assert!(now3 > now2);
fixed.forward(u64::MAX - 1000);
let now4 = fixed.now();
assert_eq!(now4.millis_since_epoch(), u64::MAX);
assert!(now4 > now3);
}
#[test]
fn monotonic_behavior() {
let clock = StdClock::new();
let t1 = clock.now();
std::thread::sleep(std::time::Duration::from_millis(1));
let t2 = clock.now();
std::thread::sleep(std::time::Duration::from_millis(1));
let t3 = clock.now();
assert!(t2 > t1, "Time must move forward");
assert!(t3 > t2, "Time must continue moving forward");
assert!(t3 > t1, "Time must be transitive");
let elapsed = t3 - t1;
assert!(elapsed.as_millis() >= 2, "At least 2ms should have elapsed");
}
#[test]
fn clock_independence() {
let clock1 = StdClock::new();
std::thread::sleep(std::time::Duration::from_millis(10));
let clock2 = StdClock::new();
let t1 = clock1.now();
let t2 = clock2.now();
assert!(t1.millis_since_epoch() >= t2.millis_since_epoch());
}