1use super::*;
2
3impl Timestamp {
4 pub fn normalize(&mut self) {
10 if self.nanos <= -NANOS_PER_SECOND || self.nanos >= NANOS_PER_SECOND {
12 if let Some(seconds) = self
13 .seconds
14 .checked_add((self.nanos / NANOS_PER_SECOND) as i64)
15 {
16 self.seconds = seconds;
17 self.nanos %= NANOS_PER_SECOND;
18 } else if self.nanos < 0 {
19 self.seconds = i64::MIN;
21 self.nanos = 0;
22 } else {
23 self.seconds = i64::MAX;
25 self.nanos = 999_999_999;
26 }
27 }
28
29 if self.nanos < 0 {
31 if let Some(seconds) = self.seconds.checked_sub(1) {
32 self.seconds = seconds;
33 self.nanos += NANOS_PER_SECOND;
34 } else {
35 debug_assert_eq!(self.seconds, i64::MIN);
37 self.nanos = 0;
38 }
39 }
40
41 }
45
46 pub fn try_normalize(mut self) -> Result<Timestamp, Timestamp> {
53 let before = self;
54 self.normalize();
55 if (self.seconds == i64::MAX || self.seconds == i64::MIN) && self.seconds != before.seconds
58 {
59 Err(before)
60 } else {
61 Ok(self)
62 }
63 }
64
65 pub fn normalized(&self) -> Self {
71 let mut result = *self;
72 result.normalize();
73 result
74 }
75
76 pub fn date(year: i64, month: u8, day: u8) -> Result<Timestamp, TimestampError> {
78 Timestamp::date_time_nanos(year, month, day, 0, 0, 0, 0)
79 }
80
81 pub fn date_time(
83 year: i64,
84 month: u8,
85 day: u8,
86 hour: u8,
87 minute: u8,
88 second: u8,
89 ) -> Result<Timestamp, TimestampError> {
90 Timestamp::date_time_nanos(year, month, day, hour, minute, second, 0)
91 }
92
93 pub fn date_time_nanos(
95 year: i64,
96 month: u8,
97 day: u8,
98 hour: u8,
99 minute: u8,
100 second: u8,
101 nanos: u32,
102 ) -> Result<Timestamp, TimestampError> {
103 let date_time = datetime::DateTime {
104 year,
105 month,
106 day,
107 hour,
108 minute,
109 second,
110 nanos,
111 };
112
113 Timestamp::try_from(date_time)
114 }
115}
116
117impl Name for Timestamp {
118 const PACKAGE: &'static str = PACKAGE;
119 const NAME: &'static str = "Timestamp";
120
121 fn type_url() -> String {
122 type_url_for::<Self>()
123 }
124}
125
126#[cfg(feature = "std")]
127impl From<std::time::SystemTime> for Timestamp {
128 fn from(system_time: std::time::SystemTime) -> Timestamp {
129 let (seconds, nanos) = match system_time.duration_since(std::time::UNIX_EPOCH) {
130 Ok(duration) => {
131 let seconds = i64::try_from(duration.as_secs()).unwrap();
132 (seconds, duration.subsec_nanos() as i32)
133 }
134 Err(error) => {
135 let duration = error.duration();
136 let seconds = i64::try_from(duration.as_secs()).unwrap();
137 let nanos = duration.subsec_nanos() as i32;
138 if nanos == 0 {
139 (-seconds, 0)
140 } else {
141 (-seconds - 1, 1_000_000_000 - nanos)
142 }
143 }
144 };
145 Timestamp { seconds, nanos }
146 }
147}
148
149#[derive(Debug, PartialEq)]
151#[non_exhaustive]
152pub enum TimestampError {
153 OutOfSystemRange(Timestamp),
161
162 ParseFailure,
164
165 InvalidDateTime,
167}
168
169impl fmt::Display for TimestampError {
170 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171 match self {
172 TimestampError::OutOfSystemRange(timestamp) => {
173 write!(
174 f,
175 "{} is not representable as a `SystemTime` because it is out of range",
176 timestamp
177 )
178 }
179 TimestampError::ParseFailure => {
180 write!(f, "failed to parse RFC-3339 formatted timestamp")
181 }
182 TimestampError::InvalidDateTime => {
183 write!(f, "invalid date or time")
184 }
185 }
186 }
187}
188
189#[cfg(feature = "std")]
190impl std::error::Error for TimestampError {}
191
192#[cfg(feature = "std")]
193impl TryFrom<Timestamp> for std::time::SystemTime {
194 type Error = TimestampError;
195
196 fn try_from(mut timestamp: Timestamp) -> Result<std::time::SystemTime, Self::Error> {
197 let orig_timestamp = timestamp;
198 timestamp.normalize();
199
200 let system_time = if timestamp.seconds >= 0 {
201 std::time::UNIX_EPOCH.checked_add(time::Duration::from_secs(timestamp.seconds as u64))
202 } else {
203 std::time::UNIX_EPOCH.checked_sub(time::Duration::from_secs(
204 timestamp
205 .seconds
206 .checked_neg()
207 .ok_or(TimestampError::OutOfSystemRange(timestamp))? as u64,
208 ))
209 };
210
211 let system_time = system_time.and_then(|system_time| {
212 system_time.checked_add(time::Duration::from_nanos(timestamp.nanos as u64))
213 });
214
215 system_time.ok_or(TimestampError::OutOfSystemRange(orig_timestamp))
216 }
217}
218
219impl FromStr for Timestamp {
220 type Err = TimestampError;
221
222 fn from_str(s: &str) -> Result<Timestamp, TimestampError> {
223 datetime::parse_timestamp(s).ok_or(TimestampError::ParseFailure)
224 }
225}
226
227impl fmt::Display for Timestamp {
228 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229 datetime::DateTime::from(*self).fmt(f)
230 }
231}
232
233#[cfg(kani)]
234mod proofs {
235 use super::*;
236
237 #[cfg(feature = "std")]
238 #[kani::proof]
239 #[kani::unwind(3)]
240 fn check_timestamp_roundtrip_via_system_time() {
241 let seconds = kani::any();
242 let nanos = kani::any();
243
244 let mut timestamp = Timestamp { seconds, nanos };
245 timestamp.normalize();
246
247 if let Ok(system_time) = std::time::SystemTime::try_from(timestamp) {
248 assert_eq!(Timestamp::from(system_time), timestamp);
249 }
250 }
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256
257 #[cfg(feature = "std")]
258 use proptest::prelude::*;
259 #[cfg(feature = "std")]
260 use std::time::{self, SystemTime, UNIX_EPOCH};
261
262 #[cfg(feature = "std")]
263 proptest! {
264 #[test]
265 fn check_system_time_roundtrip(
266 system_time in SystemTime::arbitrary(),
267 ) {
268 prop_assert_eq!(SystemTime::try_from(Timestamp::from(system_time)).unwrap(), system_time);
269 }
270 }
271
272 #[cfg(feature = "std")]
273 #[test]
274 fn check_timestamp_negative_seconds() {
275 assert_eq!(
285 Timestamp::from(UNIX_EPOCH - time::Duration::new(1_001, 0)),
286 Timestamp {
287 seconds: -1_001,
288 nanos: 0
289 }
290 );
291 assert_eq!(
292 Timestamp::from(UNIX_EPOCH - time::Duration::new(0, 999_999_900)),
293 Timestamp {
294 seconds: -1,
295 nanos: 100
296 }
297 );
298 assert_eq!(
299 Timestamp::from(UNIX_EPOCH - time::Duration::new(2_001_234, 12_300)),
300 Timestamp {
301 seconds: -2_001_235,
302 nanos: 999_987_700
303 }
304 );
305 assert_eq!(
306 Timestamp::from(UNIX_EPOCH - time::Duration::new(768, 65_432_100)),
307 Timestamp {
308 seconds: -769,
309 nanos: 934_567_900
310 }
311 );
312 }
313
314 #[cfg(all(unix, feature = "std"))]
315 #[test]
316 fn check_timestamp_negative_seconds_1ns() {
317 assert_eq!(
319 Timestamp::from(UNIX_EPOCH - time::Duration::new(0, 999_999_999)),
320 Timestamp {
321 seconds: -1,
322 nanos: 1
323 }
324 );
325 assert_eq!(
326 Timestamp::from(UNIX_EPOCH - time::Duration::new(1_234_567, 123)),
327 Timestamp {
328 seconds: -1_234_568,
329 nanos: 999_999_877
330 }
331 );
332 assert_eq!(
333 Timestamp::from(UNIX_EPOCH - time::Duration::new(890, 987_654_321)),
334 Timestamp {
335 seconds: -891,
336 nanos: 12_345_679
337 }
338 );
339 }
340
341 #[cfg(feature = "std")]
342 #[test]
343 fn check_timestamp_normalize() {
344 #[rustfmt::skip] let cases = [
347 (line!(), 0, 0, 0, 0),
350 (line!(), 1, 1, 1, 1),
351 (line!(), -1, -1, -2, 999_999_999),
352 (line!(), 0, 999_999_999, 0, 999_999_999),
353 (line!(), 0, -999_999_999, -1, 1),
354 (line!(), 0, 1_000_000_000, 1, 0),
355 (line!(), 0, -1_000_000_000, -1, 0),
356 (line!(), 0, 1_000_000_001, 1, 1),
357 (line!(), 0, -1_000_000_001, -2, 999_999_999),
358 (line!(), -1, 1, -1, 1),
359 (line!(), 1, -1, 0, 999_999_999),
360 (line!(), -1, 1_000_000_000, 0, 0),
361 (line!(), 1, -1_000_000_000, 0, 0),
362 (line!(), i64::MIN , 0, i64::MIN , 0),
363 (line!(), i64::MIN + 1, 0, i64::MIN + 1, 0),
364 (line!(), i64::MIN , 1, i64::MIN , 1),
365 (line!(), i64::MIN , 1_000_000_000, i64::MIN + 1, 0),
366 (line!(), i64::MIN , -1_000_000_000, i64::MIN , 0),
367 (line!(), i64::MIN + 1, -1_000_000_000, i64::MIN , 0),
368 (line!(), i64::MIN + 2, -1_000_000_000, i64::MIN + 1, 0),
369 (line!(), i64::MIN , -1_999_999_998, i64::MIN , 0),
370 (line!(), i64::MIN + 1, -1_999_999_998, i64::MIN , 0),
371 (line!(), i64::MIN + 2, -1_999_999_998, i64::MIN , 2),
372 (line!(), i64::MIN , -1_999_999_999, i64::MIN , 0),
373 (line!(), i64::MIN + 1, -1_999_999_999, i64::MIN , 0),
374 (line!(), i64::MIN + 2, -1_999_999_999, i64::MIN , 1),
375 (line!(), i64::MIN , -2_000_000_000, i64::MIN , 0),
376 (line!(), i64::MIN + 1, -2_000_000_000, i64::MIN , 0),
377 (line!(), i64::MIN + 2, -2_000_000_000, i64::MIN , 0),
378 (line!(), i64::MIN , -999_999_998, i64::MIN , 0),
379 (line!(), i64::MIN + 1, -999_999_998, i64::MIN , 2),
380 (line!(), i64::MAX , 0, i64::MAX , 0),
381 (line!(), i64::MAX - 1, 0, i64::MAX - 1, 0),
382 (line!(), i64::MAX , -1, i64::MAX - 1, 999_999_999),
383 (line!(), i64::MAX , 1_000_000_000, i64::MAX , 999_999_999),
384 (line!(), i64::MAX - 1, 1_000_000_000, i64::MAX , 0),
385 (line!(), i64::MAX - 2, 1_000_000_000, i64::MAX - 1, 0),
386 (line!(), i64::MAX , 1_999_999_998, i64::MAX , 999_999_999),
387 (line!(), i64::MAX - 1, 1_999_999_998, i64::MAX , 999_999_998),
388 (line!(), i64::MAX - 2, 1_999_999_998, i64::MAX - 1, 999_999_998),
389 (line!(), i64::MAX , 1_999_999_999, i64::MAX , 999_999_999),
390 (line!(), i64::MAX - 1, 1_999_999_999, i64::MAX , 999_999_999),
391 (line!(), i64::MAX - 2, 1_999_999_999, i64::MAX - 1, 999_999_999),
392 (line!(), i64::MAX , 2_000_000_000, i64::MAX , 999_999_999),
393 (line!(), i64::MAX - 1, 2_000_000_000, i64::MAX , 999_999_999),
394 (line!(), i64::MAX - 2, 2_000_000_000, i64::MAX , 0),
395 (line!(), i64::MAX , 999_999_998, i64::MAX , 999_999_998),
396 (line!(), i64::MAX - 1, 999_999_998, i64::MAX - 1, 999_999_998),
397 ];
398
399 for case in cases.iter() {
400 let test_timestamp = crate::Timestamp {
401 seconds: case.1,
402 nanos: case.2,
403 };
404
405 assert_eq!(
406 test_timestamp.normalized(),
407 crate::Timestamp {
408 seconds: case.3,
409 nanos: case.4,
410 },
411 "test case on line {} doesn't match",
412 case.0,
413 );
414 }
415 }
416
417 #[cfg(feature = "arbitrary")]
418 #[test]
419 fn check_timestamp_implements_arbitrary() {
420 use arbitrary::{Arbitrary, Unstructured};
421
422 let mut unstructured = Unstructured::new(&[]);
423
424 assert_eq!(
425 Timestamp::arbitrary(&mut unstructured),
426 Ok(Timestamp {
427 seconds: 0,
428 nanos: 0
429 })
430 );
431 }
432}