1use std::sync::atomic;
6use std::time::{SystemTime, UNIX_EPOCH};
7
8#[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 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 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 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 #[must_use]
54 pub const fn from_secs(secs: u64) -> Self {
55 Self {
56 millis: secs as u128 * 1000,
57 }
58 }
59
60 #[must_use]
62 pub const fn from_millis(millis: u128) -> Self {
63 Self { millis }
64 }
65
66 #[must_use]
68 pub fn as_secs(&self) -> u64 {
69 (self.millis / 1000).try_into().unwrap()
70 }
71
72 #[must_use]
74 pub fn as_millis(&self) -> u64 {
75 self.millis.try_into().unwrap()
76 }
77
78 #[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 #[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 pub fn elapse(&mut self, duration: LocalDuration) {
106 self.millis += duration.as_millis()
107 }
108}
109
110impl 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
119impl 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
130impl 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#[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 pub const BLOCK_INTERVAL: LocalDuration = Self::from_mins(10);
153
154 pub const MAX: LocalDuration = LocalDuration(u128::MAX);
156
157 #[must_use]
159 pub const fn from_secs(secs: u64) -> Self {
160 Self(secs as u128 * 1000)
161 }
162
163 #[must_use]
165 pub const fn from_mins(mins: u64) -> Self {
166 Self::from_secs(mins * 60)
167 }
168
169 #[must_use]
171 pub const fn from_millis(millis: u128) -> Self {
172 Self(millis)
173 }
174
175 #[must_use]
177 pub const fn as_mins(&self) -> u64 {
178 self.as_secs() / 60
179 }
180
181 #[must_use]
183 pub const fn as_secs(&self) -> u64 {
184 (self.0 / 1000) as u64
185 }
186
187 #[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}