opendal/raw/
time.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! Time related utils.
19
20use crate::*;
21use std::fmt;
22use std::ops::{Add, AddAssign, Sub, SubAssign};
23use std::str::FromStr;
24use std::time::{Duration, SystemTime};
25
26/// An instant in time represented as the number of nanoseconds since the Unix epoch.
27#[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    /// Parse a timestamp by the default [`DateTimeParser`].
34    ///
35    /// All of them are valid time:
36    ///
37    /// - `2022-03-13T07:20:04Z`
38    /// - `2022-03-01T08:12:34+00:00`
39    /// - `2022-03-01T08:12:34.00+00:00`
40    /// - `2022-07-08T02:14:07+02:00[Europe/Paris]`
41    ///
42    /// [`DateTimeParser`]: jiff::fmt::temporal::DateTimeParser
43    fn from_str(s: &str) -> Result<Self, Self::Err> {
44        match s.parse() {
45            Ok(t) => Ok(Timestamp(t)),
46            Err(err) => Err(Error::new(
47                ErrorKind::Unexpected,
48                format!("parse '{s}' into timestamp failed"),
49            )
50            .set_source(err)),
51        }
52    }
53}
54
55impl fmt::Display for Timestamp {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        write!(f, "{}", self.0)
58    }
59}
60
61impl Timestamp {
62    /// The minimum timestamp value.
63    pub const MIN: Self = Self(jiff::Timestamp::MIN);
64
65    /// The maximum timestamp value.
66    pub const MAX: Self = Self(jiff::Timestamp::MAX);
67
68    /// Create the timestamp of now.
69    pub fn now() -> Self {
70        Self(jiff::Timestamp::now())
71    }
72
73    /// Format the timestamp into http date: `Sun, 06 Nov 1994 08:49:37 GMT`
74    ///
75    /// ## Note
76    ///
77    /// HTTP date is slightly different from RFC2822.
78    ///
79    /// - Timezone is fixed to GMT.
80    /// - Day must be 2 digit.
81    pub fn format_http_date(self) -> String {
82        self.0.strftime("%a, %d %b %Y %T GMT").to_string()
83    }
84
85    /// Creates a new instant in time from the number of seconds elapsed since the Unix epoch.
86    ///
87    /// When second is negative, it corresponds to an instant in time before the Unix epoch.
88    /// A smaller number corresponds to an instant in time further into the past.
89    pub fn new(second: i64, nanosecond: i32) -> Result<Self, Error> {
90        match jiff::Timestamp::new(second, nanosecond) {
91            Ok(t) => Ok(Timestamp(t)),
92            Err(err) => Err(Error::new(
93                ErrorKind::Unexpected,
94                format!(
95                    "create timestamp from '{second}' seconds and '{nanosecond}' nanoseconds failed"
96                ),
97            )
98            .set_source(err)),
99        }
100    }
101
102    /// Creates a new instant in time from the number of milliseconds elapsed
103    /// since the Unix epoch.
104    ///
105    /// When `millisecond` is negative, it corresponds to an instant in time
106    /// before the Unix epoch. A smaller number corresponds to an instant in
107    /// time further into the past.
108    pub fn from_millisecond(millis: i64) -> Result<Self> {
109        match jiff::Timestamp::from_millisecond(millis) {
110            Ok(t) => Ok(Timestamp(t)),
111            Err(err) => Err(Error::new(
112                ErrorKind::Unexpected,
113                format!("convert '{millis}' milliseconds into timestamp failed"),
114            )
115            .set_source(err)),
116        }
117    }
118
119    /// Creates a new instant in time from the number of seconds elapsed since
120    /// the Unix epoch.
121    ///
122    /// When `second` is negative, it corresponds to an instant in time before
123    /// the Unix epoch. A smaller number corresponds to an instant in time
124    /// further into the past.
125    pub fn from_second(second: i64) -> Result<Self> {
126        match jiff::Timestamp::from_second(second) {
127            Ok(t) => Ok(Timestamp(t)),
128            Err(err) => Err(Error::new(
129                ErrorKind::Unexpected,
130                format!("convert '{second}' seconds into timestamp failed"),
131            )
132            .set_source(err)),
133        }
134    }
135
136    /// Parse a timestamp from RFC2822.
137    ///
138    /// All of them are valid time:
139    ///
140    /// - `Sat, 13 Jul 2024 15:09:59 -0400`
141    /// - `Mon, 15 Aug 2022 16:50:12 GMT`
142    pub fn parse_rfc2822(s: &str) -> Result<Timestamp> {
143        match jiff::fmt::rfc2822::parse(s) {
144            Ok(zoned) => Ok(Timestamp(zoned.timestamp())),
145            Err(err) => Err(Error::new(
146                ErrorKind::Unexpected,
147                format!("parse '{s}' into rfc2822 failed"),
148            )
149            .set_source(err)),
150        }
151    }
152
153    /// Convert to inner `jiff::Timestamp` for compatibility.
154    ///
155    /// This method is provided for accessing the underlying `jiff::Timestamp`
156    /// when needed for interoperability with jiff-specific APIs.
157    pub fn into_inner(self) -> jiff::Timestamp {
158        self.0
159    }
160}
161
162impl From<Timestamp> for jiff::Timestamp {
163    fn from(t: Timestamp) -> Self {
164        t.0
165    }
166}
167
168impl From<Timestamp> for SystemTime {
169    fn from(t: Timestamp) -> Self {
170        t.0.into()
171    }
172}
173
174impl From<jiff::Timestamp> for Timestamp {
175    fn from(t: jiff::Timestamp) -> Self {
176        Timestamp(t)
177    }
178}
179
180impl TryFrom<SystemTime> for Timestamp {
181    type Error = Error;
182
183    fn try_from(t: SystemTime) -> Result<Self> {
184        jiff::Timestamp::try_from(t).map(Timestamp).map_err(|err| {
185            Error::new(ErrorKind::Unexpected, "input timestamp overflow").set_source(err)
186        })
187    }
188}
189
190impl Add<Duration> for Timestamp {
191    type Output = Timestamp;
192
193    fn add(self, rhs: Duration) -> Timestamp {
194        let ts = self
195            .0
196            .checked_add(rhs)
197            .expect("adding unsigned duration to timestamp overflowed");
198
199        Timestamp(ts)
200    }
201}
202
203impl AddAssign<Duration> for Timestamp {
204    fn add_assign(&mut self, rhs: Duration) {
205        *self = *self + rhs
206    }
207}
208
209impl Sub<Duration> for Timestamp {
210    type Output = Timestamp;
211
212    fn sub(self, rhs: Duration) -> Timestamp {
213        let ts = self
214            .0
215            .checked_sub(rhs)
216            .expect("subtracting unsigned duration from timestamp overflowed");
217
218        Timestamp(ts)
219    }
220}
221
222impl SubAssign<Duration> for Timestamp {
223    fn sub_assign(&mut self, rhs: Duration) {
224        *self = *self - rhs
225    }
226}
227
228#[cfg(test)]
229mod tests {
230    use super::*;
231
232    fn test_time() -> Timestamp {
233        Timestamp("2022-03-01T08:12:34Z".parse().unwrap())
234    }
235
236    #[test]
237    fn test_format_http_date() {
238        let t = test_time();
239        assert_eq!("Tue, 01 Mar 2022 08:12:34 GMT", t.format_http_date())
240    }
241
242    #[test]
243    fn test_parse_rfc3339() {
244        let t = test_time();
245
246        for v in [
247            "2022-03-01T08:12:34Z",
248            "2022-03-01T08:12:34+00:00",
249            "2022-03-01T08:12:34.00+00:00",
250        ] {
251            assert_eq!(t, v.parse().expect("must be valid time"));
252        }
253    }
254
255    #[test]
256    fn test_parse_rfc2822() {
257        let s = "Sat, 29 Oct 1994 19:43:31 +0000";
258        let v = Timestamp::parse_rfc2822(s).unwrap();
259        assert_eq!("Sat, 29 Oct 1994 19:43:31 GMT", v.format_http_date());
260    }
261}