Skip to main content

ppoppo_clock/
native.rs

1use crate::{Clock, Timer};
2use futures::future::BoxFuture;
3use jiff::tz::TimeZone;
4use jiff::Timestamp;
5use std::time::Duration;
6
7/// Real wall clock backed by the system clock.
8#[derive(Debug)]
9pub struct WallClock;
10
11impl Clock for WallClock {
12    fn now(&self) -> Timestamp {
13        Timestamp::now()
14    }
15}
16
17/// Read the host's local timezone from the OS (`TZ` env var, then
18/// `/etc/localtime`) and resolve it in jiff's tzdb.
19///
20/// Falls back to `TimeZone::UTC` when the OS zone can't be determined — the
21/// same degraded default as [`crate::wasm::browser_local_tz`], its wasm twin.
22/// Call once at the composition root (e.g. CCC's `Session`); do not sample it
23/// per render site (that would reintroduce the ambient-authority leak the
24/// clock port exists to prevent).
25pub fn system_tz() -> TimeZone {
26    TimeZone::system()
27}
28
29/// Tokio-backed async timer.
30#[derive(Debug)]
31pub struct TokioTimer;
32
33impl Timer for TokioTimer {
34    fn sleep(&self, dur: Duration) -> BoxFuture<'static, ()> {
35        Box::pin(async move {
36            tokio::time::sleep(dur).await;
37        })
38    }
39
40    fn next_tick(&self) -> BoxFuture<'static, ()> {
41        Box::pin(async {
42            tokio::task::yield_now().await;
43        })
44    }
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50    use crate::Clock as ClockTrait;
51    use crate::Timer as TimerTrait;
52    use std::sync::Arc;
53    use std::time::Instant;
54
55    fn assert_send_sync<T: Send + Sync>() {}
56
57    #[test]
58    fn wall_clock_and_tokio_timer_are_send_sync() {
59        assert_send_sync::<WallClock>();
60        assert_send_sync::<TokioTimer>();
61    }
62
63    #[test]
64    fn now_is_recent() {
65        let before = Timestamp::now();
66        let clock = WallClock;
67        let t = clock.now();
68        let after = Timestamp::now();
69        assert!(t >= before - jiff::SignedDuration::from_secs(1));
70        assert!(t <= after + jiff::SignedDuration::from_secs(1));
71    }
72
73    #[test]
74    fn now_unix_millis_positive_and_recent() {
75        let before_ms = Timestamp::now().as_millisecond();
76        let clock = WallClock;
77        let ms = clock.now_unix_millis();
78        let after_ms = Timestamp::now().as_millisecond();
79        assert!(ms > 0, "unix millis must be positive");
80        assert!(ms >= before_ms - 100, "must be >= before sample");
81        assert!(ms <= after_ms + 100, "must be <= after sample");
82    }
83
84    #[tokio::test]
85    async fn tokio_timer_sleep_completes() {
86        let timer = TokioTimer;
87        let start = Instant::now();
88        timer.sleep(Duration::from_millis(50)).await;
89        let elapsed = start.elapsed();
90        assert!(elapsed.as_millis() >= 45, "sleep must take at least 45ms");
91        assert!(elapsed.as_millis() < 500, "sleep must not take more than 500ms");
92    }
93
94    #[tokio::test]
95    async fn tokio_timer_next_tick_completes_immediately() {
96        let timer = TokioTimer;
97        let start = Instant::now();
98        timer.next_tick().await;
99        assert!(start.elapsed().as_millis() < 100);
100    }
101
102    #[test]
103    fn wall_clock_arc_dyn_dispatch() {
104        let c: Arc<dyn ClockTrait> = Arc::new(WallClock);
105        let _ = c.now();
106    }
107
108    #[test]
109    fn tokio_timer_arc_dyn_dispatch() {
110        let t: Arc<dyn TimerTrait> = Arc::new(TokioTimer);
111        let _ = t.sleep(Duration::ZERO);
112    }
113}