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