1#![no_std]
2#![doc(html_root_url = "https://docs.rs/bilrost-types/0.1015.0")]
3#![forbid(unsafe_code)]
4
5extern crate alloc;
15#[cfg(feature = "std")]
16extern crate std;
17
18mod datetime;
19mod types;
20
21use core::fmt;
22use core::str::FromStr;
23use core::time;
24
25pub use types::*;
26
27const NANOS_PER_SECOND: i32 = 1_000_000_000;
32const NANOS_MAX: i32 = NANOS_PER_SECOND - 1;
33
34impl core::ops::Neg for Duration {
37 type Output = Self;
38
39 fn neg(self) -> Self {
40 Self {
41 seconds: -self.seconds,
42 nanos: -self.nanos,
43 }
44 }
45}
46
47impl Duration {
50 pub fn is_canonical(&self) -> bool {
52 (-NANOS_MAX..=NANOS_MAX).contains(&self.nanos)
53 && match self.seconds.signum() {
54 -1 => self.nanos <= 0,
55 1 => self.nanos >= 0,
56 _ => true,
57 }
58 }
59
60 pub fn normalize(&mut self) {
66 if self.nanos <= -NANOS_PER_SECOND || self.nanos >= NANOS_PER_SECOND {
68 if let Some(seconds) = self
69 .seconds
70 .checked_add((self.nanos / NANOS_PER_SECOND) as i64)
71 {
72 self.seconds = seconds;
73 self.nanos %= NANOS_PER_SECOND;
74 } else if self.nanos < 0 {
75 self.seconds = i64::MIN;
77 self.nanos = -NANOS_MAX;
78 } else {
79 self.seconds = i64::MAX;
81 self.nanos = NANOS_MAX;
82 }
83 }
84
85 if self.seconds < 0 && self.nanos > 0 {
87 if let Some(seconds) = self.seconds.checked_add(1) {
88 self.seconds = seconds;
89 self.nanos -= NANOS_PER_SECOND;
90 } else {
91 debug_assert_eq!(self.seconds, i64::MAX);
93 self.nanos = NANOS_MAX;
94 }
95 } else if self.seconds > 0 && self.nanos < 0 {
96 if let Some(seconds) = self.seconds.checked_sub(1) {
97 self.seconds = seconds;
98 self.nanos += NANOS_PER_SECOND;
99 } else {
100 debug_assert_eq!(self.seconds, i64::MIN);
102 self.nanos = -NANOS_MAX;
103 }
104 }
105 }
106}
107
108impl TryFrom<time::Duration> for Duration {
109 type Error = DurationError;
110
111 fn try_from(duration: time::Duration) -> Result<Duration, DurationError> {
113 let seconds = i64::try_from(duration.as_secs()).map_err(|_| DurationError::OutOfRange)?;
114 let nanos = duration.subsec_nanos() as i32;
115
116 let mut duration = Duration { seconds, nanos };
117 duration.normalize();
118 Ok(duration)
119 }
120}
121
122impl TryFrom<Duration> for time::Duration {
123 type Error = DurationError;
124
125 fn try_from(mut duration: Duration) -> Result<time::Duration, DurationError> {
127 duration.normalize();
128 if duration.seconds >= 0 && duration.nanos >= 0 {
129 Ok(time::Duration::new(
130 duration.seconds as u64,
131 duration.nanos as u32,
132 ))
133 } else {
134 Err(DurationError::NegativeDuration(time::Duration::new(
135 (-duration.seconds) as u64,
136 (-duration.nanos) as u32,
137 )))
138 }
139 }
140}
141
142impl fmt::Display for Duration {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 let mut d = self.clone();
145 d.normalize();
146 if self.seconds < 0 || self.nanos < 0 {
147 write!(f, "-")?;
148 }
149 write!(f, "{}", d.seconds.abs())?;
150
151 let nanos = d.nanos.abs();
153 if nanos == 0 {
154 write!(f, "s")
155 } else if nanos % 1_000_000 == 0 {
156 write!(f, ".{:03}s", nanos / 1_000_000)
157 } else if nanos % 1_000 == 0 {
158 write!(f, ".{:06}s", nanos / 1_000)
159 } else {
160 write!(f, ".{:09}s", nanos)
161 }
162 }
163}
164
165#[derive(Debug, PartialEq)]
167#[non_exhaustive]
168pub enum DurationError {
169 ParseFailure,
175
176 NegativeDuration(time::Duration),
180
181 OutOfRange,
186}
187
188impl fmt::Display for DurationError {
189 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190 match self {
191 DurationError::ParseFailure => write!(f, "failed to parse duration"),
192 DurationError::NegativeDuration(duration) => {
193 write!(f, "failed to convert negative duration: {:?}", duration)
194 }
195 DurationError::OutOfRange => {
196 write!(f, "failed to convert duration out of range")
197 }
198 }
199 }
200}
201
202#[cfg(feature = "std")]
203impl std::error::Error for DurationError {}
204
205impl FromStr for Duration {
206 type Err = DurationError;
207
208 fn from_str(s: &str) -> Result<Duration, DurationError> {
209 datetime::parse_duration(s).ok_or(DurationError::ParseFailure)
210 }
211}
212
213impl Timestamp {
214 pub fn is_canonical(&self) -> bool {
216 (0..NANOS_PER_SECOND).contains(&self.nanos)
217 }
218
219 pub fn normalize(&mut self) {
225 if self.nanos <= -NANOS_PER_SECOND || self.nanos >= NANOS_PER_SECOND {
227 if let Some(seconds) = self
228 .seconds
229 .checked_add((self.nanos / NANOS_PER_SECOND) as i64)
230 {
231 self.seconds = seconds;
232 self.nanos %= NANOS_PER_SECOND;
233 } else if self.nanos < 0 {
234 self.seconds = i64::MIN;
236 self.nanos = 0;
237 } else {
238 self.seconds = i64::MAX;
240 self.nanos = 999_999_999;
241 }
242 }
243
244 if self.nanos < 0 {
246 if let Some(seconds) = self.seconds.checked_sub(1) {
247 self.seconds = seconds;
248 self.nanos += NANOS_PER_SECOND;
249 } else {
250 debug_assert_eq!(self.seconds, i64::MIN);
252 self.nanos = 0;
253 }
254 }
255 }
256
257 pub fn try_normalize(mut self) -> Result<Timestamp, Timestamp> {
264 let before = self.clone();
265 self.normalize();
266 if (self.seconds == i64::MAX || self.seconds == i64::MIN) && self.seconds != before.seconds
269 {
270 Err(before)
271 } else {
272 Ok(self)
273 }
274 }
275
276 pub fn date(year: i64, month: u8, day: u8) -> Result<Timestamp, TimestampError> {
278 Timestamp::date_time_nanos(year, month, day, 0, 0, 0, 0)
279 }
280
281 pub fn date_time(
283 year: i64,
284 month: u8,
285 day: u8,
286 hour: u8,
287 minute: u8,
288 second: u8,
289 ) -> Result<Timestamp, TimestampError> {
290 Timestamp::date_time_nanos(year, month, day, hour, minute, second, 0)
291 }
292
293 pub fn date_time_nanos(
295 year: i64,
296 month: u8,
297 day: u8,
298 hour: u8,
299 minute: u8,
300 second: u8,
301 nanos: u32,
302 ) -> Result<Timestamp, TimestampError> {
303 let date_time = datetime::DateTime {
304 year,
305 month,
306 day,
307 hour,
308 minute,
309 second,
310 nanos,
311 };
312
313 Timestamp::try_from(date_time).map_err(|_| TimestampError::InvalidDateTime)
314 }
315}
316
317#[cfg(feature = "std")]
318impl From<std::time::SystemTime> for Timestamp {
319 fn from(system_time: std::time::SystemTime) -> Timestamp {
320 let (seconds, nanos) = match system_time.duration_since(std::time::UNIX_EPOCH) {
321 Ok(duration) => {
322 let seconds = i64::try_from(duration.as_secs()).unwrap();
323 (seconds, duration.subsec_nanos() as i32)
324 }
325 Err(error) => {
326 let duration = error.duration();
327 let seconds = i64::try_from(duration.as_secs()).unwrap();
328 let nanos = duration.subsec_nanos() as i32;
329 if nanos == 0 {
330 (-seconds, 0)
331 } else {
332 (-seconds - 1, 1_000_000_000 - nanos)
333 }
334 }
335 };
336 Timestamp { seconds, nanos }
337 }
338}
339
340#[derive(Debug, PartialEq)]
342#[non_exhaustive]
343pub enum TimestampError {
344 OutOfSystemRange(Timestamp),
352
353 ParseFailure,
355
356 InvalidDateTime,
358}
359
360impl fmt::Display for TimestampError {
361 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
362 match self {
363 TimestampError::OutOfSystemRange(timestamp) => {
364 write!(
365 f,
366 "{} is not representable as a `SystemTime` because it is out of range",
367 timestamp
368 )
369 }
370 TimestampError::ParseFailure => {
371 write!(f, "failed to parse RFC-3339 formatted timestamp")
372 }
373 TimestampError::InvalidDateTime => {
374 write!(f, "invalid date or time")
375 }
376 }
377 }
378}
379
380#[cfg(feature = "std")]
381impl std::error::Error for TimestampError {}
382
383#[cfg(feature = "std")]
384impl TryFrom<Timestamp> for std::time::SystemTime {
385 type Error = TimestampError;
386
387 fn try_from(mut timestamp: Timestamp) -> Result<std::time::SystemTime, Self::Error> {
388 let orig_timestamp = timestamp.clone();
389 timestamp.normalize();
390
391 let system_time = if timestamp.seconds >= 0 {
392 std::time::UNIX_EPOCH.checked_add(time::Duration::from_secs(timestamp.seconds as u64))
393 } else {
394 std::time::UNIX_EPOCH.checked_sub(time::Duration::from_secs(
395 timestamp
396 .seconds
397 .checked_neg()
398 .ok_or_else(|| TimestampError::OutOfSystemRange(timestamp.clone()))?
399 as u64,
400 ))
401 };
402
403 let system_time = system_time.and_then(|system_time| {
404 system_time.checked_add(time::Duration::from_nanos(timestamp.nanos as u64))
405 });
406
407 system_time.ok_or(TimestampError::OutOfSystemRange(orig_timestamp))
408 }
409}
410
411impl FromStr for Timestamp {
412 type Err = TimestampError;
413
414 fn from_str(s: &str) -> Result<Timestamp, TimestampError> {
415 datetime::parse_timestamp(s).ok_or(TimestampError::ParseFailure)
416 }
417}
418
419impl fmt::Display for Timestamp {
420 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
421 datetime::DateTime::from(self.clone()).fmt(f)
422 }
423}
424
425#[cfg(feature = "serde_json")]
426impl From<serde_json::Value> for Value {
427 fn from(from: serde_json::Value) -> Self {
428 use Value::*;
429 match from {
430 serde_json::Value::Null => Null,
431 serde_json::Value::Bool(value) => Bool(value),
432 serde_json::Value::Number(number) => {
433 if number.is_i64() {
434 Signed(number.as_i64().unwrap())
435 } else if number.is_u64() {
436 Unsigned(number.as_u64().unwrap())
437 } else {
438 Float(number.as_f64().unwrap())
439 }
440 }
441 serde_json::Value::String(value) => String(value),
442 serde_json::Value::Array(values) => List(ListValue {
443 values: values.into_iter().map(Into::into).collect(),
444 }),
445 serde_json::Value::Object(items) => Struct(StructValue {
446 fields: items
447 .into_iter()
448 .map(|(key, value)| (key, value.into()))
449 .collect(),
450 }),
451 }
452 }
453}
454
455#[cfg(feature = "serde_json")]
456impl TryFrom<Value> for serde_json::Value {
457 type Error = ();
458
459 fn try_from(value: Value) -> Result<Self, ()> {
460 Ok(match value {
461 Value::Null => serde_json::Value::Null,
462 Value::Float(value) => {
463 serde_json::Value::Number(serde_json::Number::from_f64(value).ok_or(())?)
464 }
465 Value::Signed(value) => serde_json::Value::Number(serde_json::Number::from(value)),
466 Value::Unsigned(value) => serde_json::Value::Number(serde_json::Number::from(value)),
467 Value::String(value) => serde_json::Value::String(value),
468 Value::Bool(value) => serde_json::Value::Bool(value),
469 Value::Struct(items) => serde_json::Value::Object(
470 items
471 .fields
472 .into_iter()
473 .map(|(key, value)| Ok((key, value.try_into()?)))
474 .collect::<Result<_, _>>()?,
475 ),
476 Value::List(list) => serde_json::Value::Array(
477 list.values
478 .into_iter()
479 .map(TryInto::try_into)
480 .collect::<Result<_, _>>()?,
481 ),
482 })
483 }
484}
485
486#[cfg(test)]
487mod tests {
488 use super::*;
489
490 #[cfg(feature = "std")]
491 use ::{
492 alloc::format,
493 alloc::string::ToString,
494 proptest::prelude::*,
495 std::time::{self, SystemTime, UNIX_EPOCH},
496 };
497
498 use crate::datetime::DateTime;
499
500 #[test]
501 fn check_overflowing_datetimes() {
502 assert_eq!(
504 Timestamp::try_from(DateTime {
505 year: i64::from_le_bytes([178, 2, 0, 0, 0, 0, 0, 128]),
506 month: 2,
507 day: 2,
508 hour: 8,
509 minute: 58,
510 second: 8,
511 nanos: u32::from_le_bytes([0, 0, 0, 50]),
512 }),
513 Err(())
514 );
515 assert_eq!(
516 Timestamp::try_from(DateTime {
517 year: i64::from_le_bytes([132, 7, 0, 0, 0, 0, 0, 128]),
518 month: 2,
519 day: 2,
520 hour: 8,
521 minute: 58,
522 second: 8,
523 nanos: u32::from_le_bytes([0, 0, 0, 50]),
524 }),
525 Err(())
526 );
527 assert_eq!(
528 Timestamp::try_from(DateTime {
529 year: i64::from_le_bytes([80, 96, 32, 240, 99, 0, 32, 180]),
530 month: 1,
531 day: 18,
532 hour: 19,
533 minute: 26,
534 second: 8,
535 nanos: u32::from_le_bytes([0, 0, 0, 50]),
536 }),
537 Err(())
538 );
539 assert_eq!(
540 Timestamp::try_from(DateTime {
541 year: DateTime::MIN.year - 1,
542 month: 0,
543 day: 0,
544 hour: 0,
545 minute: 0,
546 second: 0,
547 nanos: 0,
548 }),
549 Err(())
550 );
551 assert_eq!(
552 Timestamp::try_from(DateTime {
553 year: i64::MIN,
554 month: 0,
555 day: 0,
556 hour: 0,
557 minute: 0,
558 second: 0,
559 nanos: 0,
560 }),
561 Err(())
562 );
563 assert_eq!(
564 Timestamp::try_from(DateTime {
565 year: DateTime::MAX.year + 1,
566 month: u8::MAX,
567 day: u8::MAX,
568 hour: u8::MAX,
569 minute: u8::MAX,
570 second: u8::MAX,
571 nanos: u32::MAX,
572 }),
573 Err(())
574 );
575 assert_eq!(
576 Timestamp::try_from(DateTime {
577 year: i64::MAX,
578 month: u8::MAX,
579 day: u8::MAX,
580 hour: u8::MAX,
581 minute: u8::MAX,
582 second: u8::MAX,
583 nanos: u32::MAX,
584 }),
585 Err(())
586 );
587 assert_eq!(
588 Timestamp::try_from(DateTime {
589 year: 2001,
590 month: u8::MAX,
591 day: u8::MAX,
592 hour: u8::MAX,
593 minute: u8::MAX,
594 second: u8::MAX,
595 nanos: u32::MAX,
596 }),
597 Err(())
598 );
599 assert_eq!(Timestamp::try_from(DateTime::MIN), Ok(Timestamp::MIN));
600 assert_eq!(Timestamp::try_from(DateTime::MAX), Ok(Timestamp::MAX));
601 assert_eq!(DateTime::from(Timestamp::MIN), DateTime::MIN);
602 assert_eq!(DateTime::from(Timestamp::MAX), DateTime::MAX);
603 }
604
605 #[cfg(feature = "std")]
606 proptest! {
607 #[test]
608 fn check_system_time_roundtrip(
609 system_time: SystemTime,
610 ) {
611 prop_assert_eq!(SystemTime::try_from(Timestamp::from(system_time))?, system_time);
612 }
613
614 #[test]
615 fn check_timestamp_roundtrip_via_system_time(
616 seconds: i64,
617 nanos: i32,
618 ) {
619 let mut timestamp = Timestamp { seconds, nanos };
620 let is_canonical = timestamp.is_canonical();
621 timestamp.normalize();
622 prop_assert_eq!(is_canonical, timestamp == Timestamp { seconds, nanos });
623 if let Ok(system_time) = SystemTime::try_from(timestamp.clone()) {
624 prop_assert_eq!(Timestamp::from(system_time), timestamp);
625 }
626 }
627
628 #[test]
629 fn check_timestamp_datetime_roundtrip(seconds: i64, nanos: i32) {
630 let mut timestamp = Timestamp { seconds, nanos };
631 let is_canonical = timestamp.is_canonical();
632 timestamp.normalize();
633 prop_assert_eq!(is_canonical, timestamp == Timestamp { seconds, nanos });
634 let timestamp = timestamp;
635 let datetime: DateTime = timestamp.clone().into();
636 prop_assert_eq!(Timestamp::try_from(datetime), Ok(timestamp));
637 }
638
639 #[test]
640 fn check_duration_roundtrip(
641 seconds: u64,
642 nanos in 0u32..1_000_000_000u32,
643 ) {
644 let std_duration = time::Duration::new(seconds, nanos);
645 let bilrost_duration = match Duration::try_from(std_duration) {
646 Ok(duration) => duration,
647 Err(_) => return Err(TestCaseError::reject("duration out of range")),
648 };
649 prop_assert_eq!(time::Duration::try_from(bilrost_duration.clone())?, std_duration);
650
651 if std_duration != time::Duration::default() {
652 let neg_bilrost_duration = Duration {
653 seconds: -bilrost_duration.seconds,
654 nanos: -bilrost_duration.nanos,
655 };
656
657 prop_assert!(
658 matches!(
659 time::Duration::try_from(neg_bilrost_duration),
660 Err(DurationError::NegativeDuration(d)) if d == std_duration,
661 )
662 )
663 }
664 }
665
666 #[test]
667 fn check_duration_roundtrip_nanos(
668 nanos: u32,
669 ) {
670 let seconds = 0;
671 let std_duration = std::time::Duration::new(seconds, nanos);
672 let bilrost_duration = match Duration::try_from(std_duration) {
673 Ok(duration) => duration,
674 Err(_) => return Err(TestCaseError::reject("duration out of range")),
675 };
676 prop_assert_eq!(time::Duration::try_from(bilrost_duration.clone())?, std_duration);
677
678 if std_duration != time::Duration::default() {
679 let neg_bilrost_duration = Duration {
680 seconds: -bilrost_duration.seconds,
681 nanos: -bilrost_duration.nanos,
682 };
683
684 prop_assert!(
685 matches!(
686 time::Duration::try_from(neg_bilrost_duration),
687 Err(DurationError::NegativeDuration(d)) if d == std_duration,
688 )
689 )
690 }
691 }
692 }
693
694 #[cfg(feature = "std")]
695 #[test]
696 fn test_duration_from_str() {
697 assert_eq!(
698 Duration::from_str("0s"),
699 Ok(Duration {
700 seconds: 0,
701 nanos: 0
702 })
703 );
704 assert_eq!(
705 Duration::from_str("123s"),
706 Ok(Duration {
707 seconds: 123,
708 nanos: 0
709 })
710 );
711 assert_eq!(
712 Duration::from_str("0.123s"),
713 Ok(Duration {
714 seconds: 0,
715 nanos: 123_000_000
716 })
717 );
718 assert_eq!(
719 Duration::from_str("-123s"),
720 Ok(Duration {
721 seconds: -123,
722 nanos: 0
723 })
724 );
725 assert_eq!(
726 Duration::from_str("-0.123s"),
727 Ok(Duration {
728 seconds: 0,
729 nanos: -123_000_000
730 })
731 );
732 assert_eq!(
733 Duration::from_str("22041211.6666666666666s"),
734 Ok(Duration {
735 seconds: 22041211,
736 nanos: 666_666_666
737 })
738 );
739 }
740
741 #[cfg(feature = "std")]
742 #[test]
743 fn test_format_duration() {
744 assert_eq!(
745 "0s",
746 Duration {
747 seconds: 0,
748 nanos: 0
749 }
750 .to_string()
751 );
752 assert_eq!(
753 "123s",
754 Duration {
755 seconds: 123,
756 nanos: 0
757 }
758 .to_string()
759 );
760 assert_eq!(
761 "0.123s",
762 Duration {
763 seconds: 0,
764 nanos: 123_000_000
765 }
766 .to_string()
767 );
768 assert_eq!(
769 "-123s",
770 Duration {
771 seconds: -123,
772 nanos: 0
773 }
774 .to_string()
775 );
776 assert_eq!(
777 "-0.123s",
778 Duration {
779 seconds: 0,
780 nanos: -123_000_000
781 }
782 .to_string()
783 );
784 }
785
786 #[cfg(feature = "std")]
787 #[test]
788 fn check_duration_try_from_negative_nanos() {
789 let seconds: u64 = 0;
790 let nanos: u32 = 1;
791 let std_duration = time::Duration::new(seconds, nanos);
792
793 let neg_bilrost_duration = Duration {
794 seconds: 0,
795 nanos: -1,
796 };
797
798 assert!(matches!(
799 time::Duration::try_from(neg_bilrost_duration),
800 Err(DurationError::NegativeDuration(d)) if d == std_duration,
801 ))
802 }
803
804 #[cfg(feature = "std")]
805 #[test]
806 fn check_timestamp_negative_seconds() {
807 assert_eq!(
817 Timestamp::from(UNIX_EPOCH - time::Duration::new(1_001, 0)),
818 Timestamp {
819 seconds: -1_001,
820 nanos: 0,
821 }
822 );
823 assert_eq!(
824 Timestamp::from(UNIX_EPOCH - time::Duration::new(0, 999_999_900)),
825 Timestamp {
826 seconds: -1,
827 nanos: 100,
828 }
829 );
830 assert_eq!(
831 Timestamp::from(UNIX_EPOCH - time::Duration::new(2_001_234, 12_300)),
832 Timestamp {
833 seconds: -2_001_235,
834 nanos: 999_987_700,
835 }
836 );
837 assert_eq!(
838 Timestamp::from(UNIX_EPOCH - time::Duration::new(768, 65_432_100)),
839 Timestamp {
840 seconds: -769,
841 nanos: 934_567_900,
842 }
843 );
844 }
845
846 #[cfg(all(unix, feature = "std"))]
847 #[test]
848 fn check_timestamp_negative_seconds_1ns() {
849 assert_eq!(
851 Timestamp::from(UNIX_EPOCH - time::Duration::new(0, 999_999_999)),
852 Timestamp {
853 seconds: -1,
854 nanos: 1,
855 }
856 );
857 assert_eq!(
858 Timestamp::from(UNIX_EPOCH - time::Duration::new(1_234_567, 123)),
859 Timestamp {
860 seconds: -1_234_568,
861 nanos: 999_999_877,
862 }
863 );
864 assert_eq!(
865 Timestamp::from(UNIX_EPOCH - time::Duration::new(890, 987_654_321)),
866 Timestamp {
867 seconds: -891,
868 nanos: 12_345_679,
869 }
870 );
871 }
872
873 #[test]
874 fn check_duration_normalize() {
875 #[rustfmt::skip] let cases = [
877 (line!(), 0, 0, 0, 0),
880 (line!(), 1, 1, 1, 1),
881 (line!(), -1, -1, -1, -1),
882 (line!(), 0, 999_999_999, 0, 999_999_999),
883 (line!(), 0, -999_999_999, 0, -999_999_999),
884 (line!(), 0, 1_000_000_000, 1, 0),
885 (line!(), 0, -1_000_000_000, -1, 0),
886 (line!(), 0, 1_000_000_001, 1, 1),
887 (line!(), 0, -1_000_000_001, -1, -1),
888 (line!(), -1, 1, 0, -999_999_999),
889 (line!(), 1, -1, 0, 999_999_999),
890 (line!(), -1, 1_000_000_000, 0, 0),
891 (line!(), 1, -1_000_000_000, 0, 0),
892 (line!(), i64::MIN , 0, i64::MIN , 0),
893 (line!(), i64::MIN + 1, 0, i64::MIN + 1, 0),
894 (line!(), i64::MIN , 1, i64::MIN + 1, -999_999_999),
895 (line!(), i64::MIN , 1_000_000_000, i64::MIN + 1, 0),
896 (line!(), i64::MIN , -1_000_000_000, i64::MIN , -999_999_999),
897 (line!(), i64::MIN + 1, -1_000_000_000, i64::MIN , 0),
898 (line!(), i64::MIN + 2, -1_000_000_000, i64::MIN + 1, 0),
899 (line!(), i64::MIN , -1_999_999_998, i64::MIN , -999_999_999),
900 (line!(), i64::MIN + 1, -1_999_999_998, i64::MIN , -999_999_998),
901 (line!(), i64::MIN + 2, -1_999_999_998, i64::MIN + 1, -999_999_998),
902 (line!(), i64::MIN , -1_999_999_999, i64::MIN , -999_999_999),
903 (line!(), i64::MIN + 1, -1_999_999_999, i64::MIN , -999_999_999),
904 (line!(), i64::MIN + 2, -1_999_999_999, i64::MIN + 1, -999_999_999),
905 (line!(), i64::MIN , -2_000_000_000, i64::MIN , -999_999_999),
906 (line!(), i64::MIN + 1, -2_000_000_000, i64::MIN , -999_999_999),
907 (line!(), i64::MIN + 2, -2_000_000_000, i64::MIN , 0),
908 (line!(), i64::MIN , -999_999_998, i64::MIN , -999_999_998),
909 (line!(), i64::MIN + 1, -999_999_998, i64::MIN + 1, -999_999_998),
910 (line!(), i64::MAX , 0, i64::MAX , 0),
911 (line!(), i64::MAX - 1, 0, i64::MAX - 1, 0),
912 (line!(), i64::MAX , -1, i64::MAX - 1, 999_999_999),
913 (line!(), i64::MAX , 1_000_000_000, i64::MAX , 999_999_999),
914 (line!(), i64::MAX - 1, 1_000_000_000, i64::MAX , 0),
915 (line!(), i64::MAX - 2, 1_000_000_000, i64::MAX - 1, 0),
916 (line!(), i64::MAX , 1_999_999_998, i64::MAX , 999_999_999),
917 (line!(), i64::MAX - 1, 1_999_999_998, i64::MAX , 999_999_998),
918 (line!(), i64::MAX - 2, 1_999_999_998, i64::MAX - 1, 999_999_998),
919 (line!(), i64::MAX , 1_999_999_999, i64::MAX , 999_999_999),
920 (line!(), i64::MAX - 1, 1_999_999_999, i64::MAX , 999_999_999),
921 (line!(), i64::MAX - 2, 1_999_999_999, i64::MAX - 1, 999_999_999),
922 (line!(), i64::MAX , 2_000_000_000, i64::MAX , 999_999_999),
923 (line!(), i64::MAX - 1, 2_000_000_000, i64::MAX , 999_999_999),
924 (line!(), i64::MAX - 2, 2_000_000_000, i64::MAX , 0),
925 (line!(), i64::MAX , 999_999_998, i64::MAX , 999_999_998),
926 (line!(), i64::MAX - 1, 999_999_998, i64::MAX - 1, 999_999_998),
927 ];
928
929 for case in cases.into_iter() {
930 let (line, seconds, nanos, canonical_seconds, canonical_nanos) = case;
931 let mut test_duration = Duration { seconds, nanos };
932 let is_canonical = test_duration.is_canonical();
933 test_duration.normalize();
934 assert_eq!(is_canonical, test_duration == Duration { seconds, nanos });
935
936 assert_eq!(
937 test_duration,
938 Duration {
939 seconds: canonical_seconds,
940 nanos: canonical_nanos,
941 },
942 "test case on line {line} doesn't match",
943 );
944 }
945 }
946
947 #[cfg(feature = "std")]
948 #[test]
949 fn check_timestamp_normalize() {
950 #[rustfmt::skip] let cases = [
953 (line!(), 0, 0, 0, 0),
956 (line!(), 1, 1, 1, 1),
957 (line!(), -1, -1, -2, 999_999_999),
958 (line!(), 0, 999_999_999, 0, 999_999_999),
959 (line!(), 0, -999_999_999, -1, 1),
960 (line!(), 0, 1_000_000_000, 1, 0),
961 (line!(), 0, -1_000_000_000, -1, 0),
962 (line!(), 0, 1_000_000_001, 1, 1),
963 (line!(), 0, -1_000_000_001, -2, 999_999_999),
964 (line!(), -1, 1, -1, 1),
965 (line!(), 1, -1, 0, 999_999_999),
966 (line!(), -1, 1_000_000_000, 0, 0),
967 (line!(), 1, -1_000_000_000, 0, 0),
968 (line!(), i64::MIN , 0, i64::MIN , 0),
969 (line!(), i64::MIN + 1, 0, i64::MIN + 1, 0),
970 (line!(), i64::MIN , 1, i64::MIN , 1),
971 (line!(), i64::MIN , 1_000_000_000, i64::MIN + 1, 0),
972 (line!(), i64::MIN , -1_000_000_000, i64::MIN , 0),
973 (line!(), i64::MIN + 1, -1_000_000_000, i64::MIN , 0),
974 (line!(), i64::MIN + 2, -1_000_000_000, i64::MIN + 1, 0),
975 (line!(), i64::MIN , -1_999_999_998, i64::MIN , 0),
976 (line!(), i64::MIN + 1, -1_999_999_998, i64::MIN , 0),
977 (line!(), i64::MIN + 2, -1_999_999_998, i64::MIN , 2),
978 (line!(), i64::MIN , -1_999_999_999, i64::MIN , 0),
979 (line!(), i64::MIN + 1, -1_999_999_999, i64::MIN , 0),
980 (line!(), i64::MIN + 2, -1_999_999_999, i64::MIN , 1),
981 (line!(), i64::MIN , -2_000_000_000, i64::MIN , 0),
982 (line!(), i64::MIN + 1, -2_000_000_000, i64::MIN , 0),
983 (line!(), i64::MIN + 2, -2_000_000_000, i64::MIN , 0),
984 (line!(), i64::MIN , -999_999_998, i64::MIN , 0),
985 (line!(), i64::MIN + 1, -999_999_998, i64::MIN , 2),
986 (line!(), i64::MAX , 0, i64::MAX , 0),
987 (line!(), i64::MAX - 1, 0, i64::MAX - 1, 0),
988 (line!(), i64::MAX , -1, i64::MAX - 1, 999_999_999),
989 (line!(), i64::MAX , 1_000_000_000, i64::MAX , 999_999_999),
990 (line!(), i64::MAX - 1, 1_000_000_000, i64::MAX , 0),
991 (line!(), i64::MAX - 2, 1_000_000_000, i64::MAX - 1, 0),
992 (line!(), i64::MAX , 1_999_999_998, i64::MAX , 999_999_999),
993 (line!(), i64::MAX - 1, 1_999_999_998, i64::MAX , 999_999_998),
994 (line!(), i64::MAX - 2, 1_999_999_998, i64::MAX - 1, 999_999_998),
995 (line!(), i64::MAX , 1_999_999_999, i64::MAX , 999_999_999),
996 (line!(), i64::MAX - 1, 1_999_999_999, i64::MAX , 999_999_999),
997 (line!(), i64::MAX - 2, 1_999_999_999, i64::MAX - 1, 999_999_999),
998 (line!(), i64::MAX , 2_000_000_000, i64::MAX , 999_999_999),
999 (line!(), i64::MAX - 1, 2_000_000_000, i64::MAX , 999_999_999),
1000 (line!(), i64::MAX - 2, 2_000_000_000, i64::MAX , 0),
1001 (line!(), i64::MAX , 999_999_998, i64::MAX , 999_999_998),
1002 (line!(), i64::MAX - 1, 999_999_998, i64::MAX - 1, 999_999_998),
1003 ];
1004
1005 for case in cases.into_iter() {
1006 let (line, seconds, nanos, canonical_seconds, canonical_nanos) = case;
1007 let mut test_timestamp = Timestamp { seconds, nanos };
1008 let is_canonical = test_timestamp.is_canonical();
1009 test_timestamp.normalize();
1010 assert_eq!(is_canonical, test_timestamp == Timestamp { seconds, nanos });
1011
1012 assert_eq!(
1013 test_timestamp,
1014 Timestamp {
1015 seconds: canonical_seconds,
1016 nanos: canonical_nanos,
1017 },
1018 "test case on line {line} doesn't match",
1019 );
1020 }
1021 }
1022}