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;