use crate::{Clock, Timer, Tz, ZonedDateTime};
use futures::future::BoxFuture;
use time::{Date, Duration, Month, OffsetDateTime};
pub struct WallClock;
impl Clock for WallClock {
fn now_utc(&self) -> OffsetDateTime {
OffsetDateTime::now_utc()
}
fn now_in(&self, tz: &Tz) -> ZonedDateTime {
let utc = OffsetDateTime::now_utc();
ZonedDateTime::new(utc, tz.clone())
}
fn today_in(&self, tz: &Tz) -> Date {
let p = self.now_in(tz).local_parts();
Month::try_from(p.month_1)
.ok()
.and_then(|m| Date::from_calendar_date(p.year, m, p.day).ok())
.unwrap_or_else(|| OffsetDateTime::now_utc().date())
}
fn now_unix_millis(&self) -> i64 {
(OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000_000) as i64
}
}
pub struct TokioTimer;
impl Timer for TokioTimer {
fn sleep(&self, dur: Duration) -> BoxFuture<'static, ()> {
let std_dur = to_std_duration(dur);
Box::pin(async move {
tokio::time::sleep(std_dur).await;
})
}
fn next_tick(&self) -> BoxFuture<'static, ()> {
Box::pin(async {
tokio::task::yield_now().await;
})
}
}
fn to_std_duration(dur: Duration) -> std::time::Duration {
let nanos = dur.whole_nanoseconds();
if nanos <= 0 {
std::time::Duration::ZERO
} else {
std::time::Duration::from_nanos(nanos as u64)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Clock as ClockTrait;
use crate::Timer as TimerTrait;
use std::sync::Arc;
use std::time::Instant;
fn assert_send_sync<T: Send + Sync>() {}
#[test]
fn wall_clock_and_tokio_timer_are_send_sync() {
assert_send_sync::<WallClock>();
assert_send_sync::<TokioTimer>();
}
#[test]
fn now_utc_is_recent() {
let before = OffsetDateTime::now_utc();
let clock = WallClock;
let t = clock.now_utc();
let after = OffsetDateTime::now_utc();
assert!(t >= before - Duration::seconds(1));
assert!(t <= after + Duration::seconds(1));
}
#[test]
fn now_in_preserves_tz() {
let clock = WallClock;
let tz = Tz::seoul();
let zdt = clock.now_in(&tz);
assert_eq!(zdt.tz(), &tz);
}
#[test]
fn now_unix_millis_positive_and_recent() {
let before_ms = (OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000_000) as i64;
let clock = WallClock;
let ms = clock.now_unix_millis();
let after_ms = (OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000_000) as i64;
assert!(ms > 0, "unix millis must be positive");
assert!(ms >= before_ms - 100, "must be >= before sample");
assert!(ms <= after_ms + 100, "must be <= after sample");
}
#[tokio::test]
async fn tokio_timer_sleep_completes() {
let timer = TokioTimer;
let start = Instant::now();
timer.sleep(Duration::milliseconds(50)).await;
let elapsed = start.elapsed();
assert!(elapsed.as_millis() >= 45, "sleep must take at least 45ms");
assert!(elapsed.as_millis() < 500, "sleep must not take more than 500ms");
}
#[tokio::test]
async fn tokio_timer_next_tick_completes_immediately() {
let timer = TokioTimer;
let start = Instant::now();
timer.next_tick().await;
assert!(start.elapsed().as_millis() < 100);
}
#[test]
fn wall_clock_arc_dyn_dispatch() {
let c: Arc<dyn ClockTrait> = Arc::new(WallClock);
let _ = c.now_utc();
}
#[test]
fn tokio_timer_arc_dyn_dispatch() {
let t: Arc<dyn TimerTrait> = Arc::new(TokioTimer);
let _ = t.sleep(Duration::ZERO);
}
}