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")]
129impl Eq for Timestamp {}
130
131#[cfg(feature = "std")]
132impl std::hash::Hash for Timestamp {
133 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
134 self.seconds.hash(state);
135 self.nanos.hash(state);
136 }
137}
138
139#[cfg(feature = "std")]
140impl From<std::time::SystemTime> for Timestamp {
141 fn from(system_time: std::time::SystemTime) -> Timestamp {
142 let (seconds, nanos) = match system_time.duration_since(std::time::UNIX_EPOCH) {
143 Ok(duration) => {
144 let seconds = i64::try_from(duration.as_secs()).unwrap();
145 (seconds, duration.subsec_nanos() as i32)
146 }
147 Err(error) => {
148 let duration = error.duration();
149 let seconds = i64::try_from(duration.as_secs()).unwrap();
150 let nanos = duration.subsec_nanos() as i32;
151 if nanos == 0 {
152 (-seconds, 0)
153 } else {
154 (-seconds - 1, 1_000_000_000 - nanos)
155 }
156 }
157 };
158 Timestamp { seconds, nanos }
159 }
160}
161
162#[derive(Debug, PartialEq)]
164#[non_exhaustive]
165pub enum TimestampError {
166 OutOfSystemRange(Timestamp),
174
175 ParseFailure,
177
178 InvalidDateTime,
180}
181
182impl fmt::Display for TimestampError {
183 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184 match self {
185 TimestampError::OutOfSystemRange(timestamp) => {
186 write!(
187 f,
188 "{} is not representable as a `SystemTime` because it is out of range",
189 timestamp
190 )
191 }
192 TimestampError::ParseFailure => {
193 write!(f, "failed to parse RFC-3339 formatted timestamp")
194 }
195 TimestampError::InvalidDateTime => {
196 write!(f, "invalid date or time")
197 }
198 }
199 }
200}
201
202#[cfg(feature = "std")]
203impl std::error::Error for TimestampError {}
204
205#[cfg(feature = "std")]
206impl TryFrom<Timestamp> for std::time::SystemTime {
207 type Error = TimestampError;
208
209 fn try_from(mut timestamp: Timestamp) -> Result<std::time::SystemTime, Self::Error> {
210 let orig_timestamp = timestamp;
211 timestamp.normalize();
212
213 let system_time = if timestamp.seconds >= 0 {
214 std::time::UNIX_EPOCH.checked_add(time::Duration::from_secs(timestamp.seconds as u64))
215 } else {
216 std::time::UNIX_EPOCH.checked_sub(time::Duration::from_secs(
217 timestamp
218 .seconds
219 .checked_neg()
220 .ok_or(TimestampError::OutOfSystemRange(timestamp))? as u64,
221 ))
222 };
223
224 let system_time = system_time.and_then(|system_time| {
225 system_time.checked_add(time::Duration::from_nanos(timestamp.nanos as u64))
226 });
227
228 system_time.ok_or(TimestampError::OutOfSystemRange(orig_timestamp))
229 }
230}
231
232impl FromStr for Timestamp {
233 type Err = TimestampError;
234
235 fn from_str(s: &str) -> Result<Timestamp, TimestampError> {
236 datetime::parse_timestamp(s).ok_or(TimestampError::ParseFailure)
237 }
238}
239
240impl fmt::Display for Timestamp {
241 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242 datetime::DateTime::from(*self).fmt(f)
243 }
244}
245
246#[cfg(kani)]
247mod proofs {
248 use super::*;
249
250 #[cfg(feature = "std")]
251 #[kani::proof]
252 #[kani::unwind(3)]
253 fn check_timestamp_roundtrip_via_system_time() {
254 let seconds = kani::any();
255 let nanos = kani::any();
256
257 let mut timestamp = Timestamp { seconds, nanos };
258 timestamp.normalize();
259
260 if let Ok(system_time) = std::time::SystemTime::try_from(timestamp) {
261 assert_eq!(Timestamp::from(system_time), timestamp);
262 }
263 }
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269
270 #[cfg(feature = "std")]
271 use proptest::prelude::*;
272 #[cfg(feature = "std")]
273 use std::time::{self, SystemTime, UNIX_EPOCH};
274
275 #[cfg(feature = "std")]
276 proptest! {
277 #[test]
278 fn check_system_time_roundtrip(
279 system_time in SystemTime::arbitrary(),
280 ) {
281 prop_assert_eq!(SystemTime::try_from(Timestamp::from(system_time)).unwrap(), system_time);
282 }
283 }
284
285 #[cfg(feature = "std")]
286 #[test]
287 fn check_timestamp_negative_seconds() {
288 assert_eq!(
298 Timestamp::from(UNIX_EPOCH - time::Duration::new(1_001, 0)),
299 Timestamp {
300 seconds: -1_001,
301 nanos: 0
302 }
303 );
304 assert_eq!(
305 Timestamp::from(UNIX_EPOCH - time::Duration::new(0, 999_999_900)),
306 Timestamp {
307 seconds: -1,
308 nanos: 100
309 }
310 );
311 assert_eq!(
312 Timestamp::from(UNIX_EPOCH - time::Duration::new(2_001_234, 12_300)),
313 Timestamp {
314 seconds: -2_001_235,
315 nanos: 999_987_700
316 }
317 );
318 assert_eq!(
319 Timestamp::from(UNIX_EPOCH - time::Duration::new(768, 65_432_100)),
320 Timestamp {
321 seconds: -769,
322 nanos: 934_567_900
323 }
324 );
325 }
326
327 #[cfg(all(unix, feature = "std"))]
328 #[test]
329 fn check_timestamp_negative_seconds_1ns() {
330 assert_eq!(
332 Timestamp::from(UNIX_EPOCH - time::Duration::new(0, 999_999_999)),
333 Timestamp {
334 seconds: -1,
335 nanos: 1
336 }
337 );
338 assert_eq!(
339 Timestamp::from(UNIX_EPOCH - time::Duration::new(1_234_567, 123)),
340 Timestamp {
341 seconds: -1_234_568,
342 nanos: 999_999_877
343 }
344 );
345 assert_eq!(
346 Timestamp::from(UNIX_EPOCH - time::Duration::new(890, 987_654_321)),
347 Timestamp {
348 seconds: -891,
349 nanos: 12_345_679
350 }
351 );
352 }
353
354 #[cfg(feature = "std")]
355 #[test]
356 fn check_timestamp_normalize() {
357 #[rustfmt::skip] let cases = [
360 (line!(), 0, 0, 0, 0),
363 (line!(), 1, 1, 1, 1),
364 (line!(), -1, -1, -2, 999_999_999),
365 (line!(), 0, 999_999_999, 0, 999_999_999),
366 (line!(), 0, -999_999_999, -1, 1),
367 (line!(), 0, 1_000_000_000, 1, 0),
368 (line!(), 0, -1_000_000_000, -1, 0),
369 (line!(), 0, 1_000_000_001, 1, 1),
370 (line!(), 0, -1_000_000_001, -2, 999_999_999),
371 (line!(), -1, 1, -1, 1),
372 (line!(), 1, -1, 0, 999_999_999),
373 (line!(), -1, 1_000_000_000, 0, 0),
374 (line!(), 1, -1_000_000_000, 0, 0),
375 (line!(), i64::MIN , 0, i64::MIN , 0),
376 (line!(), i64::MIN + 1, 0, i64::MIN + 1, 0),
377 (line!(), i64::MIN , 1, i64::MIN , 1),
378 (line!(), i64::MIN , 1_000_000_000, i64::MIN + 1, 0),
379 (line!(), i64::MIN , -1_000_000_000, i64::MIN , 0),
380 (line!(), i64::MIN + 1, -1_000_000_000, i64::MIN , 0),
381 (line!(), i64::MIN + 2, -1_000_000_000, i64::MIN + 1, 0),
382 (line!(), i64::MIN , -1_999_999_998, i64::MIN , 0),
383 (line!(), i64::MIN + 1, -1_999_999_998, i64::MIN , 0),
384 (line!(), i64::MIN + 2, -1_999_999_998, i64::MIN , 2),
385 (line!(), i64::MIN , -1_999_999_999, i64::MIN , 0),
386 (line!(), i64::MIN + 1, -1_999_999_999, i64::MIN , 0),
387 (line!(), i64::MIN + 2, -1_999_999_999, i64::MIN , 1),
388 (line!(), i64::MIN , -2_000_000_000, i64::MIN , 0),
389 (line!(), i64::MIN + 1, -2_000_000_000, i64::MIN , 0),
390 (line!(), i64::MIN + 2, -2_000_000_000, i64::MIN , 0),
391 (line!(), i64::MIN , -999_999_998, i64::MIN , 0),
392 (line!(), i64::MIN + 1, -999_999_998, i64::MIN , 2),
393 (line!(), i64::MAX , 0, i64::MAX , 0),
394 (line!(), i64::MAX - 1, 0, i64::MAX - 1, 0),
395 (line!(), i64::MAX , -1, i64::MAX - 1, 999_999_999),
396 (line!(), i64::MAX , 1_000_000_000, i64::MAX , 999_999_999),
397 (line!(), i64::MAX - 1, 1_000_000_000, i64::MAX , 0),
398 (line!(), i64::MAX - 2, 1_000_000_000, i64::MAX - 1, 0),
399 (line!(), i64::MAX , 1_999_999_998, i64::MAX , 999_999_999),
400 (line!(), i64::MAX - 1, 1_999_999_998, i64::MAX , 999_999_998),
401 (line!(), i64::MAX - 2, 1_999_999_998, i64::MAX - 1, 999_999_998),
402 (line!(), i64::MAX , 1_999_999_999, i64::MAX , 999_999_999),
403 (line!(), i64::MAX - 1, 1_999_999_999, i64::MAX , 999_999_999),
404 (line!(), i64::MAX - 2, 1_999_999_999, i64::MAX - 1, 999_999_999),
405 (line!(), i64::MAX , 2_000_000_000, i64::MAX , 999_999_999),
406 (line!(), i64::MAX - 1, 2_000_000_000, i64::MAX , 999_999_999),
407 (line!(), i64::MAX - 2, 2_000_000_000, i64::MAX , 0),
408 (line!(), i64::MAX , 999_999_998, i64::MAX , 999_999_998),
409 (line!(), i64::MAX - 1, 999_999_998, i64::MAX - 1, 999_999_998),
410 ];
411
412 for case in cases.iter() {
413 let test_timestamp = crate::Timestamp {
414 seconds: case.1,
415 nanos: case.2,
416 };
417
418 assert_eq!(
419 test_timestamp.normalized(),
420 crate::Timestamp {
421 seconds: case.3,
422 nanos: case.4,
423 },
424 "test case on line {} doesn't match",
425 case.0,
426 );
427 }
428 }
429
430 #[cfg(feature = "arbitrary")]
431 #[test]
432 fn check_timestamp_implements_arbitrary() {
433 use arbitrary::{Arbitrary, Unstructured};
434
435 let mut unstructured = Unstructured::new(&[]);
436
437 assert_eq!(
438 Timestamp::arbitrary(&mut unstructured),
439 Ok(Timestamp {
440 seconds: 0,
441 nanos: 0
442 })
443 );
444 }
445}