Skip to main content

radicle_localtime/
lib.rs

1//! Minimal, zero-dependency, monotonic, unix time library for rust.
2//!
3//! Taken from <https://github.com/cloudhead/localtime>
4
5use std::sync::atomic;
6use std::time::{SystemTime, UNIX_EPOCH};
7
8/// Local time.
9///
10/// This clock is monotonic.
11#[derive(Debug, PartialEq, Eq, Clone, Copy, Ord, PartialOrd, Default)]
12#[cfg_attr(
13    feature = "schemars",
14    derive(schemars::JsonSchema),
15    schemars(description = "A timestamp measured locally in seconds.")
16)]
17pub struct LocalTime {
18    /// Milliseconds since Epoch.
19    millis: u128,
20}
21
22impl std::fmt::Display for LocalTime {
23    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24        write!(f, "{}", self.as_secs())
25    }
26}
27
28impl LocalTime {
29    /// Construct a local time from the current system time.
30    pub fn now() -> Self {
31        static LAST: atomic::AtomicU64 = atomic::AtomicU64::new(0);
32
33        let now = SystemTime::now()
34            .duration_since(UNIX_EPOCH)
35            .map(|duration| Self {
36                millis: duration.as_millis(),
37            })
38            .expect("should run after 1970-01-01");
39
40        let last_in_secs = LAST.load(atomic::Ordering::SeqCst);
41        let now_in_secs = now.as_secs();
42
43        // If the current time is in the past, return the last recorded time instead.
44        if now_in_secs < last_in_secs {
45            Self::from_secs(last_in_secs)
46        } else {
47            LAST.store(now_in_secs, atomic::Ordering::SeqCst);
48            now
49        }
50    }
51
52    /// Construct a local time from whole seconds since Epoch.
53    #[must_use]
54    pub const fn from_secs(secs: u64) -> Self {
55        Self {
56            millis: secs as u128 * 1000,
57        }
58    }
59
60    /// Construct a local time from milliseconds since Epoch.
61    #[must_use]
62    pub const fn from_millis(millis: u128) -> Self {
63        Self { millis }
64    }
65
66    /// Return whole seconds since Epoch.
67    #[must_use]
68    pub fn as_secs(&self) -> u64 {
69        (self.millis / 1000).try_into().unwrap()
70    }
71
72    /// Return milliseconds since Epoch.
73    #[must_use]
74    pub fn as_millis(&self) -> u64 {
75        self.millis.try_into().unwrap()
76    }
77
78    /// Get the duration since the given time.
79    ///
80    /// # Panics
81    ///
82    /// This function will panic if `earlier` is later than `self`.
83    #[must_use]
84    pub fn duration_since(&self, earlier: LocalTime) -> LocalDuration {
85        LocalDuration::from_millis(
86            self.millis
87                .checked_sub(earlier.millis)
88                .expect("supplied time is later than self"),
89        )
90    }
91
92    /// Get the difference between two times.
93    #[must_use]
94    pub fn diff(&self, other: LocalTime) -> LocalDuration {
95        if self > &other {
96            self.duration_since(other)
97        } else {
98            other.duration_since(*self)
99        }
100    }
101
102    /// Elapse time.
103    ///
104    /// Adds the given duration to the time.
105    pub fn elapse(&mut self, duration: LocalDuration) {
106        self.millis += duration.as_millis()
107    }
108}
109
110/// Subtract two local times. Yields a duration.
111impl std::ops::Sub<LocalTime> for LocalTime {
112    type Output = LocalDuration;
113
114    fn sub(self, other: LocalTime) -> LocalDuration {
115        LocalDuration(self.millis.saturating_sub(other.millis))
116    }
117}
118
119/// Subtract a duration from a local time. Yields a local time.
120impl std::ops::Sub<LocalDuration> for LocalTime {
121    type Output = LocalTime;
122
123    fn sub(self, other: LocalDuration) -> LocalTime {
124        LocalTime {
125            millis: self.millis - other.0,
126        }
127    }
128}
129
130/// Add a duration to a local time. Yields a local time.
131impl std::ops::Add<LocalDuration> for LocalTime {
132    type Output = LocalTime;
133
134    fn add(self, other: LocalDuration) -> LocalTime {
135        LocalTime {
136            millis: self.millis + other.0,
137        }
138    }
139}
140
141/// Time duration as measured locally.
142#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)]
143#[cfg_attr(
144    feature = "schemars",
145    derive(schemars::JsonSchema),
146    schemars(description = "A time duration measured locally in seconds.")
147)]
148pub struct LocalDuration(u128);
149
150impl LocalDuration {
151    /// The time interval between blocks. The "block time".
152    pub const BLOCK_INTERVAL: LocalDuration = Self::from_mins(10);
153
154    /// Maximum duration.
155    pub const MAX: LocalDuration = LocalDuration(u128::MAX);
156
157    /// Create a new duration from whole seconds.
158    #[must_use]
159    pub const fn from_secs(secs: u64) -> Self {
160        Self(secs as u128 * 1000)
161    }
162
163    /// Create a new duration from whole minutes.
164    #[must_use]
165    pub const fn from_mins(mins: u64) -> Self {
166        Self::from_secs(mins * 60)
167    }
168
169    /// Construct a new duration from milliseconds.
170    #[must_use]
171    pub const fn from_millis(millis: u128) -> Self {
172        Self(millis)
173    }
174
175    /// Return the number of minutes in this duration.
176    #[must_use]
177    pub const fn as_mins(&self) -> u64 {
178        self.as_secs() / 60
179    }
180
181    /// Return the number of seconds in this duration.
182    #[must_use]
183    pub const fn as_secs(&self) -> u64 {
184        (self.0 / 1000) as u64
185    }
186
187    /// Return the number of milliseconds in this duration.
188    #[must_use]
189    pub const fn as_millis(&self) -> u128 {
190        self.0
191    }
192}
193
194impl std::fmt::Display for LocalDuration {
195    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196        if self.as_millis() < 1000 {
197            write!(f, "{} millisecond(s)", self.as_millis())
198        } else if self.as_secs() < 60 {
199            let fraction = self.as_millis() % 1000;
200            if fraction > 0 {
201                write!(f, "{}.{} second(s)", self.as_secs(), fraction)
202            } else {
203                write!(f, "{} second(s)", self.as_secs())
204            }
205        } else if self.as_mins() < 60 {
206            let fraction = self.as_secs() % 60;
207            if fraction > 0 {
208                write!(
209                    f,
210                    "{:.2} minute(s)",
211                    self.as_mins() as f64 + (fraction as f64 / 60.)
212                )
213            } else {
214                write!(f, "{} minute(s)", self.as_mins())
215            }
216        } else {
217            let fraction = self.as_mins() % 60;
218            if fraction > 0 {
219                write!(f, "{:.2} hour(s)", self.as_mins() as f64 / 60.)
220            } else {
221                write!(f, "{} hour(s)", self.as_mins() / 60)
222            }
223        }
224    }
225}
226
227impl<'a> std::iter::Sum<&'a LocalDuration> for LocalDuration {
228    fn sum<I: Iterator<Item = &'a LocalDuration>>(iter: I) -> LocalDuration {
229        let mut total: u128 = 0;
230
231        for entry in iter {
232            total = total
233                .checked_add(entry.0)
234                .expect("iter::sum should not overflow");
235        }
236        Self(total)
237    }
238}
239
240impl std::ops::Add<LocalDuration> for LocalDuration {
241    type Output = LocalDuration;
242
243    fn add(self, other: LocalDuration) -> LocalDuration {
244        LocalDuration(self.0 + other.0)
245    }
246}
247
248impl std::ops::Div<u32> for LocalDuration {
249    type Output = LocalDuration;
250
251    fn div(self, other: u32) -> LocalDuration {
252        LocalDuration(self.0 / other as u128)
253    }
254}
255
256impl std::ops::Mul<u64> for LocalDuration {
257    type Output = LocalDuration;
258
259    fn mul(self, other: u64) -> LocalDuration {
260        LocalDuration(self.0 * other as u128)
261    }
262}
263
264impl From<LocalDuration> for std::time::Duration {
265    fn from(other: LocalDuration) -> Self {
266        std::time::Duration::from_millis(other.0 as u64)
267    }
268}
269
270#[cfg(feature = "serde")]
271mod serde_impls {
272    use super::{LocalDuration, LocalTime};
273
274    impl serde::Serialize for LocalTime {
275        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
276        where
277            S: serde::Serializer,
278        {
279            serializer.serialize_u64(self.as_secs())
280        }
281    }
282
283    impl<'de> serde::Deserialize<'de> for LocalTime {
284        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
285        where
286            D: serde::Deserializer<'de>,
287        {
288            u64::deserialize(deserializer).map(LocalTime::from_secs)
289        }
290    }
291
292    impl serde::Serialize for LocalDuration {
293        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
294        where
295            S: serde::Serializer,
296        {
297            serializer.serialize_u64(self.as_secs())
298        }
299    }
300
301    impl<'de> serde::Deserialize<'de> for LocalDuration {
302        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
303        where
304            D: serde::Deserializer<'de>,
305        {
306            u64::deserialize(deserializer).map(LocalDuration::from_secs)
307        }
308    }
309
310    #[cfg(test)]
311    mod test {
312        use crate::LocalTime;
313
314        #[test]
315        fn test_localtime() {
316            #[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Eq)]
317            struct Test {
318                time: LocalTime,
319            }
320            let value = Test {
321                time: LocalTime::from_secs(1699636852107),
322            };
323
324            assert_eq!(
325                serde_json::from_str::<Test>(r#"{"time":1699636852107}"#).unwrap(),
326                value
327            );
328            assert_eq!(
329                serde_json::from_str::<Test>(serde_json::to_string(&value).unwrap().as_str())
330                    .unwrap(),
331                value
332            );
333        }
334    }
335}