ploidy_util/
date_time.rs

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