1use super::{Time, TimeInstant, TimeScale};
11use chrono::{DateTime, Utc};
12use qtty::Days;
13use std::fmt;
14
15#[cfg(feature = "serde")]
16use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum ConversionError {
24 OutOfRange,
26}
27
28impl fmt::Display for ConversionError {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 match self {
31 ConversionError::OutOfRange => {
32 write!(f, "time instant out of representable range for target type")
33 }
34 }
35 }
36}
37
38impl std::error::Error for ConversionError {}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum InvalidIntervalError {
43 StartAfterEnd,
48}
49
50impl fmt::Display for InvalidIntervalError {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 match self {
53 InvalidIntervalError::StartAfterEnd => {
54 write!(f, "interval start must not be after end")
55 }
56 }
57 }
58}
59
60impl std::error::Error for InvalidIntervalError {}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub enum PeriodListError {
65 InvalidInterval {
67 index: usize,
69 },
70 Unsorted {
72 index: usize,
74 },
75 Overlapping {
77 index: usize,
79 },
80}
81
82impl fmt::Display for PeriodListError {
83 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84 match self {
85 PeriodListError::InvalidInterval { index } => {
86 write!(f, "interval at index {index} has start > end")
87 }
88 PeriodListError::Unsorted { index } => {
89 write!(f, "interval at index {index} is not sorted by start time")
90 }
91 PeriodListError::Overlapping { index } => {
92 write!(f, "interval at index {index} overlaps with its predecessor")
93 }
94 }
95 }
96}
97
98impl std::error::Error for PeriodListError {}
99
100pub trait PeriodTimeTarget<S: TimeScale> {
105 type Instant: TimeInstant;
106
107 fn convert(value: Time<S>) -> Result<Self::Instant, ConversionError>;
108}
109
110impl<S: TimeScale, T: TimeScale> PeriodTimeTarget<S> for T {
111 type Instant = Time<T>;
112
113 #[inline]
114 fn convert(value: Time<S>) -> Result<Self::Instant, ConversionError> {
115 Ok(value.to::<T>())
116 }
117}
118
119impl<S: TimeScale, T: TimeScale> PeriodTimeTarget<S> for Time<T> {
120 type Instant = Time<T>;
121
122 #[inline]
123 fn convert(value: Time<S>) -> Result<Self::Instant, ConversionError> {
124 Ok(value.to::<T>())
125 }
126}
127
128impl<S: TimeScale> PeriodTimeTarget<S> for DateTime<Utc> {
129 type Instant = DateTime<Utc>;
130
131 #[inline]
132 fn convert(value: Time<S>) -> Result<Self::Instant, ConversionError> {
133 value.to_utc().ok_or(ConversionError::OutOfRange)
134 }
135}
136
137pub trait PeriodUtcTarget {
139 type Instant: TimeInstant;
140
141 fn convert(value: DateTime<Utc>) -> Self::Instant;
142}
143
144impl<S: TimeScale> PeriodUtcTarget for S {
145 type Instant = Time<S>;
146
147 #[inline]
148 fn convert(value: DateTime<Utc>) -> Self::Instant {
149 Time::<S>::from_utc(value)
150 }
151}
152
153impl<S: TimeScale> PeriodUtcTarget for Time<S> {
154 type Instant = Time<S>;
155
156 #[inline]
157 fn convert(value: DateTime<Utc>) -> Self::Instant {
158 Time::<S>::from_utc(value)
159 }
160}
161
162impl PeriodUtcTarget for DateTime<Utc> {
163 type Instant = DateTime<Utc>;
164
165 #[inline]
166 fn convert(value: DateTime<Utc>) -> Self::Instant {
167 value
168 }
169}
170
171#[derive(Debug, Clone, Copy, PartialEq)]
191pub struct Interval<T: TimeInstant> {
192 pub start: T,
193 pub end: T,
194}
195
196pub type Period<S> = Interval<Time<S>>;
201
202pub type UtcPeriod = Interval<DateTime<Utc>>;
204
205impl<T: TimeInstant> Interval<T> {
206 pub fn new(start: T, end: T) -> Self {
228 Interval { start, end }
229 }
230
231 pub fn try_new(start: T, end: T) -> Result<Self, InvalidIntervalError> {
250 if start <= end {
251 Ok(Interval { start, end })
252 } else {
253 Err(InvalidIntervalError::StartAfterEnd)
254 }
255 }
256
257 pub fn duration(&self) -> T::Duration {
274 self.end.difference(&self.start)
275 }
276
277 pub fn intersection(&self, other: &Self) -> Option<Self> {
283 let start = if self.start >= other.start {
284 self.start
285 } else {
286 other.start
287 };
288 let end = if self.end <= other.end {
289 self.end
290 } else {
291 other.end
292 };
293
294 if start < end {
295 Some(Self::new(start, end))
296 } else {
297 None
298 }
299 }
300}
301
302impl<T: TimeInstant + fmt::Display> fmt::Display for Interval<T> {
304 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
305 write!(f, "{} to {}", self.start, self.end)
306 }
307}
308
309impl<S: TimeScale> Interval<Time<S>> {
310 #[inline]
339 pub fn to<Target>(
340 &self,
341 ) -> Result<Interval<<Target as PeriodTimeTarget<S>>::Instant>, ConversionError>
342 where
343 Target: PeriodTimeTarget<S>,
344 {
345 Ok(Interval::new(
346 Target::convert(self.start)?,
347 Target::convert(self.end)?,
348 ))
349 }
350}
351
352impl<T: TimeInstant<Duration = Days>> Interval<T> {
354 pub fn duration_days(&self) -> Days {
373 self.duration()
374 }
375}
376
377impl Interval<DateTime<Utc>> {
379 #[inline]
386 pub fn to<Target>(&self) -> Interval<<Target as PeriodUtcTarget>::Instant>
387 where
388 Target: PeriodUtcTarget,
389 {
390 Interval::new(Target::convert(self.start), Target::convert(self.end))
391 }
392
393 pub fn duration_days(&self) -> f64 {
397 self.duration().num_seconds() as f64 / 86400.0
398 }
399
400 pub fn duration_seconds(&self) -> i64 {
402 self.duration().num_seconds()
403 }
404}
405
406#[cfg(feature = "serde")]
411impl Serialize for Interval<crate::ModifiedJulianDate> {
412 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
413 where
414 S: Serializer,
415 {
416 let mut s = serializer.serialize_struct("Period", 2)?;
417 s.serialize_field("start_mjd", &self.start.value())?;
418 s.serialize_field("end_mjd", &self.end.value())?;
419 s.end()
420 }
421}
422
423#[cfg(feature = "serde")]
424impl<'de> Deserialize<'de> for Interval<crate::ModifiedJulianDate> {
425 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
426 where
427 D: Deserializer<'de>,
428 {
429 #[derive(Deserialize)]
430 struct Raw {
431 start_mjd: f64,
432 end_mjd: f64,
433 }
434
435 let raw = Raw::deserialize(deserializer)?;
436 if !raw.start_mjd.is_finite() || !raw.end_mjd.is_finite() {
437 return Err(serde::de::Error::custom(
438 "period MJD values must be finite (not NaN or infinity)",
439 ));
440 }
441 if raw.start_mjd > raw.end_mjd {
442 return Err(serde::de::Error::custom(
443 "period start must not be after end",
444 ));
445 }
446 Ok(Interval::new(
447 crate::ModifiedJulianDate::new(raw.start_mjd),
448 crate::ModifiedJulianDate::new(raw.end_mjd),
449 ))
450 }
451}
452
453#[cfg(feature = "serde")]
455impl Serialize for Interval<crate::JulianDate> {
456 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
457 where
458 S: Serializer,
459 {
460 let mut s = serializer.serialize_struct("Period", 2)?;
461 s.serialize_field("start_jd", &self.start.value())?;
462 s.serialize_field("end_jd", &self.end.value())?;
463 s.end()
464 }
465}
466
467#[cfg(feature = "serde")]
468impl<'de> Deserialize<'de> for Interval<crate::JulianDate> {
469 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
470 where
471 D: Deserializer<'de>,
472 {
473 #[derive(Deserialize)]
474 struct Raw {
475 start_jd: f64,
476 end_jd: f64,
477 }
478
479 let raw = Raw::deserialize(deserializer)?;
480 if !raw.start_jd.is_finite() || !raw.end_jd.is_finite() {
481 return Err(serde::de::Error::custom(
482 "period JD values must be finite (not NaN or infinity)",
483 ));
484 }
485 if raw.start_jd > raw.end_jd {
486 return Err(serde::de::Error::custom(
487 "period start must not be after end",
488 ));
489 }
490 Ok(Interval::new(
491 crate::JulianDate::new(raw.start_jd),
492 crate::JulianDate::new(raw.end_jd),
493 ))
494 }
495}
496
497pub fn complement_within<T: TimeInstant>(
512 outer: Interval<T>,
513 periods: &[Interval<T>],
514) -> Vec<Interval<T>> {
515 let mut gaps = Vec::new();
516 let mut cursor = outer.start;
517 for p in periods {
518 if p.start > cursor {
519 gaps.push(Interval::new(cursor, p.start));
520 }
521 if p.end > cursor {
522 cursor = p.end;
523 }
524 }
525 if cursor < outer.end {
526 gaps.push(Interval::new(cursor, outer.end));
527 }
528 gaps
529}
530
531pub fn intersect_periods<T: TimeInstant>(a: &[Interval<T>], b: &[Interval<T>]) -> Vec<Interval<T>> {
542 let mut result = Vec::new();
543 let (mut i, mut j) = (0, 0);
544 while i < a.len() && j < b.len() {
545 let start = if a[i].start >= b[j].start {
546 a[i].start
547 } else {
548 b[j].start
549 };
550 let end = if a[i].end <= b[j].end {
551 a[i].end
552 } else {
553 b[j].end
554 };
555 if start < end {
556 result.push(Interval::new(start, end));
557 }
558 if a[i].end <= b[j].end {
559 i += 1;
560 } else {
561 j += 1;
562 }
563 }
564 result
565}
566
567pub fn validate_period_list<T: TimeInstant>(
589 periods: &[Interval<T>],
590) -> Result<(), PeriodListError> {
591 for (i, p) in periods.iter().enumerate() {
592 if p.start
593 .partial_cmp(&p.end)
594 .is_none_or(|o| o == std::cmp::Ordering::Greater)
595 {
596 return Err(PeriodListError::InvalidInterval { index: i });
597 }
598 }
599 for i in 1..periods.len() {
600 if periods[i - 1]
601 .start
602 .partial_cmp(&periods[i].start)
603 .is_none_or(|o| o == std::cmp::Ordering::Greater)
604 {
605 return Err(PeriodListError::Unsorted { index: i });
606 }
607 if periods[i - 1].end > periods[i].start {
608 return Err(PeriodListError::Overlapping { index: i });
609 }
610 }
611 Ok(())
612}
613
614pub fn normalize_periods<T: TimeInstant>(periods: &[Interval<T>]) -> Vec<Interval<T>> {
634 if periods.is_empty() {
635 return Vec::new();
636 }
637 let mut sorted: Vec<_> = periods.to_vec();
638 sorted.sort_by(|a, b| {
639 a.start
640 .partial_cmp(&b.start)
641 .unwrap_or(std::cmp::Ordering::Equal)
642 });
643 let mut merged = vec![sorted[0]];
644 for p in &sorted[1..] {
645 let last = merged.last_mut().unwrap();
646 if p.start <= last.end {
647 if p.end > last.end {
649 last.end = p.end;
650 }
651 } else {
652 merged.push(*p);
653 }
654 }
655 merged
656}
657
658#[cfg(test)]
659mod tests {
660 use super::*;
661 use crate::{JulianDate, ModifiedJulianDate, JD, MJD};
662
663 #[test]
664 fn test_try_new_valid() {
665 let p = Interval::try_new(
666 ModifiedJulianDate::new(59000.0),
667 ModifiedJulianDate::new(59001.0),
668 );
669 assert!(p.is_ok());
670 }
671
672 #[test]
673 fn test_try_new_equal_bounds() {
674 let p = Interval::try_new(
675 ModifiedJulianDate::new(59000.0),
676 ModifiedJulianDate::new(59000.0),
677 );
678 assert!(p.is_ok()); }
680
681 #[test]
682 fn test_try_new_invalid() {
683 let p = Interval::try_new(
684 ModifiedJulianDate::new(59001.0),
685 ModifiedJulianDate::new(59000.0),
686 );
687 assert_eq!(p, Err(InvalidIntervalError::StartAfterEnd));
688 }
689
690 #[test]
691 fn test_try_new_nan_rejected() {
692 let p = Interval::try_new(
693 ModifiedJulianDate::new(f64::NAN),
694 ModifiedJulianDate::new(59000.0),
695 );
696 assert!(p.is_err());
697 }
698
699 #[test]
700 fn test_validate_period_list_ok() {
701 let periods = vec![
702 Period::new(ModifiedJulianDate::new(0.0), ModifiedJulianDate::new(3.0)),
703 Period::new(ModifiedJulianDate::new(5.0), ModifiedJulianDate::new(8.0)),
704 ];
705 assert!(validate_period_list(&periods).is_ok());
706 }
707
708 #[test]
709 fn test_validate_period_list_unsorted() {
710 let periods = vec![
711 Period::new(ModifiedJulianDate::new(5.0), ModifiedJulianDate::new(8.0)),
712 Period::new(ModifiedJulianDate::new(0.0), ModifiedJulianDate::new(3.0)),
713 ];
714 assert_eq!(
715 validate_period_list(&periods),
716 Err(PeriodListError::Unsorted { index: 1 })
717 );
718 }
719
720 #[test]
721 fn test_validate_period_list_overlapping() {
722 let periods = vec![
723 Period::new(ModifiedJulianDate::new(0.0), ModifiedJulianDate::new(5.0)),
724 Period::new(ModifiedJulianDate::new(3.0), ModifiedJulianDate::new(8.0)),
725 ];
726 assert_eq!(
727 validate_period_list(&periods),
728 Err(PeriodListError::Overlapping { index: 1 })
729 );
730 }
731
732 #[test]
733 fn test_validate_period_list_invalid_interval() {
734 let periods = vec![Period::new(
735 ModifiedJulianDate::new(5.0),
736 ModifiedJulianDate::new(3.0),
737 )];
738 assert_eq!(
739 validate_period_list(&periods),
740 Err(PeriodListError::InvalidInterval { index: 0 })
741 );
742 }
743
744 #[test]
745 fn test_normalize_periods_empty() {
746 let periods: Vec<Period<MJD>> = vec![];
747 assert!(normalize_periods(&periods).is_empty());
748 }
749
750 #[test]
751 fn test_normalize_periods_unsorted_and_overlapping() {
752 let periods = vec![
753 Period::new(ModifiedJulianDate::new(5.0), ModifiedJulianDate::new(8.0)),
754 Period::new(ModifiedJulianDate::new(0.0), ModifiedJulianDate::new(3.0)),
755 Period::new(ModifiedJulianDate::new(2.0), ModifiedJulianDate::new(6.0)),
756 ];
757 let merged = normalize_periods(&periods);
758 assert_eq!(merged.len(), 1);
759 assert_eq!(merged[0].start.quantity(), Days::new(0.0));
760 assert_eq!(merged[0].end.quantity(), Days::new(8.0));
761 }
762
763 #[test]
764 fn test_normalize_periods_disjoint() {
765 let periods = vec![
766 Period::new(ModifiedJulianDate::new(5.0), ModifiedJulianDate::new(6.0)),
767 Period::new(ModifiedJulianDate::new(0.0), ModifiedJulianDate::new(2.0)),
768 ];
769 let merged = normalize_periods(&periods);
770 assert_eq!(merged.len(), 2);
771 assert_eq!(merged[0].start.quantity(), Days::new(0.0));
772 assert_eq!(merged[1].start.quantity(), Days::new(5.0));
773 }
774
775 #[test]
776 fn test_period_creation_jd() {
777 let start = JulianDate::new(2451545.0);
778 let end = JulianDate::new(2451546.0);
779 let period = Period::new(start, end);
780
781 assert_eq!(period.start, start);
782 assert_eq!(period.end, end);
783 }
784
785 #[test]
786 fn test_period_scale_conversion_jd_to_mjd() {
787 let period_jd = Period::new(Time::<JD>::new(2_451_545.0), Time::<JD>::new(2_451_546.0));
788 let period_mjd = period_jd.to::<MJD>().unwrap();
789
790 assert!((period_mjd.start.value() - 51_544.5).abs() < 1e-12);
791 assert!((period_mjd.end.value() - 51_545.5).abs() < 1e-12);
792 }
793
794 #[test]
795 fn test_period_scale_conversion_roundtrip() {
796 let original = Period::new(Time::<MJD>::new(59_000.125), Time::<MJD>::new(59_001.75));
797 let roundtrip = original.to::<JD>().unwrap().to::<MJD>().unwrap();
798
799 assert!((roundtrip.start.value() - original.start.value()).abs() < 1e-12);
800 assert!((roundtrip.end.value() - original.end.value()).abs() < 1e-12);
801 }
802
803 #[test]
804 fn test_period_scale_conversion_to_utc() {
805 let start_utc = DateTime::from_timestamp(1_700_000_000, 0).unwrap();
806 let end_utc = DateTime::from_timestamp(1_700_000_600, 0).unwrap();
807 let period_jd = Period::new(
808 Time::<JD>::from_utc(start_utc),
809 Time::<JD>::from_utc(end_utc),
810 );
811
812 let period_utc = period_jd.to::<DateTime<Utc>>().unwrap();
813 let start_delta_ns = period_utc.start.timestamp_nanos_opt().unwrap()
814 - start_utc.timestamp_nanos_opt().unwrap();
815 let end_delta_ns =
816 period_utc.end.timestamp_nanos_opt().unwrap() - end_utc.timestamp_nanos_opt().unwrap();
817 assert!(start_delta_ns.abs() < 10_000);
818 assert!(end_delta_ns.abs() < 10_000);
819 }
820
821 #[test]
822 fn test_period_creation_mjd() {
823 let start = ModifiedJulianDate::new(59000.0);
824 let end = ModifiedJulianDate::new(59001.0);
825 let period = Period::new(start, end);
826
827 assert_eq!(period.start, start);
828 assert_eq!(period.end, end);
829 }
830
831 #[test]
832 fn test_period_duration_jd() {
833 let start = JulianDate::new(2451545.0);
834 let end = JulianDate::new(2451546.5);
835 let period = Period::new(start, end);
836
837 assert_eq!(period.duration_days(), Days::new(1.5));
838 }
839
840 #[test]
841 fn test_period_duration_mjd() {
842 let start = ModifiedJulianDate::new(59000.0);
843 let end = ModifiedJulianDate::new(59001.5);
844 let period = Period::new(start, end);
845
846 assert_eq!(period.duration_days(), Days::new(1.5));
847 }
848
849 #[test]
850 fn test_period_duration_utc() {
851 let start = DateTime::from_timestamp(0, 0).unwrap();
852 let end = DateTime::from_timestamp(86400, 0).unwrap(); let period = Interval::new(start, end);
854
855 assert_eq!(period.duration_days(), 1.0);
856 assert_eq!(period.duration_seconds(), 86400);
857 }
858
859 #[test]
860 fn test_period_to_conversion() {
861 let mjd_start = ModifiedJulianDate::new(59000.0);
862 let mjd_end = ModifiedJulianDate::new(59001.0);
863 let mjd_period = Period::new(mjd_start, mjd_end);
864
865 let utc_period = mjd_period.to::<DateTime<Utc>>().unwrap();
866
867 let duration_secs = utc_period.duration().num_seconds();
869 assert!(
870 (duration_secs - 86400).abs() <= 1,
871 "Duration was {} seconds",
872 duration_secs
873 );
874
875 let back_to_mjd = utc_period.to::<ModifiedJulianDate>();
877 let start_diff = (back_to_mjd.start.quantity() - mjd_start.quantity())
878 .value()
879 .abs();
880 let end_diff = (back_to_mjd.end.quantity() - mjd_end.quantity())
881 .value()
882 .abs();
883 assert!(start_diff < 1e-6, "Start difference: {}", start_diff);
884 assert!(end_diff < 1e-6, "End difference: {}", end_diff);
885 }
886
887 #[test]
888 fn test_period_display() {
889 let start = ModifiedJulianDate::new(59000.0);
890 let end = ModifiedJulianDate::new(59001.0);
891 let period = Period::new(start, end);
892
893 let display = format!("{}", period);
894 assert!(display.contains("MJD 59000"));
895 assert!(display.contains("MJD 59001"));
896 assert!(display.contains("to"));
897 }
898
899 #[test]
900 fn test_period_intersection_overlap() {
901 let a = Period::new(ModifiedJulianDate::new(0.0), ModifiedJulianDate::new(5.0));
902 let b = Period::new(ModifiedJulianDate::new(3.0), ModifiedJulianDate::new(8.0));
903
904 let overlap = a.intersection(&b).expect("expected overlap");
905 assert_eq!(overlap.start.quantity(), Days::new(3.0));
906 assert_eq!(overlap.end.quantity(), Days::new(5.0));
907 }
908
909 #[test]
910 fn test_period_intersection_disjoint() {
911 let a = Period::new(ModifiedJulianDate::new(0.0), ModifiedJulianDate::new(3.0));
912 let b = Period::new(ModifiedJulianDate::new(5.0), ModifiedJulianDate::new(8.0));
913
914 assert_eq!(a.intersection(&b), None);
915 }
916
917 #[test]
918 fn test_period_intersection_touching_edges() {
919 let a = Period::new(ModifiedJulianDate::new(0.0), ModifiedJulianDate::new(3.0));
920 let b = Period::new(ModifiedJulianDate::new(3.0), ModifiedJulianDate::new(8.0));
921
922 assert_eq!(a.intersection(&b), None);
923 }
924
925 #[test]
926 fn test_complement_within_gaps() {
927 let outer = Period::new(ModifiedJulianDate::new(0.0), ModifiedJulianDate::new(10.0));
928 let periods = vec![
929 Period::new(ModifiedJulianDate::new(2.0), ModifiedJulianDate::new(4.0)),
930 Period::new(ModifiedJulianDate::new(6.0), ModifiedJulianDate::new(8.0)),
931 ];
932 let gaps = complement_within(outer, &periods);
933 assert_eq!(gaps.len(), 3);
934 assert_eq!(gaps[0].start.quantity(), Days::new(0.0));
935 assert_eq!(gaps[0].end.quantity(), Days::new(2.0));
936 assert_eq!(gaps[1].start.quantity(), Days::new(4.0));
937 assert_eq!(gaps[1].end.quantity(), Days::new(6.0));
938 assert_eq!(gaps[2].start.quantity(), Days::new(8.0));
939 assert_eq!(gaps[2].end.quantity(), Days::new(10.0));
940 }
941
942 #[test]
943 fn test_complement_within_empty() {
944 let outer = Period::new(ModifiedJulianDate::new(0.0), ModifiedJulianDate::new(10.0));
945 let gaps = complement_within(outer, &[]);
946 assert_eq!(gaps.len(), 1);
947 assert_eq!(gaps[0].start.quantity(), Days::new(0.0));
948 assert_eq!(gaps[0].end.quantity(), Days::new(10.0));
949 }
950
951 #[test]
952 fn test_complement_within_full() {
953 let outer = Period::new(ModifiedJulianDate::new(0.0), ModifiedJulianDate::new(10.0));
954 let periods = vec![Period::new(
955 ModifiedJulianDate::new(0.0),
956 ModifiedJulianDate::new(10.0),
957 )];
958 let gaps = complement_within(outer, &periods);
959 assert!(gaps.is_empty());
960 }
961
962 #[test]
963 fn test_intersect_periods_overlap() {
964 let a = vec![Period::new(
965 ModifiedJulianDate::new(0.0),
966 ModifiedJulianDate::new(5.0),
967 )];
968 let b = vec![Period::new(
969 ModifiedJulianDate::new(3.0),
970 ModifiedJulianDate::new(8.0),
971 )];
972 let overlap = intersect_periods(&a, &b);
973 assert_eq!(overlap.len(), 1);
974 assert_eq!(overlap[0].start.quantity(), Days::new(3.0));
975 assert_eq!(overlap[0].end.quantity(), Days::new(5.0));
976 }
977
978 #[test]
979 fn test_intersect_periods_no_overlap() {
980 let a = vec![Period::new(
981 ModifiedJulianDate::new(0.0),
982 ModifiedJulianDate::new(3.0),
983 )];
984 let b = vec![Period::new(
985 ModifiedJulianDate::new(5.0),
986 ModifiedJulianDate::new(8.0),
987 )];
988 let overlap = intersect_periods(&a, &b);
989 assert!(overlap.is_empty());
990 }
991
992 #[test]
993 fn test_complement_intersect_roundtrip() {
994 let outer = Period::new(ModifiedJulianDate::new(0.0), ModifiedJulianDate::new(10.0));
996 let above_min = vec![
997 Period::new(ModifiedJulianDate::new(1.0), ModifiedJulianDate::new(3.0)),
998 Period::new(ModifiedJulianDate::new(5.0), ModifiedJulianDate::new(9.0)),
999 ];
1000 let above_max = vec![
1001 Period::new(ModifiedJulianDate::new(2.0), ModifiedJulianDate::new(4.0)),
1002 Period::new(ModifiedJulianDate::new(7.0), ModifiedJulianDate::new(8.0)),
1003 ];
1004 let below_max = complement_within(outer, &above_max);
1005 let between = intersect_periods(&above_min, &below_max);
1006 assert_eq!(between.len(), 3);
1011 assert_eq!(between[0].start.quantity(), Days::new(1.0));
1012 assert_eq!(between[0].end.quantity(), Days::new(2.0));
1013 assert_eq!(between[1].start.quantity(), Days::new(5.0));
1014 assert_eq!(between[1].end.quantity(), Days::new(7.0));
1015 assert_eq!(between[2].start.quantity(), Days::new(8.0));
1016 assert_eq!(between[2].end.quantity(), Days::new(9.0));
1017 }
1018}