1use crate::*;
21
22use std::fmt;
23use std::ops::{Add, AddAssign, Sub, SubAssign};
24use std::str::FromStr;
25
26pub use jiff::SignedDuration;
27pub use std::time::{Duration, UNIX_EPOCH};
28#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
29pub use std::time::{Instant, SystemTime};
30#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
31pub use web_time::{Instant, SystemTime};
32
33#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
35pub struct Timestamp(jiff::Timestamp);
36
37impl FromStr for Timestamp {
38 type Err = Error;
39
40 fn from_str(s: &str) -> Result<Self, Self::Err> {
51 match s.parse() {
52 Ok(t) => Ok(Timestamp(t)),
53 Err(err) => Err(Error::new(
54 ErrorKind::Unexpected,
55 format!("parse '{s}' into timestamp failed"),
56 )
57 .set_source(err)),
58 }
59 }
60}
61
62impl fmt::Display for Timestamp {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 write!(f, "{}", self.0)
65 }
66}
67
68impl Timestamp {
69 pub const MIN: Self = Self(jiff::Timestamp::MIN);
71
72 pub const MAX: Self = Self(jiff::Timestamp::MAX);
74
75 pub fn now() -> Self {
77 Self(jiff::Timestamp::now())
78 }
79
80 pub fn format_http_date(self) -> String {
89 self.0.strftime("%a, %d %b %Y %T GMT").to_string()
90 }
91
92 pub fn new(second: i64, nanosecond: i32) -> Result<Self, Error> {
97 match jiff::Timestamp::new(second, nanosecond) {
98 Ok(t) => Ok(Timestamp(t)),
99 Err(err) => Err(Error::new(
100 ErrorKind::Unexpected,
101 format!(
102 "create timestamp from '{second}' seconds and '{nanosecond}' nanoseconds failed"
103 ),
104 )
105 .set_source(err)),
106 }
107 }
108
109 pub fn from_millisecond(millis: i64) -> Result<Self> {
116 match jiff::Timestamp::from_millisecond(millis) {
117 Ok(t) => Ok(Timestamp(t)),
118 Err(err) => Err(Error::new(
119 ErrorKind::Unexpected,
120 format!("convert '{millis}' milliseconds into timestamp failed"),
121 )
122 .set_source(err)),
123 }
124 }
125
126 pub fn from_second(second: i64) -> Result<Self> {
133 match jiff::Timestamp::from_second(second) {
134 Ok(t) => Ok(Timestamp(t)),
135 Err(err) => Err(Error::new(
136 ErrorKind::Unexpected,
137 format!("convert '{second}' seconds into timestamp failed"),
138 )
139 .set_source(err)),
140 }
141 }
142
143 pub fn parse_rfc2822(s: &str) -> Result<Timestamp> {
150 match jiff::fmt::rfc2822::parse(s) {
151 Ok(zoned) => Ok(Timestamp(zoned.timestamp())),
152 Err(err) => Err(Error::new(
153 ErrorKind::Unexpected,
154 format!("parse '{s}' into rfc2822 failed"),
155 )
156 .set_source(err)),
157 }
158 }
159
160 pub fn into_inner(self) -> jiff::Timestamp {
165 self.0
166 }
167}
168
169impl From<Timestamp> for jiff::Timestamp {
170 fn from(t: Timestamp) -> Self {
171 t.0
172 }
173}
174
175impl From<jiff::Timestamp> for Timestamp {
176 fn from(t: jiff::Timestamp) -> Self {
177 Timestamp(t)
178 }
179}
180
181impl From<Timestamp> for SystemTime {
182 fn from(ts: Timestamp) -> Self {
183 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
184 {
185 SystemTime::from(ts.0)
186 }
187
188 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
189 {
190 use std::time::SystemTime as StdSystemTime;
191
192 let t = StdSystemTime::from(ts.0);
193 <web_time::SystemTime as web_time::web::SystemTimeExt>::from_std(t)
194 }
195 }
196}
197
198impl TryFrom<SystemTime> for Timestamp {
199 type Error = Error;
200
201 fn try_from(t: SystemTime) -> Result<Self> {
202 let t = {
203 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
204 {
205 t
206 }
207
208 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
209 {
210 <web_time::SystemTime as web_time::web::SystemTimeExt>::to_std(t)
211 }
212 };
213
214 jiff::Timestamp::try_from(t).map(Timestamp).map_err(|err| {
215 Error::new(ErrorKind::Unexpected, "input timestamp overflow").set_source(err)
216 })
217 }
218}
219
220impl Add<Duration> for Timestamp {
221 type Output = Timestamp;
222
223 fn add(self, rhs: Duration) -> Timestamp {
224 let ts = self
225 .0
226 .checked_add(rhs)
227 .expect("adding unsigned duration to timestamp overflowed");
228
229 Timestamp(ts)
230 }
231}
232
233impl AddAssign<Duration> for Timestamp {
234 fn add_assign(&mut self, rhs: Duration) {
235 *self = *self + rhs
236 }
237}
238
239impl Sub<Duration> for Timestamp {
240 type Output = Timestamp;
241
242 fn sub(self, rhs: Duration) -> Timestamp {
243 let ts = self
244 .0
245 .checked_sub(rhs)
246 .expect("subtracting unsigned duration from timestamp overflowed");
247
248 Timestamp(ts)
249 }
250}
251
252impl SubAssign<Duration> for Timestamp {
253 fn sub_assign(&mut self, rhs: Duration) {
254 *self = *self - rhs
255 }
256}
257
258#[inline]
260pub fn signed_to_duration(value: &str) -> Result<Duration> {
261 let signed = value.parse::<SignedDuration>().map_err(|err| {
262 Error::new(ErrorKind::ConfigInvalid, "failed to parse duration").set_source(err)
263 })?;
264
265 signed_duration_to_duration(signed)
266}
267
268#[inline]
270pub fn signed_duration_to_duration(value: SignedDuration) -> Result<Duration> {
271 Duration::try_from(value).map_err(|err| {
272 Error::new(
273 ErrorKind::ConfigInvalid,
274 "duration must not be negative or overflow",
275 )
276 .set_source(err)
277 })
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 fn test_time() -> Timestamp {
285 Timestamp("2022-03-01T08:12:34Z".parse().unwrap())
286 }
287
288 #[test]
289 fn test_format_http_date() {
290 let t = test_time();
291 assert_eq!("Tue, 01 Mar 2022 08:12:34 GMT", t.format_http_date())
292 }
293
294 #[test]
295 fn test_parse_rfc3339() {
296 let t = test_time();
297
298 for v in [
299 "2022-03-01T08:12:34Z",
300 "2022-03-01T08:12:34+00:00",
301 "2022-03-01T08:12:34.00+00:00",
302 ] {
303 assert_eq!(t, v.parse().expect("must be valid time"));
304 }
305 }
306
307 #[test]
308 fn test_parse_rfc2822() {
309 let s = "Sat, 29 Oct 1994 19:43:31 +0000";
310 let v = Timestamp::parse_rfc2822(s).unwrap();
311 assert_eq!("Sat, 29 Oct 1994 19:43:31 GMT", v.format_http_date());
312 }
313
314 #[test]
315 fn test_signed_duration_to_duration_rejects_negative_values() {
316 assert!(signed_duration_to_duration(SignedDuration::from_secs(-1)).is_err());
317 }
318}