Skip to main content

ocre/
clock.rs

1//! Wall-clock time that works on Workers and in native tests.
2//!
3//! `std::time::SystemTime::now()` panics on `wasm32-unknown-unknown`; on
4//! Workers the time comes from JavaScript's `Date.now()`. Workers advance it
5//! only between I/O operations, which is precise enough for expirations.
6
7/// Current Unix time in seconds, on Workers and in native tests.
8///
9/// On Workers it reads JavaScript's `Date.now()`, which Workers advance only
10/// between I/O operations: precise enough for expirations (sessions, tokens,
11/// JWT `exp`, cache TTLs), not for measuring CPU time. Natively it reads
12/// [`std::time::SystemTime`]. Use it instead of `SystemTime::now()` in app
13/// code: that one panics on `wasm32-unknown-unknown`. Costs no binding call.
14///
15/// # Panics
16///
17/// Natively only, if the system clock is set before 1970.
18///
19/// # Examples
20///
21/// ```
22/// let issued_at = ocre::now();
23/// let expires_at = issued_at + 3600;
24/// assert!(expires_at > issued_at);
25/// ```
26pub fn now() -> i64 {
27    #[cfg(target_arch = "wasm32")]
28    {
29        crate::runtime::unix_millis() / 1000
30    }
31    #[cfg(not(target_arch = "wasm32"))]
32    {
33        #[cfg(feature = "testing")]
34        if let Some(frozen) = FROZEN.get() {
35            return frozen;
36        }
37        let elapsed = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH);
38        i64::try_from(elapsed.expect("the clock is after 1970").as_secs()).expect("seconds fit in i64")
39    }
40}
41
42/// Unix time in milliseconds, for durations (D1 statements, requests).
43pub(crate) fn now_millis() -> f64 {
44    #[cfg(target_arch = "wasm32")]
45    {
46        js_sys::Date::now()
47    }
48    #[cfg(not(target_arch = "wasm32"))]
49    {
50        let elapsed = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH);
51        elapsed.map_or(0.0, |elapsed| elapsed.as_secs_f64() * 1000.0)
52    }
53}
54
55// The time `ocre::testing::travel_to` set on this thread.
56#[cfg(all(feature = "testing", not(target_arch = "wasm32")))]
57thread_local! {
58    static FROZEN: std::cell::Cell<Option<i64>> = const { std::cell::Cell::new(None) };
59}
60
61/// Freezes [`now`] at `unix` on this thread, or unfreezes it with `None`.
62#[cfg(all(feature = "testing", not(target_arch = "wasm32")))]
63pub(crate) fn set_frozen(unix: Option<i64>) {
64    FROZEN.set(unix);
65}
66
67#[cfg(test)]
68#[path = "../tests/clock.rs"]
69mod tests;