1use core::fmt;
5
6use crate::Duration;
7use crate::Timestamp;
8use crate::TimestampError;
9
10#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
12pub(crate) struct DateTime {
13 pub(crate) year: i64,
15 pub(crate) month: u8,
17 pub(crate) day: u8,
19 pub(crate) hour: u8,
21 pub(crate) minute: u8,
23 pub(crate) second: u8,
25 pub(crate) nanos: u32,
27}
28
29impl DateTime {
30 pub(crate) const MIN: DateTime = DateTime {
32 year: -292_277_022_657,
33 month: 1,
34 day: 27,
35 hour: 8,
36 minute: 29,
37 second: 52,
38 nanos: 0,
39 };
40
41 pub(crate) const MAX: DateTime = DateTime {
43 year: 292_277_026_596,
44 month: 12,
45 day: 4,
46 hour: 15,
47 minute: 30,
48 second: 7,
49 nanos: 999_999_999,
50 };
51
52 pub(crate) fn is_valid(&self) -> bool {
54 self >= &DateTime::MIN
55 && self <= &DateTime::MAX
56 && self.month > 0
57 && self.month <= 12
58 && self.day > 0
59 && self.day <= days_in_month(self.year, self.month)
60 && self.hour < 24
61 && self.minute < 60
62 && self.second < 60
63 && self.nanos < 1_000_000_000
64 }
65}
66
67impl fmt::Display for DateTime {
68 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
69 if self.year > 9999 {
71 write!(f, "+{}", self.year)?;
72 } else if self.year < 0 {
73 write!(f, "{:05}", self.year)?;
74 } else {
75 write!(f, "{:04}", self.year)?;
76 };
77
78 write!(
79 f,
80 "-{:02}-{:02}T{:02}:{:02}:{:02}",
81 self.month, self.day, self.hour, self.minute, self.second,
82 )?;
83
84 let nanos = self.nanos;
86 if nanos == 0 {
87 write!(f, "Z")
88 } else if nanos % 1_000_000 == 0 {
89 write!(f, ".{:03}Z", nanos / 1_000_000)
90 } else if nanos % 1_000 == 0 {
91 write!(f, ".{:06}Z", nanos / 1_000)
92 } else {
93 write!(f, ".{:09}Z", nanos)
94 }
95 }
96}
97
98impl From<Timestamp> for DateTime {
99 fn from(mut timestamp: Timestamp) -> DateTime {
107 timestamp.normalize();
108
109 let t = timestamp.seconds;
110 let nanos = timestamp.nanos;
111
112 const LEAPOCH: i64 = 946_684_800 + 86400 * (31 + 29);
114 const DAYS_PER_400Y: i32 = 365 * 400 + 97;
115 const DAYS_PER_100Y: i32 = 365 * 100 + 24;
116 const DAYS_PER_4Y: i32 = 365 * 4 + 1;
117 const DAYS_IN_MONTH: [u8; 12] = [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29];
118
119 let mut days: i64 = (t / 86_400) - (LEAPOCH / 86_400);
121 let mut remsecs: i32 = (t % 86_400) as i32;
122 if remsecs < 0i32 {
123 remsecs += 86_400;
124 days -= 1
125 }
126
127 let mut qc_cycles: i32 = (days / i64::from(DAYS_PER_400Y)) as i32;
128 let mut remdays: i32 = (days % i64::from(DAYS_PER_400Y)) as i32;
129 if remdays < 0 {
130 remdays += DAYS_PER_400Y;
131 qc_cycles -= 1;
132 }
133
134 let mut c_cycles: i32 = remdays / DAYS_PER_100Y;
135 if c_cycles == 4 {
136 c_cycles -= 1;
137 }
138 remdays -= c_cycles * DAYS_PER_100Y;
139
140 let mut q_cycles: i32 = remdays / DAYS_PER_4Y;
141 if q_cycles == 25 {
142 q_cycles -= 1;
143 }
144 remdays -= q_cycles * DAYS_PER_4Y;
145
146 let mut remyears: i32 = remdays / 365;
147 if remyears == 4 {
148 remyears -= 1;
149 }
150 remdays -= remyears * 365;
151
152 let mut years: i64 = i64::from(remyears)
153 + 4 * i64::from(q_cycles)
154 + 100 * i64::from(c_cycles)
155 + 400 * i64::from(qc_cycles);
156
157 let mut months: i32 = 0;
158 while i32::from(DAYS_IN_MONTH[months as usize]) <= remdays {
159 remdays -= i32::from(DAYS_IN_MONTH[months as usize]);
160 months += 1
161 }
162
163 if months >= 10 {
164 months -= 12;
165 years += 1;
166 }
167
168 let date_time = DateTime {
169 year: years + 2000,
170 month: (months + 3) as u8,
171 day: (remdays + 1) as u8,
172 hour: (remsecs / 3600) as u8,
173 minute: (remsecs / 60 % 60) as u8,
174 second: (remsecs % 60) as u8,
175 nanos: nanos as u32,
176 };
177 debug_assert!(date_time.is_valid());
178 date_time
179 }
180}
181
182fn days_in_month(year: i64, month: u8) -> u8 {
184 const DAYS_IN_MONTH: [u8; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
185 let (_, is_leap) = year_to_seconds(year);
186 DAYS_IN_MONTH[usize::from(month - 1)] + u8::from(is_leap && month == 2)
187}
188
189macro_rules! ensure {
190 ($expr:expr) => {{
191 if !$expr {
192 return None;
193 }
194 }};
195}
196
197fn parse_date(s: &str) -> Option<(i64, u8, u8, &str)> {
202 debug_assert!(s.is_ascii());
203
204 ensure!(s.len() >= 10);
206
207 let (year, s) = match s.as_bytes()[0] {
212 b'+' => {
213 let (digits, s) = parse_digits(&s[1..]);
214 ensure!(digits.len() >= 5);
215 let date: i64 = digits.parse().ok()?;
216 (date, s)
217 }
218 b'-' => {
219 let (digits, s) = parse_digits(&s[1..]);
220 ensure!(digits.len() >= 4);
221 let date: i64 = digits.parse().ok()?;
222 (-date, s)
223 }
224 _ => {
225 let (n1, s) = parse_two_digit_numeric(s)?;
227 let (n2, s) = parse_two_digit_numeric(s)?;
228 (i64::from(n1) * 100 + i64::from(n2), s)
229 }
230 };
231
232 let s = parse_char(s, b'-')?;
233 let (month, s) = parse_two_digit_numeric(s)?;
234 let s = parse_char(s, b'-')?;
235 let (day, s) = parse_two_digit_numeric(s)?;
236 Some((year, month, day, s))
237}
238
239fn parse_time(s: &str) -> Option<(u8, u8, u8, u32, &str)> {
244 debug_assert!(s.is_ascii());
245
246 let (hour, s) = parse_two_digit_numeric(s)?;
247 let s = parse_char(s, b':')?;
248 let (minute, s) = parse_two_digit_numeric(s)?;
249 let s = parse_char(s, b':')?;
250 let (second, s) = parse_two_digit_numeric(s)?;
251
252 let (nanos, s) = parse_nanos(s)?;
253
254 Some((hour, minute, second, nanos, s))
255}
256
257fn parse_nanos(s: &str) -> Option<(u32, &str)> {
260 debug_assert!(s.is_ascii());
261
262 let (nanos, s) = if let Some(s) = parse_char(s, b'.') {
264 let (mut digits, s) = parse_digits(s);
265 if digits.len() > 9 {
266 digits = digits.split_at(9).0;
267 }
268 let nanos = 10u32.pow(9 - digits.len() as u32) * digits.parse::<u32>().ok()?;
269 (nanos, s)
270 } else {
271 (0, s)
272 };
273
274 Some((nanos, s))
275}
276
277fn parse_offset(s: &str) -> Option<(i8, i8, &str)> {
280 debug_assert!(s.is_ascii());
281
282 if s.is_empty() {
283 return Some((0, 0, s));
285 }
286
287 let s = parse_char(s, b' ').unwrap_or(s);
289
290 if let Some(s) = parse_char_ignore_case(s, b'Z') {
291 Some((0, 0, s))
292 } else {
293 let (is_positive, s) = if let Some(s) = parse_char(s, b'+') {
294 (true, s)
295 } else if let Some(s) = parse_char(s, b'-') {
296 (false, s)
297 } else {
298 return None;
299 };
300
301 let (hour, s) = parse_two_digit_numeric(s)?;
302
303 let (minute, s) = if s.is_empty() {
304 (0, s)
306 } else {
307 let s = parse_char(s, b':').unwrap_or(s);
309 let (minute, s) = parse_two_digit_numeric(s)?;
310 (minute, s)
311 };
312
313 ensure!(hour < 24 && minute < 60);
314
315 let hour = hour as i8;
316 let minute = minute as i8;
317
318 if is_positive {
319 Some((hour, minute, s))
320 } else {
321 Some((-hour, -minute, s))
322 }
323 }
324}
325
326fn parse_two_digit_numeric(s: &str) -> Option<(u8, &str)> {
329 debug_assert!(s.is_ascii());
330 if s.len() < 2 {
331 return None;
332 }
333 if s.starts_with('+') {
334 return None;
335 }
336 let (digits, s) = s.split_at(2);
337 Some((digits.parse().ok()?, s))
338}
339
340fn parse_digits(s: &str) -> (&str, &str) {
342 debug_assert!(s.is_ascii());
343
344 let idx = s
345 .as_bytes()
346 .iter()
347 .position(|c| !c.is_ascii_digit())
348 .unwrap_or(s.len());
349 s.split_at(idx)
350}
351
352fn parse_char(s: &str, c: u8) -> Option<&str> {
355 debug_assert!(s.is_ascii());
356
357 ensure!(*s.as_bytes().first()? == c);
358 Some(&s[1..])
359}
360
361fn parse_char_ignore_case(s: &str, c: u8) -> Option<&str> {
364 debug_assert!(s.is_ascii());
365
366 ensure!(s.as_bytes().first()?.eq_ignore_ascii_case(&c));
367 Some(&s[1..])
368}
369
370fn date_time_to_seconds(tm: &DateTime) -> i64 {
378 let (start_of_year, is_leap) = year_to_seconds(tm.year);
379
380 let seconds_within_year = month_to_seconds(tm.month, is_leap)
381 + 86400 * u32::from(tm.day - 1)
382 + 3600 * u32::from(tm.hour)
383 + 60 * u32::from(tm.minute)
384 + u32::from(tm.second);
385
386 (start_of_year + i128::from(seconds_within_year)) as i64
387}
388
389fn month_to_seconds(month: u8, is_leap: bool) -> u32 {
395 const SECS_THROUGH_MONTH: [u32; 12] = [
396 0,
397 31 * 86400,
398 59 * 86400,
399 90 * 86400,
400 120 * 86400,
401 151 * 86400,
402 181 * 86400,
403 212 * 86400,
404 243 * 86400,
405 273 * 86400,
406 304 * 86400,
407 334 * 86400,
408 ];
409 let t = SECS_THROUGH_MONTH[usize::from(month - 1)];
410 if is_leap && month > 2 {
411 t + 86400
412 } else {
413 t
414 }
415}
416
417pub(crate) fn year_to_seconds(year: i64) -> (i128, bool) {
425 let is_leap;
426 let year = year - 1900;
427
428 if (1..=138).contains(&year) {
430 let mut leaps: i64 = (year - 68) >> 2;
431 if (year - 68).trailing_zeros() >= 2 {
432 leaps -= 1;
433 is_leap = true;
434 } else {
435 is_leap = false;
436 }
437 return (
438 i128::from(31_536_000 * (year - 70) + 86400 * leaps),
439 is_leap,
440 );
441 }
442
443 let centuries: i64;
444 let mut leaps: i64;
445
446 let mut cycles: i64 = (year - 100) / 400;
447 let mut rem: i64 = (year - 100) % 400;
448
449 if rem < 0 {
450 cycles -= 1;
451 rem += 400
452 }
453 if rem == 0 {
454 is_leap = true;
455 centuries = 0;
456 leaps = 0;
457 } else {
458 if rem >= 200 {
459 if rem >= 300 {
460 centuries = 3;
461 rem -= 300;
462 } else {
463 centuries = 2;
464 rem -= 200;
465 }
466 } else if rem >= 100 {
467 centuries = 1;
468 rem -= 100;
469 } else {
470 centuries = 0;
471 }
472 if rem == 0 {
473 is_leap = false;
474 leaps = 0;
475 } else {
476 leaps = rem / 4;
477 rem %= 4;
478 is_leap = rem == 0;
479 }
480 }
481 leaps += 97 * cycles + 24 * centuries - i64::from(is_leap);
482
483 (
484 i128::from((year - 100) * 31_536_000) + i128::from(leaps * 86400 + 946_684_800 + 86400),
485 is_leap,
486 )
487}
488
489pub(crate) fn parse_timestamp(s: &str) -> Option<Timestamp> {
491 ensure!(s.is_ascii());
493
494 let (year, month, day, s) = parse_date(s)?;
495
496 if s.is_empty() {
497 let date_time = DateTime {
499 year,
500 month,
501 day,
502 ..DateTime::default()
503 };
504
505 return Timestamp::try_from(date_time).ok();
506 }
507
508 let s = parse_char_ignore_case(s, b'T').or_else(|| parse_char(s, b' '))?;
510 let (hour, minute, mut second, nanos, s) = parse_time(s)?;
511 let (offset_hour, offset_minute, s) = parse_offset(s)?;
512
513 ensure!(s.is_empty());
514
515 if second == 60 {
522 second = 59;
523 }
524
525 let date_time = DateTime {
526 year,
527 month,
528 day,
529 hour,
530 minute,
531 second,
532 nanos,
533 };
534
535 let Timestamp { seconds, nanos } = Timestamp::try_from(date_time).ok()?;
536
537 let seconds =
538 seconds.checked_sub(i64::from(offset_hour) * 3600 + i64::from(offset_minute) * 60)?;
539
540 Some(Timestamp { seconds, nanos })
541}
542
543pub(crate) fn parse_duration(s: &str) -> Option<Duration> {
547 ensure!(s.is_ascii());
549
550 let (is_negative, s) = match parse_char(s, b'-') {
551 Some(s) => (true, s),
552 None => (false, s),
553 };
554
555 let (digits, s) = parse_digits(s);
556 let seconds = digits.parse::<i64>().ok()?;
557
558 let (nanos, s) = parse_nanos(s)?;
559
560 let s = parse_char(s, b's')?;
561 ensure!(s.is_empty());
562 ensure!(nanos < crate::NANOS_PER_SECOND as u32);
563
564 let (seconds, nanos) = if is_negative {
566 (-seconds, -(nanos as i32))
567 } else {
568 (seconds, nanos as i32)
569 };
570
571 Some(Duration { seconds, nanos })
572}
573
574impl TryFrom<DateTime> for Timestamp {
575 type Error = TimestampError;
576
577 fn try_from(date_time: DateTime) -> Result<Timestamp, TimestampError> {
578 if !date_time.is_valid() {
579 return Err(TimestampError::InvalidDateTime);
580 }
581 let seconds = date_time_to_seconds(&date_time);
582 let nanos = date_time.nanos;
583 Ok(Timestamp {
584 seconds,
585 nanos: nanos as i32,
586 })
587 }
588}
589
590#[cfg(test)]
591mod tests {
592 use super::*;
593 use proptest::prelude::*;
594 use prost::alloc::format;
595
596 #[test]
597 fn test_min_max() {
598 assert_eq!(
599 DateTime::MIN,
600 DateTime::from(Timestamp {
601 seconds: i64::MIN,
602 nanos: 0
603 }),
604 );
605 assert_eq!(
606 DateTime::MAX,
607 DateTime::from(Timestamp {
608 seconds: i64::MAX,
609 nanos: 999_999_999
610 }),
611 );
612 }
613
614 #[cfg(feature = "std")]
615 #[test]
616 fn test_datetime_from_timestamp() {
617 let case = |expected: &str, secs: i64, nanos: i32| {
618 let timestamp = Timestamp {
619 seconds: secs,
620 nanos,
621 };
622 assert_eq!(
623 expected,
624 format!("{}", DateTime::from(timestamp)),
625 "timestamp: {:?}",
626 timestamp
627 );
628 };
629
630 case("1970-01-01T00:00:00Z", 0, 0);
635
636 case("1970-01-01T00:00:00.000000001Z", 0, 1);
637 case("1970-01-01T00:00:00.123450Z", 0, 123_450_000);
638 case("1970-01-01T00:00:00.050Z", 0, 50_000_000);
639 case("1970-01-01T00:00:01.000000001Z", 1, 1);
640 case("1970-01-01T00:01:01.000000001Z", 60 + 1, 1);
641 case("1970-01-01T01:01:01.000000001Z", 60 * 60 + 60 + 1, 1);
642 case(
643 "1970-01-02T01:01:01.000000001Z",
644 24 * 60 * 60 + 60 * 60 + 60 + 1,
645 1,
646 );
647
648 case("1969-12-31T23:59:59Z", -1, 0);
649 case("1969-12-31T23:59:59.000001Z", -1, 1_000);
650 case("1969-12-31T23:59:59.500Z", -1, 500_000_000);
651 case("1969-12-31T23:58:59.000001Z", -60 - 1, 1_000);
652 case("1969-12-31T22:58:59.000001Z", -60 * 60 - 60 - 1, 1_000);
653 case(
654 "1969-12-30T22:58:59.000000001Z",
655 -24 * 60 * 60 - 60 * 60 - 60 - 1,
656 1,
657 );
658
659 case("2038-01-19T03:14:07Z", i32::MAX as i64, 0);
660 case("2038-01-19T03:14:08Z", i32::MAX as i64 + 1, 0);
661 case("1901-12-13T20:45:52Z", i32::MIN as i64, 0);
662 case("1901-12-13T20:45:51Z", i32::MIN as i64 - 1, 0);
663
664 #[cfg(not(target_os = "windows"))]
668 {
669 case("+292277026596-12-04T15:30:07Z", i64::MAX, 0);
670 case("+292277026596-12-04T15:30:06Z", i64::MAX - 1, 0);
671 case("-292277022657-01-27T08:29:53Z", i64::MIN + 1, 0);
672 }
673
674 case("1900-01-01T00:00:00Z", -2_208_988_800, 0);
675 case("1899-12-31T23:59:59Z", -2_208_988_801, 0);
676 case("0000-01-01T00:00:00Z", -62_167_219_200, 0);
677 case("-0001-12-31T23:59:59Z", -62_167_219_201, 0);
678
679 case("1234-05-06T07:08:09Z", -23_215_049_511, 0);
680 case("-1234-05-06T07:08:09Z", -101_097_651_111, 0);
681 case("2345-06-07T08:09:01Z", 11_847_456_541, 0);
682 case("-2345-06-07T08:09:01Z", -136_154_620_259, 0);
683 }
684
685 #[test]
686 fn test_parse_timestamp() {
687 assert_eq!(
689 "1985-04-12T23:20:50.52Z".parse::<Timestamp>(),
690 Timestamp::date_time_nanos(1985, 4, 12, 23, 20, 50, 520_000_000),
691 );
692 assert_eq!(
693 "1996-12-19T16:39:57-08:00".parse::<Timestamp>(),
694 Timestamp::date_time(1996, 12, 20, 0, 39, 57),
695 );
696 assert_eq!(
697 "1996-12-19T16:39:57-08:00".parse::<Timestamp>(),
698 Timestamp::date_time(1996, 12, 20, 0, 39, 57),
699 );
700 assert_eq!(
701 "1990-12-31T23:59:60Z".parse::<Timestamp>(),
702 Timestamp::date_time(1990, 12, 31, 23, 59, 59),
703 );
704 assert_eq!(
705 "1990-12-31T15:59:60-08:00".parse::<Timestamp>(),
706 Timestamp::date_time(1990, 12, 31, 23, 59, 59),
707 );
708 assert_eq!(
709 "1937-01-01T12:00:27.87+00:20".parse::<Timestamp>(),
710 Timestamp::date_time_nanos(1937, 1, 1, 11, 40, 27, 870_000_000),
711 );
712
713 assert_eq!(
715 "1937-01-01".parse::<Timestamp>(),
716 Timestamp::date(1937, 1, 1),
717 );
718
719 assert_eq!(
721 "-0008-01-01".parse::<Timestamp>(),
722 Timestamp::date(-8, 1, 1),
723 );
724
725 assert_eq!(
727 "+19370-01-01".parse::<Timestamp>(),
728 Timestamp::date(19370, 1, 1),
729 );
730
731 assert_eq!(
733 "2020-02-03T01:02:03.123456789Z".parse::<Timestamp>(),
734 Timestamp::date_time_nanos(2020, 2, 3, 1, 2, 3, 123_456_789),
735 );
736
737 assert_eq!(
739 "2020-02-29T01:02:03.00Z".parse::<Timestamp>(),
740 Timestamp::try_from(DateTime {
741 year: 2020,
742 month: 2,
743 day: 29,
744 hour: 1,
745 minute: 2,
746 second: 3,
747 nanos: 0,
748 })
749 );
750
751 assert_eq!(
754 "1985-04-12 23:20:50.52Z".parse::<Timestamp>(),
755 Timestamp::date_time_nanos(1985, 4, 12, 23, 20, 50, 520_000_000),
756 );
757
758 assert_eq!(
760 "1985-04-12T23:20:50.52".parse::<Timestamp>(),
761 Timestamp::date_time_nanos(1985, 4, 12, 23, 20, 50, 520_000_000),
762 );
763
764 assert_eq!(
766 "1996-12-19T16:39:57-08".parse::<Timestamp>(),
767 Timestamp::date_time(1996, 12, 20, 0, 39, 57),
768 );
769
770 assert_eq!(
772 "2015-09-12 00:47:19.591 Z".parse::<Timestamp>(),
773 Timestamp::date_time_nanos(2015, 9, 12, 0, 47, 19, 591_000_000),
774 );
775 assert_eq!(
776 "2020-06-15 00:01:02.123 +0800".parse::<Timestamp>(),
777 Timestamp::date_time_nanos(2020, 6, 14, 16, 1, 2, 123_000_000),
778 );
779
780 assert_eq!(
782 "-11111111-z".parse::<Timestamp>(),
783 Err(crate::TimestampError::ParseFailure),
784 );
785 assert_eq!(
786 "1900-01-10".parse::<Timestamp>(),
787 Ok(Timestamp {
788 seconds: -2208211200,
789 nanos: 0
790 }),
791 );
792 assert_eq!(
794 "19+1-+2-+3T+4:+5:+6Z".parse::<Timestamp>(),
795 Err(crate::TimestampError::ParseFailure),
796 );
797
798 assert_eq!(
800 "1343-08-16 18:33:44.1666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666660404z".parse::<Timestamp>(),
801 Timestamp::date_time_nanos(1343, 8, 16, 18, 33, 44, 166_666_666),
802 );
803 }
804
805 #[test]
806 fn test_parse_duration() {
807 let case = |s: &str, seconds: i64, nanos: i32| {
808 assert_eq!(
809 s.parse::<Duration>().unwrap(),
810 Duration { seconds, nanos },
811 "duration: {}",
812 s
813 );
814 };
815
816 case("0s", 0, 0);
817 case("0.0s", 0, 0);
818 case("0.000s", 0, 0);
819
820 case("-0s", 0, 0);
821 case("-0.0s", 0, 0);
822 case("-0.000s", 0, 0);
823
824 case("-0s", 0, 0);
825 case("-0.0s", 0, 0);
826 case("-0.000s", 0, 0);
827
828 case("0.05s", 0, 50_000_000);
829 case("0.050s", 0, 50_000_000);
830
831 case("-0.05s", 0, -50_000_000);
832 case("-0.050s", 0, -50_000_000);
833
834 case("1s", 1, 0);
835 case("1.0s", 1, 0);
836 case("1.000s", 1, 0);
837
838 case("-1s", -1, 0);
839 case("-1.0s", -1, 0);
840 case("-1.000s", -1, 0);
841
842 case("15s", 15, 0);
843 case("15.1s", 15, 100_000_000);
844 case("15.100s", 15, 100_000_000);
845
846 case("-15s", -15, 0);
847 case("-15.1s", -15, -100_000_000);
848 case("-15.100s", -15, -100_000_000);
849
850 case("100.000000009s", 100, 9);
851 case("-100.000000009s", -100, -9);
852 }
853
854 #[test]
855 fn test_parse_non_ascii() {
856 assert!("2021️⃣-06-15 00:01:02.123 +0800"
857 .parse::<Timestamp>()
858 .is_err());
859
860 assert!("1️⃣s".parse::<Duration>().is_err());
861 }
862
863 #[test]
864 fn check_invalid_datetimes() {
865 assert_eq!(
866 Timestamp::try_from(DateTime {
867 year: i64::from_le_bytes([178, 2, 0, 0, 0, 0, 0, 128]),
868 month: 2,
869 day: 2,
870 hour: 8,
871 minute: 58,
872 second: 8,
873 nanos: u32::from_le_bytes([0, 0, 0, 50]),
874 }),
875 Err(TimestampError::InvalidDateTime)
876 );
877 assert_eq!(
878 Timestamp::try_from(DateTime {
879 year: i64::from_le_bytes([132, 7, 0, 0, 0, 0, 0, 128]),
880 month: 2,
881 day: 2,
882 hour: 8,
883 minute: 58,
884 second: 8,
885 nanos: u32::from_le_bytes([0, 0, 0, 50]),
886 }),
887 Err(TimestampError::InvalidDateTime)
888 );
889 assert_eq!(
890 Timestamp::try_from(DateTime {
891 year: i64::from_le_bytes([80, 96, 32, 240, 99, 0, 32, 180]),
892 month: 1,
893 day: 18,
894 hour: 19,
895 minute: 26,
896 second: 8,
897 nanos: u32::from_le_bytes([0, 0, 0, 50]),
898 }),
899 Err(TimestampError::InvalidDateTime)
900 );
901 assert_eq!(
902 Timestamp::try_from(DateTime {
903 year: DateTime::MIN.year - 1,
904 month: 0,
905 day: 0,
906 hour: 0,
907 minute: 0,
908 second: 0,
909 nanos: 0,
910 }),
911 Err(TimestampError::InvalidDateTime)
912 );
913 assert_eq!(
914 Timestamp::try_from(DateTime {
915 year: i64::MIN,
916 month: 0,
917 day: 0,
918 hour: 0,
919 minute: 0,
920 second: 0,
921 nanos: 0,
922 }),
923 Err(TimestampError::InvalidDateTime)
924 );
925 assert_eq!(
926 Timestamp::try_from(DateTime {
927 year: DateTime::MAX.year + 1,
928 month: u8::MAX,
929 day: u8::MAX,
930 hour: u8::MAX,
931 minute: u8::MAX,
932 second: u8::MAX,
933 nanos: u32::MAX,
934 }),
935 Err(TimestampError::InvalidDateTime)
936 );
937 assert_eq!(
938 Timestamp::try_from(DateTime {
939 year: i64::MAX,
940 month: u8::MAX,
941 day: u8::MAX,
942 hour: u8::MAX,
943 minute: u8::MAX,
944 second: u8::MAX,
945 nanos: u32::MAX,
946 }),
947 Err(TimestampError::InvalidDateTime)
948 );
949 }
950
951 proptest! {
952 #[cfg(feature = "std")]
953 #[test]
954 fn check_timestamp_parse_to_string_roundtrip(
955 system_time in std::time::SystemTime::arbitrary(),
956 ) {
957
958 let ts = Timestamp::from(system_time);
959
960 assert_eq!(
961 ts,
962 ts.to_string().parse::<Timestamp>().unwrap(),
963 )
964 }
965
966 #[cfg(feature = "std")]
967 #[test]
968 fn check_duration_parse_to_string_roundtrip(
969 duration in core::time::Duration::arbitrary(),
970 ) {
971 let duration = match Duration::try_from(duration) {
972 Ok(duration) => duration,
973 Err(_) => return Err(TestCaseError::reject("duration out of range")),
974 };
975
976 prop_assert_eq!(
977 &duration,
978 &duration.to_string().parse::<Duration>().unwrap(),
979 "{}", duration.to_string()
980 );
981 }
982
983 #[test]
984 fn check_timestamp_roundtrip_with_date_time(
985 seconds in i64::arbitrary(),
986 nanos in i32::arbitrary(),
987 ) {
988 let timestamp = Timestamp { seconds, nanos };
989 let date_time = DateTime::from(timestamp);
990 let roundtrip = Timestamp::try_from(date_time).unwrap();
991
992 prop_assert_eq!(timestamp.normalized(), roundtrip);
993 }
994
995 #[test]
996 fn check_date_time_roundtrip_with_timestamp(
997 year in i64::arbitrary(),
998 month in u8::arbitrary(),
999 day in u8::arbitrary(),
1000 hour in u8::arbitrary(),
1001 minute in u8::arbitrary(),
1002 second in u8::arbitrary(),
1003 nanos in u32::arbitrary(),
1004 ) {
1005 let date_time = DateTime {
1006 year,
1007 month,
1008 day,
1009 hour,
1010 minute,
1011 second,
1012 nanos
1013 };
1014
1015 if date_time.is_valid() {
1016 let timestamp = Timestamp::try_from(date_time).unwrap();
1017 let roundtrip = DateTime::from(timestamp);
1018
1019 prop_assert_eq!(date_time, roundtrip);
1020 } else {
1021 prop_assert_eq!(Timestamp::try_from(date_time), Err(TimestampError::InvalidDateTime));
1022 }
1023 }
1024 }
1025}