namada_core/
time.rs

1//! Types for dealing with time and durations.
2
3use std::collections::BTreeMap;
4use std::fmt::Display;
5use std::io::Read;
6use std::ops::{Add, Sub};
7use std::str::FromStr;
8
9use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
10use chrono::ParseError;
11pub use chrono::{DateTime, Duration, TimeZone, Utc};
12use namada_macros::BorshDeserializer;
13#[cfg(feature = "migrations")]
14use namada_migrations::*;
15use serde::{Deserialize, Serialize};
16
17/// Check if the given `duration` has passed since the given `start.
18#[allow(clippy::arithmetic_side_effects)]
19pub fn duration_passed(
20    current: DateTimeUtc,
21    start: DateTimeUtc,
22    duration: DurationSecs,
23) -> bool {
24    start + duration <= current
25}
26
27/// A duration in seconds precision.
28#[derive(
29    Clone,
30    Copy,
31    Debug,
32    PartialEq,
33    Eq,
34    PartialOrd,
35    Ord,
36    Hash,
37    Serialize,
38    Deserialize,
39    BorshSerialize,
40    BorshDeserialize,
41    BorshDeserializer,
42    BorshSchema,
43)]
44pub struct DurationSecs(pub u64);
45
46impl From<Duration> for DurationSecs {
47    fn from(duration_chrono: Duration) -> Self {
48        let duration_std = duration_chrono
49            .to_std()
50            .expect("Duration must not be negative");
51        duration_std.into()
52    }
53}
54
55impl From<std::time::Duration> for DurationSecs {
56    fn from(duration_std: std::time::Duration) -> Self {
57        DurationSecs(duration_std.as_secs())
58    }
59}
60
61impl From<DurationSecs> for std::time::Duration {
62    fn from(duration_secs: DurationSecs) -> Self {
63        std::time::Duration::new(duration_secs.0, 0)
64    }
65}
66
67impl Display for DurationSecs {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        write!(f, "{}", self.0)
70    }
71}
72
73/// A duration in nanos precision.
74#[derive(
75    Clone,
76    Copy,
77    Debug,
78    PartialEq,
79    Eq,
80    PartialOrd,
81    Ord,
82    Hash,
83    Serialize,
84    Deserialize,
85    BorshSerialize,
86    BorshDeserialize,
87    BorshDeserializer,
88    BorshSchema,
89)]
90pub struct DurationNanos {
91    /// The seconds
92    pub secs: u64,
93    /// The nano seconds
94    pub nanos: u32,
95}
96
97impl From<std::time::Duration> for DurationNanos {
98    fn from(duration_std: std::time::Duration) -> Self {
99        DurationNanos {
100            secs: duration_std.as_secs(),
101            nanos: duration_std.subsec_nanos(),
102        }
103    }
104}
105
106impl From<DurationNanos> for std::time::Duration {
107    fn from(DurationNanos { secs, nanos }: DurationNanos) -> Self {
108        Self::new(secs, nanos)
109    }
110}
111
112/// An RFC 3339 timestamp (e.g., "1970-01-01T00:00:00Z").
113#[derive(
114    Clone,
115    Debug,
116    Deserialize,
117    Serialize,
118    BorshDeserialize,
119    BorshDeserializer,
120    BorshSerialize,
121    PartialEq,
122    Eq,
123    PartialOrd,
124    Ord,
125    Hash,
126)]
127pub struct Rfc3339String(pub String);
128
129/// A duration in seconds precision.
130#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
131#[derive(
132    Clone,
133    Copy,
134    Debug,
135    Default,
136    PartialEq,
137    Eq,
138    PartialOrd,
139    Ord,
140    Hash,
141    serde::Serialize,
142    serde::Deserialize,
143    BorshDeserializer,
144)]
145#[serde(try_from = "Rfc3339String", into = "Rfc3339String")]
146pub struct DateTimeUtc(pub DateTime<Utc>);
147
148/// The minimum possible `DateTime<Utc>`.
149pub const MIN_UTC: DateTimeUtc = DateTimeUtc(chrono::DateTime::<Utc>::MIN_UTC);
150
151impl Display for DateTimeUtc {
152    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153        write!(f, "{}", self.to_rfc3339())
154    }
155}
156
157impl DateTimeUtc {
158    const FORMAT: &'static str = "%Y-%m-%dT%H:%M:%S%.9f+00:00";
159
160    /// Returns a DateTimeUtc which corresponds to the current date.
161    pub fn now() -> Self {
162        Self(
163            #[allow(clippy::disallowed_methods)]
164            Utc::now(),
165        )
166    }
167
168    /// Returns the unix timestamp associated with this [`DateTimeUtc`].
169    #[inline]
170    pub fn to_unix_timestamp(&self) -> i64 {
171        self.0.timestamp()
172    }
173
174    /// Returns a [`DateTimeUtc`] corresponding to the provided Unix timestamp.
175    #[inline]
176    pub fn from_unix_timestamp(timestamp: i64) -> Option<Self> {
177        Some(Self(chrono::DateTime::<Utc>::from_timestamp(timestamp, 0)?))
178    }
179
180    /// Returns a [`DateTimeUtc`] corresponding to the Unix epoch.
181    #[inline]
182    pub fn unix_epoch() -> Self {
183        Self::from_unix_timestamp(0)
184            .expect("This operation should be infallible")
185    }
186
187    /// Returns an rfc3339 string or an error.
188    pub fn to_rfc3339(&self) -> String {
189        self.0.format(DateTimeUtc::FORMAT).to_string()
190    }
191
192    /// Parses a rfc3339 string, or returns an error.
193    pub fn from_rfc3339(s: &str) -> Result<Self, ParseError> {
194        use chrono::format;
195        use chrono::format::strftime::StrftimeItems;
196
197        let format = StrftimeItems::new(Self::FORMAT);
198        let mut parsed = format::Parsed::new();
199        format::parse(&mut parsed, s, format)?;
200
201        parsed.to_datetime_with_timezone(&chrono::Utc).map(Self)
202    }
203
204    /// Returns the DateTimeUtc corresponding to one second in the future
205    #[allow(clippy::arithmetic_side_effects)]
206    pub fn next_second(&self) -> Self {
207        *self + DurationSecs(1)
208    }
209
210    /// Returns the number of seconds in between two `DateTimeUtc` instances.
211    /// Assumes that `self` is later than `earlier`.
212    #[allow(clippy::arithmetic_side_effects)]
213    pub fn time_diff(&self, earlier: DateTimeUtc) -> DurationSecs {
214        (self.0 - earlier.0)
215            .to_std()
216            .map(DurationSecs::from)
217            .unwrap_or(DurationSecs(0))
218    }
219}
220
221impl FromStr for DateTimeUtc {
222    type Err = ParseError;
223
224    #[inline]
225    fn from_str(s: &str) -> Result<Self, Self::Err> {
226        Self::from_rfc3339(s)
227    }
228}
229
230impl Add<DurationSecs> for DateTimeUtc {
231    type Output = DateTimeUtc;
232
233    #[allow(clippy::arithmetic_side_effects)]
234    fn add(self, duration: DurationSecs) -> Self::Output {
235        let duration_std = std::time::Duration::from_secs(duration.0);
236        let duration_chrono = Duration::from_std(duration_std).expect(
237            "Duration shouldn't be larger than the maximum value supported \
238             for chrono::Duration",
239        );
240        (self.0 + duration_chrono).into()
241    }
242}
243
244impl Add<Duration> for DateTimeUtc {
245    type Output = DateTimeUtc;
246
247    #[allow(clippy::arithmetic_side_effects)]
248    fn add(self, rhs: Duration) -> Self::Output {
249        (self.0 + rhs).into()
250    }
251}
252
253impl Sub<Duration> for DateTimeUtc {
254    type Output = DateTimeUtc;
255
256    #[allow(clippy::arithmetic_side_effects)]
257    fn sub(self, rhs: Duration) -> Self::Output {
258        (self.0 - rhs).into()
259    }
260}
261
262impl Sub<DateTimeUtc> for DateTimeUtc {
263    type Output = DurationSecs;
264
265    #[allow(clippy::arithmetic_side_effects)]
266    fn sub(self, rhs: DateTimeUtc) -> Self::Output {
267        (self.0 - rhs.0)
268            .to_std()
269            .map(DurationSecs::from)
270            .unwrap_or(DurationSecs(0))
271    }
272}
273
274impl BorshSerialize for DateTimeUtc {
275    fn serialize<W: std::io::Write>(
276        &self,
277        writer: &mut W,
278    ) -> std::io::Result<()> {
279        let raw = self.to_rfc3339();
280        BorshSerialize::serialize(&raw, writer)
281    }
282}
283
284impl BorshDeserialize for DateTimeUtc {
285    fn deserialize_reader<R: Read>(reader: &mut R) -> std::io::Result<Self> {
286        use std::io::{Error, ErrorKind};
287        let raw: String = BorshDeserialize::deserialize_reader(reader)?;
288        Self::from_rfc3339(&raw)
289            .map_err(|err| Error::new(ErrorKind::InvalidData, err))
290    }
291}
292
293impl BorshSchema for DateTimeUtc {
294    fn add_definitions_recursively(
295        definitions: &mut BTreeMap<
296            borsh::schema::Declaration,
297            borsh::schema::Definition,
298        >,
299    ) {
300        // Encoded as rfc3339 `String`
301        let fields =
302            borsh::schema::Fields::UnnamedFields(vec!["string".into()]);
303        let definition = borsh::schema::Definition::Struct { fields };
304        definitions.insert(Self::declaration(), definition);
305    }
306
307    fn declaration() -> borsh::schema::Declaration {
308        "DateTimeUtc".into()
309    }
310}
311
312impl From<DateTime<Utc>> for DateTimeUtc {
313    fn from(dt: DateTime<Utc>) -> Self {
314        Self(dt)
315    }
316}
317
318impl TryFrom<prost_types::Timestamp> for DateTimeUtc {
319    type Error = prost_types::TimestampError;
320
321    fn try_from(
322        timestamp: prost_types::Timestamp,
323    ) -> Result<Self, Self::Error> {
324        let system_time: std::time::SystemTime = timestamp.try_into()?;
325        Ok(Self(system_time.into()))
326    }
327}
328
329impl From<DateTimeUtc> for prost_types::Timestamp {
330    fn from(dt: DateTimeUtc) -> Self {
331        let seconds = dt.0.timestamp();
332        // The cast cannot wrap as the value is at most 1_999_999_999
333        #[allow(clippy::cast_possible_wrap)]
334        let nanos = dt.0.timestamp_subsec_nanos() as i32;
335        prost_types::Timestamp { seconds, nanos }
336    }
337}
338
339impl TryFrom<crate::tendermint_proto::google::protobuf::Timestamp>
340    for DateTimeUtc
341{
342    type Error = prost_types::TimestampError;
343
344    fn try_from(
345        timestamp: crate::tendermint_proto::google::protobuf::Timestamp,
346    ) -> Result<Self, Self::Error> {
347        Self::try_from(prost_types::Timestamp {
348            seconds: timestamp.seconds,
349            nanos: timestamp.nanos,
350        })
351    }
352}
353
354impl From<DateTimeUtc> for std::time::SystemTime {
355    fn from(dt: DateTimeUtc) -> Self {
356        dt.0.into()
357    }
358}
359
360impl TryFrom<Rfc3339String> for DateTimeUtc {
361    type Error = chrono::ParseError;
362
363    fn try_from(str: Rfc3339String) -> Result<Self, Self::Error> {
364        Self::from_rfc3339(&str.0)
365    }
366}
367
368impl From<DateTimeUtc> for Rfc3339String {
369    fn from(dt: DateTimeUtc) -> Self {
370        Self(dt.to_rfc3339())
371    }
372}
373
374impl TryFrom<DateTimeUtc> for crate::tendermint::time::Time {
375    type Error = crate::tendermint::Error;
376
377    fn try_from(dt: DateTimeUtc) -> Result<Self, Self::Error> {
378        Self::parse_from_rfc3339(&dt.to_rfc3339())
379    }
380}
381
382impl TryFrom<crate::tendermint::time::Time> for DateTimeUtc {
383    type Error = prost_types::TimestampError;
384
385    fn try_from(t: crate::tendermint::time::Time) -> Result<Self, Self::Error> {
386        crate::tendermint_proto::google::protobuf::Timestamp::from(t).try_into()
387    }
388}
389
390impl From<crate::tendermint::Timeout> for DurationNanos {
391    fn from(val: crate::tendermint::Timeout) -> Self {
392        Self::from(std::time::Duration::from(val))
393    }
394}
395
396impl From<DurationNanos> for crate::tendermint::Timeout {
397    fn from(val: DurationNanos) -> Self {
398        Self::from(std::time::Duration::from(val))
399    }
400}
401
402#[cfg(any(test, feature = "testing"))]
403pub mod test_utils {
404    //! Time related test utilities.
405
406    /// Genesis time used during tests.
407    pub const GENESIS_TIME: &str = "2023-08-30T00:00:00.000000000+00:00";
408}
409
410#[cfg(test)]
411mod core_time_tests {
412    use proptest::prelude::*;
413
414    use super::*;
415
416    proptest! {
417        #[test]
418        fn test_valid_reverse_datetime_utc_encoding_roundtrip(
419            year in 1974..=3_000,
420            month in 1..=12,
421            day in 1..=28,
422            hour in 0..=23,
423            min in 0..=59,
424            sec in 0..=59,
425            nanos in 0..=999_999_999,
426        )
427        {
428            let timestamp = format!("{year:04}-{month:02}-{day:02}T{hour:02}:{min:02}:{sec:02}.{nanos:09}+00:00");
429            println!("Testing timestamp: {timestamp}");
430            test_valid_reverse_datetime_utc_encoding_roundtrip_inner(&timestamp);
431        }
432    }
433
434    fn test_valid_reverse_datetime_utc_encoding_roundtrip_inner(
435        timestamp: &str,
436    ) {
437        // we should be able to parse our custom datetime
438        let datetime = DateTimeUtc::from_rfc3339(timestamp).unwrap();
439
440        // the chrono datetime, which uses a superset of
441        // our datetime format should also be parsable
442        let datetime_inner = DateTime::parse_from_rfc3339(timestamp)
443            .unwrap()
444            .with_timezone(&Utc);
445        assert_eq!(datetime, DateTimeUtc(datetime_inner));
446
447        let encoded = datetime.to_rfc3339();
448
449        assert_eq!(encoded, timestamp);
450    }
451
452    #[test]
453    fn test_invalid_datetime_utc_encoding() {
454        // NB: this is a valid rfc3339 string, but we enforce
455        // a subset of the format to get deterministic encoding
456        // results
457        const TIMESTAMP: &str = "1966-03-03T00:06:56.520Z";
458        // const TIMESTAMP: &str = "1966-03-03T00:06:56.520+00:00";
459
460        // this is a valid rfc3339 string
461        assert!(DateTime::parse_from_rfc3339(TIMESTAMP).is_ok());
462
463        // but it cannot be parsed as a `DateTimeUtc`
464        assert!(DateTimeUtc::from_rfc3339(TIMESTAMP).is_err());
465    }
466
467    #[test]
468    fn test_valid_test_utils_genesis_time() {
469        assert!(DateTimeUtc::from_rfc3339(test_utils::GENESIS_TIME).is_ok());
470    }
471}