defuse_deadline/
lib.rs

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