1use std::fmt;
4use std::time::{Duration, Instant, SystemTime};
5
6use chrono::{DateTime, TimeZone, Utc};
7use serde::{Deserialize, Serialize};
8
9pub fn instant_to_system_time(instant: Instant) -> SystemTime {
11 SystemTime::now() - instant.elapsed()
12}
13
14pub fn duration_to_millis(duration: Duration) -> f64 {
28 (duration.as_secs_f64() * 1_000_000_000f64).round() / 1_000_000f64
29}
30
31pub fn chrono_to_positive_millis(duration: chrono::Duration) -> f64 {
56 duration_to_millis(duration.to_std().unwrap_or_default())
57}
58
59#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
61pub struct UnixTimestamp(u64);
62
63impl UnixTimestamp {
64 pub const fn from_secs(secs: u64) -> Self {
66 Self(secs)
67 }
68
69 pub fn from_system(time: SystemTime) -> Self {
71 let duration = time
72 .duration_since(SystemTime::UNIX_EPOCH)
73 .unwrap_or_default()
74 .as_secs();
75
76 Self(duration)
77 }
78
79 pub fn from_datetime(date_time: DateTime<impl TimeZone>) -> Option<Self> {
84 let timestamp = date_time.timestamp();
85 if timestamp >= 0 {
86 Some(UnixTimestamp::from_secs(timestamp as u64))
87 } else {
88 None
89 }
90 }
91
92 #[inline]
97 pub fn from_instant(instant: Instant) -> Self {
98 Self::from_system(instant_to_system_time(instant))
99 }
100
101 #[inline]
103 pub fn now() -> Self {
104 Self::from_system(SystemTime::now())
105 }
106
107 pub fn as_secs(self) -> u64 {
109 self.0
110 }
111
112 pub fn as_nanos(self) -> u64 {
114 self.as_secs() * 1_000_000_000
115 }
116
117 pub fn as_datetime(self) -> Option<DateTime<Utc>> {
119 DateTime::from_timestamp(self.0 as i64, 0)
120 }
121}
122
123impl fmt::Debug for UnixTimestamp {
124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125 write!(f, "UnixTimestamp({})", self.as_secs())
126 }
127}
128
129impl fmt::Display for UnixTimestamp {
130 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131 self.as_secs().fmt(f)
132 }
133}
134
135impl std::ops::Add<Duration> for UnixTimestamp {
137 type Output = Self;
138
139 fn add(self, rhs: Duration) -> Self::Output {
140 Self(self.0.saturating_add(rhs.as_secs()))
141 }
142}
143
144impl std::ops::Sub for UnixTimestamp {
145 type Output = Duration;
146
147 fn sub(self, rhs: Self) -> Self::Output {
148 Duration::from_secs(self.0 - rhs.0)
149 }
150}
151
152#[derive(Debug)]
153pub struct ParseUnixTimestampError(());
155
156impl std::str::FromStr for UnixTimestamp {
157 type Err = ParseUnixTimestampError;
158
159 fn from_str(s: &str) -> Result<Self, Self::Err> {
160 if let Ok(datetime) = s.parse::<DateTime<Utc>>() {
161 let timestamp = datetime.timestamp();
162 if timestamp >= 0 {
163 return Ok(UnixTimestamp(timestamp as u64));
164 }
165 }
166 let ts = s.parse().or(Err(ParseUnixTimestampError(())))?;
167 Ok(Self(ts))
168 }
169}
170
171impl Serialize for UnixTimestamp {
172 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
173 where
174 S: serde::Serializer,
175 {
176 serializer.serialize_u64(self.as_secs())
177 }
178}
179
180struct UnixTimestampVisitor;
181
182impl serde::de::Visitor<'_> for UnixTimestampVisitor {
183 type Value = UnixTimestamp;
184
185 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
186 formatter.write_str("a non-negative timestamp or datetime string")
187 }
188
189 fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
190 where
191 E: serde::de::Error,
192 {
193 Ok(UnixTimestamp::from_secs(v))
194 }
195
196 fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
197 where
198 E: serde::de::Error,
199 {
200 if v < 0.0 || v > u64::MAX as f64 {
201 return Err(E::custom("timestamp out-of-range"));
202 }
203
204 Ok(UnixTimestamp::from_secs(v.trunc() as u64))
205 }
206
207 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
208 where
209 E: serde::de::Error,
210 {
211 let datetime = v.parse::<DateTime<Utc>>().map_err(E::custom)?;
212 let timestamp = datetime.timestamp();
213
214 if timestamp >= 0 {
215 Ok(UnixTimestamp(timestamp as u64))
216 } else {
217 Err(E::custom("timestamp out-of-range"))
218 }
219 }
220}
221
222impl<'de> Deserialize<'de> for UnixTimestamp {
223 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
224 where
225 D: serde::Deserializer<'de>,
226 {
227 deserializer.deserialize_any(UnixTimestampVisitor)
228 }
229}
230
231#[cfg(test)]
232mod tests {
233 use serde_test::{assert_de_tokens, assert_de_tokens_error, assert_tokens, Token};
234
235 use super::*;
236
237 #[test]
238 fn test_parse_timestamp_int() {
239 assert_tokens(&UnixTimestamp::from_secs(123), &[Token::U64(123)]);
240 }
241
242 #[test]
243 fn test_parse_timestamp_neg_int() {
244 assert_de_tokens_error::<UnixTimestamp>(
245 &[Token::I64(-1)],
246 "invalid type: integer `-1`, expected a non-negative timestamp or datetime string",
247 );
248 }
249
250 #[test]
251 fn test_parse_timestamp_float() {
252 assert_de_tokens(&UnixTimestamp::from_secs(123), &[Token::F64(123.4)]);
253 }
254
255 #[test]
256 fn test_parse_timestamp_large_float() {
257 assert_de_tokens_error::<UnixTimestamp>(
258 &[Token::F64(2.0 * (u64::MAX as f64))],
259 "timestamp out-of-range",
260 );
261 }
262
263 #[test]
264 fn test_parse_timestamp_neg_float() {
265 assert_de_tokens_error::<UnixTimestamp>(&[Token::F64(-1.0)], "timestamp out-of-range");
266 }
267
268 #[test]
269 fn test_parse_timestamp_str() {
270 assert_de_tokens(
271 &UnixTimestamp::from_secs(123),
272 &[Token::Str("1970-01-01T00:02:03Z")],
273 );
274 }
275
276 #[test]
277 fn test_parse_timestamp_other() {
278 assert_de_tokens_error::<UnixTimestamp>(
279 &[Token::Bool(true)],
280 "invalid type: boolean `true`, expected a non-negative timestamp or datetime string",
281 );
282 }
283
284 #[test]
285 fn test_parse_datetime_bogus() {
286 assert_de_tokens_error::<UnixTimestamp>(
287 &[Token::Str("adf3rt546")],
288 "input contains invalid characters",
289 );
290 }
291}