kitsune_p2p_timestamp/
chrono_ext.rs

1use super::*;
2use std::{convert::TryFrom, fmt, ops::Sub, str::FromStr};
3
4pub(crate) type DateTime = chrono::DateTime<chrono::Utc>;
5
6/// Display as RFC3339 Date+Time for sane value ranges (0000-9999AD).  Beyond that, format
7/// as (seconds, nanoseconds) tuple (output and parsing of large +/- years is unreliable).
8impl fmt::Display for Timestamp {
9    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
10        let ce = -(62167219200 * MM)..=(253402214400 * MM);
11        if ce.contains(&self.0) {
12            if let Ok(ts) = chrono::DateTime::<chrono::Utc>::try_from(self) {
13                return write!(
14                    f,
15                    "{}",
16                    ts.to_rfc3339_opts(chrono::SecondsFormat::AutoSi, true)
17                );
18            }
19        }
20        // Outside 0000-01-01 to 9999-12-31; Display raw value tuple, or not a valid DateTime<Utc>;
21        // Display raw value tuple
22        write!(f, "({}μs)", self.0)
23    }
24}
25
26impl fmt::Debug for Timestamp {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        write!(f, "Timestamp({})", self)
29    }
30}
31
32impl TryFrom<String> for Timestamp {
33    type Error = TimestampError;
34
35    fn try_from(t: String) -> Result<Self, Self::Error> {
36        Timestamp::from_str(t.as_ref())
37    }
38}
39
40impl TryFrom<&String> for Timestamp {
41    type Error = TimestampError;
42
43    fn try_from(t: &String) -> Result<Self, Self::Error> {
44        Timestamp::from_str(t.as_ref())
45    }
46}
47
48impl TryFrom<&str> for Timestamp {
49    type Error = TimestampError;
50
51    fn try_from(t: &str) -> Result<Self, Self::Error> {
52        Timestamp::from_str(t)
53    }
54}
55
56impl From<DateTime> for Timestamp {
57    fn from(t: DateTime) -> Self {
58        std::convert::From::from(&t)
59    }
60}
61
62impl From<&DateTime> for Timestamp {
63    fn from(t: &DateTime) -> Self {
64        let t = t.naive_utc().and_utc();
65        Timestamp(t.timestamp() * MM + t.timestamp_subsec_nanos() as i64 / 1000)
66    }
67}
68
69// Implementation note: There are *no* infallible conversions from a Timestamp to a DateTime.  These
70// may panic in from_timestamp due to out-of-range secs or nsecs, making all code using/displaying a
71// Timestamp this way dangerously fragile!  Use try_from, and handle any failures.
72
73impl TryFrom<Timestamp> for DateTime {
74    type Error = TimestampError;
75
76    fn try_from(t: Timestamp) -> Result<Self, Self::Error> {
77        std::convert::TryFrom::try_from(&t)
78    }
79}
80
81impl TryFrom<&Timestamp> for DateTime {
82    type Error = TimestampError;
83
84    fn try_from(t: &Timestamp) -> Result<Self, Self::Error> {
85        let (secs, nsecs) = t.as_seconds_and_nanos();
86        chrono::DateTime::from_timestamp(secs, nsecs).ok_or(TimestampError::Overflow)
87    }
88}
89
90impl FromStr for Timestamp {
91    type Err = TimestampError;
92
93    fn from_str(t: &str) -> Result<Self, Self::Err> {
94        let t = chrono::DateTime::parse_from_rfc3339(t)?;
95        let t = chrono::DateTime::from_naive_utc_and_offset(t.naive_utc(), chrono::Utc);
96        Ok(t.into())
97    }
98}
99
100impl Timestamp {
101    /// Returns the current system time as a Timestamp.
102    ///
103    /// This is behind a feature because we need Timestamp to be WASM compatible, and
104    /// chrono doesn't have a now() implementation for WASM.
105    #[cfg(feature = "now")]
106    pub fn now() -> Timestamp {
107        Timestamp::from(chrono::offset::Utc::now())
108    }
109    /// Compute signed difference between two Timestamp, returning `None` if overflow occurred, or
110    /// Some(chrono::Duration).  Produces Duration for differences of up to +/- i64::MIN/MAX
111    /// microseconds.
112    pub fn checked_difference_signed(&self, rhs: &Timestamp) -> Option<chrono::Duration> {
113        Some(chrono::Duration::microseconds(self.0.checked_sub(rhs.0)?))
114    }
115
116    /// Add a signed chrono::Duration{ secs: i64, nanos: i32 } to a Timestamp.
117    pub fn checked_add_signed(&self, rhs: &chrono::Duration) -> Option<Timestamp> {
118        Some(Self(self.0.checked_add(rhs.num_microseconds()?)?))
119    }
120
121    /// Subtracts a chrono::Duration from a Timestamp
122    pub fn checked_sub_signed(&self, rhs: &chrono::Duration) -> Option<Timestamp> {
123        self.checked_add_signed(&-*rhs)
124    }
125}
126/// Distance between two Timestamps as a chrono::Duration (subject to overflow).  A Timestamp
127/// represents a *signed* distance from the UNIX Epoch (1970-01-01T00:00:00Z).  A chrono::Duration
128/// is limited to +/- i64::MIN/MAX microseconds.
129impl Sub<Timestamp> for Timestamp {
130    type Output = TimestampResult<chrono::Duration>;
131
132    fn sub(self, rhs: Timestamp) -> Self::Output {
133        self.checked_difference_signed(&rhs)
134            .ok_or(TimestampError::Overflow)
135    }
136}