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 #[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}