willow_data_model/timestamp/
mod.rs

1use core::fmt;
2
3use core::ops::{Add, AddAssign, Sub, SubAssign};
4
5#[cfg(feature = "dev")]
6use arbitrary::Arbitrary;
7
8#[cfg(feature = "std")]
9use hifitime::HifitimeError;
10use hifitime::{Duration, Epoch, J2000_REF_EPOCH};
11
12use order_theory::{
13    GreatestElement, LeastElement, LowerSemilattice, PredecessorExceptForLeast,
14    SuccessorExceptForGreatest, TryPredecessor, TrySuccessor, UpperSemilattice,
15};
16
17/// A [Willow Timestamp](https://willowprotocol.org/specs/data-model/index.html#Timestamp) is a 64-bit unsigned integer, specifying the number of microseconds which have elapsed since the J2000 reference epoch (January 1, 2000, at noon, i.e., 12:00 TT) according to [International Atomic Time](https://en.wikipedia.org/wiki/International_Atomic_Time) (aka TAI).
18///
19/// This type is a thin wrapper around `u64`, and provides convenient interoperability with the [`hifitime`] crate.
20///
21/// Use the [`TryFrom`] impls to convert between [`Timestamp`] and the [`hifitime::Epoch`] and [`hifitime::Duration`] types. Use the [`From`] impls to convert from and to `u64` if you need low-level access.
22///
23/// ```
24/// # #[cfg(feature = "std")] {
25/// use willow_data_model::prelude::*;
26///
27/// let now = Timestamp::now().expect("The standard library should provide a valid timestamp");
28/// let three_days_later = now + 3.days();
29/// assert!(three_days_later > now);
30/// # }
31/// ```
32#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash, Debug)]
33#[cfg_attr(feature = "dev", derive(Arbitrary))]
34pub struct Timestamp(u64);
35
36impl fmt::Display for Timestamp {
37    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
38        Epoch::from(*self).fmt(f)
39    }
40}
41
42impl From<u64> for Timestamp {
43    fn from(value: u64) -> Self {
44        Self(value)
45    }
46}
47
48impl From<Timestamp> for u64 {
49    fn from(value: Timestamp) -> Self {
50        value.0
51    }
52}
53
54impl AsRef<u64> for Timestamp {
55    fn as_ref(&self) -> &u64 {
56        &self.0
57    }
58}
59
60impl AsMut<u64> for Timestamp {
61    fn as_mut(&mut self) -> &mut u64 {
62        &mut self.0
63    }
64}
65
66/// Conversion fails if the resulting timestamp would not fit into a `u64`.
67impl TryFrom<Epoch> for Timestamp {
68    type Error = <i128 as TryInto<u64>>::Error;
69
70    /// Conversion fails if the resulting timestamp would not fit into a `u64`.
71    fn try_from(value: Epoch) -> Result<Self, Self::Error> {
72        let diff = value - J2000_REF_EPOCH;
73        let microseconds = diff.total_nanoseconds() / 1000;
74        Ok(Self(microseconds.try_into()?))
75    }
76}
77
78impl From<Timestamp> for Epoch {
79    fn from(value: Timestamp) -> Self {
80        let duration_from_j2000 = Duration::from(value);
81        J2000_REF_EPOCH + duration_from_j2000
82    }
83}
84
85/// Creates a timestamp whose value is the number of microseconds in the duration. Conversion fails if the resulting timestamp would not fit into a `u64`.
86impl TryFrom<Duration> for Timestamp {
87    type Error = <i128 as TryInto<u64>>::Error;
88
89    /// Conversion fails if the resulting timestamp would not fit into a `u64`.
90    fn try_from(value: Duration) -> Result<Self, Self::Error> {
91        Ok(Self((value.total_nanoseconds() / 1000).try_into()?))
92    }
93}
94
95/// Creates a duration which corresponds to the number of microsecond represented in the timestamp.
96impl From<Timestamp> for Duration {
97    fn from(value: Timestamp) -> Self {
98        Duration::from_total_nanoseconds((value.0 as i128) * 1000)
99    }
100}
101
102impl Timestamp {
103    /// Creates the timestamp corresponding to the current instant in time. WARNING: This assumes that the system time returns the time in UTC. Uses [`std::time::SystemTime::now`] under the hood.
104    #[cfg(feature = "std")]
105    pub fn now() -> Result<Self, HifitimeError> {
106        Self::try_from(Epoch::now()?).map_err(|_| HifitimeError::SystemTimeError)
107    }
108}
109
110impl Add<Duration> for Timestamp {
111    type Output = Self;
112
113    fn add(self, rhs: Duration) -> Self::Output {
114        (Duration::from(self) + rhs)
115            .try_into()
116            .expect("Timestamp overflow!")
117    }
118}
119
120impl AddAssign<Duration> for Timestamp {
121    fn add_assign(&mut self, rhs: Duration) {
122        *self = *self + rhs;
123    }
124}
125
126impl Add for Timestamp {
127    type Output = Self;
128
129    fn add(self, rhs: Self) -> Self::Output {
130        Self(self.0 + rhs.0)
131    }
132}
133
134impl AddAssign for Timestamp {
135    fn add_assign(&mut self, rhs: Self) {
136        *self = Self(self.0 + rhs.0);
137    }
138}
139
140impl Sub<Duration> for Timestamp {
141    type Output = Self;
142
143    fn sub(self, rhs: Duration) -> Self::Output {
144        (Duration::from(self) - rhs)
145            .try_into()
146            .expect("Timestamp overflow!")
147    }
148}
149
150impl SubAssign<Duration> for Timestamp {
151    fn sub_assign(&mut self, rhs: Duration) {
152        *self = *self - rhs;
153    }
154}
155
156impl Sub for Timestamp {
157    type Output = Self;
158
159    fn sub(self, rhs: Self) -> Self::Output {
160        Self(self.0 - rhs.0)
161    }
162}
163
164impl SubAssign for Timestamp {
165    fn sub_assign(&mut self, rhs: Self) {
166        *self = Self(self.0 - rhs.0);
167    }
168}
169
170impl LeastElement for Timestamp {
171    fn least() -> Self {
172        u64::least().into()
173    }
174}
175
176impl GreatestElement for Timestamp {
177    fn greatest() -> Self {
178        u64::greatest().into()
179    }
180}
181
182impl LowerSemilattice for Timestamp {
183    fn greatest_lower_bound(&self, other: &Self) -> Self {
184        self.0.greatest_lower_bound(other.as_ref()).into()
185    }
186}
187
188impl UpperSemilattice for Timestamp {
189    fn least_upper_bound(&self, other: &Self) -> Self {
190        self.0.least_upper_bound(other.as_ref()).into()
191    }
192}
193
194impl TryPredecessor for Timestamp {
195    fn try_predecessor(&self) -> Option<Self> {
196        self.0.try_predecessor().map(Self)
197    }
198}
199
200impl TrySuccessor for Timestamp {
201    fn try_successor(&self) -> Option<Self> {
202        self.0.try_successor().map(Self)
203    }
204}
205
206impl PredecessorExceptForLeast for Timestamp {}
207
208impl SuccessorExceptForGreatest for Timestamp {}