1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6pub fn now() -> DateTime<Utc> {
8 Utc::now()
9}
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(transparent)]
14pub struct Timestamp(DateTime<Utc>);
15
16impl Timestamp {
17 pub fn now() -> Self {
19 Self(Utc::now())
20 }
21
22 pub fn from_datetime(dt: DateTime<Utc>) -> Self {
24 Self(dt)
25 }
26
27 pub fn inner(&self) -> DateTime<Utc> {
29 self.0
30 }
31
32 pub fn millis(&self) -> i64 {
34 self.0.timestamp_millis()
35 }
36
37 pub fn from_millis(millis: i64) -> Option<Self> {
39 DateTime::from_timestamp_millis(millis).map(Self)
40 }
41}
42
43impl Default for Timestamp {
44 fn default() -> Self {
45 Self::now()
46 }
47}
48
49impl std::fmt::Display for Timestamp {
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 write!(f, "{}", self.0.to_rfc3339())
52 }
53}
54
55impl From<DateTime<Utc>> for Timestamp {
56 fn from(dt: DateTime<Utc>) -> Self {
57 Self(dt)
58 }
59}
60
61impl From<Timestamp> for DateTime<Utc> {
62 fn from(ts: Timestamp) -> Self {
63 ts.0
64 }
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
69#[serde(transparent)]
70pub struct DurationMs(u64);
71
72impl DurationMs {
73 pub fn new(ms: u64) -> Self {
74 Self(ms)
75 }
76
77 pub fn from_secs(secs: u64) -> Self {
78 Self(secs * 1000)
79 }
80
81 pub fn from_mins(mins: u64) -> Self {
82 Self(mins * 60 * 1000)
83 }
84
85 pub fn as_millis(&self) -> u64 {
86 self.0
87 }
88
89 pub fn as_secs(&self) -> u64 {
90 self.0 / 1000
91 }
92
93 pub fn as_std(&self) -> std::time::Duration {
94 std::time::Duration::from_millis(self.0)
95 }
96}
97
98impl From<u64> for DurationMs {
99 fn from(ms: u64) -> Self {
100 Self(ms)
101 }
102}
103
104impl From<std::time::Duration> for DurationMs {
105 fn from(d: std::time::Duration) -> Self {
106 Self(d.as_millis() as u64)
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
119 fn test_timestamp_now_creates_current_time() {
120 let before = Utc::now();
121 let ts = Timestamp::now();
122 let after = Utc::now();
123
124 assert!(ts.inner() >= before);
125 assert!(ts.inner() <= after);
126 }
127
128 #[test]
129 fn test_timestamp_from_datetime() {
130 let dt = Utc::now();
131 let ts = Timestamp::from_datetime(dt);
132 assert_eq!(ts.inner(), dt);
133 }
134
135 #[test]
136 fn test_timestamp_inner_returns_datetime() {
137 let dt = Utc::now();
138 let ts = Timestamp::from_datetime(dt);
139 assert_eq!(ts.inner(), dt);
140 }
141
142 #[test]
143 fn test_timestamp_millis_conversion() {
144 let ts = Timestamp::now();
145 let millis = ts.millis();
146 assert!(millis > 0);
147 }
148
149 #[test]
150 fn test_timestamp_from_millis() {
151 let ts = Timestamp::now();
152 let millis = ts.millis();
153 let ts2 = Timestamp::from_millis(millis).unwrap();
154 assert_eq!(ts.millis(), ts2.millis());
156 }
157
158 #[test]
159 fn test_timestamp_from_invalid_millis_returns_none() {
160 let result = Timestamp::from_millis(i64::MIN);
162 let _ = result;
165 }
166
167 #[test]
168 fn test_timestamp_default_creates_now() {
169 let before = Utc::now();
170 let ts = Timestamp::default();
171 let after = Utc::now();
172
173 assert!(ts.inner() >= before);
174 assert!(ts.inner() <= after);
175 }
176
177 #[test]
182 fn test_timestamp_display_is_rfc3339() {
183 let ts = Timestamp::now();
184 let display = ts.to_string();
185 assert!(display.contains('T'));
187 assert!(display.ends_with('Z') || display.contains('+') || display.contains('-'));
188 }
189
190 #[test]
191 fn test_timestamp_from_datetime_trait() {
192 let dt = Utc::now();
193 let ts: Timestamp = dt.into();
194 assert_eq!(ts.inner(), dt);
195 }
196
197 #[test]
198 fn test_timestamp_into_datetime_trait() {
199 let ts = Timestamp::now();
200 let dt: DateTime<Utc> = ts.into();
201 assert_eq!(dt, ts.inner());
202 }
203
204 #[test]
205 #[allow(clippy::clone_on_copy)]
206 fn test_timestamp_is_cloneable() {
207 let ts = Timestamp::now();
208 let cloned = ts.clone();
209 assert_eq!(ts, cloned);
210 }
211
212 #[test]
213 fn test_timestamp_is_copyable() {
214 let ts = Timestamp::now();
215 let copied = ts;
216 assert_eq!(ts, copied);
217 }
218
219 #[test]
220 fn test_timestamp_equality() {
221 let dt = Utc::now();
222 let ts1 = Timestamp::from_datetime(dt);
223 let ts2 = Timestamp::from_datetime(dt);
224 assert_eq!(ts1, ts2);
225 }
226
227 #[test]
228 fn test_timestamp_is_debuggable() {
229 let ts = Timestamp::now();
230 let debug = format!("{:?}", ts);
231 assert!(debug.contains("Timestamp"));
232 }
233
234 #[test]
239 fn test_timestamp_serializes_to_string() {
240 let ts = Timestamp::now();
241 let json = serde_json::to_string(&ts).unwrap();
242 assert!(json.starts_with('"'));
244 assert!(json.ends_with('"'));
245 }
246
247 #[test]
248 fn test_timestamp_deserializes_from_string() {
249 let json = r#""2024-01-15T10:30:00Z""#;
250 let ts: Timestamp = serde_json::from_str(json).unwrap();
251 assert_eq!(ts.inner().year(), 2024);
252 assert_eq!(ts.inner().month(), 1);
253 assert_eq!(ts.inner().day(), 15);
254 }
255
256 #[test]
257 fn test_timestamp_roundtrip_serialization() {
258 let ts = Timestamp::now();
259 let json = serde_json::to_string(&ts).unwrap();
260 let ts2: Timestamp = serde_json::from_str(&json).unwrap();
261 assert_eq!(ts.millis(), ts2.millis());
263 }
264
265 #[test]
270 fn test_duration_ms_new() {
271 let d = DurationMs::new(5000);
272 assert_eq!(d.as_millis(), 5000);
273 }
274
275 #[test]
276 fn test_duration_ms_from_secs() {
277 let d = DurationMs::from_secs(5);
278 assert_eq!(d.as_millis(), 5000);
279 }
280
281 #[test]
282 fn test_duration_ms_from_mins() {
283 let d = DurationMs::from_mins(2);
284 assert_eq!(d.as_millis(), 120000);
285 }
286
287 #[test]
288 fn test_duration_ms_as_millis() {
289 let d = DurationMs::new(12345);
290 assert_eq!(d.as_millis(), 12345);
291 }
292
293 #[test]
294 fn test_duration_ms_as_secs() {
295 let d = DurationMs::new(5500);
296 assert_eq!(d.as_secs(), 5); }
298
299 #[test]
300 fn test_duration_ms_as_std() {
301 let d = DurationMs::new(5000);
302 let std_d = d.as_std();
303 assert_eq!(std_d.as_millis(), 5000);
304 }
305
306 #[test]
311 fn test_duration_ms_from_u64() {
312 let d: DurationMs = 3000u64.into();
313 assert_eq!(d.as_millis(), 3000);
314 }
315
316 #[test]
317 fn test_duration_ms_from_std_duration() {
318 let std_d = std::time::Duration::from_millis(2500);
319 let d: DurationMs = std_d.into();
320 assert_eq!(d.as_millis(), 2500);
321 }
322
323 #[test]
324 #[allow(clippy::clone_on_copy)]
325 fn test_duration_ms_is_cloneable() {
326 let d = DurationMs::new(1000);
327 let cloned = d.clone();
328 assert_eq!(d, cloned);
329 }
330
331 #[test]
332 fn test_duration_ms_is_copyable() {
333 let d = DurationMs::new(1000);
334 let copied = d;
335 assert_eq!(d, copied);
336 }
337
338 #[test]
339 fn test_duration_ms_equality() {
340 let d1 = DurationMs::new(1000);
341 let d2 = DurationMs::new(1000);
342 assert_eq!(d1, d2);
343 }
344
345 #[test]
346 fn test_duration_ms_inequality() {
347 let d1 = DurationMs::new(1000);
348 let d2 = DurationMs::new(2000);
349 assert_ne!(d1, d2);
350 }
351
352 #[test]
353 fn test_duration_ms_is_debuggable() {
354 let d = DurationMs::new(1000);
355 let debug = format!("{:?}", d);
356 assert!(debug.contains("DurationMs"));
357 }
358
359 #[test]
364 fn test_duration_ms_serializes_to_number() {
365 let d = DurationMs::new(5000);
366 let json = serde_json::to_string(&d).unwrap();
367 assert_eq!(json, "5000");
368 }
369
370 #[test]
371 fn test_duration_ms_deserializes_from_number() {
372 let json = "3000";
373 let d: DurationMs = serde_json::from_str(json).unwrap();
374 assert_eq!(d.as_millis(), 3000);
375 }
376
377 #[test]
378 fn test_duration_ms_roundtrip_serialization() {
379 let d = DurationMs::new(7500);
380 let json = serde_json::to_string(&d).unwrap();
381 let d2: DurationMs = serde_json::from_str(&json).unwrap();
382 assert_eq!(d, d2);
383 }
384
385 #[test]
390 fn test_now_returns_current_time() {
391 let before = Utc::now();
392 let result = now();
393 let after = Utc::now();
394
395 assert!(result >= before);
396 assert!(result <= after);
397 }
398
399 #[test]
400 fn test_now_is_monotonic() {
401 let t1 = now();
402 let t2 = now();
403 assert!(t2 >= t1);
404 }
405
406 #[test]
411 fn test_duration_ms_zero() {
412 let d = DurationMs::new(0);
413 assert_eq!(d.as_millis(), 0);
414 assert_eq!(d.as_secs(), 0);
415 }
416
417 #[test]
418 fn test_duration_ms_large_value() {
419 let d = DurationMs::new(u64::MAX);
420 assert_eq!(d.as_millis(), u64::MAX);
421 }
422
423 #[test]
424 fn test_timestamp_epoch() {
425 let ts = Timestamp::from_millis(0).unwrap();
426 assert_eq!(ts.millis(), 0);
427 }
428
429 use chrono::Datelike;
430}