proto_types/timestamp/
mod.rs

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