zebra_chain/serialization/
date_time.rs

1//! DateTime types with specific serialization invariants.
2
3use std::{
4    fmt,
5    num::{ParseIntError, TryFromIntError},
6    str::FromStr,
7};
8
9use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
10use chrono::{TimeZone, Utc};
11
12use crate::serialization::{SerializationError, ZcashDeserialize, ZcashSerialize};
13
14/// A date and time, represented by a 32-bit number of seconds since the UNIX epoch.
15#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
16#[serde(transparent)]
17pub struct DateTime32 {
18    timestamp: u32,
19}
20
21/// An unsigned time duration, represented by a 32-bit number of seconds.
22#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
23pub struct Duration32 {
24    seconds: u32,
25}
26
27impl DateTime32 {
28    /// The earliest possible `DateTime32` value.
29    pub const MIN: DateTime32 = DateTime32 {
30        timestamp: u32::MIN,
31    };
32
33    /// The latest possible `DateTime32` value.
34    pub const MAX: DateTime32 = DateTime32 {
35        timestamp: u32::MAX,
36    };
37
38    /// Returns the number of seconds since the UNIX epoch.
39    pub fn timestamp(&self) -> u32 {
40        self.timestamp
41    }
42
43    /// Returns the equivalent [`chrono::DateTime`].
44    pub fn to_chrono(self) -> chrono::DateTime<Utc> {
45        self.into()
46    }
47
48    /// Returns the current time.
49    ///
50    /// # Panics
51    ///
52    /// If the number of seconds since the UNIX epoch is greater than `u32::MAX`.
53    pub fn now() -> DateTime32 {
54        chrono::Utc::now()
55            .try_into()
56            .expect("unexpected out of range chrono::DateTime")
57    }
58
59    /// Returns the duration elapsed between `earlier` and this time,
60    /// or `None` if `earlier` is later than this time.
61    pub fn checked_duration_since(&self, earlier: DateTime32) -> Option<Duration32> {
62        self.timestamp
63            .checked_sub(earlier.timestamp)
64            .map(Duration32::from)
65    }
66
67    /// Returns duration elapsed between `earlier` and this time,
68    /// or zero if `earlier` is later than this time.
69    pub fn saturating_duration_since(&self, earlier: DateTime32) -> Duration32 {
70        Duration32::from(self.timestamp.saturating_sub(earlier.timestamp))
71    }
72
73    /// Returns the duration elapsed since this time,
74    /// or if this time is in the future, returns `None`.
75    #[allow(clippy::unwrap_in_result)]
76    pub fn checked_elapsed(&self, now: chrono::DateTime<Utc>) -> Option<Duration32> {
77        DateTime32::try_from(now)
78            .expect("unexpected out of range chrono::DateTime")
79            .checked_duration_since(*self)
80    }
81
82    /// Returns the duration elapsed since this time,
83    /// or if this time is in the future, returns zero.
84    pub fn saturating_elapsed(&self, now: chrono::DateTime<Utc>) -> Duration32 {
85        DateTime32::try_from(now)
86            .expect("unexpected out of range chrono::DateTime")
87            .saturating_duration_since(*self)
88    }
89
90    /// Returns the time that is `duration` after this time.
91    /// If the calculation overflows, returns `None`.
92    pub fn checked_add(&self, duration: Duration32) -> Option<DateTime32> {
93        self.timestamp
94            .checked_add(duration.seconds)
95            .map(DateTime32::from)
96    }
97
98    /// Returns the time that is `duration` after this time.
99    /// If the calculation overflows, returns `DateTime32::MAX`.
100    pub fn saturating_add(&self, duration: Duration32) -> DateTime32 {
101        DateTime32::from(self.timestamp.saturating_add(duration.seconds))
102    }
103
104    /// Returns the time that is `duration` before this time.
105    /// If the calculation underflows, returns `None`.
106    pub fn checked_sub(&self, duration: Duration32) -> Option<DateTime32> {
107        self.timestamp
108            .checked_sub(duration.seconds)
109            .map(DateTime32::from)
110    }
111
112    /// Returns the time that is `duration` before this time.
113    /// If the calculation underflows, returns `DateTime32::MIN`.
114    pub fn saturating_sub(&self, duration: Duration32) -> DateTime32 {
115        DateTime32::from(self.timestamp.saturating_sub(duration.seconds))
116    }
117}
118
119impl Duration32 {
120    /// The earliest possible `Duration32` value.
121    pub const MIN: Duration32 = Duration32 { seconds: u32::MIN };
122
123    /// The latest possible `Duration32` value.
124    pub const MAX: Duration32 = Duration32 { seconds: u32::MAX };
125
126    /// Creates a new [`Duration32`] to represent the given amount of seconds.
127    pub const fn from_seconds(seconds: u32) -> Self {
128        Duration32 { seconds }
129    }
130
131    /// Creates a new [`Duration32`] to represent the given amount of minutes.
132    ///
133    /// If the resulting number of seconds does not fit in a [`u32`], [`Duration32::MAX`] is
134    /// returned.
135    pub const fn from_minutes(minutes: u32) -> Self {
136        Duration32::from_seconds(minutes.saturating_mul(60))
137    }
138
139    /// Creates a new [`Duration32`] to represent the given amount of hours.
140    ///
141    /// If the resulting number of seconds does not fit in a [`u32`], [`Duration32::MAX`] is
142    /// returned.
143    pub const fn from_hours(hours: u32) -> Self {
144        Duration32::from_minutes(hours.saturating_mul(60))
145    }
146
147    /// Creates a new [`Duration32`] to represent the given amount of days.
148    ///
149    /// If the resulting number of seconds does not fit in a [`u32`], [`Duration32::MAX`] is
150    /// returned.
151    pub const fn from_days(days: u32) -> Self {
152        Duration32::from_hours(days.saturating_mul(24))
153    }
154
155    /// Returns the number of seconds in this duration.
156    pub fn seconds(&self) -> u32 {
157        self.seconds
158    }
159
160    /// Returns the equivalent [`chrono::Duration`].
161    pub fn to_chrono(self) -> chrono::Duration {
162        self.into()
163    }
164
165    /// Returns the equivalent [`std::time::Duration`].
166    pub fn to_std(self) -> std::time::Duration {
167        self.into()
168    }
169
170    /// Returns a duration that is `duration` longer than this duration.
171    /// If the calculation overflows, returns `None`.
172    pub fn checked_add(&self, duration: Duration32) -> Option<Duration32> {
173        self.seconds
174            .checked_add(duration.seconds)
175            .map(Duration32::from)
176    }
177
178    /// Returns a duration that is `duration` longer than this duration.
179    /// If the calculation overflows, returns `Duration32::MAX`.
180    pub fn saturating_add(&self, duration: Duration32) -> Duration32 {
181        Duration32::from(self.seconds.saturating_add(duration.seconds))
182    }
183
184    /// Returns a duration that is `duration` shorter than this duration.
185    /// If the calculation underflows, returns `None`.
186    pub fn checked_sub(&self, duration: Duration32) -> Option<Duration32> {
187        self.seconds
188            .checked_sub(duration.seconds)
189            .map(Duration32::from)
190    }
191
192    /// Returns a duration that is `duration` shorter than this duration.
193    /// If the calculation underflows, returns `Duration32::MIN`.
194    pub fn saturating_sub(&self, duration: Duration32) -> Duration32 {
195        Duration32::from(self.seconds.saturating_sub(duration.seconds))
196    }
197}
198
199impl fmt::Debug for DateTime32 {
200    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201        f.debug_struct("DateTime32")
202            .field("timestamp", &self.timestamp)
203            .field("calendar", &chrono::DateTime::<Utc>::from(*self))
204            .finish()
205    }
206}
207
208impl fmt::Debug for Duration32 {
209    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210        f.debug_struct("Duration32")
211            .field("seconds", &self.seconds)
212            .field("calendar", &chrono::Duration::from(*self))
213            .finish()
214    }
215}
216
217impl From<u32> for DateTime32 {
218    fn from(value: u32) -> Self {
219        DateTime32 { timestamp: value }
220    }
221}
222
223impl From<&u32> for DateTime32 {
224    fn from(value: &u32) -> Self {
225        (*value).into()
226    }
227}
228
229impl From<DateTime32> for chrono::DateTime<Utc> {
230    fn from(value: DateTime32) -> Self {
231        // chrono::DateTime is guaranteed to hold 32-bit values
232        Utc.timestamp_opt(value.timestamp.into(), 0)
233            .single()
234            .expect("in-range number of seconds and valid nanosecond")
235    }
236}
237
238impl From<&DateTime32> for chrono::DateTime<Utc> {
239    fn from(value: &DateTime32) -> Self {
240        (*value).into()
241    }
242}
243
244impl From<u32> for Duration32 {
245    fn from(value: u32) -> Self {
246        Duration32 { seconds: value }
247    }
248}
249
250impl From<&u32> for Duration32 {
251    fn from(value: &u32) -> Self {
252        (*value).into()
253    }
254}
255
256impl From<Duration32> for chrono::Duration {
257    fn from(value: Duration32) -> Self {
258        // chrono::Duration is guaranteed to hold 32-bit values
259        chrono::Duration::seconds(value.seconds.into())
260    }
261}
262
263impl From<&Duration32> for chrono::Duration {
264    fn from(value: &Duration32) -> Self {
265        (*value).into()
266    }
267}
268
269impl From<Duration32> for std::time::Duration {
270    fn from(value: Duration32) -> Self {
271        // std::time::Duration is guaranteed to hold 32-bit values
272        std::time::Duration::from_secs(value.seconds.into())
273    }
274}
275
276impl From<&Duration32> for std::time::Duration {
277    fn from(value: &Duration32) -> Self {
278        (*value).into()
279    }
280}
281
282impl TryFrom<chrono::DateTime<Utc>> for DateTime32 {
283    type Error = TryFromIntError;
284
285    /// Convert from a [`chrono::DateTime`] to a [`DateTime32`], discarding any nanoseconds.
286    ///
287    /// Conversion fails if the number of seconds since the UNIX epoch is outside the `u32` range.
288    fn try_from(value: chrono::DateTime<Utc>) -> Result<Self, Self::Error> {
289        Ok(Self {
290            timestamp: value.timestamp().try_into()?,
291        })
292    }
293}
294
295impl TryFrom<&chrono::DateTime<Utc>> for DateTime32 {
296    type Error = TryFromIntError;
297
298    /// Convert from a [`chrono::DateTime`] to a [`DateTime32`], discarding any nanoseconds.
299    ///
300    /// Conversion fails if the number of seconds since the UNIX epoch is outside the `u32` range.
301    fn try_from(value: &chrono::DateTime<Utc>) -> Result<Self, Self::Error> {
302        (*value).try_into()
303    }
304}
305
306impl TryFrom<chrono::Duration> for Duration32 {
307    type Error = TryFromIntError;
308
309    /// Convert from a [`chrono::Duration`] to a [`Duration32`], discarding any nanoseconds.
310    ///
311    /// Conversion fails if the number of seconds since the UNIX epoch is outside the `u32` range.
312    fn try_from(value: chrono::Duration) -> Result<Self, Self::Error> {
313        Ok(Self {
314            seconds: value.num_seconds().try_into()?,
315        })
316    }
317}
318
319impl TryFrom<&chrono::Duration> for Duration32 {
320    type Error = TryFromIntError;
321
322    /// Convert from a [`chrono::Duration`] to a [`Duration32`], discarding any nanoseconds.
323    ///
324    /// Conversion fails if the number of seconds in the duration is outside the `u32` range.
325    fn try_from(value: &chrono::Duration) -> Result<Self, Self::Error> {
326        (*value).try_into()
327    }
328}
329
330impl TryFrom<std::time::Duration> for Duration32 {
331    type Error = TryFromIntError;
332
333    /// Convert from a [`std::time::Duration`] to a [`Duration32`], discarding any nanoseconds.
334    ///
335    /// Conversion fails if the number of seconds in the duration is outside the `u32` range.
336    fn try_from(value: std::time::Duration) -> Result<Self, Self::Error> {
337        Ok(Self {
338            seconds: value.as_secs().try_into()?,
339        })
340    }
341}
342
343impl TryFrom<&std::time::Duration> for Duration32 {
344    type Error = TryFromIntError;
345
346    /// Convert from a [`std::time::Duration`] to a [`Duration32`], discarding any nanoseconds.
347    ///
348    /// Conversion fails if the number of seconds in the duration is outside the `u32` range.
349    fn try_from(value: &std::time::Duration) -> Result<Self, Self::Error> {
350        (*value).try_into()
351    }
352}
353
354impl FromStr for DateTime32 {
355    type Err = ParseIntError;
356
357    fn from_str(s: &str) -> Result<Self, Self::Err> {
358        Ok(DateTime32 {
359            timestamp: s.parse()?,
360        })
361    }
362}
363
364impl FromStr for Duration32 {
365    type Err = ParseIntError;
366
367    fn from_str(s: &str) -> Result<Self, Self::Err> {
368        Ok(Duration32 {
369            seconds: s.parse()?,
370        })
371    }
372}
373
374impl ZcashSerialize for DateTime32 {
375    fn zcash_serialize<W: std::io::Write>(&self, mut writer: W) -> Result<(), std::io::Error> {
376        writer.write_u32::<LittleEndian>(self.timestamp)
377    }
378}
379
380impl ZcashDeserialize for DateTime32 {
381    fn zcash_deserialize<R: std::io::Read>(mut reader: R) -> Result<Self, SerializationError> {
382        Ok(DateTime32 {
383            timestamp: reader.read_u32::<LittleEndian>()?,
384        })
385    }
386}