pbjson_types/
timestamp.rs

1use crate::Timestamp;
2use chrono::{DateTime, NaiveDateTime, TimeZone, Utc};
3use serde::de::Visitor;
4use serde::Serialize;
5
6impl TryFrom<Timestamp> for DateTime<Utc> {
7    type Error = &'static str;
8    fn try_from(value: Timestamp) -> Result<Self, Self::Error> {
9        let Timestamp { seconds, nanos } = value;
10
11        let dt = NaiveDateTime::from_timestamp_opt(
12            seconds,
13            nanos
14                .try_into()
15                .map_err(|_| "out of range integral type conversion attempted")?,
16        )
17        .ok_or("invalid or out-of-range datetime")?;
18        Ok(Utc.from_utc_datetime(&dt))
19    }
20}
21
22impl From<DateTime<Utc>> for Timestamp {
23    fn from(value: DateTime<Utc>) -> Self {
24        Self {
25            seconds: value.timestamp(),
26            nanos: value.timestamp_subsec_nanos() as i32,
27        }
28    }
29}
30
31impl Serialize for Timestamp {
32    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
33    where
34        S: serde::Serializer,
35    {
36        let t: DateTime<Utc> = self.clone().try_into().map_err(serde::ser::Error::custom)?;
37        serializer.serialize_str(t.to_rfc3339().as_str())
38    }
39}
40
41struct TimestampVisitor;
42
43impl<'de> Visitor<'de> for TimestampVisitor {
44    type Value = Timestamp;
45
46    fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
47        formatter.write_str("a date string")
48    }
49
50    fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
51    where
52        E: serde::de::Error,
53    {
54        let d = DateTime::parse_from_rfc3339(s).map_err(serde::de::Error::custom)?;
55        let d: DateTime<Utc> = d.into();
56        Ok(d.into())
57    }
58}
59
60impl<'de> serde::Deserialize<'de> for Timestamp {
61    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
62    where
63        D: serde::Deserializer<'de>,
64    {
65        deserializer.deserialize_str(TimestampVisitor)
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72    use chrono::{FixedOffset, TimeZone};
73    use serde::de::value::{BorrowedStrDeserializer, Error};
74    use serde::Deserialize;
75
76    #[test]
77    fn test_date() {
78        let datetime = FixedOffset::east_opt(5 * 3600)
79            .expect("time zone offset should be valid")
80            .with_ymd_and_hms(2016, 11, 8, 21, 7, 9)
81            .unwrap();
82        let encoded = datetime.to_rfc3339();
83        assert_eq!(&encoded, "2016-11-08T21:07:09+05:00");
84
85        let utc: DateTime<Utc> = datetime.into();
86        let utc_encoded = utc.to_rfc3339();
87        assert_eq!(&utc_encoded, "2016-11-08T16:07:09+00:00");
88
89        let deserializer = BorrowedStrDeserializer::<'_, Error>::new(&encoded);
90        let a: Timestamp = Timestamp::deserialize(deserializer).unwrap();
91        assert_eq!(a.seconds, utc.timestamp());
92        assert_eq!(a.nanos, utc.timestamp_subsec_nanos() as i32);
93
94        let encoded = serde_json::to_string(&a).unwrap();
95        assert_eq!(encoded, alloc::format!("\"{}\"", utc_encoded));
96    }
97}