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 "{timestamp} is not representable as a `SystemTime` because it is out of range",
176 )
177 }
178 TimestampError::ParseFailure => {
179 write!(f, "failed to parse RFC-3339 formatted timestamp")
180 }
181 TimestampError::InvalidDateTime => {
182 write!(f, "invalid date or time")
183 }
184 }
185 }
186}
187
188impl core::error::Error for TimestampError {}
189
190#[cfg(feature = "std")]
191impl TryFrom<Timestamp> for std::time::SystemTime {
192 type Error = TimestampError;
193
194 fn try_from(mut timestamp: Timestamp) -> Result<std::time::SystemTime, Self::Error> {
195 let orig_timestamp = timestamp;
196 timestamp.normalize();
197
198 let system_time = if timestamp.seconds >= 0 {
199 std::time::UNIX_EPOCH.checked_add(time::Duration::from_secs(timestamp.seconds as u64))
200 } else {
201 std::time::UNIX_EPOCH.checked_sub(time::Duration::from_secs(
202 timestamp
203 .seconds
204 .checked_neg()
205 .ok_or(TimestampError::OutOfSystemRange(timestamp))? as u64,
206 ))
207 };
208
209 let system_time = system_time.and_then(|system_time| {
210 system_time.checked_add(time::Duration::from_nanos(timestamp.nanos as u64))
211 });
212
213 system_time.ok_or(TimestampError::OutOfSystemRange(orig_timestamp))
214 }
215}
216
217impl FromStr for Timestamp {
218 type Err = TimestampError;
219
220 fn from_str(s: &str) -> Result<Timestamp, TimestampError> {
221 datetime::parse_timestamp(s).ok_or(TimestampError::ParseFailure)
222 }
223}
224
225impl fmt::Display for Timestamp {
226 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227 datetime::DateTime::from(*self).fmt(f)
228 }
229}
230
231#[cfg(kani)]
232mod proofs {
233 use super::*;
234
235 #[cfg(feature = "std")]
236 #[kani::proof]
237 #[kani::unwind(3)]
238 fn check_timestamp_roundtrip_via_system_time() {
239 let seconds = kani::any();
240 let nanos = kani::any();
241
242 let mut timestamp = Timestamp { seconds, nanos };
243 timestamp.normalize();
244
245 if let Ok(system_time) = std::time::SystemTime::try_from(timestamp) {
246 assert_eq!(Timestamp::from(system_time), timestamp);
247 }
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254
255 #[cfg(feature = "std")]
256 use proptest::prelude::*;
257 #[cfg(feature = "std")]
258 use std::time::{self, SystemTime, UNIX_EPOCH};
259
260 #[cfg(feature = "std")]
261 proptest! {
262 #[test]
263 fn check_system_time_roundtrip(
264 system_time in SystemTime::arbitrary(),
265 ) {
266 prop_assert_eq!(SystemTime::try_from(Timestamp::from(system_time)).unwrap(), system_time);
267 }
268 }
269
270 #[cfg(feature = "std")]
271 #[test]
272 fn check_timestamp_negative_seconds() {
273 assert_eq!(
283 Timestamp::from(UNIX_EPOCH - time::Duration::new(1_001, 0)),
284 Timestamp {
285 seconds: -1_001,
286 nanos: 0
287 }
288 );
289 assert_eq!(
290 Timestamp::from(UNIX_EPOCH - time::Duration::new(0, 999_999_900)),
291 Timestamp {
292 seconds: -1,
293 nanos: 100
294 }
295 );
296 assert_eq!(
297 Timestamp::from(UNIX_EPOCH - time::Duration::new(2_001_234, 12_300)),
298 Timestamp {
299 seconds: -2_001_235,
300 nanos: 999_987_700
301 }
302 );
303 assert_eq!(
304 Timestamp::from(UNIX_EPOCH - time::Duration::new(768, 65_432_100)),
305 Timestamp {
306 seconds: -769,
307 nanos: 934_567_900
308 }
309 );
310 }
311
312 #[cfg(all(unix, feature = "std"))]
313 #[test]
314 fn check_timestamp_negative_seconds_1ns() {
315 assert_eq!(
317 Timestamp::from(UNIX_EPOCH - time::Duration::new(0, 999_999_999)),
318 Timestamp {
319 seconds: -1,
320 nanos: 1
321 }
322 );
323 assert_eq!(
324 Timestamp::from(UNIX_EPOCH - time::Duration::new(1_234_567, 123)),
325 Timestamp {
326 seconds: -1_234_568,
327 nanos: 999_999_877
328 }
329 );
330 assert_eq!(
331 Timestamp::from(UNIX_EPOCH - time::Duration::new(890, 987_654_321)),
332 Timestamp {
333 seconds: -891,
334 nanos: 12_345_679
335 }
336 );
337 }
338
339 #[cfg(feature = "std")]
340 #[test]
341 fn check_timestamp_normalize() {
342 #[rustfmt::skip] let cases = [
345 (line!(), 0, 0, 0, 0),
348 (line!(), 1, 1, 1, 1),
349 (line!(), -1, -1, -2, 999_999_999),
350 (line!(), 0, 999_999_999, 0, 999_999_999),
351 (line!(), 0, -999_999_999, -1, 1),
352 (line!(), 0, 1_000_000_000, 1, 0),
353 (line!(), 0, -1_000_000_000, -1, 0),
354 (line!(), 0, 1_000_000_001, 1, 1),
355 (line!(), 0, -1_000_000_001, -2, 999_999_999),
356 (line!(), -1, 1, -1, 1),
357 (line!(), 1, -1, 0, 999_999_999),
358 (line!(), -1, 1_000_000_000, 0, 0),
359 (line!(), 1, -1_000_000_000, 0, 0),
360 (line!(), i64::MIN , 0, i64::MIN , 0),
361 (line!(), i64::MIN + 1, 0, i64::MIN + 1, 0),
362 (line!(), i64::MIN , 1, i64::MIN , 1),
363 (line!(), i64::MIN , 1_000_000_000, i64::MIN + 1, 0),
364 (line!(), i64::MIN , -1_000_000_000, i64::MIN , 0),
365 (line!(), i64::MIN + 1, -1_000_000_000, i64::MIN , 0),
366 (line!(), i64::MIN + 2, -1_000_000_000, i64::MIN + 1, 0),
367 (line!(), i64::MIN , -1_999_999_998, i64::MIN , 0),
368 (line!(), i64::MIN + 1, -1_999_999_998, i64::MIN , 0),
369 (line!(), i64::MIN + 2, -1_999_999_998, i64::MIN , 2),
370 (line!(), i64::MIN , -1_999_999_999, i64::MIN , 0),
371 (line!(), i64::MIN + 1, -1_999_999_999, i64::MIN , 0),
372 (line!(), i64::MIN + 2, -1_999_999_999, i64::MIN , 1),
373 (line!(), i64::MIN , -2_000_000_000, i64::MIN , 0),
374 (line!(), i64::MIN + 1, -2_000_000_000, i64::MIN , 0),
375 (line!(), i64::MIN + 2, -2_000_000_000, i64::MIN , 0),
376 (line!(), i64::MIN , -999_999_998, i64::MIN , 0),
377 (line!(), i64::MIN + 1, -999_999_998, i64::MIN , 2),
378 (line!(), i64::MAX , 0, i64::MAX , 0),
379 (line!(), i64::MAX - 1, 0, i64::MAX - 1, 0),
380 (line!(), i64::MAX , -1, i64::MAX - 1, 999_999_999),
381 (line!(), i64::MAX , 1_000_000_000, i64::MAX , 999_999_999),
382 (line!(), i64::MAX - 1, 1_000_000_000, i64::MAX , 0),
383 (line!(), i64::MAX - 2, 1_000_000_000, i64::MAX - 1, 0),
384 (line!(), i64::MAX , 1_999_999_998, i64::MAX , 999_999_999),
385 (line!(), i64::MAX - 1, 1_999_999_998, i64::MAX , 999_999_998),
386 (line!(), i64::MAX - 2, 1_999_999_998, i64::MAX - 1, 999_999_998),
387 (line!(), i64::MAX , 1_999_999_999, i64::MAX , 999_999_999),
388 (line!(), i64::MAX - 1, 1_999_999_999, i64::MAX , 999_999_999),
389 (line!(), i64::MAX - 2, 1_999_999_999, i64::MAX - 1, 999_999_999),
390 (line!(), i64::MAX , 2_000_000_000, i64::MAX , 999_999_999),
391 (line!(), i64::MAX - 1, 2_000_000_000, i64::MAX , 999_999_999),
392 (line!(), i64::MAX - 2, 2_000_000_000, i64::MAX , 0),
393 (line!(), i64::MAX , 999_999_998, i64::MAX , 999_999_998),
394 (line!(), i64::MAX - 1, 999_999_998, i64::MAX - 1, 999_999_998),
395 ];
396
397 for case in cases.iter() {
398 let test_timestamp = crate::Timestamp {
399 seconds: case.1,
400 nanos: case.2,
401 };
402
403 assert_eq!(
404 test_timestamp.normalized(),
405 crate::Timestamp {
406 seconds: case.3,
407 nanos: case.4,
408 },
409 "test case on line {} doesn't match",
410 case.0,
411 );
412 }
413 }
414
415 #[cfg(feature = "arbitrary")]
416 #[test]
417 fn check_timestamp_implements_arbitrary() {
418 use arbitrary::{Arbitrary, Unstructured};
419
420 let mut unstructured = Unstructured::new(&[]);
421
422 assert_eq!(
423 Timestamp::arbitrary(&mut unstructured),
424 Ok(Timestamp {
425 seconds: 0,
426 nanos: 0
427 })
428 );
429 }
430}