serde_chrono_str/
ts_milliseconds_str.rs

1use chrono::NaiveDateTime;
2use serde::{de::Visitor, Deserializer, Serializer};
3
4pub fn serialize<S: Serializer>(date_time: &NaiveDateTime, s: S) -> Result<S::Ok, S::Error> {
5    s.serialize_str(&i64::to_string(&date_time.timestamp_millis()))
6}
7
8pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<NaiveDateTime, D::Error> {
9    struct V;
10
11    impl Visitor<'_> for V {
12        type Value = NaiveDateTime;
13
14        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
15            formatter.write_str("a string containing a millisecond timestamp")
16        }
17
18        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
19        where
20            E: serde::de::Error,
21        {
22            let milliseconds = v.parse::<i64>().map_err(E::custom)?;
23            let seconds = milliseconds / 1000;
24            let milliseconds = (milliseconds % 1000) as u32;
25            let nanos = milliseconds * 1_000_000;
26
27            Ok(NaiveDateTime::from_timestamp(seconds, nanos))
28        }
29    }
30
31    d.deserialize_str(V)
32}
33
34#[cfg(test)]
35mod tests {
36    use chrono::{NaiveDateTime, Utc, TimeZone, DateTime};
37    use serde::{Deserialize, Serialize};
38    use serde_json::{from_str, from_value, json, to_string};
39
40    #[derive(Deserialize, Serialize)]
41    struct Foo {
42        #[serde(with = "crate::ts_milliseconds_str")]
43        time: NaiveDateTime,
44    }
45
46    const TIMESTAMP_MS: i64 = 1640995200000;
47
48
49    #[test]
50    fn can_deserialize() {
51        let date_time = Utc.ymd(2022, 1, 1).and_hms(0, 0, 0);
52        let foo: Foo = from_value(json!({"time": TIMESTAMP_MS.to_string()})).unwrap();
53        let time = Utc.from_utc_datetime(&foo.time);
54        assert_eq!(time, date_time);
55    }
56
57    #[test]
58    fn round_trip() {
59        let foo: Foo = from_value(json!({"time": TIMESTAMP_MS.to_string()})).unwrap();
60        let s = to_string(&foo).unwrap();
61        let foo_again: Foo = from_str(&s).unwrap();
62        assert_eq!(foo.time, foo_again.time);
63    }
64}