opening_hours/
schedule.rs

1use std::cmp::{max, min};
2use std::iter::Peekable;
3use std::mem::take;
4use std::ops::Range;
5use std::sync::Arc;
6
7use opening_hours_syntax::sorted_vec::UniqueSortedVec;
8use opening_hours_syntax::{ExtendedTime, RuleKind};
9
10/// An period of time in a schedule annotated with a state and comments.
11#[derive(Clone, Debug, Eq, PartialEq)]
12pub struct TimeRange {
13    /// Active period for this range
14    pub range: Range<ExtendedTime>,
15    /// State of the schedule while this period is active
16    pub kind: RuleKind,
17    /// Comments raised while this period is active
18    pub comments: UniqueSortedVec<Arc<str>>,
19}
20
21impl TimeRange {
22    /// Small helper to create a new range.
23    pub fn new(
24        range: Range<ExtendedTime>,
25        kind: RuleKind,
26        comments: UniqueSortedVec<Arc<str>>,
27    ) -> Self {
28        TimeRange { range, kind, comments }
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        comments: &UniqueSortedVec<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, comments: comments.clone() })
87            .collect();
88
89        // Ensure ranges are disjoint and in increasing order
90        inner.sort_unstable_by_key(|rng| rng.range.start);
91        let mut i = 0;
92
93        while i + 1 < inner.len() {
94            if inner[i].range.end >= inner[i + 1].range.start {
95                inner[i].range.end = inner[i + 1].range.end;
96                let comments_left = std::mem::take(&mut inner[i].comments);
97                let comments_right = inner.remove(i + 1).comments;
98                inner[i].comments = comments_left.union(comments_right);
99            } else {
100                i += 1;
101            }
102        }
103
104        Self { inner }
105    }
106
107    /// Check if a schedule is empty.
108    ///
109    /// ```
110    /// use opening_hours::schedule::Schedule;
111    ///
112    /// assert!(Schedule::new().is_empty());
113    /// ```
114    pub fn is_empty(&self) -> bool {
115        self.inner.is_empty()
116    }
117
118    /// Check if a schedule is always closed.
119    pub(crate) fn is_always_closed(&self) -> bool {
120        self.inner.iter().all(|rg| rg.kind == RuleKind::Closed)
121    }
122
123    /// Merge two schedules together.
124    pub fn addition(self, mut other: Self) -> Self {
125        // TODO (optimisation): this is implemented with quadratic time where it could probably be
126        // linear.
127        match other.inner.pop() {
128            None => self,
129            Some(tr) => self.insert(tr).addition(other),
130        }
131    }
132
133    /// Insert a new time range in a schedule.
134    fn insert(self, mut ins_tr: TimeRange) -> Self {
135        // Build sets of intervals before and after the inserted interval
136
137        let ins_start = ins_tr.range.start;
138        let ins_end = ins_tr.range.end;
139
140        let mut before: Vec<_> = self
141            .inner
142            .iter()
143            .filter(|tr| tr.range.start < ins_end)
144            .cloned()
145            .filter_map(|mut tr| {
146                tr.range.end = min(tr.range.end, ins_tr.range.start);
147
148                if tr.range.start < tr.range.end {
149                    Some(tr)
150                } else {
151                    ins_tr.comments = take(&mut ins_tr.comments).union(tr.comments);
152                    None
153                }
154            })
155            .collect();
156
157        let mut after = self
158            .inner
159            .into_iter()
160            .filter(|tr| tr.range.end > ins_start)
161            .filter_map(|mut tr| {
162                tr.range.start = max(tr.range.start, ins_tr.range.end);
163
164                if tr.range.start < tr.range.end {
165                    Some(tr)
166                } else {
167                    ins_tr.comments = take(&mut ins_tr.comments).union(tr.comments);
168                    None
169                }
170            })
171            .collect::<Vec<_>>()
172            .into_iter()
173            .peekable();
174
175        // Extend the inserted interval if it has adjacent intervals with same value
176
177        while before
178            .last()
179            .map(|tr| tr.range.end == ins_tr.range.start && tr.kind == ins_tr.kind)
180            .unwrap_or(false)
181        {
182            let tr = before.pop().unwrap();
183            ins_tr.range.start = tr.range.start;
184            ins_tr.comments = tr.comments.union(ins_tr.comments);
185        }
186
187        while after
188            .peek()
189            .map(|tr| ins_tr.range.end == tr.range.start && tr.kind == ins_tr.kind)
190            .unwrap_or(false)
191        {
192            let tr = after.next().unwrap();
193            ins_tr.range.end = tr.range.end;
194            ins_tr.comments = tr.comments.union(ins_tr.comments);
195        }
196
197        // Build final set of intervals
198
199        let mut inner = before;
200        inner.push(ins_tr);
201        inner.extend(after);
202        Schedule { inner }
203    }
204}
205
206impl IntoIterator for Schedule {
207    type Item = TimeRange;
208    type IntoIter = IntoIter;
209
210    fn into_iter(self) -> Self::IntoIter {
211        IntoIter::new(self)
212    }
213}
214
215/// Return value for [`Schedule::into_iter`].
216#[derive(Debug)]
217pub struct IntoIter {
218    last_end: ExtendedTime,
219    ranges: Peekable<std::vec::IntoIter<TimeRange>>,
220}
221
222impl IntoIter {
223    /// The value that will fill holes
224    const HOLES_STATE: RuleKind = RuleKind::Closed;
225
226    /// Create a new iterator from a schedule.
227    fn new(schedule: Schedule) -> Self {
228        Self {
229            last_end: ExtendedTime::MIDNIGHT_00,
230            ranges: schedule.inner.into_iter().peekable(),
231        }
232    }
233
234    /// Must be called before a value is yielded.
235    fn pre_yield(&mut self, value: TimeRange) -> Option<TimeRange> {
236        assert!(
237            value.range.start < value.range.end,
238            "infinite loop detected"
239        );
240
241        self.last_end = value.range.end;
242        Some(value)
243    }
244}
245
246impl Iterator for IntoIter {
247    type Item = TimeRange;
248
249    fn next(&mut self) -> Option<Self::Item> {
250        if self.last_end >= ExtendedTime::MIDNIGHT_24 {
251            // Iteration ended
252            return None;
253        }
254
255        let mut yielded_range = {
256            let next_start = self.ranges.peek().map(|tr| tr.range.start);
257
258            if next_start == Some(self.last_end) {
259                // Start from an interval
260                self.ranges.next().unwrap()
261            } else {
262                // Start from a hole
263                TimeRange::new(
264                    self.last_end..next_start.unwrap_or(self.last_end),
265                    Self::HOLES_STATE,
266                    UniqueSortedVec::new(),
267                )
268            }
269        };
270
271        while let Some(next_range) = self.ranges.peek() {
272            if next_range.range.start > yielded_range.range.end {
273                if yielded_range.kind == Self::HOLES_STATE {
274                    // Just extend the closed range with this hole
275                    yielded_range.range.end = next_range.range.start;
276                } else {
277                    // The range before the hole is not closed
278                    return self.pre_yield(yielded_range);
279                }
280            }
281
282            if yielded_range.kind != next_range.kind {
283                // The next range has a different state
284                return self.pre_yield(yielded_range);
285            }
286
287            let next_range = self.ranges.next().unwrap();
288            yielded_range.range.end = next_range.range.end;
289            yielded_range.comments = yielded_range.comments.union(next_range.comments);
290        }
291
292        if yielded_range.kind == Self::HOLES_STATE {
293            // Extend with the last hole
294            yielded_range.range.end = ExtendedTime::MIDNIGHT_24;
295        }
296
297        self.pre_yield(yielded_range)
298    }
299}
300
301impl std::iter::FusedIterator for IntoIter {}
302
303/// Macro that allows to quickly create a complex schedule.
304///
305/// ## Syntax
306///
307/// You can define multiple sequences of time as follows :
308///
309/// ```plain
310/// {time_0} => {state_1} => {time_2} => {state_2} => ... => {state_n} => {time_n};
311/// ```
312///
313/// Where the time values are written `{hour},{minutes}` and states are a
314/// [`RuleKind`] value, optionally followed by a list of comment literals.
315///
316/// ```
317/// use opening_hours_syntax::{ExtendedTime, RuleKind};
318///
319/// opening_hours::schedule! {
320///      9,00 => RuleKind::Open => 12,00;
321///     14,00 => RuleKind::Open => 18,00
322///           => RuleKind::Unknown, "Closes when stock is depleted" => 20,00;
323///     22,00 => RuleKind::Closed, "Maintenance team only" => 26,00;
324/// };
325/// ```
326#[macro_export]
327macro_rules! schedule {
328    (
329        $( $hh1:expr,$mm1:expr $( => $kind:expr $( , $comment:expr )* => $hh2:expr,$mm2:expr )+ );*
330        $( ; )?
331    ) => {{
332        #[allow(unused_imports)]
333        use $crate::schedule::{Schedule, TimeRange};
334
335        #[allow(unused_imports)]
336        use opening_hours_syntax::extended_time::ExtendedTime;
337
338        #[allow(unused_mut)]
339        let mut schedule = Schedule::new();
340
341        $(
342            let mut prev = ExtendedTime::new($hh1, $mm1)
343                .expect("Invalid interval start");
344
345            $(
346                let curr = ExtendedTime::new($hh2, $mm2)
347                    .expect("Invalid interval end");
348
349                let comments = vec![$(std::sync::Arc::from($comment)),*].into();
350                let next_schedule = Schedule::from_ranges([prev..curr], $kind, &comments);
351                schedule = schedule.addition(next_schedule);
352
353                #[allow(unused_assignments)]
354                { prev = curr }
355            )+
356        )*
357
358        schedule
359    }};
360}