prost_wkt_types/pbtime/
timestamp.rs

1use super::*;
2
3////////////////////////////////////////////////////////////////////////////////
4/// FROM prost-types/src/timestamp.rs
5////////////////////////////////////////////////////////////////////////////////
6
7impl Timestamp {
8    /// Normalizes the timestamp to a canonical format.
9    ///
10    /// Based on [`google::protobuf::util::CreateNormalized`][1].
11    ///
12    /// [1]: https://github.com/google/protobuf/blob/v3.3.2/src/google/protobuf/util/time_util.cc#L59-L77
13    pub fn normalize(&mut self) {
14        // Make sure nanos is in the range.
15        if self.nanos <= -NANOS_PER_SECOND || self.nanos >= NANOS_PER_SECOND {
16            if let Some(seconds) = self
17                .seconds
18                .checked_add((self.nanos / NANOS_PER_SECOND) as i64)
19            {
20                self.seconds = seconds;
21                self.nanos %= NANOS_PER_SECOND;
22            } else if self.nanos < 0 {
23                // Negative overflow! Set to the earliest normal value.
24                self.seconds = i64::MIN;
25                self.nanos = 0;
26            } else {
27                // Positive overflow! Set to the latest normal value.
28                self.seconds = i64::MAX;
29                self.nanos = 999_999_999;
30            }
31        }
32
33        // For Timestamp nanos should be in the range [0, 999999999].
34        if self.nanos < 0 {
35            if let Some(seconds) = self.seconds.checked_sub(1) {
36                self.seconds = seconds;
37                self.nanos += NANOS_PER_SECOND;
38            } else {
39                // Negative overflow! Set to the earliest normal value.
40                debug_assert_eq!(self.seconds, i64::MIN);
41                self.nanos = 0;
42            }
43        }
44
45        // TODO: should this be checked?
46        // debug_assert!(self.seconds >= -62_135_596_800 && self.seconds <= 253_402_300_799,
47        //               "invalid timestamp: {:?}", self);
48    }
49
50    /// Normalizes the timestamp to a canonical format, returning the original value if it cannot be
51    /// normalized.
52    ///
53    /// Normalization is based on [`google::protobuf::util::CreateNormalized`][1].
54    ///
55    /// [1]: https://github.com/google/protobuf/blob/v3.3.2/src/google/protobuf/util/time_util.cc#L59-L77
56    pub fn try_normalize(mut self) -> Result<Timestamp, Timestamp> {
57        let before = self;
58        self.normalize();
59        // If the seconds value has changed, and is either i64::MIN or i64::MAX, then the timestamp
60        // normalization overflowed.
61        if (self.seconds == i64::MAX || self.seconds == i64::MIN) && self.seconds != before.seconds
62        {
63            Err(before)
64        } else {
65            Ok(self)
66        }
67    }
68
69    /// Creates a new `Timestamp` at the start of the provided UTC date.
70    pub fn date(year: i64, month: u8, day: u8) -> Result<Timestamp, TimestampError> {
71        Timestamp::date_time_nanos(year, month, day, 0, 0, 0, 0)
72    }
73
74    /// Creates a new `Timestamp` instance with the provided UTC date and time.
75    pub fn date_time(
76        year: i64,
77        month: u8,
78        day: u8,
79        hour: u8,
80        minute: u8,
81        second: u8,
82    ) -> Result<Timestamp, TimestampError> {
83        Timestamp::date_time_nanos(year, month, day, hour, minute, second, 0)
84    }
85
86    /// Creates a new `Timestamp` instance with the provided UTC date and time.
87    pub fn date_time_nanos(
88        year: i64,
89        month: u8,
90        day: u8,
91        hour: u8,
92        minute: u8,
93        second: u8,
94        nanos: u32,
95    ) -> Result<Timestamp, TimestampError> {
96        let date_time = datetime::DateTime {
97            year,
98            month,
99            day,
100            hour,
101            minute,
102            second,
103            nanos,
104        };
105
106        Timestamp::try_from(date_time)
107    }
108}
109
110// impl Name for Timestamp {
111//     const PACKAGE: &'static str = PACKAGE;
112//     const NAME: &'static str = "Timestamp";
113
114//     fn type_url() -> String {
115//         type_url_for::<Self>()
116//     }
117// }
118
119#[cfg(feature = "std")]
120impl From<std::time::SystemTime> for Timestamp {
121    fn from(system_time: std::time::SystemTime) -> Timestamp {
122        let (seconds, nanos) = match system_time.duration_since(std::time::UNIX_EPOCH) {
123            Ok(duration) => {
124                let seconds = i64::try_from(duration.as_secs()).unwrap();
125                (seconds, duration.subsec_nanos() as i32)
126            }
127            Err(error) => {
128                let duration = error.duration();
129                let seconds = i64::try_from(duration.as_secs()).unwrap();
130                let nanos = duration.subsec_nanos() as i32;
131                if nanos == 0 {
132                    (-seconds, 0)
133                } else {
134                    (-seconds - 1, 1_000_000_000 - nanos)
135                }
136            }
137        };
138        Timestamp { seconds, nanos }
139    }
140}
141
142/// A timestamp handling error.
143#[allow(clippy::derive_partial_eq_without_eq)]
144#[derive(Debug, PartialEq)]
145#[non_exhaustive]
146pub enum TimestampError {
147    /// Indicates that a [`Timestamp`] could not be converted to
148    /// [`SystemTime`][std::time::SystemTime] because it is out of range.
149    ///
150    /// The range of times that can be represented by `SystemTime` depends on the platform. All
151    /// `Timestamp`s are likely representable on 64-bit Unix-like platforms, but other platforms,
152    /// such as Windows and 32-bit Linux, may not be able to represent the full range of
153    /// `Timestamp`s.
154    OutOfSystemRange(Timestamp),
155
156    /// An error indicating failure to parse a timestamp in RFC-3339 format.
157    ParseFailure,
158
159    /// Indicates an error when constructing a timestamp due to invalid date or time data.
160    InvalidDateTime,
161}
162
163impl fmt::Display for TimestampError {
164    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165        match self {
166            TimestampError::OutOfSystemRange(timestamp) => {
167                write!(
168                    f,
169                    "{} is not representable as a `SystemTime` because it is out of range",
170                    timestamp
171                )
172            }
173            TimestampError::ParseFailure => {
174                write!(f, "failed to parse RFC-3339 formatted timestamp")
175            }
176            TimestampError::InvalidDateTime => {
177                write!(f, "invalid date or time")
178            }
179        }
180    }
181}
182
183#[cfg(feature = "std")]
184impl std::error::Error for TimestampError {}
185
186#[cfg(feature = "std")]
187impl TryFrom<Timestamp> for std::time::SystemTime {
188    type Error = TimestampError;
189
190    fn try_from(mut timestamp: Timestamp) -> Result<std::time::SystemTime, Self::Error> {
191        let orig_timestamp = timestamp;
192        timestamp.normalize();
193
194        let system_time = if timestamp.seconds >= 0 {
195            std::time::UNIX_EPOCH.checked_add(time::Duration::from_secs(timestamp.seconds as u64))
196        } else {
197            std::time::UNIX_EPOCH.checked_sub(time::Duration::from_secs(
198                timestamp
199                    .seconds
200                    .checked_neg()
201                    .ok_or(TimestampError::OutOfSystemRange(timestamp))? as u64,
202            ))
203        };
204
205        let system_time = system_time.and_then(|system_time| {
206            system_time.checked_add(time::Duration::from_nanos(timestamp.nanos as u64))
207        });
208
209        system_time.ok_or(TimestampError::OutOfSystemRange(orig_timestamp))
210    }
211}
212
213impl FromStr for Timestamp {
214    type Err = TimestampError;
215
216    fn from_str(s: &str) -> Result<Timestamp, TimestampError> {
217        datetime::parse_timestamp(s).ok_or(TimestampError::ParseFailure)
218    }
219}
220
221impl fmt::Display for Timestamp {
222    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223        datetime::DateTime::from(*self).fmt(f)
224    }
225}
226
227////////////////////////////////////////////////////////////////////////////////
228/// Chrono conversion
229////////////////////////////////////////////////////////////////////////////////
230
231/// Converts chrono's `NaiveDateTime` to `Timestamp`..
232impl From<NaiveDateTime> for Timestamp {
233    fn from(dt: NaiveDateTime) -> Self {
234        Timestamp {
235            seconds: dt.and_utc().timestamp(),
236            nanos: dt.and_utc().timestamp_subsec_nanos() as i32,
237        }
238    }
239}
240
241/// Converts chrono's `DateTime<UTtc>` to `Timestamp`
242impl From<DateTime<Utc>> for Timestamp {
243    fn from(dt: DateTime<Utc>) -> Self {
244        Timestamp {
245            seconds: dt.timestamp(),
246            nanos: dt.timestamp_subsec_nanos() as i32,
247        }
248    }
249}
250
251/// Converts proto timestamp to chrono's DateTime<Utc>
252impl From<Timestamp> for DateTime<Utc> {
253    fn from(val: Timestamp) -> Self {
254        let mut value = val;
255        // A call to `normalize` should capture all out-of-bound sitations hopefully
256        // ensuring a panic never happens! Ideally this implementation should be
257        // deprecated in favour of TryFrom but unfortunately having `TryFrom` along with
258        // `From` causes a conflict.
259        value.normalize();
260        DateTime::from_timestamp(value.seconds, value.nanos as u32)
261            .expect("invalid or out-of-range datetime")
262    }
263}
264
265impl Serialize for Timestamp {
266    fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
267    where
268        S: Serializer,
269    {
270        let mut ts = Timestamp {
271            seconds: self.seconds,
272            nanos: self.nanos,
273        };
274        ts.normalize();
275        let dt: DateTime<Utc> = ts.try_into().map_err(serde::ser::Error::custom)?;
276        serializer.serialize_str(format!("{dt:?}").as_str())
277    }
278}
279
280impl<'de> Deserialize<'de> for Timestamp {
281    fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
282    where
283        D: Deserializer<'de>,
284    {
285        struct TimestampVisitor;
286
287        impl<'de> Visitor<'de> for TimestampVisitor {
288            type Value = Timestamp;
289
290            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
291                formatter.write_str("Timestamp in RFC3339 format")
292            }
293
294            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
295            where
296                E: de::Error,
297            {
298                let utc: DateTime<Utc> = chrono::DateTime::from_str(value).map_err(|err| {
299                    serde::de::Error::custom(format!(
300                        "Failed to parse {value} as datetime: {err:?}"
301                    ))
302                })?;
303                let ts = Timestamp::from(utc);
304                Ok(ts)
305            }
306        }
307        deserializer.deserialize_str(TimestampVisitor)
308    }
309}
310
311#[cfg(feature = "schemars")]
312mod schemars_impl {
313    use super::Timestamp;
314    use schemars::generate::SchemaGenerator;
315    use schemars::{json_schema, JsonSchema, Schema};
316    use std::borrow::Cow;
317
318    impl JsonSchema for Timestamp {
319        fn schema_name() -> Cow<'static, str> {
320            Cow::Borrowed("Timestamp")
321        }
322
323        fn schema_id() -> Cow<'static, str> {
324            Cow::Borrowed("prost_wkt_types::Timestamp")
325        }
326
327        fn json_schema(_gen: &mut SchemaGenerator) -> Schema {
328            json_schema!({
329                "type": "string",
330                "description": "A timestamp in RFC 3339 format",
331                "examples": [
332                    "2025-04-11T12:00:00Z",
333                    "2025-04-11T12:00:00.123456789Z",
334                ],
335            })
336        }
337    }
338}