1use std::fmt;
2
3use js_int::{UInt, uint};
4use serde::{Deserialize, Serialize};
5use thiserror::Error;
6use time::OffsetDateTime;
7use web_time::{Duration, SystemTime, UNIX_EPOCH};
8
9#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
11#[allow(clippy::exhaustive_structs)]
12#[serde(transparent)]
13pub struct MilliSecondsSinceUnixEpoch(pub UInt);
14
15impl MilliSecondsSinceUnixEpoch {
16 pub fn from_system_time(time: SystemTime) -> Option<Self> {
19 let duration = time.duration_since(UNIX_EPOCH).ok()?;
20 let millis = duration.as_millis().try_into().ok()?;
21 Some(Self(millis))
22 }
23
24 pub fn now() -> Self {
26 Self::from_system_time(SystemTime::now()).expect("date out of range")
27 }
28
29 pub fn to_system_time(self) -> Option<SystemTime> {
31 UNIX_EPOCH.checked_add(Duration::from_millis(self.0.into()))
32 }
33
34 pub fn checked_add(self, rhs: Duration) -> Option<Self> {
36 Some(Self(self.0.checked_add(rhs.as_millis().try_into().ok()?)?))
37 }
38
39 pub fn get(&self) -> UInt {
41 self.0
42 }
43
44 pub fn as_secs(&self) -> UInt {
46 self.0 / uint!(1000)
47 }
48}
49
50impl fmt::Debug for MilliSecondsSinceUnixEpoch {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 match OffsetDateTime::from_unix_timestamp(i64::from(self.0) / 1000) {
53 Ok(date) => {
54 let date = date + Duration::from_millis(u64::from(self.0) % 1000);
55
56 let (year, month, day) = date.to_calendar_date();
57 let month = month as u8;
58 let (hours, minutes, seconds, milliseconds) = date.to_hms_milli();
59
60 write!(
61 f,
62 "{year}-{month:02}-{day:02}T\
63 {hours:02}:{minutes:02}:{seconds:02}.{milliseconds:03}"
64 )
65 }
66 Err(_) => {
68 write!(f, "MilliSecondsSinceUnixEpoch({})", self.0)
72 }
73 }
74 }
75}
76
77impl TryFrom<SystemTime> for MilliSecondsSinceUnixEpoch {
78 type Error = TimeConversionError;
79
80 fn try_from(value: SystemTime) -> Result<Self, Self::Error> {
81 Self::from_system_time(value).ok_or(TimeConversionError::SystemTime(value))
82 }
83}
84
85impl TryFrom<MilliSecondsSinceUnixEpoch> for SystemTime {
86 type Error = TimeConversionError;
87
88 fn try_from(value: MilliSecondsSinceUnixEpoch) -> Result<Self, Self::Error> {
89 value.to_system_time().ok_or(TimeConversionError::MilliSecondsSinceUnixEpoch(value))
90 }
91}
92
93#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
95#[allow(clippy::exhaustive_structs)]
96#[serde(transparent)]
97pub struct SecondsSinceUnixEpoch(pub UInt);
98
99impl SecondsSinceUnixEpoch {
100 pub fn from_system_time(time: SystemTime) -> Option<Self> {
103 let duration = time.duration_since(UNIX_EPOCH).ok()?;
104 let millis = duration.as_secs().try_into().ok()?;
105 Some(Self(millis))
106 }
107
108 pub fn now() -> Self {
110 Self::from_system_time(SystemTime::now()).expect("date out of range")
111 }
112
113 pub fn to_system_time(self) -> Option<SystemTime> {
115 UNIX_EPOCH.checked_add(Duration::from_secs(self.0.into()))
116 }
117
118 pub fn checked_add(self, rhs: Duration) -> Option<Self> {
120 Some(Self(self.0.checked_add(rhs.as_millis().try_into().ok()?)?))
121 }
122
123 pub fn get(&self) -> UInt {
125 self.0
126 }
127}
128
129impl fmt::Debug for SecondsSinceUnixEpoch {
130 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131 match OffsetDateTime::from_unix_timestamp(i64::from(self.0)) {
132 Ok(date) => {
133 let (year, month, day) = date.to_calendar_date();
134 let month = month as u8;
135 let (hours, minutes, seconds) = date.to_hms();
136
137 write!(
138 f,
139 "{year}-{month:02}-{day:02}T\
140 {hours:02}:{minutes:02}:{seconds:02}"
141 )
142 }
143 Err(_) => {
145 write!(f, "SecondsSinceUnixEpoch({})", self.0)
149 }
150 }
151 }
152}
153
154impl TryFrom<SystemTime> for SecondsSinceUnixEpoch {
155 type Error = TimeConversionError;
156
157 fn try_from(value: SystemTime) -> Result<Self, Self::Error> {
158 Self::from_system_time(value).ok_or(TimeConversionError::SystemTime(value))
159 }
160}
161
162impl TryFrom<SecondsSinceUnixEpoch> for SystemTime {
163 type Error = TimeConversionError;
164
165 fn try_from(value: SecondsSinceUnixEpoch) -> Result<Self, Self::Error> {
166 value.to_system_time().ok_or(TimeConversionError::SecondsSinceUnixEpoch(value))
167 }
168}
169
170#[derive(Debug, Error)]
172#[non_exhaustive]
173pub enum TimeConversionError {
174 #[error("Cannot represent {0:?} as SystemTime")]
175 MilliSecondsSinceUnixEpoch(MilliSecondsSinceUnixEpoch),
176
177 #[error("Cannot represent {0:?} as SystemTime")]
178 SecondsSinceUnixEpoch(SecondsSinceUnixEpoch),
179
180 #[error("Cannot represent {0:?} as Ruma time type")]
181 SystemTime(SystemTime),
182}
183
184#[cfg(test)]
185mod tests {
186 use std::time::{Duration, UNIX_EPOCH};
187
188 use js_int::uint;
189 use serde::{Deserialize, Serialize};
190 use serde_json::json;
191
192 use super::{MilliSecondsSinceUnixEpoch, SecondsSinceUnixEpoch};
193
194 #[derive(Clone, Debug, Deserialize, Serialize)]
195 struct SystemTimeTest {
196 millis: MilliSecondsSinceUnixEpoch,
197 secs: SecondsSinceUnixEpoch,
198 }
199
200 #[test]
201 fn deserialize() {
202 let json = json!({ "millis": 3000, "secs": 60 });
203
204 let time = serde_json::from_value::<SystemTimeTest>(json).unwrap();
205 assert_eq!(time.millis.to_system_time(), Some(UNIX_EPOCH + Duration::from_millis(3000)));
206 assert_eq!(time.secs.to_system_time(), Some(UNIX_EPOCH + Duration::from_secs(60)));
207 }
208
209 #[test]
210 fn serialize() {
211 let request = SystemTimeTest {
212 millis: MilliSecondsSinceUnixEpoch::from_system_time(UNIX_EPOCH + Duration::new(2, 0))
213 .unwrap(),
214 secs: SecondsSinceUnixEpoch(uint!(0)),
215 };
216
217 assert_eq!(serde_json::to_value(request).unwrap(), json!({ "millis": 2000, "secs": 0 }));
218 }
219
220 #[test]
221 fn debug_s() {
222 let seconds = SecondsSinceUnixEpoch(uint!(0));
223 assert_eq!(format!("{seconds:?}"), "1970-01-01T00:00:00");
224 }
225
226 #[test]
227 fn debug_ms() {
228 let seconds = MilliSecondsSinceUnixEpoch(uint!(0));
229 assert_eq!(format!("{seconds:?}"), "1970-01-01T00:00:00.000");
230 }
231}