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