Skip to main content

ploidy_util/
date_time.rs

1use chrono::{DateTime, Utc};
2use ploidy_pointer::{JsonPointee, JsonPointeeError, JsonPointer, JsonPointerTypeError};
3use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
4
5#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
6pub struct UnixMicroseconds(DateTime<Utc>);
7
8impl From<DateTime<Utc>> for UnixMicroseconds {
9    #[inline]
10    fn from(value: DateTime<Utc>) -> Self {
11        Self(value)
12    }
13}
14
15impl From<UnixMicroseconds> for DateTime<Utc> {
16    #[inline]
17    fn from(value: UnixMicroseconds) -> Self {
18        value.0
19    }
20}
21
22impl TryFrom<i64> for UnixMicroseconds {
23    type Error = TryFromTimestampError;
24
25    #[inline]
26    fn try_from(value: i64) -> Result<Self, Self::Error> {
27        Ok(Self(DateTime::from_timestamp_micros(value).ok_or_else(
28            || TryFromTimestampError::Range(value.into()),
29        )?))
30    }
31}
32
33impl Serialize for UnixMicroseconds {
34    #[inline]
35    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
36        serializer.serialize_i64(self.0.timestamp_micros())
37    }
38}
39
40impl<'de> Deserialize<'de> for UnixMicroseconds {
41    #[inline]
42    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
43        use de::Error;
44        let timestamp = NumericTimestamp::deserialize(deserializer)?;
45        let micros = timestamp.try_into().map_err(D::Error::custom)?;
46        DateTime::from_timestamp_micros(micros)
47            .map(Self)
48            .ok_or_else(|| D::Error::custom(TryFromTimestampError::Range(micros.into())))
49    }
50}
51
52#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
53pub struct UnixMilliseconds(DateTime<Utc>);
54
55impl From<DateTime<Utc>> for UnixMilliseconds {
56    #[inline]
57    fn from(value: DateTime<Utc>) -> Self {
58        Self(value)
59    }
60}
61
62impl From<UnixMilliseconds> for DateTime<Utc> {
63    #[inline]
64    fn from(value: UnixMilliseconds) -> Self {
65        value.0
66    }
67}
68
69impl TryFrom<i64> for UnixMilliseconds {
70    type Error = TryFromTimestampError;
71
72    #[inline]
73    fn try_from(value: i64) -> Result<Self, Self::Error> {
74        Ok(Self(DateTime::from_timestamp_millis(value).ok_or_else(
75            || TryFromTimestampError::Range(value.into()),
76        )?))
77    }
78}
79
80impl Serialize for UnixMilliseconds {
81    #[inline]
82    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
83        serializer.serialize_i64(self.0.timestamp_millis())
84    }
85}
86
87impl<'de> Deserialize<'de> for UnixMilliseconds {
88    #[inline]
89    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
90        use de::Error;
91        let timestamp = NumericTimestamp::deserialize(deserializer)?;
92        let millis = timestamp.try_into().map_err(D::Error::custom)?;
93        DateTime::from_timestamp_millis(millis)
94            .map(Self)
95            .ok_or_else(|| D::Error::custom(TryFromTimestampError::Range(millis.into())))
96    }
97}
98
99#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
100pub struct UnixNanoseconds(DateTime<Utc>);
101
102impl From<DateTime<Utc>> for UnixNanoseconds {
103    #[inline]
104    fn from(value: DateTime<Utc>) -> Self {
105        Self(value)
106    }
107}
108
109impl From<UnixNanoseconds> for DateTime<Utc> {
110    #[inline]
111    fn from(value: UnixNanoseconds) -> Self {
112        value.0
113    }
114}
115
116impl From<i64> for UnixNanoseconds {
117    #[inline]
118    fn from(value: i64) -> Self {
119        Self(DateTime::from_timestamp_nanos(value))
120    }
121}
122
123impl Serialize for UnixNanoseconds {
124    #[inline]
125    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
126        serializer.serialize_i64(self.0.timestamp_nanos_opt().unwrap_or(i64::MAX))
127    }
128}
129
130impl<'de> Deserialize<'de> for UnixNanoseconds {
131    #[inline]
132    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
133        use de::Error;
134        let timestamp = NumericTimestamp::deserialize(deserializer)?;
135        let nanos = timestamp.try_into().map_err(D::Error::custom)?;
136        Ok(Self(DateTime::from_timestamp_nanos(nanos)))
137    }
138}
139
140#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
141pub struct UnixSeconds(DateTime<Utc>);
142
143impl From<DateTime<Utc>> for UnixSeconds {
144    #[inline]
145    fn from(value: DateTime<Utc>) -> Self {
146        Self(value)
147    }
148}
149
150impl From<UnixSeconds> for DateTime<Utc> {
151    #[inline]
152    fn from(value: UnixSeconds) -> Self {
153        value.0
154    }
155}
156
157impl TryFrom<i64> for UnixSeconds {
158    type Error = TryFromTimestampError;
159
160    #[inline]
161    fn try_from(value: i64) -> Result<Self, Self::Error> {
162        Ok(Self(DateTime::from_timestamp_secs(value).ok_or_else(
163            || TryFromTimestampError::Range(value.into()),
164        )?))
165    }
166}
167
168impl Serialize for UnixSeconds {
169    #[inline]
170    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
171        serializer.serialize_i64(self.0.timestamp())
172    }
173}
174
175impl<'de> Deserialize<'de> for UnixSeconds {
176    #[inline]
177    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
178        use de::Error;
179        let timestamp = NumericTimestamp::deserialize(deserializer)?;
180        let secs = timestamp.try_into().map_err(D::Error::custom)?;
181        DateTime::from_timestamp_secs(secs)
182            .map(Self)
183            .ok_or_else(|| D::Error::custom(TryFromTimestampError::Range(secs.into())))
184    }
185}
186
187#[derive(Deserialize)]
188#[serde(untagged)]
189enum NumericTimestamp<'a> {
190    I64(i64),
191    U64(u64),
192    Str(&'a str),
193}
194
195impl TryFrom<NumericTimestamp<'_>> for i64 {
196    type Error = TryFromTimestampError;
197
198    fn try_from(value: NumericTimestamp<'_>) -> Result<Self, Self::Error> {
199        Ok(match value {
200            NumericTimestamp::I64(n) => n,
201            NumericTimestamp::U64(n) => {
202                Self::try_from(n).map_err(|_| TryFromTimestampError::Range(n.into()))?
203            }
204            NumericTimestamp::Str(s) => s
205                .parse()
206                .map_err(|_| TryFromTimestampError::Str(s.to_owned()))?,
207        })
208    }
209}
210
211#[derive(Debug, thiserror::Error)]
212pub enum TryFromTimestampError {
213    #[error("timestamp `{0}` out of range for `DateTime<Utc>`")]
214    Range(i128),
215    #[error("can't convert `{0}` to timestamp")]
216    Str(String),
217}
218
219macro_rules! impl_pointee_for {
220    ($($ty:ty),*) => {$(
221        impl JsonPointee for $ty {
222            fn resolve(&self, pointer: &JsonPointer) -> Result<&dyn JsonPointee, JsonPointeeError> {
223                if pointer.is_empty() {
224                    Ok(self as &dyn JsonPointee)
225                } else {
226                    Err(JsonPointerTypeError::new(pointer).into())
227                }
228            }
229        }
230    )*};
231}
232
233impl_pointee_for!(
234    UnixMicroseconds,
235    UnixMilliseconds,
236    UnixNanoseconds,
237    UnixSeconds
238);
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243
244    // MARK: Unix seconds
245
246    #[test]
247    fn test_unix_seconds_deserialize_from_number() {
248        let json = "1609459200";
249        let result: UnixSeconds = serde_json::from_str(json).unwrap();
250        let dt: DateTime<Utc> = result.into();
251        assert_eq!(dt.timestamp(), 1609459200);
252    }
253
254    #[test]
255    fn test_unix_seconds_deserialize_from_string() {
256        let json = r#""1609459200""#;
257        let result: UnixSeconds = serde_json::from_str(json).unwrap();
258        let dt: DateTime<Utc> = result.into();
259        assert_eq!(dt.timestamp(), 1609459200);
260    }
261
262    #[test]
263    fn test_unix_seconds_serialize() {
264        let dt = DateTime::from_timestamp_secs(1609459200).unwrap();
265        let ts = UnixSeconds::from(dt);
266        let json = serde_json::to_string(&ts).unwrap();
267        assert_eq!(json, "1609459200");
268    }
269
270    #[test]
271    fn test_unix_seconds_invalid_string() {
272        let json = r#""not-a-number""#;
273        let result: Result<UnixSeconds, _> = serde_json::from_str(json);
274        assert!(result.is_err());
275    }
276
277    // MARK: Unix milliseconds
278
279    #[test]
280    fn test_unix_milliseconds_deserialize_from_number() {
281        let json = "1609459200123";
282        let result: UnixMilliseconds = serde_json::from_str(json).unwrap();
283        let dt: DateTime<Utc> = result.into();
284        assert_eq!(dt.timestamp_millis(), 1609459200123);
285    }
286
287    #[test]
288    fn test_unix_milliseconds_deserialize_from_string() {
289        let json = r#""1609459200123""#;
290        let result: UnixMilliseconds = serde_json::from_str(json).unwrap();
291        let dt: DateTime<Utc> = result.into();
292        assert_eq!(dt.timestamp_millis(), 1609459200123);
293    }
294
295    #[test]
296    fn test_unix_milliseconds_serialize() {
297        let dt = DateTime::from_timestamp_millis(1609459200123).unwrap();
298        let ts = UnixMilliseconds::from(dt);
299        let json = serde_json::to_string(&ts).unwrap();
300        assert_eq!(json, "1609459200123");
301    }
302
303    #[test]
304    fn test_unix_milliseconds_invalid_string() {
305        let json = r#""not-a-number""#;
306        let result: Result<UnixMilliseconds, _> = serde_json::from_str(json);
307        assert!(result.is_err());
308    }
309
310    // MARK: Unix microseconds
311
312    #[test]
313    fn test_unix_microseconds_deserialize_from_number() {
314        let json = "1609459200123456";
315        let result: UnixMicroseconds = serde_json::from_str(json).unwrap();
316        let dt: DateTime<Utc> = result.into();
317        assert_eq!(dt.timestamp_micros(), 1609459200123456);
318    }
319
320    #[test]
321    fn test_unix_microseconds_deserialize_from_string() {
322        let json = r#""1609459200123456""#;
323        let result: UnixMicroseconds = serde_json::from_str(json).unwrap();
324        let dt: DateTime<Utc> = result.into();
325        assert_eq!(dt.timestamp_micros(), 1609459200123456);
326    }
327
328    #[test]
329    fn test_unix_microseconds_serialize() {
330        let dt = DateTime::from_timestamp_micros(1609459200123456).unwrap();
331        let ts = UnixMicroseconds::from(dt);
332        let json = serde_json::to_string(&ts).unwrap();
333        assert_eq!(json, "1609459200123456");
334    }
335
336    #[test]
337    fn test_unix_microseconds_invalid_string() {
338        let json = r#""not-a-number""#;
339        let result: Result<UnixMicroseconds, _> = serde_json::from_str(json);
340        assert!(result.is_err());
341    }
342
343    // MARK: Unix nanoseconds
344
345    #[test]
346    fn test_unix_nanoseconds_deserialize_from_number() {
347        let json = "1609459200123456789";
348        let result: UnixNanoseconds = serde_json::from_str(json).unwrap();
349        let dt: DateTime<Utc> = result.into();
350        assert_eq!(dt.timestamp_nanos_opt(), Some(1609459200123456789));
351    }
352
353    #[test]
354    fn test_unix_nanoseconds_deserialize_from_string() {
355        let json = r#""1609459200123456789""#;
356        let result: UnixNanoseconds = serde_json::from_str(json).unwrap();
357        let dt: DateTime<Utc> = result.into();
358        assert_eq!(dt.timestamp_nanos_opt(), Some(1609459200123456789));
359    }
360
361    #[test]
362    fn test_unix_nanoseconds_serialize() {
363        let dt = DateTime::from_timestamp_nanos(1609459200123456789);
364        let ts = UnixNanoseconds::from(dt);
365        let json = serde_json::to_string(&ts).unwrap();
366        assert_eq!(json, "1609459200123456789");
367    }
368
369    #[test]
370    fn test_unix_nanoseconds_invalid_string() {
371        let json = r#""not-a-number""#;
372        let result: Result<UnixNanoseconds, _> = serde_json::from_str(json);
373        assert!(result.is_err());
374    }
375}