Skip to main content

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::*;
21
22use std::fmt;
23use std::ops::{Add, AddAssign, Sub, SubAssign};
24use std::str::FromStr;
25
26pub use jiff::SignedDuration;
27pub use std::time::{Duration, UNIX_EPOCH};
28#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
29pub use std::time::{Instant, SystemTime};
30#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
31pub use web_time::{Instant, SystemTime};
32
33/// An instant in time represented as the number of nanoseconds since the Unix epoch.
34#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
35pub struct Timestamp(jiff::Timestamp);
36
37impl FromStr for Timestamp {
38    type Err = Error;
39
40    /// Parse a timestamp by the default [`DateTimeParser`].
41    ///
42    /// All of them are valid time:
43    ///
44    /// - `2022-03-13T07:20:04Z`
45    /// - `2022-03-01T08:12:34+00:00`
46    /// - `2022-03-01T08:12:34.00+00:00`
47    /// - `2022-07-08T02:14:07+02:00[Europe/Paris]`
48    ///
49    /// [`DateTimeParser`]: jiff::fmt::temporal::DateTimeParser
50    fn from_str(s: &str) -> Result<Self, Self::Err> {
51        match s.parse() {
52            Ok(t) => Ok(Timestamp(t)),
53            Err(err) => Err(Error::new(
54                ErrorKind::Unexpected,
55                format!("parse '{s}' into timestamp failed"),
56            )
57            .set_source(err)),
58        }
59    }
60}
61
62impl fmt::Display for Timestamp {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        write!(f, "{}", self.0)
65    }
66}
67
68impl Timestamp {
69    /// The minimum timestamp value.
70    pub const MIN: Self = Self(jiff::Timestamp::MIN);
71
72    /// The maximum timestamp value.
73    pub const MAX: Self = Self(jiff::Timestamp::MAX);
74
75    /// Create the timestamp of now.
76    pub fn now() -> Self {
77        Self(jiff::Timestamp::now())
78    }
79
80    /// Format the timestamp into http date: `Sun, 06 Nov 1994 08:49:37 GMT`
81    ///
82    /// ## Note
83    ///
84    /// HTTP date is slightly different from RFC2822.
85    ///
86    /// - Timezone is fixed to GMT.
87    /// - Day must be 2 digit.
88    pub fn format_http_date(self) -> String {
89        self.0.strftime("%a, %d %b %Y %T GMT").to_string()
90    }
91
92    /// Creates a new instant in time from the number of seconds elapsed since the Unix epoch.
93    ///
94    /// When second is negative, it corresponds to an instant in time before the Unix epoch.
95    /// A smaller number corresponds to an instant in time further into the past.
96    pub fn new(second: i64, nanosecond: i32) -> Result<Self, Error> {
97        match jiff::Timestamp::new(second, nanosecond) {
98            Ok(t) => Ok(Timestamp(t)),
99            Err(err) => Err(Error::new(
100                ErrorKind::Unexpected,
101                format!(
102                    "create timestamp from '{second}' seconds and '{nanosecond}' nanoseconds failed"
103                ),
104            )
105            .set_source(err)),
106        }
107    }
108
109    /// Creates a new instant in time from the number of milliseconds elapsed
110    /// since the Unix epoch.
111    ///
112    /// When `millisecond` is negative, it corresponds to an instant in time
113    /// before the Unix epoch. A smaller number corresponds to an instant in
114    /// time further into the past.
115    pub fn from_millisecond(millis: i64) -> Result<Self> {
116        match jiff::Timestamp::from_millisecond(millis) {
117            Ok(t) => Ok(Timestamp(t)),
118            Err(err) => Err(Error::new(
119                ErrorKind::Unexpected,
120                format!("convert '{millis}' milliseconds into timestamp failed"),
121            )
122            .set_source(err)),
123        }
124    }
125
126    /// Creates a new instant in time from the number of seconds elapsed since
127    /// the Unix epoch.
128    ///
129    /// When `second` is negative, it corresponds to an instant in time before
130    /// the Unix epoch. A smaller number corresponds to an instant in time
131    /// further into the past.
132    pub fn from_second(second: i64) -> Result<Self> {
133        match jiff::Timestamp::from_second(second) {
134            Ok(t) => Ok(Timestamp(t)),
135            Err(err) => Err(Error::new(
136                ErrorKind::Unexpected,
137                format!("convert '{second}' seconds into timestamp failed"),
138            )
139            .set_source(err)),
140        }
141    }
142
143    /// Parse a timestamp from RFC2822.
144    ///
145    /// All of them are valid time:
146    ///
147    /// - `Sat, 13 Jul 2024 15:09:59 -0400`
148    /// - `Mon, 15 Aug 2022 16:50:12 GMT`
149    pub fn parse_rfc2822(s: &str) -> Result<Timestamp> {
150        match jiff::fmt::rfc2822::parse(s) {
151            Ok(zoned) => Ok(Timestamp(zoned.timestamp())),
152            Err(err) => Err(Error::new(
153                ErrorKind::Unexpected,
154                format!("parse '{s}' into rfc2822 failed"),
155            )
156            .set_source(err)),
157        }
158    }
159
160    /// Convert to inner `jiff::Timestamp` for compatibility.
161    ///
162    /// This method is provided for accessing the underlying `jiff::Timestamp`
163    /// when needed for interoperability with jiff-specific APIs.
164    pub fn into_inner(self) -> jiff::Timestamp {
165        self.0
166    }
167}
168
169impl From<Timestamp> for jiff::Timestamp {
170    fn from(t: Timestamp) -> Self {
171        t.0
172    }
173}
174
175impl From<jiff::Timestamp> for Timestamp {
176    fn from(t: jiff::Timestamp) -> Self {
177        Timestamp(t)
178    }
179}
180
181impl From<Timestamp> for SystemTime {
182    fn from(ts: Timestamp) -> Self {
183        #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
184        {
185            SystemTime::from(ts.0)
186        }
187
188        #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
189        {
190            use std::time::SystemTime as StdSystemTime;
191
192            let t = StdSystemTime::from(ts.0);
193            <web_time::SystemTime as web_time::web::SystemTimeExt>::from_std(t)
194        }
195    }
196}
197
198impl TryFrom<SystemTime> for Timestamp {
199    type Error = Error;
200
201    fn try_from(t: SystemTime) -> Result<Self> {
202        let t = {
203            #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
204            {
205                t
206            }
207
208            #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
209            {
210                <web_time::SystemTime as web_time::web::SystemTimeExt>::to_std(t)
211            }
212        };
213
214        jiff::Timestamp::try_from(t).map(Timestamp).map_err(|err| {
215            Error::new(ErrorKind::Unexpected, "input timestamp overflow").set_source(err)
216        })
217    }
218}
219
220impl Add<Duration> for Timestamp {
221    type Output = Timestamp;
222
223    fn add(self, rhs: Duration) -> Timestamp {
224        let ts = self
225            .0
226            .checked_add(rhs)
227            .expect("adding unsigned duration to timestamp overflowed");
228
229        Timestamp(ts)
230    }
231}
232
233impl AddAssign<Duration> for Timestamp {
234    fn add_assign(&mut self, rhs: Duration) {
235        *self = *self + rhs
236    }
237}
238
239impl Sub<Duration> for Timestamp {
240    type Output = Timestamp;
241
242    fn sub(self, rhs: Duration) -> Timestamp {
243        let ts = self
244            .0
245            .checked_sub(rhs)
246            .expect("subtracting unsigned duration from timestamp overflowed");
247
248        Timestamp(ts)
249    }
250}
251
252impl SubAssign<Duration> for Timestamp {
253    fn sub_assign(&mut self, rhs: Duration) {
254        *self = *self - rhs
255    }
256}
257
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    signed_duration_to_duration(signed)
266}
267
268/// Convert a jiff [`SignedDuration`] into an unsigned [`Duration`].
269#[inline]
270pub fn signed_duration_to_duration(value: SignedDuration) -> Result<Duration> {
271    Duration::try_from(value).map_err(|err| {
272        Error::new(
273            ErrorKind::ConfigInvalid,
274            "duration must not be negative or overflow",
275        )
276        .set_source(err)
277    })
278}
279
280#[cfg(test)]
281mod tests {
282    use super::*;
283
284    fn test_time() -> Timestamp {
285        Timestamp("2022-03-01T08:12:34Z".parse().unwrap())
286    }
287
288    #[test]
289    fn test_format_http_date() {
290        let t = test_time();
291        assert_eq!("Tue, 01 Mar 2022 08:12:34 GMT", t.format_http_date())
292    }
293
294    #[test]
295    fn test_parse_rfc3339() {
296        let t = test_time();
297
298        for v in [
299            "2022-03-01T08:12:34Z",
300            "2022-03-01T08:12:34+00:00",
301            "2022-03-01T08:12:34.00+00:00",
302        ] {
303            assert_eq!(t, v.parse().expect("must be valid time"));
304        }
305    }
306
307    #[test]
308    fn test_parse_rfc2822() {
309        let s = "Sat, 29 Oct 1994 19:43:31 +0000";
310        let v = Timestamp::parse_rfc2822(s).unwrap();
311        assert_eq!("Sat, 29 Oct 1994 19:43:31 GMT", v.format_http_date());
312    }
313
314    #[test]
315    fn test_signed_duration_to_duration_rejects_negative_values() {
316        assert!(signed_duration_to_duration(SignedDuration::from_secs(-1)).is_err());
317    }
318}