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}