opening_hours/utils/
range.rs

1use std::cmp::{max, min};
2use std::ops::{Range, RangeInclusive};
3use std::sync::Arc;
4
5use chrono::NaiveDateTime;
6use opening_hours_syntax::rules::RuleKind;
7use opening_hours_syntax::sorted_vec::UniqueSortedVec;
8
9// DateTimeRange
10
11#[non_exhaustive]
12#[derive(Clone, Debug, Eq, PartialEq)]
13pub struct DateTimeRange<D = NaiveDateTime> {
14    pub range: Range<D>,
15    pub kind: RuleKind,
16    pub comments: UniqueSortedVec<Arc<str>>,
17}
18
19impl<D> DateTimeRange<D> {
20    pub(crate) fn new_with_sorted_comments(
21        range: Range<D>,
22        kind: RuleKind,
23        comments: UniqueSortedVec<Arc<str>>,
24    ) -> Self {
25        Self { range, kind, comments }
26    }
27}
28
29// WrappingRange
30
31pub(crate) trait WrappingRange<T> {
32    fn wrapping_contains(&self, elt: &T) -> bool;
33}
34
35impl<T: PartialOrd> WrappingRange<T> for RangeInclusive<T> {
36    fn wrapping_contains(&self, elt: &T) -> bool {
37        if self.start() <= self.end() {
38            self.contains(elt)
39        } else {
40            self.start() <= elt || elt <= self.end()
41        }
42    }
43}
44
45// Range operations
46
47pub(crate) fn ranges_union<T: Ord>(
48    ranges: impl IntoIterator<Item = Range<T>>,
49) -> impl Iterator<Item = Range<T>> {
50    // TODO (optimisation): we could gain performance by ensuring that range iterators are always
51    // sorted.
52    let mut ranges: Vec<_> = ranges.into_iter().collect();
53    ranges.sort_unstable_by(|r1, r2| r1.start.cmp(&r2.start));
54
55    // Get ranges by increasing start
56    let mut ranges = ranges.into_iter();
57    let mut current_opt = ranges.next();
58
59    std::iter::from_fn(move || {
60        if let Some(ref mut current) = current_opt {
61            #[allow(clippy::while_let_on_iterator)]
62            while let Some(item) = ranges.next() {
63                if current.end >= item.start {
64                    // The two intervals intersect with each other
65                    if item.end > current.end {
66                        current.end = item.end;
67                    }
68                } else {
69                    return Some(current_opt.replace(item).unwrap());
70                }
71            }
72
73            Some(current_opt.take().unwrap())
74        } else {
75            None
76        }
77    })
78}
79
80pub(crate) fn range_intersection<T: Ord>(range_1: Range<T>, range_2: Range<T>) -> Option<Range<T>> {
81    let result = max(range_1.start, range_2.start)..min(range_1.end, range_2.end);
82
83    if result.start < result.end {
84        Some(result)
85    } else {
86        None
87    }
88}
89
90#[cfg(test)]
91mod test {
92    use super::{range_intersection, ranges_union};
93
94    #[test]
95    fn test_unions() {
96        assert_eq!(
97            &ranges_union([1..5, 0..1, 3..7, 8..9]).collect::<Vec<_>>(),
98            &[0..7, 8..9]
99        );
100    }
101
102    #[test]
103    fn test_intersection() {
104        assert!(range_intersection(0..1, 1..2).is_none());
105        assert_eq!(range_intersection(0..3, 1..2).unwrap(), 1..2);
106        assert_eq!(range_intersection(0..3, 2..4).unwrap(), 2..3);
107    }
108}