Skip to main content

opening_hours/
schedule.rs

1use std::cmp::{max, min};
2use std::iter::Peekable;
3use std::ops::Range;
4use std::sync::Arc;
5
6use opening_hours_syntax::{ExtendedTime, RuleKind};
7
8/// An period of time in a schedule annotated with a state and comments.
9#[derive(Clone, Debug, Eq, PartialEq)]
10pub struct TimeRange {
11    /// Active period for this range
12    pub range: Range<ExtendedTime>,
13    /// State of the schedule while this period is active
14    pub kind: RuleKind,
15    /// Comment raised while this period is active
16    pub comment: Arc<str>,
17}
18
19impl TimeRange {
20    /// Small helper to create a new range.
21    pub fn new(range: Range<ExtendedTime>, kind: RuleKind, comment: Arc<str>) -> Self {
22        TimeRange { range, kind, comment }
23    }
24
25    /// Extract the kind and comment from the range, which are the values that define current state
26    /// of an expression.
27    pub fn as_state(&self) -> (RuleKind, &str) {
28        (self.kind, &self.comment)
29    }
30}
31
32/// Describe a full schedule for a day, keeping track of open, closed and
33/// unknown periods.
34///
35/// It can be turned into an iterator which will yield consecutive ranges of
36/// different states, with no holes or overlapping.
37#[derive(Clone, Debug, Default, Eq, PartialEq)]
38pub struct Schedule {
39    /// Always keep a sequence of non-overlaping, increasing time ranges.
40    pub(crate) inner: Vec<TimeRange>,
41}
42
43impl Schedule {
44    /// Creates a new empty schedule, which represents an always closed period.
45    ///
46    /// ```
47    /// use opening_hours::schedule::Schedule;
48    ///
49    /// assert!(Schedule::new().is_empty());
50    /// ```
51    pub fn new() -> Self {
52        Self::default()
53    }
54
55    /// Create a new schedule from a list of ranges of same kind and comment.
56    ///
57    /// ```
58    /// use opening_hours::schedule::Schedule;
59    /// use opening_hours_syntax::{ExtendedTime, RuleKind};
60    ///
61    /// let sch1 = Schedule::from_ranges(
62    ///     [
63    ///         ExtendedTime::new(10, 0).unwrap()..ExtendedTime::new(14, 0).unwrap(),
64    ///         ExtendedTime::new(12, 0).unwrap()..ExtendedTime::new(16, 0).unwrap(),
65    ///     ],
66    ///     RuleKind::Open,
67    ///     Default::default(),
68    /// );
69    ///
70    /// let sch2 = Schedule::from_ranges(
71    ///     [ExtendedTime::new(10, 0).unwrap()..ExtendedTime::new(16, 0).unwrap()],
72    ///     RuleKind::Open,
73    ///     Default::default(),
74    /// );
75    ///
76    /// assert_eq!(sch1, sch2);
77    /// ```
78    pub fn from_ranges(
79        ranges: impl IntoIterator<Item = Range<ExtendedTime>>,
80        kind: RuleKind,
81        comment: Arc<str>,
82    ) -> Self {
83        let mut inner: Vec<_> = ranges
84            .into_iter()
85            .filter(|range| range.start < range.end)
86            .map(|range| TimeRange { range, kind, comment: comment.clone() })
87            .collect();
88
89        // Ensure ranges are disjoint and in increasing order
90        if inner.len() > 1 {
91            inner.sort_unstable_by_key(|rng| rng.range.start);
92            let mut i_kept = 0; // the last element to be kept
93
94            for i_next in 1..inner.len() {
95                if inner[i_kept].range.end >= inner[i_next].range.start {
96                    inner[i_kept].range.end = inner[i_next].range.end;
97                } else {
98                    i_kept += 1;
99                    inner.swap(i_kept, i_next);
100                }
101            }
102
103            inner.truncate(i_kept + 1);
104        }
105
106        Self { inner }
107    }
108
109    /// Check if a schedule is empty.
110    ///
111    /// ```
112    /// use opening_hours::schedule::Schedule;
113    ///
114    /// assert!(Schedule::new().is_empty());
115    /// ```
116    pub fn is_empty(&self) -> bool {
117        self.inner.is_empty()
118    }
119
120    /// Check if a schedule is always closed with no comments.
121    pub(crate) fn is_always_closed_with_no_comments(&self) -> bool {
122        self.inner
123            .iter()
124            .all(|rg| rg.kind == RuleKind::Closed && rg.comment.is_empty())
125    }
126
127    /// Remove closed with no comment sections
128    pub fn filter_closed_ranges(mut self) -> Self {
129        self.inner
130            .retain(|rg| rg.kind != RuleKind::Closed || !rg.comment.is_empty());
131
132        self
133    }
134
135    /// Merge two schedules together.
136    pub fn addition(self, mut other: Self) -> Self {
137        // TODO (optimisation): this is implemented with quadratic time where it could probably be
138        // linear.
139        match other.inner.pop() {
140            None => self,
141            Some(tr) => self.insert(tr).addition(other),
142        }
143    }
144
145    /// Insert a new time range in a schedule.
146    fn insert(self, mut ins_tr: TimeRange) -> Self {
147        // Build sets of intervals before and after the inserted interval
148
149        let ins_start = ins_tr.range.start;
150        let ins_end = ins_tr.range.end;
151
152        let mut before: Vec<_> = self
153            .inner
154            .iter()
155            .filter(|tr| tr.range.start < ins_end)
156            .cloned()
157            .filter_map(|mut tr| {
158                tr.range.end = min(tr.range.end, ins_tr.range.start);
159
160                if tr.range.start < tr.range.end {
161                    Some(tr)
162                } else {
163                    None
164                }
165            })
166            .collect();
167
168        let mut after = self
169            .inner
170            .into_iter()
171            .filter(|tr| tr.range.end > ins_start)
172            .filter_map(|mut tr| {
173                tr.range.start = max(tr.range.start, ins_tr.range.end);
174
175                if tr.range.start < tr.range.end {
176                    Some(tr)
177                } else {
178                    None
179                }
180            })
181            .collect::<Vec<_>>()
182            .into_iter()
183            .peekable();
184
185        // Extend the inserted interval if it has adjacent intervals with same value
186
187        while let Some(tr) = before
188            .pop_if(|tr| tr.range.end == ins_tr.range.start && tr.as_state() == ins_tr.as_state())
189        {
190            ins_tr.range.start = tr.range.start;
191        }
192
193        while let Some(tr) = after
194            .next_if(|tr| ins_tr.range.end == tr.range.start && tr.as_state() == ins_tr.as_state())
195        {
196            ins_tr.range.end = tr.range.end;
197        }
198
199        // Build final set of intervals
200
201        let mut inner = before;
202        inner.push(ins_tr);
203        inner.extend(after);
204        Schedule { inner }
205    }
206}
207
208impl IntoIterator for Schedule {
209    type Item = TimeRange;
210    type IntoIter = IntoIter;
211
212    fn into_iter(self) -> Self::IntoIter {
213        IntoIter::new(self)
214    }
215}
216
217/// Return value for [`Schedule::into_iter`].
218#[derive(Debug)]
219pub struct IntoIter {
220    last_end: ExtendedTime,
221    ranges: Peekable<std::vec::IntoIter<TimeRange>>,
222}
223
224impl IntoIter {
225    /// The value that will fill holes
226    const HOLES_STATE: (RuleKind, &str) = (RuleKind::Closed, "");
227
228    /// Create a new iterator from a schedule.
229    fn new(schedule: Schedule) -> Self {
230        Self {
231            last_end: ExtendedTime::MIDNIGHT_00,
232            ranges: schedule.inner.into_iter().peekable(),
233        }
234    }
235
236    /// Must be called before a value is yielded.
237    fn yielded(&mut self, value: TimeRange) -> Option<TimeRange> {
238        assert!(
239            value.range.start < value.range.end,
240            "infinite loop detected"
241        );
242
243        self.last_end = value.range.end;
244        Some(value)
245    }
246}
247
248impl Iterator for IntoIter {
249    type Item = TimeRange;
250
251    fn next(&mut self) -> Option<Self::Item> {
252        if self.last_end >= ExtendedTime::MIDNIGHT_24 {
253            // Iteration ended
254            return None;
255        }
256
257        let mut yielded_range = (self.ranges)
258            // Start from an interval
259            .next_if(|tr| tr.range.start == self.last_end)
260            // Start from a hole
261            .unwrap_or_else(|| {
262                let start = self.last_end;
263                let end = self.ranges.peek().map(|tr| tr.range.start).unwrap_or(start);
264                TimeRange::new(start..end, Self::HOLES_STATE.0, Default::default())
265            });
266
267        while let Some(next_range) = self.ranges.peek() {
268            // If there is a gap before next range
269            if next_range.range.start > yielded_range.range.end {
270                // If current range is the "holes" values (typicaly closed)
271                if yielded_range.as_state() == Self::HOLES_STATE {
272                    // Just extend the closed range with this hole
273                    yielded_range.range.end = next_range.range.start;
274                } else {
275                    // The range before the hole is not closed, then this is
276                    // were current range stops.
277                    return self.yielded(yielded_range);
278                }
279            }
280
281            let Some(next_range) = (self.ranges)
282                .next_if(|next_range| yielded_range.as_state() == next_range.as_state())
283            else {
284                // The next range has a different state, then this is where the current range
285                // stops.
286                return self.yielded(yielded_range);
287            };
288
289            yielded_range.range.end = next_range.range.end;
290        }
291
292        if yielded_range.as_state() == Self::HOLES_STATE {
293            // Extend with the last hole
294            yielded_range.range.end = ExtendedTime::MIDNIGHT_24;
295        }
296
297        self.yielded(yielded_range)
298    }
299}
300
301impl std::iter::FusedIterator for IntoIter {}