sentry_types/
utils.rs

1use std::convert::{TryFrom, TryInto};
2use std::time::{Duration, SystemTime};
3
4use time::format_description::well_known::Rfc3339;
5use time::OffsetDateTime;
6
7/// Converts a `SystemTime` object into a float timestamp.
8pub fn datetime_to_timestamp(st: &SystemTime) -> f64 {
9    match st.duration_since(SystemTime::UNIX_EPOCH) {
10        Ok(duration) => duration.as_secs_f64(),
11        Err(_) => 0.0,
12    }
13}
14
15pub fn timestamp_to_datetime(ts: f64) -> Option<SystemTime> {
16    let duration = Duration::try_from_secs_f64(ts).ok()?;
17    SystemTime::UNIX_EPOCH.checked_add(duration)
18}
19
20pub fn to_rfc3339(st: &SystemTime) -> String {
21    st.duration_since(SystemTime::UNIX_EPOCH)
22        .ok()
23        .and_then(|duration| TryFrom::try_from(duration).ok())
24        .and_then(|duration| OffsetDateTime::UNIX_EPOCH.checked_add(duration))
25        .and_then(|dt| dt.format(&Rfc3339).ok())
26        .unwrap_or_default()
27}
28
29pub mod ts_seconds_float {
30    use std::fmt;
31
32    use serde::{de, ser};
33
34    use super::*;
35
36    pub fn deserialize<'de, D>(d: D) -> Result<SystemTime, D::Error>
37    where
38        D: de::Deserializer<'de>,
39    {
40        d.deserialize_any(SecondsTimestampVisitor)
41    }
42
43    pub fn serialize<S>(st: &SystemTime, serializer: S) -> Result<S::Ok, S::Error>
44    where
45        S: ser::Serializer,
46    {
47        match st.duration_since(SystemTime::UNIX_EPOCH) {
48            Ok(duration) => {
49                if duration.subsec_nanos() == 0 {
50                    serializer.serialize_u64(duration.as_secs())
51                } else {
52                    serializer.serialize_f64(duration.as_secs_f64())
53                }
54            }
55            Err(_) => Err(ser::Error::custom(format!(
56                "invalid `SystemTime` instance: {st:?}"
57            ))),
58        }
59    }
60
61    struct SecondsTimestampVisitor;
62
63    impl de::Visitor<'_> for SecondsTimestampVisitor {
64        type Value = SystemTime;
65
66        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
67            write!(formatter, "a unix timestamp")
68        }
69
70        fn visit_f64<E>(self, value: f64) -> Result<SystemTime, E>
71        where
72            E: de::Error,
73        {
74            match timestamp_to_datetime(value) {
75                Some(st) => Ok(st),
76                None => Err(E::custom(format!("invalid timestamp: {value}"))),
77            }
78        }
79
80        fn visit_i64<E>(self, value: i64) -> Result<SystemTime, E>
81        where
82            E: de::Error,
83        {
84            let value = value.try_into().map_err(|e| E::custom(format!("{e}")))?;
85            let duration = Duration::from_secs(value);
86            match SystemTime::UNIX_EPOCH.checked_add(duration) {
87                Some(st) => Ok(st),
88                None => Err(E::custom(format!("invalid timestamp: {value}"))),
89            }
90        }
91
92        fn visit_u64<E>(self, value: u64) -> Result<SystemTime, E>
93        where
94            E: de::Error,
95        {
96            let duration = Duration::from_secs(value);
97            match SystemTime::UNIX_EPOCH.checked_add(duration) {
98                Some(st) => Ok(st),
99                None => Err(E::custom(format!("invalid timestamp: {value}"))),
100            }
101        }
102
103        fn visit_str<E>(self, value: &str) -> Result<SystemTime, E>
104        where
105            E: de::Error,
106        {
107            let rfc3339_deser = super::ts_rfc3339::Rfc3339Deserializer;
108            rfc3339_deser.visit_str(value)
109        }
110    }
111}
112
113pub mod ts_rfc3339 {
114    use std::fmt;
115
116    use serde::{de, ser};
117
118    use super::*;
119
120    pub fn deserialize<'de, D>(d: D) -> Result<SystemTime, D::Error>
121    where
122        D: de::Deserializer<'de>,
123    {
124        d.deserialize_any(Rfc3339Deserializer)
125    }
126
127    pub fn serialize<S>(st: &SystemTime, serializer: S) -> Result<S::Ok, S::Error>
128    where
129        S: ser::Serializer,
130    {
131        match st
132            .duration_since(SystemTime::UNIX_EPOCH)
133            .ok()
134            .and_then(|duration| TryFrom::try_from(duration).ok())
135            .and_then(|duration| OffsetDateTime::UNIX_EPOCH.checked_add(duration))
136            .and_then(|dt| dt.format(&Rfc3339).ok())
137        {
138            Some(formatted) => serializer.serialize_str(&formatted),
139            None => Err(ser::Error::custom(format!(
140                "invalid `SystemTime` instance: {st:?}"
141            ))),
142        }
143    }
144
145    pub(super) struct Rfc3339Deserializer;
146
147    impl de::Visitor<'_> for Rfc3339Deserializer {
148        type Value = SystemTime;
149
150        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
151            write!(formatter, "an RFC3339 timestamp")
152        }
153
154        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
155        where
156            E: de::Error,
157        {
158            let dt = OffsetDateTime::parse(v, &Rfc3339).map_err(|e| E::custom(format!("{e}")))?;
159            let secs = u64::try_from(dt.unix_timestamp()).map_err(|e| E::custom(format!("{e}")))?;
160            let nanos = dt.nanosecond();
161            let duration = Duration::new(secs, nanos);
162            SystemTime::UNIX_EPOCH
163                .checked_add(duration)
164                .ok_or_else(|| E::custom("invalid timestamp"))
165        }
166    }
167}
168
169pub mod ts_rfc3339_opt {
170    use serde::{de, ser};
171
172    use super::*;
173
174    pub fn deserialize<'de, D>(d: D) -> Result<Option<SystemTime>, D::Error>
175    where
176        D: de::Deserializer<'de>,
177    {
178        ts_rfc3339::deserialize(d).map(Some)
179    }
180
181    pub fn serialize<S>(st: &Option<SystemTime>, serializer: S) -> Result<S::Ok, S::Error>
182    where
183        S: ser::Serializer,
184    {
185        match st {
186            Some(st) => ts_rfc3339::serialize(st, serializer),
187            None => serializer.serialize_none(),
188        }
189    }
190}
191
192#[cfg(test)]
193mod tests {
194    use super::timestamp_to_datetime;
195
196    #[test]
197    fn test_timestamp_to_datetime() {
198        assert!(timestamp_to_datetime(-10000.0).is_none());
199        assert!(timestamp_to_datetime(f64::INFINITY).is_none());
200        assert!(timestamp_to_datetime(f64::MAX).is_none());
201        assert!(timestamp_to_datetime(123123123.0).is_some());
202    }
203}