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