opendal_core/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 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/// An instant in time represented as the number of nanoseconds since the Unix epoch.
33#[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    /// Parse a timestamp by the default [`DateTimeParser`].
40    ///
41    /// All of them are valid time:
42    ///
43    /// - `2022-03-13T07:20:04Z`
44    /// - `2022-03-01T08:12:34+00:00`
45    /// - `2022-03-01T08:12:34.00+00:00`
46    /// - `2022-07-08T02:14:07+02:00[Europe/Paris]`
47    ///
48    /// [`DateTimeParser`]: jiff::fmt::temporal::DateTimeParser
49    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    /// The minimum timestamp value.
69    pub const MIN: Self = Self(jiff::Timestamp::MIN);
70
71    /// The maximum timestamp value.
72    pub const MAX: Self = Self(jiff::Timestamp::MAX);
73
74    /// Create the timestamp of now.
75    pub fn now() -> Self {
76        Self(jiff::Timestamp::now())
77    }
78
79    /// Format the timestamp into http date: `Sun, 06 Nov 1994 08:49:37 GMT`
80    ///
81    /// ## Note
82    ///
83    /// HTTP date is slightly different from RFC2822.
84    ///
85    /// - Timezone is fixed to GMT.
86    /// - Day must be 2 digit.
87    pub fn format_http_date(self) -> String {
88        self.0.strftime("%a, %d %b %Y %T GMT").to_string()
89    }
90
91    /// Creates a new instant in time from the number of seconds elapsed since the Unix epoch.
92    ///
93    /// When second is negative, it corresponds to an instant in time before the Unix epoch.
94    /// A smaller number corresponds to an instant in time further into the past.
95    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    /// Creates a new instant in time from the number of milliseconds elapsed
109    /// since the Unix epoch.
110    ///
111    /// When `millisecond` is negative, it corresponds to an instant in time
112    /// before the Unix epoch. A smaller number corresponds to an instant in
113    /// time further into the past.
114    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    /// Creates a new instant in time from the number of seconds elapsed since
126    /// the Unix epoch.
127    ///
128    /// When `second` is negative, it corresponds to an instant in time before
129    /// the Unix epoch. A smaller number corresponds to an instant in time
130    /// further into the past.
131    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    /// Parse a timestamp from RFC2822.
143    ///
144    /// All of them are valid time:
145    ///
146    /// - `Sat, 13 Jul 2024 15:09:59 -0400`
147    /// - `Mon, 15 Aug 2022 16:50:12 GMT`
148    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    /// Convert to inner `jiff::Timestamp` for compatibility.
160    ///
161    /// This method is provided for accessing the underlying `jiff::Timestamp`
162    /// when needed for interoperability with jiff-specific APIs.
163    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/// Convert an unsigned [`Duration`] into a jiff [`SignedDuration`].
258/// Parse a duration encoded either as ISO-8601 (e.g. `PT5M`) or friendly (e.g. `5m`).
259#[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}