casper_types/
timestamp.rs

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