1use crate::*;
21use jiff::SignedDuration;
22use std::fmt;
23use std::ops::{Add, AddAssign, Sub, SubAssign};
24use std::str::FromStr;
25use std::time::{Duration, SystemTime};
26
27#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
29pub struct Timestamp(jiff::Timestamp);
30
31impl FromStr for Timestamp {
32 type Err = Error;
33
34 fn from_str(s: &str) -> Result<Self, Self::Err> {
45 match s.parse() {
46 Ok(t) => Ok(Timestamp(t)),
47 Err(err) => Err(Error::new(
48 ErrorKind::Unexpected,
49 format!("parse '{s}' into timestamp failed"),
50 )
51 .set_source(err)),
52 }
53 }
54}
55
56impl fmt::Display for Timestamp {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 write!(f, "{}", self.0)
59 }
60}
61
62impl Timestamp {
63 pub const MIN: Self = Self(jiff::Timestamp::MIN);
65
66 pub const MAX: Self = Self(jiff::Timestamp::MAX);
68
69 pub fn now() -> Self {
71 Self(jiff::Timestamp::now())
72 }
73
74 pub fn format_http_date(self) -> String {
83 self.0.strftime("%a, %d %b %Y %T GMT").to_string()
84 }
85
86 pub fn new(second: i64, nanosecond: i32) -> Result<Self, Error> {
91 match jiff::Timestamp::new(second, nanosecond) {
92 Ok(t) => Ok(Timestamp(t)),
93 Err(err) => Err(Error::new(
94 ErrorKind::Unexpected,
95 format!(
96 "create timestamp from '{second}' seconds and '{nanosecond}' nanoseconds failed"
97 ),
98 )
99 .set_source(err)),
100 }
101 }
102
103 pub fn from_millisecond(millis: i64) -> Result<Self> {
110 match jiff::Timestamp::from_millisecond(millis) {
111 Ok(t) => Ok(Timestamp(t)),
112 Err(err) => Err(Error::new(
113 ErrorKind::Unexpected,
114 format!("convert '{millis}' milliseconds into timestamp failed"),
115 )
116 .set_source(err)),
117 }
118 }
119
120 pub fn from_second(second: i64) -> Result<Self> {
127 match jiff::Timestamp::from_second(second) {
128 Ok(t) => Ok(Timestamp(t)),
129 Err(err) => Err(Error::new(
130 ErrorKind::Unexpected,
131 format!("convert '{second}' seconds into timestamp failed"),
132 )
133 .set_source(err)),
134 }
135 }
136
137 pub fn parse_rfc2822(s: &str) -> Result<Timestamp> {
144 match jiff::fmt::rfc2822::parse(s) {
145 Ok(zoned) => Ok(Timestamp(zoned.timestamp())),
146 Err(err) => Err(Error::new(
147 ErrorKind::Unexpected,
148 format!("parse '{s}' into rfc2822 failed"),
149 )
150 .set_source(err)),
151 }
152 }
153
154 pub fn into_inner(self) -> jiff::Timestamp {
159 self.0
160 }
161}
162
163impl From<Timestamp> for jiff::Timestamp {
164 fn from(t: Timestamp) -> Self {
165 t.0
166 }
167}
168
169impl From<Timestamp> for SystemTime {
170 fn from(t: Timestamp) -> Self {
171 t.0.into()
172 }
173}
174
175impl From<jiff::Timestamp> for Timestamp {
176 fn from(t: jiff::Timestamp) -> Self {
177 Timestamp(t)
178 }
179}
180
181impl TryFrom<SystemTime> for Timestamp {
182 type Error = Error;
183
184 fn try_from(t: SystemTime) -> Result<Self> {
185 jiff::Timestamp::try_from(t).map(Timestamp).map_err(|err| {
186 Error::new(ErrorKind::Unexpected, "input timestamp overflow").set_source(err)
187 })
188 }
189}
190
191impl Add<Duration> for Timestamp {
192 type Output = Timestamp;
193
194 fn add(self, rhs: Duration) -> Timestamp {
195 let ts = self
196 .0
197 .checked_add(rhs)
198 .expect("adding unsigned duration to timestamp overflowed");
199
200 Timestamp(ts)
201 }
202}
203
204impl AddAssign<Duration> for Timestamp {
205 fn add_assign(&mut self, rhs: Duration) {
206 *self = *self + rhs
207 }
208}
209
210impl Sub<Duration> for Timestamp {
211 type Output = Timestamp;
212
213 fn sub(self, rhs: Duration) -> Timestamp {
214 let ts = self
215 .0
216 .checked_sub(rhs)
217 .expect("subtracting unsigned duration from timestamp overflowed");
218
219 Timestamp(ts)
220 }
221}
222
223impl SubAssign<Duration> for Timestamp {
224 fn sub_assign(&mut self, rhs: Duration) {
225 *self = *self - rhs
226 }
227}
228
229#[inline]
232pub fn signed_to_duration(value: &str) -> Result<Duration> {
233 let signed = value.parse::<SignedDuration>().map_err(|err| {
234 Error::new(ErrorKind::ConfigInvalid, "failed to parse duration").set_source(err)
235 })?;
236
237 Duration::try_from(signed).map_err(|err| {
238 Error::new(
239 ErrorKind::ConfigInvalid,
240 "duration must not be negative or overflow",
241 )
242 .set_source(err)
243 })
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249
250 fn test_time() -> Timestamp {
251 Timestamp("2022-03-01T08:12:34Z".parse().unwrap())
252 }
253
254 #[test]
255 fn test_format_http_date() {
256 let t = test_time();
257 assert_eq!("Tue, 01 Mar 2022 08:12:34 GMT", t.format_http_date())
258 }
259
260 #[test]
261 fn test_parse_rfc3339() {
262 let t = test_time();
263
264 for v in [
265 "2022-03-01T08:12:34Z",
266 "2022-03-01T08:12:34+00:00",
267 "2022-03-01T08:12:34.00+00:00",
268 ] {
269 assert_eq!(t, v.parse().expect("must be valid time"));
270 }
271 }
272
273 #[test]
274 fn test_parse_rfc2822() {
275 let s = "Sat, 29 Oct 1994 19:43:31 +0000";
276 let v = Timestamp::parse_rfc2822(s).unwrap();
277 assert_eq!("Sat, 29 Oct 1994 19:43:31 GMT", v.format_http_date());
278 }
279}