Skip to main content

json_web_tolkien/util/
epoch.rs

1use std::ops::{Deref, Sub};
2use std::{fmt, ops::Add};
3
4use chrono::{DateTime, TimeDelta, TimeZone, Utc};
5use serde::de::Unexpected;
6use serde::{Deserialize, Serialize};
7
8#[derive(Clone, Debug, PartialEq, PartialOrd)]
9pub struct UtcDateTime(pub DateTime<Utc>);
10
11impl Add<TimeDelta> for UtcDateTime {
12    type Output = UtcDateTime;
13
14    fn add(self, rhs: TimeDelta) -> Self::Output {
15        let inner: DateTime<Utc> = self.0 + rhs;
16
17        UtcDateTime(inner)
18    }
19}
20
21impl AsRef<DateTime<Utc>> for UtcDateTime {
22    fn as_ref(&self) -> &DateTime<Utc> {
23        &self.0
24    }
25}
26
27impl Deref for UtcDateTime {
28    type Target = DateTime<Utc>;
29
30    fn deref(&self) -> &Self::Target {
31        &self.0
32    }
33}
34
35impl<'de> Deserialize<'de> for UtcDateTime {
36    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
37    where
38        D: serde::Deserializer<'de>,
39    {
40        deserializer.deserialize_any(Visitor)
41    }
42}
43
44impl PartialEq<DateTime<Utc>> for UtcDateTime {
45    fn eq(&self, other: &DateTime<Utc>) -> bool {
46        self.0.eq(other)
47    }
48}
49
50impl PartialOrd<DateTime<Utc>> for UtcDateTime {
51    fn partial_cmp(&self, other: &DateTime<Utc>) -> Option<std::cmp::Ordering> {
52        self.0.partial_cmp(other)
53    }
54}
55
56impl Serialize for UtcDateTime {
57    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
58    where
59        S: serde::Serializer,
60    {
61        let value: i64 = self.timestamp();
62
63        serializer.serialize_i64(value)
64    }
65}
66
67impl Sub for UtcDateTime {
68    type Output = TimeDelta;
69
70    fn sub(self, rhs: Self) -> Self::Output {
71        self.0 - rhs.0
72    }
73}
74
75impl Sub<DateTime<Utc>> for UtcDateTime {
76    type Output = TimeDelta;
77
78    fn sub(self, rhs: DateTime<Utc>) -> Self::Output {
79        self.0 - rhs
80    }
81}
82
83impl Sub<TimeDelta> for UtcDateTime {
84    type Output = UtcDateTime;
85
86    fn sub(self, rhs: TimeDelta) -> Self::Output {
87        let value: DateTime<Utc> = self.0 - rhs;
88
89        UtcDateTime(value)
90    }
91}
92
93impl PartialEq<UtcDateTime> for DateTime<Utc> {
94    fn eq(&self, other: &UtcDateTime) -> bool {
95        self.eq(&other.0)
96    }
97}
98
99impl PartialOrd<UtcDateTime> for DateTime<Utc> {
100    fn partial_cmp(&self, other: &UtcDateTime) -> Option<std::cmp::Ordering> {
101        self.partial_cmp(&other.0)
102    }
103}
104
105impl Sub<UtcDateTime> for DateTime<Utc> {
106    type Output = TimeDelta;
107
108    fn sub(self, rhs: UtcDateTime) -> Self::Output {
109        self - rhs.0
110    }
111}
112
113struct Visitor;
114
115impl<'de> serde::de::Visitor<'de> for Visitor {
116    type Value = UtcDateTime;
117
118    fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
119        f.write_str("an integer between -2^63 and 2^63 that represents epoch seconds")
120    }
121
122    fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
123    where
124        E: serde::de::Error,
125    {
126        let Some(v): Option<DateTime<Utc>> = Utc.timestamp_opt(v, 0).single() else {
127            return Err(E::invalid_value(Unexpected::Signed(v), &self));
128        };
129
130        let v = UtcDateTime(v);
131
132        Ok(v)
133    }
134
135    fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
136    where
137        E: serde::de::Error,
138    {
139        let v = i64::try_from(v).map_err(|_| E::invalid_value(Unexpected::Unsigned(v), &self))?;
140
141        self.visit_i64(v)
142    }
143
144    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
145    where
146        E: serde::de::Error,
147    {
148        let v: i64 = v
149            .parse()
150            .map_err(|_| E::invalid_value(Unexpected::Str(v), &self))?;
151
152        self.visit_i64(v)
153    }
154}
155
156#[cfg(test)]
157mod test {
158    use serde_json::json;
159
160    use super::*;
161
162    #[test]
163    fn it_serializes_and_deserializes() {
164        #[derive(Deserialize, Serialize)]
165        struct Bar {
166            foo: UtcDateTime,
167        }
168
169        let epoch: i64 = 1772144586;
170        let expected: DateTime<Utc> = Utc.with_ymd_and_hms(2026, 2, 26, 22, 23, 6).unwrap();
171        let future = expected.clone() + TimeDelta::days(1);
172
173        let input = json!({ "foo": "1772144586" });
174
175        let actual: UtcDateTime = {
176            let bar: Bar = serde_json::from_value(input).unwrap();
177
178            bar.foo
179        };
180
181        assert_eq!(expected, actual);
182        assert!(actual < future);
183
184        let input = json!({ "foo": epoch });
185
186        let actual: UtcDateTime = {
187            let bar: Bar = serde_json::from_value(input).unwrap();
188
189            bar.foo
190        };
191
192        assert_eq!(expected, actual);
193    }
194}