time.rsannotatedtime.rssource104 lines · 3.1 KB · raw
1//! Time as the ports see it: an instant in milliseconds and a UTC day. A port never reads a clock
2//! itself (there is none to read in a Worker's pure code); it is handed the moment.
3
4use ::log::trace;
5use serde::{Deserialize, Serialize};
6
7/// An instant, in milliseconds since the Unix epoch.
8#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
9#[serde(transparent)]
10pub struct Millis(u64);
11
12impl Millis {
13    pub const fn new(ms: u64) -> Self {
14        Self(ms)
15    }
16
17    pub const fn get(self) -> u64 {
18        self.0
19    }
20
21    pub const fn saturating_add(self, ms: u64) -> Self {
22        Self(self.0.saturating_add(ms))
23    }
24}
25
26/// One UTC day, counted from the epoch. The natural voice's daily allowance is spent against one of
27/// these, so two backends in different time zones still agree on when "today" ends.
28#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
29#[serde(transparent)]
30pub struct Day(u64);
31
32const DAY_MS: u64 = 86_400_000;
33
34impl Day {
35    pub const fn containing(at: Millis) -> Self {
36        Self(at.get() / DAY_MS)
37    }
38
39    /// The day's number since the epoch.
40    pub const fn number(self) -> u64 {
41        self.0
42    }
43
44    /// The moment the following day begins: when a spent daily allowance frees up.
45    pub const fn ends_at(self) -> Millis {
46        Millis::new(self.0.saturating_add(1).saturating_mul(DAY_MS))
47    }
48}
49
50/// What time it is now. Sync because every backend can answer from memory (`Date.now()`, the system
51/// clock) and a port that had to await the time would make every decision a suspension point.
52pub trait Clock {
53    fn now(&self) -> Millis;
54}
55
56/// A clock that only moves when told to, for the in-memory adapter and for tests.
57#[derive(Debug, Default)]
58pub struct ManualClock(std::sync::atomic::AtomicU64);
59
60impl ManualClock {
61    pub fn at(ms: u64) -> Self {
62        Self(std::sync::atomic::AtomicU64::new(ms))
63    }
64
65    pub fn set(&self, at: Millis) {
66        trace!("manual clock set to {} ms", at.get());
67        self.0.store(at.get(), std::sync::atomic::Ordering::SeqCst);
68    }
69
70    pub fn advance(&self, ms: u64) {
71        trace!("manual clock advances {ms} ms");
72        self.0.fetch_add(ms, std::sync::atomic::Ordering::SeqCst);
73    }
74}
75
76impl Clock for ManualClock {
77    fn now(&self) -> Millis {
78        Millis::new(self.0.load(std::sync::atomic::Ordering::SeqCst))
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn a_day_ends_when_the_next_one_begins() {
88        let d = Day::containing(Millis::new(DAY_MS * 5 + 17));
89        assert_eq!(d.number(), 5);
90        assert_eq!(d.ends_at(), Millis::new(DAY_MS * 6));
91        assert_eq!(Day::containing(d.ends_at()).number(), 6, "the next day starts exactly there");
92        assert_eq!(Day::containing(Millis::new(d.ends_at().get() - 1)), d);
93    }
94
95    #[test]
96    fn the_manual_clock_moves_only_when_told() {
97        let c = ManualClock::at(10);
98        assert_eq!(c.now(), Millis::new(10));
99        c.advance(5);
100        assert_eq!(c.now().get(), 15);
101        c.set(Millis::new(3));
102        assert_eq!(c.now().get(), 3);
103    }
104}