ibc_types_timestamp/
lib.rs1#![no_std]
3#![cfg_attr(docsrs, feature(doc_auto_cfg))]
5
6extern crate alloc;
7#[cfg(any(test, feature = "std"))]
8extern crate std;
9
10mod prelude;
11use prelude::*;
12
13use core::fmt::{Display, Error as FmtError, Formatter};
14use core::hash::{Hash, Hasher};
15use core::num::ParseIntError;
16use core::ops::{Add, Sub};
17use core::str::FromStr;
18use core::time::Duration;
19
20use displaydoc::Display;
21use tendermint::Time;
22use time::OffsetDateTime;
23
24pub const ZERO_DURATION: Duration = Duration::from_secs(0);
25
26#[cfg_attr(feature = "with_serde", derive(serde::Serialize, serde::Deserialize))]
34#[derive(PartialEq, Eq, Copy, Clone, Debug, Default)]
35pub struct Timestamp {
36 pub time: Option<Time>,
37}
38
39#[allow(clippy::derived_hash_with_manual_eq)]
42impl Hash for Timestamp {
43 fn hash<H: Hasher>(&self, state: &mut H) {
44 let odt: Option<OffsetDateTime> = self.time.map(Into::into);
45 odt.hash(state);
46 }
47}
48
49#[cfg_attr(feature = "with_serde", derive(serde::Serialize, serde::Deserialize))]
57#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)]
58pub enum Expiry {
59 Expired,
60 NotExpired,
61 InvalidTimestamp,
62}
63
64impl Timestamp {
65 pub fn from_nanoseconds(nanoseconds: u64) -> Result<Timestamp, ParseTimestampError> {
73 if nanoseconds == 0 {
74 Ok(Timestamp { time: None })
75 } else {
76 let ts = OffsetDateTime::from_unix_timestamp_nanos(nanoseconds as i128)
80 .unwrap()
81 .try_into()
82 .unwrap();
83 Ok(Timestamp { time: Some(ts) })
84 }
85 }
86
87 #[cfg(feature = "std")]
89 pub fn now() -> Timestamp {
90 Time::now().into()
91 }
92
93 pub fn none() -> Self {
95 Timestamp { time: None }
96 }
97
98 pub fn duration_since(&self, other: &Timestamp) -> Option<Duration> {
103 match (self.time, other.time) {
104 (Some(time1), Some(time2)) => time1.duration_since(time2).ok(),
105 _ => None,
106 }
107 }
108
109 #[deprecated(since = "0.9.1", note = "use `nanoseconds` instead")]
113 pub fn as_nanoseconds(&self) -> u64 {
114 (*self).nanoseconds()
115 }
116
117 pub fn nanoseconds(self) -> u64 {
134 self.time.map_or(0, |time| {
135 let t: OffsetDateTime = time.into();
136 let s = t.unix_timestamp_nanos();
137 assert!(s >= 0, "time {time:?} has negative `.timestamp()`");
138 s.try_into().unwrap()
139 })
140 }
141
142 pub fn into_datetime(self) -> Option<OffsetDateTime> {
144 self.time.map(Into::into)
145 }
146
147 pub fn into_tm_time(self) -> Option<Time> {
149 self.time
150 }
151
152 pub fn check_expiry(&self, other: &Timestamp) -> Expiry {
155 match (self.time, other.time) {
156 (Some(time1), Some(time2)) => {
157 if time1 > time2 {
158 Expiry::Expired
159 } else {
160 Expiry::NotExpired
161 }
162 }
163 _ => Expiry::InvalidTimestamp,
164 }
165 }
166
167 pub fn after(&self, other: &Timestamp) -> bool {
171 match (self.time, other.time) {
172 (Some(time1), Some(time2)) => time1 > time2,
173 _ => false,
174 }
175 }
176}
177
178impl Display for Timestamp {
180 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
181 write!(
182 f,
183 "Timestamp({})",
184 self.time
185 .map_or("NoTimestamp".to_string(), |time| time.to_rfc3339())
186 )
187 }
188}
189
190#[derive(Debug, Display)]
191pub enum TimestampOverflowError {
192 TimestampOverflow,
194}
195
196#[cfg(feature = "std")]
197impl std::error::Error for TimestampOverflowError {}
198
199impl Add<Duration> for Timestamp {
200 type Output = Result<Timestamp, TimestampOverflowError>;
201
202 fn add(self, duration: Duration) -> Result<Timestamp, TimestampOverflowError> {
203 match self.time {
204 Some(time) => {
205 let time =
206 (time + duration).map_err(|_| TimestampOverflowError::TimestampOverflow)?;
207 Ok(Timestamp { time: Some(time) })
208 }
209 None => Ok(self),
210 }
211 }
212}
213
214impl Sub<Duration> for Timestamp {
215 type Output = Result<Timestamp, TimestampOverflowError>;
216
217 fn sub(self, duration: Duration) -> Result<Timestamp, TimestampOverflowError> {
218 match self.time {
219 Some(time) => {
220 let time =
221 (time - duration).map_err(|_| TimestampOverflowError::TimestampOverflow)?;
222 Ok(Timestamp { time: Some(time) })
223 }
224 None => Ok(self),
225 }
226 }
227}
228
229#[derive(Debug, Display)]
230pub enum ParseTimestampError {
231 ParseInt(ParseIntError),
233}
234
235#[cfg(feature = "std")]
236impl std::error::Error for ParseTimestampError {
237 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
238 match &self {
239 ParseTimestampError::ParseInt(e) => Some(e),
240 }
241 }
242}
243
244impl FromStr for Timestamp {
245 type Err = ParseTimestampError;
246
247 fn from_str(s: &str) -> Result<Self, Self::Err> {
248 let nanoseconds = u64::from_str(s).map_err(ParseTimestampError::ParseInt)?;
249
250 Timestamp::from_nanoseconds(nanoseconds)
251 }
252}
253
254impl From<Time> for Timestamp {
255 fn from(tendermint_time: Time) -> Timestamp {
256 Timestamp {
257 time: Some(tendermint_time),
258 }
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use time::OffsetDateTime;
265
266 use core::time::Duration;
267 use std::thread::sleep;
268 use test_log::test;
269
270 use super::{Expiry, Timestamp, ZERO_DURATION};
271
272 #[test]
273 fn test_timestamp_comparisons() {
274 let nil_timestamp = Timestamp::from_nanoseconds(0).unwrap();
275 assert_eq!(nil_timestamp.time, None);
276 assert_eq!(nil_timestamp.nanoseconds(), 0);
277
278 let timestamp1 = Timestamp::from_nanoseconds(1).unwrap();
279 let dt: OffsetDateTime = timestamp1.time.unwrap().into();
280 assert_eq!(dt.unix_timestamp_nanos(), 1);
281 assert_eq!(timestamp1.nanoseconds(), 1);
282
283 let timestamp2 = Timestamp::from_nanoseconds(1_000_000_000).unwrap();
284 let dt: OffsetDateTime = timestamp2.time.unwrap().into();
285 assert_eq!(dt.unix_timestamp_nanos(), 1_000_000_000);
286 assert_eq!(timestamp2.nanoseconds(), 1_000_000_000);
287
288 assert!(Timestamp::from_nanoseconds(u64::MAX).is_ok());
289 assert!(Timestamp::from_nanoseconds(i64::MAX.try_into().unwrap()).is_ok());
290
291 assert_eq!(timestamp1.check_expiry(×tamp2), Expiry::NotExpired);
292 assert_eq!(timestamp1.check_expiry(×tamp1), Expiry::NotExpired);
293 assert_eq!(timestamp2.check_expiry(×tamp2), Expiry::NotExpired);
294 assert_eq!(timestamp2.check_expiry(×tamp1), Expiry::Expired);
295 assert_eq!(
296 timestamp1.check_expiry(&nil_timestamp),
297 Expiry::InvalidTimestamp
298 );
299 assert_eq!(
300 nil_timestamp.check_expiry(×tamp2),
301 Expiry::InvalidTimestamp
302 );
303 assert_eq!(
304 nil_timestamp.check_expiry(&nil_timestamp),
305 Expiry::InvalidTimestamp
306 );
307 }
308
309 #[test]
310 fn test_timestamp_arithmetic() {
311 let time0 = Timestamp::none();
312 let time1 = Timestamp::from_nanoseconds(100).unwrap();
313 let time2 = Timestamp::from_nanoseconds(150).unwrap();
314 let time3 = Timestamp::from_nanoseconds(50).unwrap();
315 let duration = Duration::from_nanos(50);
316
317 assert_eq!(time1, (time1 + ZERO_DURATION).unwrap());
318 assert_eq!(time2, (time1 + duration).unwrap());
319 assert_eq!(time3, (time1 - duration).unwrap());
320 assert_eq!(time0, (time0 + duration).unwrap());
321 assert_eq!(time0, (time0 - duration).unwrap());
322 }
323
324 #[test]
325 fn subtract_compare() {
326 let sleep_duration = Duration::from_micros(100);
327
328 let start = Timestamp::now();
329 sleep(sleep_duration);
330 let end = Timestamp::now();
331
332 let res = end.duration_since(&start);
333 assert!(res.is_some());
334
335 let inner = res.unwrap();
336 assert!(inner > sleep_duration);
337 }
338}