1//! DateTime types with specific serialization invariants.
23use std::{
4 fmt,
5 num::{ParseIntError, TryFromIntError},
6 str::FromStr,
7};
89use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
10use chrono::{TimeZone, Utc};
1112use crate::serialization::{SerializationError, ZcashDeserialize, ZcashSerialize};
1314/// 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}
2021/// 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}
2627impl DateTime32 {
28/// The earliest possible `DateTime32` value.
29pub const MIN: DateTime32 = DateTime32 {
30 timestamp: u32::MIN,
31 };
3233/// The latest possible `DateTime32` value.
34pub const MAX: DateTime32 = DateTime32 {
35 timestamp: u32::MAX,
36 };
3738/// Returns the number of seconds since the UNIX epoch.
39pub fn timestamp(&self) -> u32 {
40self.timestamp
41 }
4243/// Returns the equivalent [`chrono::DateTime`].
44pub fn to_chrono(self) -> chrono::DateTime<Utc> {
45self.into()
46 }
4748/// Returns the current time.
49 ///
50 /// # Panics
51 ///
52 /// If the number of seconds since the UNIX epoch is greater than `u32::MAX`.
53pub fn now() -> DateTime32 {
54 chrono::Utc::now()
55 .try_into()
56 .expect("unexpected out of range chrono::DateTime")
57 }
5859/// Returns the duration elapsed between `earlier` and this time,
60 /// or `None` if `earlier` is later than this time.
61pub fn checked_duration_since(&self, earlier: DateTime32) -> Option<Duration32> {
62self.timestamp
63 .checked_sub(earlier.timestamp)
64 .map(Duration32::from)
65 }
6667/// Returns duration elapsed between `earlier` and this time,
68 /// or zero if `earlier` is later than this time.
69pub fn saturating_duration_since(&self, earlier: DateTime32) -> Duration32 {
70 Duration32::from(self.timestamp.saturating_sub(earlier.timestamp))
71 }
7273/// Returns the duration elapsed since this time,
74 /// or if this time is in the future, returns `None`.
75#[allow(clippy::unwrap_in_result)]
76pub 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 }
8182/// Returns the duration elapsed since this time,
83 /// or if this time is in the future, returns zero.
84pub 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 }
8990/// Returns the time that is `duration` after this time.
91 /// If the calculation overflows, returns `None`.
92pub fn checked_add(&self, duration: Duration32) -> Option<DateTime32> {
93self.timestamp
94 .checked_add(duration.seconds)
95 .map(DateTime32::from)
96 }
9798/// Returns the time that is `duration` after this time.
99 /// If the calculation overflows, returns `DateTime32::MAX`.
100pub fn saturating_add(&self, duration: Duration32) -> DateTime32 {
101 DateTime32::from(self.timestamp.saturating_add(duration.seconds))
102 }
103104/// Returns the time that is `duration` before this time.
105 /// If the calculation underflows, returns `None`.
106pub fn checked_sub(&self, duration: Duration32) -> Option<DateTime32> {
107self.timestamp
108 .checked_sub(duration.seconds)
109 .map(DateTime32::from)
110 }
111112/// Returns the time that is `duration` before this time.
113 /// If the calculation underflows, returns `DateTime32::MIN`.
114pub fn saturating_sub(&self, duration: Duration32) -> DateTime32 {
115 DateTime32::from(self.timestamp.saturating_sub(duration.seconds))
116 }
117}
118119impl Duration32 {
120/// The earliest possible `Duration32` value.
121pub const MIN: Duration32 = Duration32 { seconds: u32::MIN };
122123/// The latest possible `Duration32` value.
124pub const MAX: Duration32 = Duration32 { seconds: u32::MAX };
125126/// Creates a new [`Duration32`] to represent the given amount of seconds.
127pub const fn from_seconds(seconds: u32) -> Self {
128 Duration32 { seconds }
129 }
130131/// 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.
135pub const fn from_minutes(minutes: u32) -> Self {
136 Duration32::from_seconds(minutes.saturating_mul(60))
137 }
138139/// 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.
143pub const fn from_hours(hours: u32) -> Self {
144 Duration32::from_minutes(hours.saturating_mul(60))
145 }
146147/// 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.
151pub const fn from_days(days: u32) -> Self {
152 Duration32::from_hours(days.saturating_mul(24))
153 }
154155/// Returns the number of seconds in this duration.
156pub fn seconds(&self) -> u32 {
157self.seconds
158 }
159160/// Returns the equivalent [`chrono::Duration`].
161pub fn to_chrono(self) -> chrono::Duration {
162self.into()
163 }
164165/// Returns the equivalent [`std::time::Duration`].
166pub fn to_std(self) -> std::time::Duration {
167self.into()
168 }
169170/// Returns a duration that is `duration` longer than this duration.
171 /// If the calculation overflows, returns `None`.
172pub fn checked_add(&self, duration: Duration32) -> Option<Duration32> {
173self.seconds
174 .checked_add(duration.seconds)
175 .map(Duration32::from)
176 }
177178/// Returns a duration that is `duration` longer than this duration.
179 /// If the calculation overflows, returns `Duration32::MAX`.
180pub fn saturating_add(&self, duration: Duration32) -> Duration32 {
181 Duration32::from(self.seconds.saturating_add(duration.seconds))
182 }
183184/// Returns a duration that is `duration` shorter than this duration.
185 /// If the calculation underflows, returns `None`.
186pub fn checked_sub(&self, duration: Duration32) -> Option<Duration32> {
187self.seconds
188 .checked_sub(duration.seconds)
189 .map(Duration32::from)
190 }
191192/// Returns a duration that is `duration` shorter than this duration.
193 /// If the calculation underflows, returns `Duration32::MIN`.
194pub fn saturating_sub(&self, duration: Duration32) -> Duration32 {
195 Duration32::from(self.seconds.saturating_sub(duration.seconds))
196 }
197}
198199impl fmt::Debug for DateTime32 {
200fn 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}
207208impl fmt::Debug for Duration32 {
209fn 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}
216217impl From<u32> for DateTime32 {
218fn from(value: u32) -> Self {
219 DateTime32 { timestamp: value }
220 }
221}
222223impl From<&u32> for DateTime32 {
224fn from(value: &u32) -> Self {
225 (*value).into()
226 }
227}
228229impl From<DateTime32> for chrono::DateTime<Utc> {
230fn from(value: DateTime32) -> Self {
231// chrono::DateTime is guaranteed to hold 32-bit values
232Utc.timestamp_opt(value.timestamp.into(), 0)
233 .single()
234 .expect("in-range number of seconds and valid nanosecond")
235 }
236}
237238impl From<&DateTime32> for chrono::DateTime<Utc> {
239fn from(value: &DateTime32) -> Self {
240 (*value).into()
241 }
242}
243244impl From<u32> for Duration32 {
245fn from(value: u32) -> Self {
246 Duration32 { seconds: value }
247 }
248}
249250impl From<&u32> for Duration32 {
251fn from(value: &u32) -> Self {
252 (*value).into()
253 }
254}
255256impl From<Duration32> for chrono::Duration {
257fn from(value: Duration32) -> Self {
258// chrono::Duration is guaranteed to hold 32-bit values
259chrono::Duration::seconds(value.seconds.into())
260 }
261}
262263impl From<&Duration32> for chrono::Duration {
264fn from(value: &Duration32) -> Self {
265 (*value).into()
266 }
267}
268269impl From<Duration32> for std::time::Duration {
270fn from(value: Duration32) -> Self {
271// std::time::Duration is guaranteed to hold 32-bit values
272std::time::Duration::from_secs(value.seconds.into())
273 }
274}
275276impl From<&Duration32> for std::time::Duration {
277fn from(value: &Duration32) -> Self {
278 (*value).into()
279 }
280}
281282impl TryFrom<chrono::DateTime<Utc>> for DateTime32 {
283type Error = TryFromIntError;
284285/// 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.
288fn try_from(value: chrono::DateTime<Utc>) -> Result<Self, Self::Error> {
289Ok(Self {
290 timestamp: value.timestamp().try_into()?,
291 })
292 }
293}
294295impl TryFrom<&chrono::DateTime<Utc>> for DateTime32 {
296type Error = TryFromIntError;
297298/// 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.
301fn try_from(value: &chrono::DateTime<Utc>) -> Result<Self, Self::Error> {
302 (*value).try_into()
303 }
304}
305306impl TryFrom<chrono::Duration> for Duration32 {
307type Error = TryFromIntError;
308309/// 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.
312fn try_from(value: chrono::Duration) -> Result<Self, Self::Error> {
313Ok(Self {
314 seconds: value.num_seconds().try_into()?,
315 })
316 }
317}
318319impl TryFrom<&chrono::Duration> for Duration32 {
320type Error = TryFromIntError;
321322/// 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.
325fn try_from(value: &chrono::Duration) -> Result<Self, Self::Error> {
326 (*value).try_into()
327 }
328}
329330impl TryFrom<std::time::Duration> for Duration32 {
331type Error = TryFromIntError;
332333/// 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.
336fn try_from(value: std::time::Duration) -> Result<Self, Self::Error> {
337Ok(Self {
338 seconds: value.as_secs().try_into()?,
339 })
340 }
341}
342343impl TryFrom<&std::time::Duration> for Duration32 {
344type Error = TryFromIntError;
345346/// 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.
349fn try_from(value: &std::time::Duration) -> Result<Self, Self::Error> {
350 (*value).try_into()
351 }
352}
353354impl FromStr for DateTime32 {
355type Err = ParseIntError;
356357fn from_str(s: &str) -> Result<Self, Self::Err> {
358Ok(DateTime32 {
359 timestamp: s.parse()?,
360 })
361 }
362}
363364impl FromStr for Duration32 {
365type Err = ParseIntError;
366367fn from_str(s: &str) -> Result<Self, Self::Err> {
368Ok(Duration32 {
369 seconds: s.parse()?,
370 })
371 }
372}
373374impl ZcashSerialize for DateTime32 {
375fn zcash_serialize<W: std::io::Write>(&self, mut writer: W) -> Result<(), std::io::Error> {
376 writer.write_u32::<LittleEndian>(self.timestamp)
377 }
378}
379380impl ZcashDeserialize for DateTime32 {
381fn zcash_deserialize<R: std::io::Read>(mut reader: R) -> Result<Self, SerializationError> {
382Ok(DateTime32 {
383 timestamp: reader.read_u32::<LittleEndian>()?,
384 })
385 }
386}