1use core::fmt::{Display, Error as FmtError, Formatter};
4use core::hash::Hash;
5use core::num::{ParseIntError, TryFromIntError};
6use core::ops::{Add, Sub};
7use core::str::FromStr;
8use core::time::Duration;
9
10use displaydoc::Display;
11use ibc_proto::google::protobuf::Timestamp as RawTimestamp;
12use ibc_proto::Protobuf;
13#[cfg(feature = "serde")]
14use serde::{Deserialize, Serialize};
15use time::macros::offset;
16use time::{OffsetDateTime, PrimitiveDateTime};
17
18use crate::prelude::*;
19
20pub const ZERO_DURATION: Duration = Duration::from_secs(0);
21
22#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
30#[derive(PartialEq, Eq, Copy, Clone, Debug, PartialOrd, Ord, Hash)]
31pub struct Timestamp {
32 #[cfg_attr(feature = "schema", schemars(with = "u64"))]
34 time: PrimitiveDateTime,
35}
36
37#[cfg(feature = "arbitrary")]
38impl<'a> arbitrary::Arbitrary<'a> for Timestamp {
39 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
40 let nanos: u64 = arbitrary::Arbitrary::arbitrary(u)?;
41 Ok(Timestamp::from_nanoseconds(nanos))
42 }
43}
44
45impl Timestamp {
46 pub fn from_nanoseconds(nanoseconds: u64) -> Self {
47 let odt = OffsetDateTime::from_unix_timestamp_nanos(nanoseconds.into())
50 .expect("nanoseconds as u64 is in the range");
51 Self::from_utc(odt).expect("nanoseconds as u64 is in the range")
52 }
53
54 pub fn from_unix_timestamp(secs: u64, nanos: u32) -> Result<Self, TimestampError> {
55 if nanos > 999_999_999 {
56 return Err(TimestampError::InvalidDate);
57 }
58
59 let total_nanos = secs as i128 * 1_000_000_000 + nanos as i128;
60
61 let odt = OffsetDateTime::from_unix_timestamp_nanos(total_nanos)?;
62
63 Self::from_utc(odt)
64 }
65
66 fn from_utc(t: OffsetDateTime) -> Result<Self, TimestampError> {
70 debug_assert_eq!(t.offset(), offset!(UTC));
71 match t.year() {
72 1970..=9999 => Ok(Self {
73 time: PrimitiveDateTime::new(t.date(), t.time()),
74 }),
75 _ => Err(TimestampError::InvalidDate),
76 }
77 }
78
79 #[cfg(feature = "std")]
81 pub fn now() -> Self {
82 OffsetDateTime::now_utc()
83 .try_into()
84 .expect("now is in the range of 0..=9999 years")
85 }
86
87 pub fn duration_since(&self, other: &Self) -> Option<Duration> {
90 let duration = self.time.assume_utc() - other.time.assume_utc();
91 duration.try_into().ok()
92 }
93
94 pub fn nanoseconds(self) -> u64 {
109 let odt: OffsetDateTime = self.into();
110 let s = odt.unix_timestamp_nanos();
111 s.try_into()
112 .expect("Fails UNIX timestamp is negative, but we don't allow that to be constructed")
113 }
114}
115
116impl Protobuf<RawTimestamp> for Timestamp {}
117
118impl TryFrom<RawTimestamp> for Timestamp {
119 type Error = TimestampError;
120
121 fn try_from(raw: RawTimestamp) -> Result<Self, Self::Error> {
122 let nanos = raw.nanos.try_into()?;
123 let seconds = raw.seconds.try_into()?;
124 Self::from_unix_timestamp(seconds, nanos)
125 }
126}
127
128impl From<Timestamp> for RawTimestamp {
129 fn from(value: Timestamp) -> Self {
130 let t = value.time.assume_utc();
131 let seconds = t.unix_timestamp();
132 let nanos = t.nanosecond() as i32;
135 RawTimestamp { seconds, nanos }
136 }
137}
138
139impl TryFrom<OffsetDateTime> for Timestamp {
140 type Error = TimestampError;
141
142 fn try_from(t: OffsetDateTime) -> Result<Self, Self::Error> {
143 Self::from_utc(t.to_offset(offset!(UTC)))
144 }
145}
146
147impl From<Timestamp> for OffsetDateTime {
148 fn from(t: Timestamp) -> Self {
149 t.time.assume_utc()
150 }
151}
152
153impl FromStr for Timestamp {
154 type Err = TimestampError;
155
156 fn from_str(s: &str) -> Result<Self, Self::Err> {
157 let nanoseconds = u64::from_str(s)?;
158 Ok(Self::from_nanoseconds(nanoseconds))
159 }
160}
161
162impl Display for Timestamp {
163 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
164 write!(f, "Timestamp({})", self.time)
165 }
166}
167
168impl Add<Duration> for Timestamp {
169 type Output = Result<Self, TimestampError>;
170
171 fn add(self, rhs: Duration) -> Self::Output {
172 let duration = rhs.try_into().map_err(|_| TimestampError::InvalidDate)?;
173 let t = self
174 .time
175 .checked_add(duration)
176 .ok_or(TimestampError::InvalidDate)?;
177 Self::from_utc(t.assume_utc())
178 }
179}
180
181impl Sub<Duration> for Timestamp {
182 type Output = Result<Self, TimestampError>;
183
184 fn sub(self, rhs: Duration) -> Self::Output {
185 let duration = rhs.try_into().map_err(|_| TimestampError::InvalidDate)?;
186 let t = self
187 .time
188 .checked_sub(duration)
189 .ok_or(TimestampError::InvalidDate)?;
190 Self::from_utc(t.assume_utc())
191 }
192}
193
194pub trait IntoHostTime<T: Sized> {
196 fn into_host_time(self) -> Result<T, TimestampError>;
202}
203
204pub trait IntoTimestamp {
207 fn into_timestamp(self) -> Result<Timestamp, TimestampError>;
212}
213
214impl<T: TryFrom<OffsetDateTime>> IntoHostTime<T> for Timestamp {
215 fn into_host_time(self) -> Result<T, TimestampError> {
216 T::try_from(self.into()).map_err(|_| TimestampError::InvalidDate)
217 }
218}
219
220impl<T: Into<OffsetDateTime>> IntoTimestamp for T {
221 fn into_timestamp(self) -> Result<Timestamp, TimestampError> {
222 Timestamp::try_from(self.into())
223 }
224}
225
226#[cfg(feature = "serde")]
227impl Serialize for Timestamp {
228 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
229 where
230 S: serde::Serializer,
231 {
232 self.nanoseconds().serialize(serializer)
233 }
234}
235
236#[cfg(feature = "serde")]
237impl<'de> Deserialize<'de> for Timestamp {
238 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
239 where
240 D: serde::Deserializer<'de>,
241 {
242 let timestamp = u64::deserialize(deserializer)?;
243 Ok(Timestamp::from_nanoseconds(timestamp))
244 }
245}
246
247#[cfg(feature = "borsh")]
248impl borsh::BorshSerialize for Timestamp {
249 fn serialize<W: borsh::io::Write>(&self, writer: &mut W) -> borsh::io::Result<()> {
250 let timestamp = self.nanoseconds();
251 borsh::BorshSerialize::serialize(×tamp, writer)
252 }
253}
254
255#[cfg(feature = "borsh")]
256impl borsh::BorshDeserialize for Timestamp {
257 fn deserialize_reader<R: borsh::io::Read>(reader: &mut R) -> borsh::io::Result<Self> {
258 let timestamp = u64::deserialize_reader(reader)?;
259 Ok(Self::from_nanoseconds(timestamp))
260 }
261}
262
263#[cfg(feature = "parity-scale-codec")]
264impl parity_scale_codec::Encode for Timestamp {
265 fn encode_to<T: parity_scale_codec::Output + ?Sized>(&self, writer: &mut T) {
266 let timestamp = self.nanoseconds();
267 timestamp.encode_to(writer);
268 }
269}
270#[cfg(feature = "parity-scale-codec")]
271impl parity_scale_codec::Decode for Timestamp {
272 fn decode<I: parity_scale_codec::Input>(
273 input: &mut I,
274 ) -> Result<Self, parity_scale_codec::Error> {
275 let timestamp = u64::decode(input)?;
276 Ok(Self::from_nanoseconds(timestamp))
277 }
278}
279
280#[cfg(feature = "parity-scale-codec")]
281impl scale_info::TypeInfo for Timestamp {
282 type Identity = Self;
283
284 fn type_info() -> scale_info::Type {
285 scale_info::Type::builder()
286 .path(scale_info::Path::new("Timestamp", module_path!()))
287 .composite(
288 scale_info::build::Fields::named()
289 .field(|f| f.ty::<u64>().name("time").type_name("u64")),
290 )
291 }
292}
293
294#[derive(Debug, Display, derive_more::From)]
295pub enum TimestampError {
296 FailedToParseInt(ParseIntError),
298 FailedTryFromInt(TryFromIntError),
300 FailedToConvert(time::error::ComponentRange),
302 InvalidDate,
304 OverflowedTimestamp,
306}
307
308#[cfg(feature = "std")]
309impl std::error::Error for TimestampError {
310 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
311 match &self {
312 Self::FailedToParseInt(e) => Some(e),
313 Self::FailedTryFromInt(e) => Some(e),
314 Self::FailedToConvert(e) => Some(e),
315 _ => None,
316 }
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use core::time::Duration;
323 use std::thread::sleep;
324
325 use rstest::rstest;
326 use time::OffsetDateTime;
327
328 use super::{Timestamp, ZERO_DURATION};
329
330 #[rstest]
331 #[case::zero(0)]
332 #[case::one(1)]
333 #[case::billions(1_000_000_000)]
334 #[case::u64_max(u64::MAX)]
335 #[case::i64_max(i64::MAX.try_into().unwrap())]
336 fn test_timestamp_from_nanoseconds(#[case] nanos: u64) {
337 let timestamp = Timestamp::from_nanoseconds(nanos);
338 let dt: OffsetDateTime = timestamp.into();
339 assert_eq!(dt.unix_timestamp_nanos(), nanos as i128);
340 assert_eq!(timestamp.nanoseconds(), nanos);
341 }
342
343 #[rstest]
344 #[case::one(1, 0)]
345 #[case::billions(1_000_000_000, 0)]
346 #[case::u64_max(u64::MAX, 0)]
347 #[case::u64_from_i62_max(u64::MAX, i64::MAX.try_into().unwrap())]
348 fn test_timestamp_comparisons(#[case] nanos_1: u64, #[case] nanos_2: u64) {
349 let t_1 = Timestamp::from_nanoseconds(nanos_1);
350 let t_2 = Timestamp::from_nanoseconds(nanos_2);
351 assert!(t_1 > t_2);
352 }
353
354 #[rstest]
355 #[case::zero(0, 0, true)]
356 #[case::one_sec(1, 0, true)]
357 #[case::one_nano(0, 1, true)]
358 #[case::max_nanos(0, 999_999_999, true)]
359 #[case::max_nanos_plus_one(0, 1_000_000_000, false)]
360 #[case::sec_overflow(u64::MAX, 0, false)]
361 #[case::max_valid(253_402_300_799, 999_999_999, true)] #[case::max_plus_one(253_402_300_800, 0, false)]
363 fn test_timestamp_from_unix_nanoseconds(
364 #[case] secs: u64,
365 #[case] nanos: u32,
366 #[case] expect: bool,
367 ) {
368 let timestamp = Timestamp::from_unix_timestamp(secs, nanos);
369 assert_eq!(timestamp.is_ok(), expect);
370 if expect {
371 let odt = timestamp.unwrap().time.assume_utc();
372 assert_eq!(odt.unix_timestamp() as u64, secs);
373 assert_eq!(odt.nanosecond(), nanos);
374 }
375 }
376
377 #[rstest]
378 #[case::one(1)]
379 #[case::billions(1_000_000_000)]
380 #[case::min(u64::MIN)]
381 #[case::u64_max(u64::MAX)]
382 fn test_timestamp_from_u64(#[case] nanos: u64) {
383 let _ = Timestamp::from_nanoseconds(nanos);
384 }
385
386 #[test]
387 fn test_timestamp_arithmetic() {
388 let time0 = Timestamp::from_nanoseconds(0);
389 let time1 = Timestamp::from_nanoseconds(100);
390 let time2 = Timestamp::from_nanoseconds(150);
391 let time3 = Timestamp::from_nanoseconds(50);
392 let duration = Duration::from_nanos(50);
393
394 assert_eq!(time1, (time1 + ZERO_DURATION).unwrap());
395 assert_eq!(time2, (time1 + duration).unwrap());
396 assert_eq!(time3, (time1 - duration).unwrap());
397 assert_eq!(time0, (time3 - duration).unwrap());
398 assert!((time0 - duration).is_err());
399 }
400
401 #[test]
402 fn subtract_compare() {
403 let sleep_duration = Duration::from_micros(100);
404
405 let start = Timestamp::now();
406 sleep(sleep_duration);
407 let end = Timestamp::now();
408
409 let res = end.duration_since(&start);
410 assert!(res.is_some());
411
412 let inner = res.unwrap();
413 assert!(inner > sleep_duration);
414 }
415
416 #[cfg(feature = "serde")]
417 #[rstest]
418 #[case::zero(0)]
419 #[case::one(1)]
420 #[case::billions(1_000_000_000)]
421 #[case::u64_max(u64::MAX)]
422 fn test_timestamp_serde(#[case] nanos: u64) {
423 let timestamp = Timestamp::from_nanoseconds(nanos);
424 let serialized = serde_json::to_string(×tamp).unwrap();
425 let deserialized = serde_json::from_str::<Timestamp>(&serialized).unwrap();
426 assert_eq!(timestamp, deserialized);
427 }
428
429 #[test]
430 #[cfg(feature = "borsh")]
431 fn test_timestamp_borsh_ser_der() {
432 let timestamp = Timestamp::now();
433 let encode_timestamp = borsh::to_vec(×tamp).unwrap();
434 let _ = borsh::from_slice::<Timestamp>(&encode_timestamp).unwrap();
435 }
436
437 #[test]
438 #[cfg(feature = "parity-scale-codec")]
439 fn test_timestamp_parity_scale_codec_ser_der() {
440 use parity_scale_codec::{Decode, Encode};
441 let timestamp = Timestamp::now();
442 let encode_timestamp = timestamp.encode();
443 let _ = Timestamp::decode(&mut encode_timestamp.as_slice()).unwrap();
444 }
445}