flams_utils/
time.rs

1#![allow(clippy::cast_precision_loss)]
2#![allow(clippy::float_cmp)]
3#![allow(clippy::cast_possible_truncation)]
4#![allow(clippy::cast_sign_loss)]
5#![allow(clippy::cast_possible_wrap)]
6
7use std::fmt::Display;
8use std::num::NonZeroU64;
9use std::ops::Mul;
10use std::str::FromStr;
11
12pub fn measure<F: FnOnce() -> R, R>(f: F) -> (R, Delta) {
13    let now = Timestamp::now();
14    let r = f();
15    let delta = now.since_now();
16    (r, delta)
17}
18
19#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
22#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
23pub struct Timestamp(#[cfg_attr(feature = "wasm", tsify(type = "number"))] pub NonZeroU64);
24
25trait AsU64 {
26    fn into_u64(self) -> u64;
27}
28impl AsU64 for u64 {
29    #[inline]
30    fn into_u64(self) -> u64 {
31        self
32    }
33}
34impl AsU64 for i64 {
35    #[inline]
36    fn into_u64(self) -> u64 {
37        self as u64
38    }
39}
40
41#[inline]
42fn non_zero<I: AsU64>(i: I) -> NonZeroU64 {
43    NonZeroU64::new(i.into_u64()).unwrap_or(unsafe { NonZeroU64::new_unchecked(1) })
44}
45impl Timestamp {
46    #[must_use]
47    #[inline]
48    pub fn now() -> Self {
49        let t = chrono::Utc::now().timestamp_millis();
50        Self(non_zero(t))
51    }
52
53    #[must_use]
54    #[inline]
55    pub fn zero() -> Self {
56        Self(non_zero(1u64))
57    }
58
59    #[must_use]
60    pub fn since_now(self) -> Delta {
61        let t = self.0.get();
62        let n = chrono::Utc::now().timestamp_millis() as u64;
63        Delta(non_zero(n - t))
64    }
65    #[must_use]
66    pub fn since(self, o: Self) -> Delta {
67        let o = o.0.get();
68        let s = self.0.get();
69        Delta(non_zero(s - o))
70    }
71
72    #[must_use]
73    pub fn into_date(self) -> Date {
74        Date(self.0)
75    }
76}
77impl Default for Timestamp {
78    #[inline]
79    fn default() -> Self {
80        Self::now()
81    }
82}
83impl From<std::time::SystemTime> for Timestamp {
84    #[inline]
85    fn from(t: std::time::SystemTime) -> Self {
86        let t = t
87            .duration_since(std::time::SystemTime::UNIX_EPOCH)
88            .unwrap_or_else(|_| unreachable!());
89        Self(non_zero(t.as_millis() as u64))
90    }
91}
92impl FromStr for Timestamp {
93    type Err = ();
94    fn from_str(s: &str) -> Result<Self, Self::Err> {
95        let c = chrono::DateTime::<chrono::Utc>::from_str(s).map_err(|_| ())?;
96        Ok(Self(non_zero(c.timestamp_millis())))
97    }
98}
99impl Display for Timestamp {
100    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101        let ts = self.0.get() as i64;
102        let timestamp = chrono::DateTime::<chrono::Utc>::from_timestamp_millis(ts)
103            .unwrap_or_else(|| unreachable!())
104            .with_timezone(&chrono::Local);
105        timestamp.format("%Y-%m-%d %H:%M:%S").fmt(f)
106    }
107}
108
109pub struct Date(NonZeroU64);
110impl From<Timestamp> for Date {
111    #[inline]
112    fn from(t: Timestamp) -> Self {
113        Self(t.0)
114    }
115}
116impl Display for Date {
117    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118        let ts = self.0.get() as i64;
119        let timestamp = chrono::DateTime::<chrono::Utc>::from_timestamp_millis(ts)
120            .unwrap_or_else(|| unreachable!())
121            .with_timezone(&chrono::Local);
122        timestamp.format("%Y-%m-%d").fmt(f)
123    }
124}
125
126#[allow(clippy::unsafe_derive_deserialize)]
127#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
128#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
129pub struct Delta(NonZeroU64);
130impl Default for Delta {
131    fn default() -> Self {
132        Self(unsafe { NonZeroU64::new_unchecked(1) })
133    }
134}
135impl Delta {
136    #[must_use]
137    pub fn max_seconds(self) -> impl Display {
138        MaxSeconds(self)
139    }
140    pub fn update_average(&mut self, scale: f64, new: Self) {
141        let old = self.0.get() as f64;
142        if old == 1.0 {
143            self.0 = new.0;
144            return;
145        }
146        let new = new.0.get() as f64;
147        let t = scale.mul_add(old, (1.0 - scale) * new) as u64;
148        self.0 = non_zero(t);
149    }
150    #[must_use]
151    pub fn step_second(self) -> Self {
152        let t = self.0.get();
153        if t > 1000 {
154            Self(non_zero(t - 1000))
155        } else {
156            Self::default()
157        }
158    }
159}
160impl Mul<f64> for Delta {
161    type Output = Self;
162    fn mul(self, rhs: f64) -> Self::Output {
163        let t = (self.0.get() as f64 * rhs) as u64;
164        Self(non_zero(t))
165    }
166}
167impl Delta {
168    pub const SECOND: Self = Self(unsafe { NonZeroU64::new_unchecked(1000) });
169    pub const MINUTE: Self = Self(unsafe { NonZeroU64::new_unchecked(60_000) });
170    pub const HOUR: Self = Self(unsafe { NonZeroU64::new_unchecked(3_600_000) });
171    pub const DAY: Self = Self(unsafe { NonZeroU64::new_unchecked(86_400_000) });
172    pub const WEEK: Self = Self(unsafe { NonZeroU64::new_unchecked(604_800_000) });
173    pub const MONTH: Self = Self(unsafe { NonZeroU64::new_unchecked(2_592_000_000) });
174    pub const YEAR: Self = Self(unsafe { NonZeroU64::new_unchecked(31_536_000_000) });
175}
176impl Display for Delta {
177    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178        let duration = chrono::Duration::milliseconds(self.0.get() as i64);
179        let days = duration.num_days();
180        let hours = duration.num_hours() % 24;
181        let minutes = duration.num_minutes() % 60;
182        let seconds = duration.num_seconds() % 60;
183        let millis = duration.num_milliseconds() % 1000;
184
185        let mut is_empty = true;
186
187        if days > 0 {
188            is_empty = false;
189            f.write_str(&format!("{days:02}d "))?;
190        }
191        if hours > 0 {
192            is_empty = false;
193            f.write_str(&format!("{hours:02}h "))?;
194        }
195        if minutes > 0 {
196            is_empty = false;
197            f.write_str(&format!("{minutes:02}m "))?;
198        }
199        if seconds > 0 {
200            is_empty = false;
201            f.write_str(&format!("{seconds:02}s "))?;
202        }
203        if millis > 0 {
204            is_empty = false;
205            f.write_str(&format!("{millis:02}ms"))?;
206        }
207        if is_empty {
208            f.write_str("0ms")?;
209        }
210
211        Ok(())
212    }
213}
214
215#[derive(Debug, Copy, Clone)]
216pub struct MaxSeconds(Delta);
217impl Display for MaxSeconds {
218    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
219        let duration = chrono::Duration::milliseconds(self.0 .0.get() as i64);
220        let days = duration.num_days();
221        let hours = duration.num_hours() % 24;
222        let minutes = duration.num_minutes() % 60;
223        let seconds = duration.num_seconds() % 60;
224
225        let mut is_empty = true;
226
227        if days > 0 {
228            is_empty = false;
229            f.write_str(&format!("{days:02}d "))?;
230        }
231        if hours > 0 {
232            is_empty = false;
233            f.write_str(&format!("{hours:02}h "))?;
234        }
235        if minutes > 0 {
236            is_empty = false;
237            f.write_str(&format!("{minutes:02}m "))?;
238        }
239        if seconds > 0 {
240            is_empty = false;
241            f.write_str(&format!("{seconds:02}s "))?;
242        }
243        if is_empty {
244            f.write_str("0s")?;
245        }
246        Ok(())
247    }
248}
249
250#[derive(Debug, Copy, Clone, Default)]
251#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
252pub struct Eta {
253    pub time_left: Delta,
254    pub done: usize,
255    pub total: usize,
256}