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}