objectstore_types/
time.rs1use std::borrow::Cow;
11use std::fmt;
12use std::ops::{Add, Sub};
13use std::str::FromStr;
14use std::time::{Duration, SystemTime};
15
16use humantime::TimestampError;
17use serde::{Deserialize, Deserializer, Serialize, Serializer};
18use thiserror::Error;
19
20#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
41pub struct Timestamp(u64);
42
43impl Timestamp {
44 pub const UNIX_EPOCH: Self = Self(0);
46
47 const MAX: u64 = 253_402_300_799;
49
50 pub fn now() -> Self {
55 Self::try_from(SystemTime::now()).expect("system clock outside timestamp range")
56 }
57
58 pub fn from_unix_secs(seconds: u64) -> Result<Self, InvalidTimestamp> {
60 if seconds <= Self::MAX {
61 Ok(Self(seconds))
62 } else {
63 Err(InvalidTimestamp)
64 }
65 }
66
67 pub fn from_unix_micros(micros: i64) -> Result<Self, InvalidTimestamp> {
69 let micros = u64::try_from(micros).map_err(|_| InvalidTimestamp)?;
70 Self::from_unix_secs(micros.div_ceil(1_000_000))
71 }
72
73 pub fn from_rfc3339(value: &str) -> Result<Self, TimestampError> {
77 let time = humantime::parse_rfc3339(value)?;
78 Self::try_from(time).map_err(|_| TimestampError::OutOfRange)
79 }
80
81 pub fn as_secs(self) -> u64 {
83 self.0
84 }
85
86 pub fn as_micros(self) -> u64 {
88 self.0 * 1_000_000
89 }
90
91 pub fn as_rfc3339(self) -> Rfc3339Timestamp {
93 Rfc3339Timestamp(self)
94 }
95
96 pub fn checked_add(self, duration: Duration) -> Option<Self> {
98 let seconds = self.0.checked_add(duration.as_secs())?;
99 let seconds = seconds.checked_add(u64::from(duration.subsec_nanos() != 0))?;
100 Self::from_unix_secs(seconds).ok()
101 }
102
103 pub fn saturating_add(self, duration: Duration) -> Self {
105 self.checked_add(duration).unwrap_or(Self(Self::MAX))
106 }
107
108 pub fn checked_sub(self, duration: Duration) -> Option<Self> {
110 if duration > Duration::from_secs(self.0) {
113 return None;
114 }
115 Some(Self(self.0 - duration.as_secs()))
116 }
117
118 pub fn checked_duration_since(self, earlier: Self) -> Option<Duration> {
120 self.0.checked_sub(earlier.0).map(Duration::from_secs)
121 }
122}
123
124impl TryFrom<SystemTime> for Timestamp {
125 type Error = InvalidTimestamp;
126
127 fn try_from(time: SystemTime) -> Result<Self, Self::Error> {
128 let duration = time
129 .duration_since(SystemTime::UNIX_EPOCH)
130 .map_err(|_| InvalidTimestamp)?;
131 let seconds = duration
132 .as_secs()
133 .checked_add(u64::from(duration.subsec_nanos() != 0))
134 .ok_or(InvalidTimestamp)?;
135 Self::from_unix_secs(seconds)
136 }
137}
138
139impl From<Timestamp> for SystemTime {
140 fn from(time: Timestamp) -> Self {
141 Self::UNIX_EPOCH + Duration::from_secs(time.0)
142 }
143}
144
145impl Add<Duration> for Timestamp {
146 type Output = Self;
147
148 fn add(self, duration: Duration) -> Self {
150 self.checked_add(duration)
151 .expect("timestamp addition out of range")
152 }
153}
154
155impl Sub<Duration> for Timestamp {
156 type Output = Self;
157
158 fn sub(self, duration: Duration) -> Self {
160 self.checked_sub(duration)
161 .expect("timestamp subtraction out of range")
162 }
163}
164
165impl Serialize for Timestamp {
166 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
167 SystemTime::from(*self).serialize(serializer)
168 }
169}
170
171impl<'de> Deserialize<'de> for Timestamp {
172 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
173 Self::try_from(SystemTime::deserialize(deserializer)?).map_err(serde::de::Error::custom)
174 }
175}
176
177#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
179#[error("timestamp is outside the Unix epoch through year 9999")]
180pub struct InvalidTimestamp;
181
182#[derive(Clone, Copy, Debug, Eq, PartialEq)]
186pub struct Rfc3339Timestamp(Timestamp);
187
188impl Rfc3339Timestamp {
189 pub fn into_inner(self) -> Timestamp {
191 self.0
192 }
193}
194
195impl fmt::Display for Rfc3339Timestamp {
196 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197 humantime::format_rfc3339_seconds(self.0.into()).fmt(f)
198 }
199}
200
201impl FromStr for Rfc3339Timestamp {
202 type Err = TimestampError;
203
204 fn from_str(value: &str) -> Result<Self, Self::Err> {
205 Timestamp::from_rfc3339(value).map(Self)
206 }
207}
208
209impl Serialize for Rfc3339Timestamp {
210 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
211 serializer.collect_str(self)
212 }
213}
214
215impl<'de> Deserialize<'de> for Rfc3339Timestamp {
216 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
217 #[derive(Deserialize)]
219 #[serde(transparent)]
220 struct BorrowedStr<'a>(#[serde(borrow)] Cow<'a, str>);
221
222 let BorrowedStr(value) = BorrowedStr::deserialize(deserializer)?;
223 value.parse().map_err(serde::de::Error::custom)
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230
231 #[test]
232 fn rounding_and_arithmetic() {
233 let epoch = SystemTime::UNIX_EPOCH;
234 for (nanos, seconds) in [(0, 0), (1, 1), (999_999_999, 1), (1_000_000_000, 1)] {
235 let time = Timestamp::try_from(epoch + Duration::from_nanos(nanos)).unwrap();
236 assert_eq!(time.as_secs(), seconds);
237 assert_eq!(time.as_micros(), seconds * 1_000_000);
238 assert_eq!(Timestamp::try_from(SystemTime::from(time)).unwrap(), time);
239 }
240 let time = Timestamp::from_unix_secs(10).unwrap();
241 assert_eq!((time + Duration::from_millis(1500)).as_secs(), 12);
242 assert_eq!((time - Duration::from_millis(1500)).as_secs(), 9);
243 assert_eq!(time.checked_duration_since(time), Some(Duration::ZERO));
244 assert!(Timestamp::try_from(epoch - Duration::from_nanos(1)).is_err());
245 assert!(Timestamp::from_unix_micros(-1).is_err());
246 assert_eq!(Timestamp::from_unix_micros(1).unwrap().as_secs(), 1);
247 assert!(Timestamp::from_unix_micros(i64::MAX).is_err());
248 assert!(
249 Timestamp::UNIX_EPOCH
250 .checked_sub(Duration::from_nanos(1))
251 .is_none()
252 );
253 let max = Timestamp::from_unix_secs(Timestamp::MAX).unwrap();
254 assert!(max.checked_add(Duration::from_nanos(1)).is_none());
255 assert!(max.checked_add(Duration::MAX).is_none());
256 assert_eq!(max.saturating_add(Duration::from_nanos(1)), max);
257 assert_eq!(
258 time.saturating_add(Duration::from_millis(1500)).as_secs(),
259 12
260 );
261 assert_eq!(max.as_rfc3339().to_string(), "9999-12-31T23:59:59Z");
262 }
263
264 #[test]
265 fn serialization_formats() {
266 let legacy = r#"{"secs_since_epoch":1700000000,"nanos_since_epoch":1}"#;
267 let time: Timestamp = serde_json::from_str(legacy).unwrap();
268 assert_eq!(time.as_secs(), 1_700_000_001);
269 let json = serde_json::to_string(&time).unwrap();
270 assert_eq!(
271 json,
272 r#"{"secs_since_epoch":1700000001,"nanos_since_epoch":0}"#
273 );
274 assert_eq!(serde_json::from_str::<Timestamp>(&json).unwrap(), time);
275 assert_eq!(
276 serde_json::from_str::<SystemTime>(&json).unwrap(),
277 time.into()
278 );
279 let rfc: Rfc3339Timestamp =
280 serde_json::from_str(r#""2023-11-14T22:13:20.000001Z""#).unwrap();
281 assert_eq!(rfc.into_inner(), time);
282 assert_eq!(
283 serde_json::to_string(&rfc).unwrap(),
284 r#""2023-11-14T22:13:21Z""#
285 );
286 assert_eq!(rfc.to_string(), "2023-11-14T22:13:21Z");
287 assert!(
288 "9999-12-31T23:59:59.1Z"
289 .parse::<Rfc3339Timestamp>()
290 .is_err()
291 );
292 }
293}