tendermint_proto/google/protobuf/
timestamp.rs

1// Original code from <https://github.com/influxdata/pbjson/blob/main/pbjson-types/src/timestamp.rs>
2// Copyright 2022 Dan Burkert & Tokio Contributors
3
4use prost::Name;
5
6use crate::prelude::*;
7
8use super::type_url::type_url_for;
9use super::PACKAGE;
10
11/// A Timestamp represents a point in time independent of any time zone or local
12/// calendar, encoded as a count of seconds and fractions of seconds at
13/// nanosecond resolution. The count is relative to an epoch at UTC midnight on
14/// January 1, 1970, in the proleptic Gregorian calendar which extends the
15/// Gregorian calendar backwards to year one.
16///
17/// All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
18/// second table is needed for interpretation, using a
19/// [24-hour linear smear](https://developers.google.com/time/smear).
20///
21/// The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
22/// restricting to that range, we ensure that we can convert to and from
23/// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
24#[derive(Copy, Clone, PartialEq, ::prost::Message, ::serde::Deserialize, ::serde::Serialize)]
25#[serde(
26    from = "crate::serializers::timestamp::Rfc3339",
27    into = "crate::serializers::timestamp::Rfc3339"
28)]
29#[cfg_attr(feature = "json-schema", derive(::schemars::JsonSchema))]
30pub struct Timestamp {
31    /// Represents seconds of UTC time since Unix epoch
32    /// 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
33    /// 9999-12-31T23:59:59Z inclusive.
34    #[prost(int64, tag = "1")]
35    pub seconds: i64,
36    /// Non-negative fractions of a second at nanosecond resolution. Negative
37    /// second values with fractions must still have non-negative nanos values
38    /// that count forward in time. Must be from 0 to 999,999,999
39    /// inclusive.
40    #[prost(int32, tag = "2")]
41    pub nanos: i32,
42}
43
44impl Name for Timestamp {
45    const PACKAGE: &'static str = PACKAGE;
46    const NAME: &'static str = "Timestamp";
47
48    fn type_url() -> String {
49        type_url_for::<Self>()
50    }
51}
52
53const NANOS_PER_SECOND: i32 = 1_000_000_000;
54
55impl Timestamp {
56    /// Normalizes the timestamp to a canonical format.
57    pub fn normalize(&mut self) {
58        // Make sure nanos is in the range.
59        if self.nanos <= -NANOS_PER_SECOND || self.nanos >= NANOS_PER_SECOND {
60            if let Some(seconds) = self
61                .seconds
62                .checked_add((self.nanos / NANOS_PER_SECOND) as i64)
63            {
64                self.seconds = seconds;
65                self.nanos %= NANOS_PER_SECOND;
66            } else if self.nanos < 0 {
67                // Negative overflow! Set to the earliest normal value.
68                self.seconds = i64::MIN;
69                self.nanos = 0;
70            } else {
71                // Positive overflow! Set to the latest normal value.
72                self.seconds = i64::MAX;
73                self.nanos = 999_999_999;
74            }
75        }
76
77        // For Timestamp nanos should be in the range [0, 999999999].
78        if self.nanos < 0 {
79            if let Some(seconds) = self.seconds.checked_sub(1) {
80                self.seconds = seconds;
81                self.nanos += NANOS_PER_SECOND;
82            } else {
83                // Negative overflow! Set to the earliest normal value.
84                debug_assert_eq!(self.seconds, i64::MIN);
85                self.nanos = 0;
86            }
87        }
88    }
89}
90
91/// Implements the unstable/naive version of `Eq`: a basic equality check on the internal fields of the `Timestamp`.
92/// This implies that `normalized_ts != non_normalized_ts` even if `normalized_ts == non_normalized_ts.normalized()`.
93impl Eq for Timestamp {}
94
95// Derived logic is correct: comparing the 2 fields for equality
96#[allow(clippy::derived_hash_with_manual_eq)]
97impl core::hash::Hash for Timestamp {
98    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
99        self.seconds.hash(state);
100        self.nanos.hash(state);
101    }
102}
103
104#[cfg(feature = "std")]
105impl From<std::time::SystemTime> for Timestamp {
106    fn from(system_time: std::time::SystemTime) -> Timestamp {
107        let (seconds, nanos) = match system_time.duration_since(std::time::UNIX_EPOCH) {
108            Ok(duration) => {
109                let seconds = i64::try_from(duration.as_secs()).unwrap();
110                (seconds, duration.subsec_nanos() as i32)
111            },
112            Err(error) => {
113                let duration = error.duration();
114                let seconds = i64::try_from(duration.as_secs()).unwrap();
115                let nanos = duration.subsec_nanos() as i32;
116                if nanos == 0 {
117                    (-seconds, 0)
118                } else {
119                    (-seconds - 1, 1_000_000_000 - nanos)
120                }
121            },
122        };
123        Timestamp { seconds, nanos }
124    }
125}
126
127/// Indicates that a [`Timestamp`] could not be converted to
128/// [`SystemTime`][std::time::SystemTime] because it is out of range.
129///
130/// The range of times that can be represented by `SystemTime` depends on the platform.
131/// All `Timestamp`s are likely representable on 64-bit Unix-like platforms, but
132/// other platforms, such as Windows and 32-bit Linux, may not be able to represent
133/// the full range of `Timestamp`s.
134#[cfg(feature = "std")]
135#[derive(Debug)]
136#[non_exhaustive]
137pub struct TimestampOutOfSystemRangeError {
138    pub timestamp: Timestamp,
139}
140
141#[cfg(feature = "std")]
142impl core::fmt::Display for TimestampOutOfSystemRangeError {
143    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
144        write!(
145            f,
146            "{self:?} is not representable as a `SystemTime` because it is out of range"
147        )
148    }
149}
150
151#[cfg(feature = "std")]
152impl std::error::Error for TimestampOutOfSystemRangeError {}
153
154#[cfg(feature = "std")]
155impl TryFrom<Timestamp> for std::time::SystemTime {
156    type Error = TimestampOutOfSystemRangeError;
157
158    fn try_from(mut timestamp: Timestamp) -> Result<std::time::SystemTime, Self::Error> {
159        let orig_timestamp = timestamp;
160
161        timestamp.normalize();
162
163        let system_time = if timestamp.seconds >= 0 {
164            std::time::UNIX_EPOCH
165                .checked_add(core::time::Duration::from_secs(timestamp.seconds as u64))
166        } else {
167            std::time::UNIX_EPOCH
168                .checked_sub(core::time::Duration::from_secs((-timestamp.seconds) as u64))
169        };
170
171        let system_time = system_time.and_then(|system_time| {
172            system_time.checked_add(core::time::Duration::from_nanos(timestamp.nanos as u64))
173        });
174
175        system_time.ok_or(TimestampOutOfSystemRangeError {
176            timestamp: orig_timestamp,
177        })
178    }
179}