proto_types/timestamp/
mod.rs

1#[cfg(feature = "serde")]
2mod serde;
3
4mod timestamp_conversions;
5mod timestamp_impls;
6mod timestamp_operations;
7
8// From (prost-types)[https://github.com/tokio-rs/prost/blob/master/prost-types/src/timestamp.rs]
9use super::*;
10use crate::{
11  constants::{NANOS_PER_SECOND, PACKAGE_PREFIX},
12  Timestamp,
13};
14
15impl Timestamp {
16  /// Normalizes the timestamp to a canonical format.
17  ///
18  /// Based on [`google::protobuf::util::CreateNormalized`][1].
19  ///
20  /// [1]: https://github.com/google/protobuf/blob/v3.3.2/src/google/protobuf/util/time_util.cc#L59-L77
21  pub fn normalize(&mut self) {
22    // Make sure nanos is in the range.
23    if self.nanos <= -NANOS_PER_SECOND || self.nanos >= NANOS_PER_SECOND {
24      if let Some(seconds) = self
25        .seconds
26        .checked_add((self.nanos / NANOS_PER_SECOND) as i64)
27      {
28        self.seconds = seconds;
29
30        self.nanos %= NANOS_PER_SECOND;
31      } else if self.nanos < 0 {
32        // Negative overflow! Set to the earliest normal value.
33
34        self.seconds = i64::MIN;
35
36        self.nanos = 0;
37      } else {
38        // Positive overflow! Set to the latest normal value.
39
40        self.seconds = i64::MAX;
41
42        self.nanos = 999_999_999;
43      }
44    }
45
46    // For Timestamp nanos should be in the range [0, 999999999].
47
48    if self.nanos < 0 {
49      if let Some(seconds) = self.seconds.checked_sub(1) {
50        self.seconds = seconds;
51
52        self.nanos += NANOS_PER_SECOND;
53      } else {
54        // Negative overflow! Set to the earliest normal value.
55
56        debug_assert_eq!(self.seconds, i64::MIN);
57
58        self.nanos = 0;
59      }
60    }
61
62    // TODO: should this be checked?
63
64    // debug_assert!(self.seconds >= -62_135_596_800 && self.seconds <= 253_402_300_799,
65
66    //               "invalid timestamp: {:?}", self);
67  }
68
69  /// Normalizes the timestamp to a canonical format, returning the original value if it cannot be
70  /// normalized.
71  ///
72  /// Normalization is based on [`google::protobuf::util::CreateNormalized`][1].
73  ///
74  /// [1]: https://github.com/google/protobuf/blob/v3.3.2/src/google/protobuf/util/time_util.cc#L59-L77
75  pub fn try_normalize(mut self) -> Result<Timestamp, Timestamp> {
76    let before = self;
77
78    self.normalize();
79
80    // If the seconds value has changed, and is either i64::MIN or i64::MAX, then the timestamp
81
82    // normalization overflowed.
83
84    if (self.seconds == i64::MAX || self.seconds == i64::MIN) && self.seconds != before.seconds {
85      Err(before)
86    } else {
87      Ok(self)
88    }
89  }
90
91  /// Return a normalized copy of the timestamp to a canonical format.
92  ///
93  /// Based on [`google::protobuf::util::CreateNormalized`][1].
94  ///
95  /// [1]: https://github.com/google/protobuf/blob/v3.3.2/src/google/protobuf/util/time_util.cc#L59-L77
96  pub fn normalized(&self) -> Self {
97    let mut result = *self;
98
99    result.normalize();
100
101    result
102  }
103
104  /// Creates a new `Timestamp` at the start of the provided UTC date.
105  pub fn date(year: i64, month: u8, day: u8) -> Result<Timestamp, TimestampError> {
106    Timestamp::date_time_nanos(year, month, day, 0, 0, 0, 0)
107  }
108
109  /// Creates a new `Timestamp` instance with the provided UTC date and time.
110  pub fn date_time(
111    year: i64,
112
113    month: u8,
114
115    day: u8,
116
117    hour: u8,
118
119    minute: u8,
120
121    second: u8,
122  ) -> Result<Timestamp, TimestampError> {
123    Timestamp::date_time_nanos(year, month, day, hour, minute, second, 0)
124  }
125
126  /// Creates a new `Timestamp` instance with the provided UTC date and time.
127  pub fn date_time_nanos(
128    year: i64,
129
130    month: u8,
131
132    day: u8,
133
134    hour: u8,
135
136    minute: u8,
137
138    second: u8,
139
140    nanos: u32,
141  ) -> Result<Timestamp, TimestampError> {
142    let date_time = datetime_internal::DateTime {
143      year,
144
145      month,
146
147      day,
148
149      hour,
150
151      minute,
152
153      second,
154
155      nanos,
156    };
157
158    Timestamp::try_from(date_time)
159  }
160}
161
162impl Name for Timestamp {
163  const PACKAGE: &'static str = PACKAGE_PREFIX;
164
165  const NAME: &'static str = "Timestamp";
166
167  fn type_url() -> String {
168    type_url_for::<Self>()
169  }
170}
171
172impl From<std::time::SystemTime> for Timestamp {
173  fn from(system_time: std::time::SystemTime) -> Timestamp {
174    let (seconds, nanos) = match system_time.duration_since(std::time::UNIX_EPOCH) {
175      Ok(duration) => {
176        let seconds = i64::try_from(duration.as_secs()).unwrap();
177
178        (seconds, duration.subsec_nanos() as i32)
179      }
180
181      Err(error) => {
182        let duration = error.duration();
183
184        let seconds = i64::try_from(duration.as_secs()).unwrap();
185
186        let nanos = duration.subsec_nanos() as i32;
187
188        if nanos == 0 {
189          (-seconds, 0)
190        } else {
191          (-seconds - 1, 1_000_000_000 - nanos)
192        }
193      }
194    };
195
196    Timestamp { seconds, nanos }
197  }
198}
199
200/// A timestamp handling error.
201#[derive(Debug, PartialEq, Eq, Clone)]
202#[non_exhaustive]
203pub enum TimestampError {
204  /// Indicates that a [`Timestamp`] could not be converted to
205  /// [`SystemTime`][std::time::SystemTime] because it is out of range.
206  ///
207  /// The range of times that can be represented by `SystemTime` depends on the platform. All
208  /// `Timestamp`s are likely representable on 64-bit Unix-like platforms, but other platforms,
209  /// such as Windows and 32-bit Linux, may not be able to represent the full range of
210  /// `Timestamp`s.
211  OutOfSystemRange(Timestamp),
212  /// An error indicating failure to parse a timestamp in RFC-3339 format.
213  ParseFailure,
214  /// Indicates an error when constructing a timestamp due to invalid date or time data.
215  InvalidDateTime,
216}
217
218impl fmt::Display for TimestampError {
219  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220    match self {
221      TimestampError::OutOfSystemRange(timestamp) => {
222        write!(
223          f,
224          "{} is not representable as a `SystemTime` because it is out of range",
225          timestamp
226        )
227      }
228
229      TimestampError::ParseFailure => {
230        write!(f, "failed to parse RFC-3339 formatted timestamp")
231      }
232
233      TimestampError::InvalidDateTime => {
234        write!(f, "invalid date or time")
235      }
236    }
237  }
238}
239
240impl std::error::Error for TimestampError {}
241
242impl TryFrom<Timestamp> for std::time::SystemTime {
243  type Error = TimestampError;
244
245  fn try_from(mut timestamp: Timestamp) -> Result<std::time::SystemTime, Self::Error> {
246    let orig_timestamp = timestamp;
247
248    timestamp.normalize();
249
250    let system_time = if timestamp.seconds >= 0 {
251      std::time::UNIX_EPOCH.checked_add(time::Duration::from_secs(timestamp.seconds as u64))
252    } else {
253      std::time::UNIX_EPOCH.checked_sub(time::Duration::from_secs(
254        timestamp
255          .seconds
256          .checked_neg()
257          .ok_or(TimestampError::OutOfSystemRange(timestamp))? as u64,
258      ))
259    };
260
261    let system_time = system_time.and_then(|system_time| {
262      system_time.checked_add(time::Duration::from_nanos(timestamp.nanos as u64))
263    });
264
265    system_time.ok_or(TimestampError::OutOfSystemRange(orig_timestamp))
266  }
267}
268
269impl FromStr for Timestamp {
270  type Err = TimestampError;
271
272  fn from_str(s: &str) -> Result<Timestamp, TimestampError> {
273    datetime_internal::parse_timestamp(s).ok_or(TimestampError::ParseFailure)
274  }
275}
276
277impl fmt::Display for Timestamp {
278  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
279    datetime_internal::DateTime::from(*self).fmt(f)
280  }
281}