defuse_deadline/
lib.rs

1use chrono::{DateTime, SubsecRound, Utc};
2use core::{
3    ops::{Add, AddAssign, Sub, SubAssign},
4    time::Duration,
5};
6#[cfg_attr(
7    feature = "serde",
8    derive(::serde::Serialize, ::serde::Deserialize),
9    cfg_attr(feature = "abi", derive(::schemars::JsonSchema))
10)]
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
12#[repr(transparent)]
13pub struct Deadline(
14    #[cfg_attr(all(feature = "serde", feature = "abi"), schemars(with = "String"))] DateTime<Utc>,
15);
16
17impl Deadline {
18    pub const UNIX_EPOCH: Self = Self::new(DateTime::UNIX_EPOCH);
19    pub const MAX: Self = Self::new(DateTime::<Utc>::MAX_UTC);
20
21    pub const fn new(d: DateTime<Utc>) -> Self {
22        Self(d)
23    }
24
25    #[cfg(target_arch = "wasm32")]
26    #[must_use]
27    pub fn now() -> Self {
28        Self(defuse_near_utils::time::now())
29    }
30
31    #[cfg(not(target_arch = "wasm32"))]
32    #[must_use]
33    #[inline]
34    pub fn now() -> Self {
35        Self(Utc::now())
36    }
37
38    #[must_use]
39    #[inline]
40    pub fn timeout(timeout: Duration) -> Self {
41        Self::now() + timeout
42    }
43
44    #[must_use]
45    #[inline]
46    pub fn has_expired(self) -> bool {
47        Self::now() > self
48    }
49
50    /// Truncate `Deadline` down to seconds part.
51    /// E.g. `2026-03-10T09:32:16.123Z` would be truncated down to
52    /// `2026-03-10T09:32:16Z`
53    #[must_use]
54    #[inline]
55    pub fn trunc_subsecs(self) -> Self {
56        Self::new(self.into_timestamp().trunc_subsecs(0))
57    }
58
59    #[must_use]
60    #[inline]
61    pub const fn into_timestamp(self) -> DateTime<Utc> {
62        self.0
63    }
64}
65
66impl Add<Duration> for Deadline {
67    type Output = Self;
68
69    #[inline]
70    fn add(self, rhs: Duration) -> Self::Output {
71        Self(self.0 + rhs)
72    }
73}
74
75impl Sub<Duration> for Deadline {
76    type Output = Self;
77
78    fn sub(self, rhs: Duration) -> Self::Output {
79        Self(self.0 - rhs)
80    }
81}
82
83impl AddAssign<Duration> for Deadline {
84    #[inline]
85    fn add_assign(&mut self, rhs: Duration) {
86        self.0 += rhs;
87    }
88}
89
90impl SubAssign<Duration> for Deadline {
91    fn sub_assign(&mut self, rhs: Duration) {
92        self.0 -= rhs;
93    }
94}
95
96#[cfg(any(test, feature = "borsh"))]
97const _: () = {
98    use defuse_borsh_utils::adapters::{
99        BorshDeserializeAs, BorshSerializeAs, TimestampMicroSeconds, TimestampMilliSeconds,
100        TimestampNanoSeconds, TimestampSeconds,
101    };
102
103    macro_rules! impl_borsh_serde_as {
104    ($($a:ident,)+) => {
105        const _: () = {
106            $(
107                impl<I> BorshSerializeAs<Deadline> for $a<I>
108                where
109                    $a<I>: BorshSerializeAs<DateTime<Utc>>,
110                {
111                    fn serialize_as<W>(source: &Deadline, writer: &mut W) -> std::io::Result<()>
112                    where
113                        W: std::io::Write,
114                    {
115                        Self::serialize_as(&source.0, writer)
116                    }
117                }
118
119                impl<I> BorshDeserializeAs<Deadline> for $a<I>
120                where
121                    $a<I>: BorshDeserializeAs<DateTime<Utc>>,
122                {
123                    fn deserialize_as<R>(reader: &mut R) -> std::io::Result<Deadline>
124                    where
125                        R: std::io::Read,
126                    {
127                        Self::deserialize_as(reader).map(Deadline)
128                    }
129                }
130            )*
131        };
132    };
133}
134
135    impl_borsh_serde_as! {
136        TimestampSeconds, TimestampMilliSeconds, TimestampMicroSeconds, TimestampNanoSeconds,
137    }
138};
139
140#[cfg(test)]
141mod tests {
142    #[test]
143    fn schema_as_usage() {
144        use super::*;
145        use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
146        use chrono::TimeZone;
147        use defuse_borsh_utils::adapters::{As, TimestampNanoSeconds};
148
149        #[derive(BorshSerialize, BorshDeserialize, BorshSchema)]
150        struct S {
151            #[borsh(
152                serialize_with = "As::<TimestampNanoSeconds>::serialize",
153                deserialize_with = "As::<TimestampNanoSeconds>::deserialize",
154                schema(with_funcs(
155                    declaration = "As::<TimestampNanoSeconds>::declaration",
156                    definitions = "As::<TimestampNanoSeconds>::add_definitions_recursively",
157                ))
158            )]
159            pub deadline: Deadline,
160        }
161
162        let val = S {
163            deadline: Deadline::new(Utc.timestamp_opt(1_600_000_000, 123_456_789).unwrap()),
164        };
165        let bytes = borsh::to_vec(&val).unwrap();
166        let decoded = S::try_from_slice(&bytes).unwrap();
167        assert_eq!(val.deadline, decoded.deadline);
168    }
169}