Skip to main content

ruma_common/
time.rs

1use std::fmt;
2
3use js_int::{UInt, uint};
4use serde::{Deserialize, Serialize};
5use time::OffsetDateTime;
6use web_time::{Duration, SystemTime, UNIX_EPOCH};
7
8/// A timestamp represented as the number of milliseconds since the unix epoch.
9#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
10#[allow(clippy::exhaustive_structs)]
11#[serde(transparent)]
12pub struct MilliSecondsSinceUnixEpoch(pub UInt);
13
14impl MilliSecondsSinceUnixEpoch {
15    /// Creates a new `MilliSecondsSinceUnixEpoch` from the given `SystemTime`, if it is not before
16    /// the unix epoch, or too large to be represented.
17    pub fn from_system_time(time: SystemTime) -> Option<Self> {
18        let duration = time.duration_since(UNIX_EPOCH).ok()?;
19        let millis = duration.as_millis().try_into().ok()?;
20        Some(Self(millis))
21    }
22
23    /// The current system time in milliseconds since the unix epoch.
24    pub fn now() -> Self {
25        Self::from_system_time(SystemTime::now()).expect("date out of range")
26    }
27
28    /// Creates a new `SystemTime` from `self`, if it can be represented.
29    pub fn to_system_time(self) -> Option<SystemTime> {
30        UNIX_EPOCH.checked_add(Duration::from_millis(self.0.into()))
31    }
32
33    /// Get the time since the unix epoch in milliseconds.
34    pub fn get(&self) -> UInt {
35        self.0
36    }
37
38    /// Get time since the unix epoch in seconds.
39    pub fn as_secs(&self) -> UInt {
40        self.0 / uint!(1000)
41    }
42}
43
44impl fmt::Debug for MilliSecondsSinceUnixEpoch {
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        match OffsetDateTime::from_unix_timestamp(i64::from(self.0) / 1000) {
47            Ok(date) => {
48                let date = date + Duration::from_millis(u64::from(self.0) % 1000);
49
50                let (year, month, day) = date.to_calendar_date();
51                let month = month as u8;
52                let (hours, minutes, seconds, milliseconds) = date.to_hms_milli();
53
54                write!(
55                    f,
56                    "{year}-{month:02}-{day:02}T\
57                     {hours:02}:{minutes:02}:{seconds:02}.{milliseconds:03}"
58                )
59            }
60            // Probably dead code..
61            Err(_) => {
62                // The default Debug impl would put the inner value on its own
63                // line if the formatter's alternate mode is enabled, which
64                // bloats debug strings unnecessarily
65                write!(f, "MilliSecondsSinceUnixEpoch({})", self.0)
66            }
67        }
68    }
69}
70
71/// A timestamp represented as the number of seconds since the unix epoch.
72#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
73#[allow(clippy::exhaustive_structs)]
74#[serde(transparent)]
75pub struct SecondsSinceUnixEpoch(pub UInt);
76
77impl SecondsSinceUnixEpoch {
78    /// Creates a new `MilliSecondsSinceUnixEpoch` from the given `SystemTime`, if it is not before
79    /// the unix epoch, or too large to be represented.
80    pub fn from_system_time(time: SystemTime) -> Option<Self> {
81        let duration = time.duration_since(UNIX_EPOCH).ok()?;
82        let millis = duration.as_secs().try_into().ok()?;
83        Some(Self(millis))
84    }
85
86    /// The current system-time as seconds since the unix epoch.
87    pub fn now() -> Self {
88        Self::from_system_time(SystemTime::now()).expect("date out of range")
89    }
90
91    /// Creates a new `SystemTime` from `self`, if it can be represented.
92    pub fn to_system_time(self) -> Option<SystemTime> {
93        UNIX_EPOCH.checked_add(Duration::from_secs(self.0.into()))
94    }
95
96    /// Get time since the unix epoch in seconds.
97    pub fn get(&self) -> UInt {
98        self.0
99    }
100}
101
102impl fmt::Debug for SecondsSinceUnixEpoch {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        match OffsetDateTime::from_unix_timestamp(i64::from(self.0)) {
105            Ok(date) => {
106                let (year, month, day) = date.to_calendar_date();
107                let month = month as u8;
108                let (hours, minutes, seconds) = date.to_hms();
109
110                write!(
111                    f,
112                    "{year}-{month:02}-{day:02}T\
113                     {hours:02}:{minutes:02}:{seconds:02}"
114                )
115            }
116            // Probably dead code..
117            Err(_) => {
118                // The default Debug impl would put the inner value on its own
119                // line if the formatter's alternate mode is enabled, which
120                // bloats debug strings unnecessarily
121                write!(f, "SecondsSinceUnixEpoch({})", self.0)
122            }
123        }
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use std::time::{Duration, UNIX_EPOCH};
130
131    use js_int::uint;
132    use serde::{Deserialize, Serialize};
133    use serde_json::json;
134
135    use super::{MilliSecondsSinceUnixEpoch, SecondsSinceUnixEpoch};
136
137    #[derive(Clone, Debug, Deserialize, Serialize)]
138    struct SystemTimeTest {
139        millis: MilliSecondsSinceUnixEpoch,
140        secs: SecondsSinceUnixEpoch,
141    }
142
143    #[test]
144    fn deserialize() {
145        let json = json!({ "millis": 3000, "secs": 60 });
146
147        let time = serde_json::from_value::<SystemTimeTest>(json).unwrap();
148        assert_eq!(time.millis.to_system_time(), Some(UNIX_EPOCH + Duration::from_millis(3000)));
149        assert_eq!(time.secs.to_system_time(), Some(UNIX_EPOCH + Duration::from_secs(60)));
150    }
151
152    #[test]
153    fn serialize() {
154        let request = SystemTimeTest {
155            millis: MilliSecondsSinceUnixEpoch::from_system_time(UNIX_EPOCH + Duration::new(2, 0))
156                .unwrap(),
157            secs: SecondsSinceUnixEpoch(uint!(0)),
158        };
159
160        assert_eq!(serde_json::to_value(request).unwrap(), json!({ "millis": 2000, "secs": 0 }));
161    }
162
163    #[test]
164    fn debug_s() {
165        let seconds = SecondsSinceUnixEpoch(uint!(0));
166        assert_eq!(format!("{seconds:?}"), "1970-01-01T00:00:00");
167    }
168
169    #[test]
170    fn debug_ms() {
171        let seconds = MilliSecondsSinceUnixEpoch(uint!(0));
172        assert_eq!(format!("{seconds:?}"), "1970-01-01T00:00:00.000");
173    }
174}