casper_types/
timestamp.rs

1// TODO - remove once schemars stops causing warning.
2#![allow(clippy::field_reassign_with_default)]
3
4use alloc::vec::Vec;
5use core::{
6    ops::{Add, AddAssign, Div, Mul, Rem, Shl, Shr, Sub, SubAssign},
7    time::Duration,
8};
9#[cfg(any(feature = "std", test))]
10use std::{
11    fmt::{self, Display, Formatter},
12    str::FromStr,
13    time::SystemTime,
14};
15
16#[cfg(feature = "datasize")]
17use datasize::DataSize;
18#[cfg(any(feature = "std", test))]
19use humantime::{DurationError, TimestampError};
20#[cfg(any(feature = "testing", test))]
21use rand::Rng;
22#[cfg(feature = "json-schema")]
23use schemars::JsonSchema;
24#[cfg(any(feature = "std", test))]
25use serde::{de::Error as SerdeError, Deserialize, Deserializer, Serialize, Serializer};
26
27use crate::bytesrepr::{self, FromBytes, ToBytes};
28
29#[cfg(any(feature = "testing", test))]
30use crate::testing::TestRng;
31
32/// A timestamp type, representing a concrete moment in time.
33#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
34#[cfg_attr(feature = "datasize", derive(DataSize))]
35#[cfg_attr(
36    feature = "json-schema",
37    derive(JsonSchema),
38    schemars(with = "String", description = "Timestamp formatted as per RFC 3339")
39)]
40pub struct Timestamp(u64);
41
42impl Timestamp {
43    /// The maximum value a timestamp can have.
44    pub const MAX: Timestamp = Timestamp(u64::MAX);
45
46    #[cfg(any(feature = "std", test))]
47    /// Returns the timestamp of the current moment.
48    pub fn now() -> Self {
49        let millis = SystemTime::UNIX_EPOCH.elapsed().unwrap().as_millis() as u64;
50        Timestamp(millis)
51    }
52
53    #[cfg(any(feature = "std", test))]
54    /// Returns the time that has elapsed since this timestamp.
55    pub fn elapsed(&self) -> TimeDiff {
56        TimeDiff(Timestamp::now().0.saturating_sub(self.0))
57    }
58
59    /// Returns a zero timestamp.
60    pub fn zero() -> Self {
61        Timestamp(0)
62    }
63
64    /// Returns the timestamp as the number of milliseconds since the Unix epoch
65    pub fn millis(&self) -> u64 {
66        self.0
67    }
68
69    /// Returns the difference between `self` and `other`, or `0` if `self` is earlier than `other`.
70    pub fn saturating_diff(self, other: Timestamp) -> TimeDiff {
71        TimeDiff(self.0.saturating_sub(other.0))
72    }
73
74    /// Returns the difference between `self` and `other`, or `0` if that would be before the epoch.
75    #[must_use]
76    pub fn saturating_sub(self, other: TimeDiff) -> Timestamp {
77        Timestamp(self.0.saturating_sub(other.0))
78    }
79
80    /// Returns the sum of `self` and `other`, or the maximum possible value if that would be
81    /// exceeded.
82    #[must_use]
83    pub fn saturating_add(self, other: TimeDiff) -> Timestamp {
84        Timestamp(self.0.saturating_add(other.0))
85    }
86
87    /// Returns the number of trailing zeros in the number of milliseconds since the epoch.
88    pub fn trailing_zeros(&self) -> u8 {
89        self.0.trailing_zeros() as u8
90    }
91}
92
93#[cfg(any(feature = "testing", test))]
94impl Timestamp {
95    /// Generates a random instance using a `TestRng`.
96    pub fn random(rng: &mut TestRng) -> Self {
97        Timestamp(1_596_763_000_000 + rng.gen_range(200_000..1_000_000))
98    }
99
100    /// Checked subtraction for timestamps
101    pub fn checked_sub(self, other: TimeDiff) -> Option<Timestamp> {
102        self.0.checked_sub(other.0).map(Timestamp)
103    }
104}
105
106#[cfg(any(feature = "std", test))]
107impl Display for Timestamp {
108    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
109        match SystemTime::UNIX_EPOCH.checked_add(Duration::from_millis(self.0)) {
110            Some(system_time) => write!(f, "{}", humantime::format_rfc3339_millis(system_time))
111                .or_else(|e| write!(f, "Invalid timestamp: {}: {}", e, self.0)),
112            None => write!(f, "invalid Timestamp: {} ms after the Unix epoch", self.0),
113        }
114    }
115}
116
117#[cfg(any(feature = "std", test))]
118impl FromStr for Timestamp {
119    type Err = TimestampError;
120
121    fn from_str(value: &str) -> Result<Self, Self::Err> {
122        let system_time = humantime::parse_rfc3339_weak(value)?;
123        let inner = system_time
124            .duration_since(SystemTime::UNIX_EPOCH)
125            .map_err(|_| TimestampError::OutOfRange)?
126            .as_millis() as u64;
127        Ok(Timestamp(inner))
128    }
129}
130
131impl Add<TimeDiff> for Timestamp {
132    type Output = Timestamp;
133
134    fn add(self, diff: TimeDiff) -> Timestamp {
135        Timestamp(self.0 + diff.0)
136    }
137}
138
139impl AddAssign<TimeDiff> for Timestamp {
140    fn add_assign(&mut self, rhs: TimeDiff) {
141        self.0 += rhs.0;
142    }
143}
144
145#[cfg(any(feature = "testing", test))]
146impl std::ops::Sub<TimeDiff> for Timestamp {
147    type Output = Timestamp;
148
149    fn sub(self, diff: TimeDiff) -> Timestamp {
150        Timestamp(self.0 - diff.0)
151    }
152}
153
154impl Rem<TimeDiff> for Timestamp {
155    type Output = TimeDiff;
156
157    fn rem(self, diff: TimeDiff) -> TimeDiff {
158        TimeDiff(self.0 % diff.0)
159    }
160}
161
162impl<T> Shl<T> for Timestamp
163where
164    u64: Shl<T, Output = u64>,
165{
166    type Output = Timestamp;
167
168    fn shl(self, rhs: T) -> Timestamp {
169        Timestamp(self.0 << rhs)
170    }
171}
172
173impl<T> Shr<T> for Timestamp
174where
175    u64: Shr<T, Output = u64>,
176{
177    type Output = Timestamp;
178
179    fn shr(self, rhs: T) -> Timestamp {
180        Timestamp(self.0 >> rhs)
181    }
182}
183
184#[cfg(any(feature = "std", test))]
185impl Serialize for Timestamp {
186    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
187        if serializer.is_human_readable() {
188            self.to_string().serialize(serializer)
189        } else {
190            self.0.serialize(serializer)
191        }
192    }
193}
194
195#[cfg(any(feature = "std", test))]
196impl<'de> Deserialize<'de> for Timestamp {
197    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
198        if deserializer.is_human_readable() {
199            let value_as_string = String::deserialize(deserializer)?;
200            Timestamp::from_str(&value_as_string).map_err(SerdeError::custom)
201        } else {
202            let inner = u64::deserialize(deserializer)?;
203            Ok(Timestamp(inner))
204        }
205    }
206}
207
208impl ToBytes for Timestamp {
209    fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
210        self.0.to_bytes()
211    }
212
213    fn serialized_length(&self) -> usize {
214        self.0.serialized_length()
215    }
216}
217
218impl FromBytes for Timestamp {
219    fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
220        u64::from_bytes(bytes).map(|(inner, remainder)| (Timestamp(inner), remainder))
221    }
222}
223
224impl From<u64> for Timestamp {
225    fn from(milliseconds_since_epoch: u64) -> Timestamp {
226        Timestamp(milliseconds_since_epoch)
227    }
228}
229
230/// A time difference between two timestamps.
231#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
232#[cfg_attr(feature = "datasize", derive(DataSize))]
233#[cfg_attr(
234    feature = "json-schema",
235    derive(JsonSchema),
236    schemars(with = "String", description = "Human-readable duration.")
237)]
238pub struct TimeDiff(u64);
239
240#[cfg(any(feature = "std", test))]
241impl Display for TimeDiff {
242    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
243        write!(f, "{}", humantime::format_duration(Duration::from(*self)))
244    }
245}
246
247#[cfg(any(feature = "std", test))]
248impl FromStr for TimeDiff {
249    type Err = DurationError;
250
251    fn from_str(value: &str) -> Result<Self, Self::Err> {
252        let inner = humantime::parse_duration(value)?.as_millis() as u64;
253        Ok(TimeDiff(inner))
254    }
255}
256
257impl TimeDiff {
258    /// Returns the time difference as the number of milliseconds since the Unix epoch
259    pub fn millis(&self) -> u64 {
260        self.0
261    }
262
263    /// Creates a new time difference from seconds.
264    pub const fn from_seconds(seconds: u32) -> Self {
265        TimeDiff(seconds as u64 * 1_000)
266    }
267
268    /// Creates a new time difference from milliseconds.
269    pub const fn from_millis(millis: u64) -> Self {
270        TimeDiff(millis)
271    }
272
273    /// Returns the sum, or `TimeDiff(u64::MAX)` if it would overflow.
274    #[must_use]
275    pub fn saturating_add(self, rhs: u64) -> Self {
276        TimeDiff(self.0.saturating_add(rhs))
277    }
278
279    /// Returns the product, or `TimeDiff(u64::MAX)` if it would overflow.
280    #[must_use]
281    pub fn saturating_mul(self, rhs: u64) -> Self {
282        TimeDiff(self.0.saturating_mul(rhs))
283    }
284
285    /// Returns the product, or `None` if it would overflow.
286    #[must_use]
287    pub fn checked_mul(self, rhs: u64) -> Option<Self> {
288        Some(TimeDiff(self.0.checked_mul(rhs)?))
289    }
290}
291
292impl Add<TimeDiff> for TimeDiff {
293    type Output = TimeDiff;
294
295    fn add(self, rhs: TimeDiff) -> TimeDiff {
296        TimeDiff(self.0 + rhs.0)
297    }
298}
299
300impl AddAssign<TimeDiff> for TimeDiff {
301    fn add_assign(&mut self, rhs: TimeDiff) {
302        self.0 += rhs.0;
303    }
304}
305
306impl Sub<TimeDiff> for TimeDiff {
307    type Output = TimeDiff;
308
309    fn sub(self, rhs: TimeDiff) -> TimeDiff {
310        TimeDiff(self.0 - rhs.0)
311    }
312}
313
314impl SubAssign<TimeDiff> for TimeDiff {
315    fn sub_assign(&mut self, rhs: TimeDiff) {
316        self.0 -= rhs.0;
317    }
318}
319
320impl Mul<u64> for TimeDiff {
321    type Output = TimeDiff;
322
323    fn mul(self, rhs: u64) -> TimeDiff {
324        TimeDiff(self.0 * rhs)
325    }
326}
327
328impl Div<u64> for TimeDiff {
329    type Output = TimeDiff;
330
331    fn div(self, rhs: u64) -> TimeDiff {
332        TimeDiff(self.0 / rhs)
333    }
334}
335
336impl Div<TimeDiff> for TimeDiff {
337    type Output = u64;
338
339    fn div(self, rhs: TimeDiff) -> u64 {
340        self.0 / rhs.0
341    }
342}
343
344impl From<TimeDiff> for Duration {
345    fn from(diff: TimeDiff) -> Duration {
346        Duration::from_millis(diff.0)
347    }
348}
349
350#[cfg(any(feature = "std", test))]
351impl Serialize for TimeDiff {
352    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
353        if serializer.is_human_readable() {
354            self.to_string().serialize(serializer)
355        } else {
356            self.0.serialize(serializer)
357        }
358    }
359}
360
361#[cfg(any(feature = "std", test))]
362impl<'de> Deserialize<'de> for TimeDiff {
363    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
364        if deserializer.is_human_readable() {
365            let value_as_string = String::deserialize(deserializer)?;
366            TimeDiff::from_str(&value_as_string).map_err(SerdeError::custom)
367        } else {
368            let inner = u64::deserialize(deserializer)?;
369            Ok(TimeDiff(inner))
370        }
371    }
372}
373
374impl ToBytes for TimeDiff {
375    fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
376        self.0.to_bytes()
377    }
378
379    fn serialized_length(&self) -> usize {
380        self.0.serialized_length()
381    }
382}
383
384impl FromBytes for TimeDiff {
385    fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
386        u64::from_bytes(bytes).map(|(inner, remainder)| (TimeDiff(inner), remainder))
387    }
388}
389
390impl From<Duration> for TimeDiff {
391    fn from(duration: Duration) -> TimeDiff {
392        TimeDiff(duration.as_millis() as u64)
393    }
394}
395
396/// A module for the `[serde(with = serde_option_time_diff)]` attribute, to serialize and
397/// deserialize `Option<TimeDiff>` treating `None` as 0.
398#[cfg(any(feature = "std", test))]
399pub mod serde_option_time_diff {
400    use super::*;
401
402    /// Serializes an `Option<TimeDiff>`, using `0` if the value is `None`.
403    pub fn serialize<S: Serializer>(
404        maybe_td: &Option<TimeDiff>,
405        serializer: S,
406    ) -> Result<S::Ok, S::Error> {
407        maybe_td
408            .unwrap_or_else(|| TimeDiff::from_millis(0))
409            .serialize(serializer)
410    }
411
412    /// Deserializes an `Option<TimeDiff>`, returning `None` if the value is `0`.
413    pub fn deserialize<'de, D: Deserializer<'de>>(
414        deserializer: D,
415    ) -> Result<Option<TimeDiff>, D::Error> {
416        let td = TimeDiff::deserialize(deserializer)?;
417        if td.0 == 0 {
418            Ok(None)
419        } else {
420            Ok(Some(td))
421        }
422    }
423}
424
425#[cfg(test)]
426mod tests {
427    use super::*;
428
429    #[test]
430    fn timestamp_serialization_roundtrip() {
431        let timestamp = Timestamp::now();
432
433        let timestamp_as_string = timestamp.to_string();
434        assert_eq!(
435            timestamp,
436            Timestamp::from_str(&timestamp_as_string).unwrap()
437        );
438
439        let serialized_json = serde_json::to_string(&timestamp).unwrap();
440        assert_eq!(timestamp, serde_json::from_str(&serialized_json).unwrap());
441
442        let serialized_bincode = bincode::serialize(&timestamp).unwrap();
443        assert_eq!(
444            timestamp,
445            bincode::deserialize(&serialized_bincode).unwrap()
446        );
447
448        bytesrepr::test_serialization_roundtrip(&timestamp);
449    }
450
451    #[test]
452    fn timediff_serialization_roundtrip() {
453        let mut rng = TestRng::new();
454        let timediff = TimeDiff(rng.gen());
455
456        let timediff_as_string = timediff.to_string();
457        assert_eq!(timediff, TimeDiff::from_str(&timediff_as_string).unwrap());
458
459        let serialized_json = serde_json::to_string(&timediff).unwrap();
460        assert_eq!(timediff, serde_json::from_str(&serialized_json).unwrap());
461
462        let serialized_bincode = bincode::serialize(&timediff).unwrap();
463        assert_eq!(timediff, bincode::deserialize(&serialized_bincode).unwrap());
464
465        bytesrepr::test_serialization_roundtrip(&timediff);
466    }
467
468    #[test]
469    fn does_not_crash_for_big_timestamp_value() {
470        assert!(Timestamp::MAX.to_string().starts_with("Invalid timestamp:"));
471    }
472}