1use crate::Error;
21use std::fmt;
22use std::ops::{Add, AddAssign, Sub, SubAssign};
23use std::str::FromStr;
24use std::time::{Duration, SystemTime};
25
26#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
28pub struct Timestamp(jiff::Timestamp);
29
30impl FromStr for Timestamp {
31 type Err = Error;
32
33 fn from_str(s: &str) -> Result<Self, Self::Err> {
44 match s.parse() {
45 Ok(t) => Ok(Timestamp(t)),
46 Err(err) => Err(
47 Error::unexpected(format!("parse '{s}' into timestamp failed")).with_source(err),
48 ),
49 }
50 }
51}
52
53impl fmt::Display for Timestamp {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 write!(f, "{}", self.0)
56 }
57}
58
59impl Timestamp {
60 pub fn now() -> Self {
62 Self(jiff::Timestamp::now())
63 }
64
65 pub fn format_date(self) -> String {
67 self.0.strftime("%Y%m%d").to_string()
68 }
69
70 pub fn format_iso8601(self) -> String {
72 self.0.strftime("%Y%m%dT%H%M%SZ").to_string()
73 }
74
75 pub fn format_http_date(self) -> String {
84 self.0.strftime("%a, %d %b %Y %T GMT").to_string()
85 }
86
87 pub fn format_rfc3339_zulu(self) -> String {
89 self.0.strftime("%FT%TZ").to_string()
90 }
91
92 pub fn as_second(self) -> i64 {
97 self.0.as_second()
98 }
99
100 pub fn subsec_nanosecond(self) -> i32 {
106 self.0.subsec_nanosecond()
107 }
108
109 pub fn as_system_time(self) -> SystemTime {
111 SystemTime::from(self.0)
112 }
113
114 pub fn from_millisecond(millis: i64) -> crate::Result<Self> {
121 match jiff::Timestamp::from_millisecond(millis) {
122 Ok(t) => Ok(Timestamp(t)),
123 Err(err) => Err(Error::unexpected(format!(
124 "convert '{millis}' milliseconds into timestamp failed"
125 ))
126 .with_source(err)),
127 }
128 }
129
130 pub fn from_second(second: i64) -> crate::Result<Self> {
137 match jiff::Timestamp::from_second(second) {
138 Ok(t) => Ok(Timestamp(t)),
139 Err(err) => Err(Error::unexpected(format!(
140 "convert '{second}' seconds into timestamp failed"
141 ))
142 .with_source(err)),
143 }
144 }
145
146 pub fn parse_rfc2822(s: &str) -> crate::Result<Timestamp> {
153 match jiff::fmt::rfc2822::parse(s) {
154 Ok(zoned) => Ok(Timestamp(zoned.timestamp())),
155 Err(err) => {
156 Err(Error::unexpected(format!("parse '{s}' into rfc2822 failed")).with_source(err))
157 }
158 }
159 }
160
161 pub fn parse_datetime_utc(s: &str) -> crate::Result<Timestamp> {
163 let dt = s.parse::<jiff::civil::DateTime>().map_err(|err| {
164 Error::unexpected(format!("parse '{s}' into datetime failed")).with_source(err)
165 })?;
166
167 let ts = jiff::tz::TimeZone::UTC.to_timestamp(dt).map_err(|err| {
168 Error::unexpected(format!("convert '{s}' into timestamp failed")).with_source(err)
169 })?;
170
171 Ok(Timestamp(ts))
172 }
173}
174
175impl Add<Duration> for Timestamp {
176 type Output = Timestamp;
177
178 fn add(self, rhs: Duration) -> Timestamp {
179 let ts = self
180 .0
181 .checked_add(rhs)
182 .expect("adding unsigned duration to timestamp overflowed");
183
184 Timestamp(ts)
185 }
186}
187
188impl AddAssign<Duration> for Timestamp {
189 fn add_assign(&mut self, rhs: Duration) {
190 *self = *self + rhs
191 }
192}
193
194impl Sub<Duration> for Timestamp {
195 type Output = Timestamp;
196
197 fn sub(self, rhs: Duration) -> Timestamp {
198 let ts = self
199 .0
200 .checked_sub(rhs)
201 .expect("subtracting unsigned duration from timestamp overflowed");
202
203 Timestamp(ts)
204 }
205}
206
207impl SubAssign<Duration> for Timestamp {
208 fn sub_assign(&mut self, rhs: Duration) {
209 *self = *self - rhs
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 fn test_time() -> Timestamp {
218 Timestamp("2022-03-01T08:12:34Z".parse().unwrap())
219 }
220
221 #[test]
222 fn test_format_date() {
223 let t = test_time();
224 assert_eq!("20220301", t.format_date())
225 }
226
227 #[test]
228 fn test_format_ios8601() {
229 let t = test_time();
230 assert_eq!("20220301T081234Z", t.format_iso8601())
231 }
232
233 #[test]
234 fn test_format_http_date() {
235 let t = test_time();
236 assert_eq!("Tue, 01 Mar 2022 08:12:34 GMT", t.format_http_date())
237 }
238
239 #[test]
240 fn test_format_rfc3339() {
241 let t = test_time();
242 assert_eq!("2022-03-01T08:12:34Z", t.format_rfc3339_zulu())
243 }
244
245 #[test]
246 fn test_parse_rfc3339() {
247 let t = test_time();
248
249 for v in [
250 "2022-03-01T08:12:34Z",
251 "2022-03-01T08:12:34+00:00",
252 "2022-03-01T08:12:34.00+00:00",
253 ] {
254 assert_eq!(t, v.parse().expect("must be valid time"));
255 }
256 }
257}