chrono/
weekday_set.rs

1use core::{
2    fmt::{self, Debug},
3    iter::FusedIterator,
4};
5
6use crate::Weekday;
7
8/// A collection of [`Weekday`]s stored as a single byte.
9///
10/// This type is `Copy` and provides efficient set-like and slice-like operations.
11/// Many operations are `const` as well.
12///
13/// Implemented as a bitmask where bits 1-7 correspond to Monday-Sunday.
14#[derive(Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
15pub struct WeekdaySet(u8); // Invariant: the 8-th bit is always 0.
16
17impl WeekdaySet {
18    /// Create a `WeekdaySet` from an array of [`Weekday`]s.
19    ///
20    /// # Example
21    /// ```
22    /// # use chrono::WeekdaySet;
23    /// use chrono::Weekday::*;
24    /// assert_eq!(WeekdaySet::EMPTY, WeekdaySet::from_array([]));
25    /// assert_eq!(WeekdaySet::single(Mon), WeekdaySet::from_array([Mon]));
26    /// assert_eq!(WeekdaySet::ALL, WeekdaySet::from_array([Mon, Tue, Wed, Thu, Fri, Sat, Sun]));
27    /// ```
28    pub const fn from_array<const C: usize>(days: [Weekday; C]) -> Self {
29        let mut acc = Self::EMPTY;
30        let mut idx = 0;
31        while idx < days.len() {
32            acc.0 |= Self::single(days[idx]).0;
33            idx += 1;
34        }
35        acc
36    }
37
38    /// Create a `WeekdaySet` from a single [`Weekday`].
39    pub const fn single(weekday: Weekday) -> Self {
40        match weekday {
41            Weekday::Mon => Self(0b000_0001),
42            Weekday::Tue => Self(0b000_0010),
43            Weekday::Wed => Self(0b000_0100),
44            Weekday::Thu => Self(0b000_1000),
45            Weekday::Fri => Self(0b001_0000),
46            Weekday::Sat => Self(0b010_0000),
47            Weekday::Sun => Self(0b100_0000),
48        }
49    }
50
51    /// Returns `Some(day)` if this collection contains exactly one day.
52    ///
53    /// Returns `None` otherwise.
54    ///
55    /// # Example
56    /// ```
57    /// # use chrono::WeekdaySet;
58    /// use chrono::Weekday::*;
59    /// assert_eq!(WeekdaySet::single(Mon).single_day(), Some(Mon));
60    /// assert_eq!(WeekdaySet::from_array([Mon, Tue]).single_day(), None);
61    /// assert_eq!(WeekdaySet::EMPTY.single_day(), None);
62    /// assert_eq!(WeekdaySet::ALL.single_day(), None);
63    /// ```
64    pub const fn single_day(self) -> Option<Weekday> {
65        match self {
66            Self(0b000_0001) => Some(Weekday::Mon),
67            Self(0b000_0010) => Some(Weekday::Tue),
68            Self(0b000_0100) => Some(Weekday::Wed),
69            Self(0b000_1000) => Some(Weekday::Thu),
70            Self(0b001_0000) => Some(Weekday::Fri),
71            Self(0b010_0000) => Some(Weekday::Sat),
72            Self(0b100_0000) => Some(Weekday::Sun),
73            _ => None,
74        }
75    }
76
77    /// Adds a day to the collection.
78    ///
79    /// Returns `true` if the day was new to the collection.
80    ///
81    /// # Example
82    /// ```
83    /// # use chrono::WeekdaySet;
84    /// use chrono::Weekday::*;
85    /// let mut weekdays = WeekdaySet::single(Mon);
86    /// assert!(weekdays.insert(Tue));
87    /// assert!(!weekdays.insert(Tue));
88    /// ```
89    pub fn insert(&mut self, day: Weekday) -> bool {
90        if self.contains(day) {
91            return false;
92        }
93
94        self.0 |= Self::single(day).0;
95        true
96    }
97
98    /// Removes a day from the collection.
99    ///
100    /// Returns `true` if the collection did contain the day.
101    ///
102    /// # Example
103    /// ```
104    /// # use chrono::WeekdaySet;
105    /// use chrono::Weekday::*;
106    /// let mut weekdays = WeekdaySet::single(Mon);
107    /// assert!(weekdays.remove(Mon));
108    /// assert!(!weekdays.remove(Mon));
109    /// ```
110    pub fn remove(&mut self, day: Weekday) -> bool {
111        if self.contains(day) {
112            self.0 &= !Self::single(day).0;
113            return true;
114        }
115
116        false
117    }
118
119    /// Returns `true` if `other` contains all days in `self`.
120    ///
121    /// # Example
122    /// ```
123    /// # use chrono::WeekdaySet;
124    /// use chrono::Weekday::*;
125    /// assert!(WeekdaySet::single(Mon).is_subset(WeekdaySet::ALL));
126    /// assert!(!WeekdaySet::single(Mon).is_subset(WeekdaySet::EMPTY));
127    /// assert!(WeekdaySet::EMPTY.is_subset(WeekdaySet::single(Mon)));
128    /// ```
129    pub const fn is_subset(self, other: Self) -> bool {
130        self.intersection(other).0 == self.0
131    }
132
133    /// Returns days that are in both `self` and `other`.
134    ///
135    /// # Example
136    /// ```
137    /// # use chrono::WeekdaySet;
138    /// use chrono::Weekday::*;
139    /// assert_eq!(WeekdaySet::single(Mon).intersection(WeekdaySet::single(Mon)), WeekdaySet::single(Mon));
140    /// assert_eq!(WeekdaySet::single(Mon).intersection(WeekdaySet::single(Tue)), WeekdaySet::EMPTY);
141    /// assert_eq!(WeekdaySet::ALL.intersection(WeekdaySet::single(Mon)), WeekdaySet::single(Mon));
142    /// assert_eq!(WeekdaySet::ALL.intersection(WeekdaySet::EMPTY), WeekdaySet::EMPTY);
143    /// ```
144    pub const fn intersection(self, other: Self) -> Self {
145        Self(self.0 & other.0)
146    }
147
148    /// Returns days that are in either `self` or `other`.
149    ///
150    /// # Example
151    /// ```
152    /// # use chrono::WeekdaySet;
153    /// use chrono::Weekday::*;
154    /// assert_eq!(WeekdaySet::single(Mon).union(WeekdaySet::single(Mon)), WeekdaySet::single(Mon));
155    /// assert_eq!(WeekdaySet::single(Mon).union(WeekdaySet::single(Tue)), WeekdaySet::from_array([Mon, Tue]));
156    /// assert_eq!(WeekdaySet::ALL.union(WeekdaySet::single(Mon)), WeekdaySet::ALL);
157    /// assert_eq!(WeekdaySet::ALL.union(WeekdaySet::EMPTY), WeekdaySet::ALL);
158    /// ```
159    pub const fn union(self, other: Self) -> Self {
160        Self(self.0 | other.0)
161    }
162
163    /// Returns days that are in `self` or `other` but not in both.
164    ///
165    /// # Example
166    /// ```
167    /// # use chrono::WeekdaySet;
168    /// use chrono::Weekday::*;
169    /// assert_eq!(WeekdaySet::single(Mon).symmetric_difference(WeekdaySet::single(Mon)), WeekdaySet::EMPTY);
170    /// assert_eq!(WeekdaySet::single(Mon).symmetric_difference(WeekdaySet::single(Tue)), WeekdaySet::from_array([Mon, Tue]));
171    /// assert_eq!(
172    ///     WeekdaySet::ALL.symmetric_difference(WeekdaySet::single(Mon)),
173    ///     WeekdaySet::from_array([Tue, Wed, Thu, Fri, Sat, Sun]),
174    /// );
175    /// assert_eq!(WeekdaySet::ALL.symmetric_difference(WeekdaySet::EMPTY), WeekdaySet::ALL);
176    /// ```
177    pub const fn symmetric_difference(self, other: Self) -> Self {
178        Self(self.0 ^ other.0)
179    }
180
181    /// Returns days that are in `self` but not in `other`.
182    ///
183    /// # Example
184    /// ```
185    /// # use chrono::WeekdaySet;
186    /// use chrono::Weekday::*;
187    /// assert_eq!(WeekdaySet::single(Mon).difference(WeekdaySet::single(Mon)), WeekdaySet::EMPTY);
188    /// assert_eq!(WeekdaySet::single(Mon).difference(WeekdaySet::single(Tue)), WeekdaySet::single(Mon));
189    /// assert_eq!(WeekdaySet::EMPTY.difference(WeekdaySet::single(Mon)), WeekdaySet::EMPTY);
190    /// ```
191    pub const fn difference(self, other: Self) -> Self {
192        Self(self.0 & !other.0)
193    }
194
195    /// Get the first day in the collection, starting from Monday.
196    ///
197    /// Returns `None` if the collection is empty.
198    ///
199    /// # Example
200    /// ```
201    /// # use chrono::WeekdaySet;
202    /// use chrono::Weekday::*;
203    /// assert_eq!(WeekdaySet::single(Mon).first(), Some(Mon));
204    /// assert_eq!(WeekdaySet::single(Tue).first(), Some(Tue));
205    /// assert_eq!(WeekdaySet::ALL.first(), Some(Mon));
206    /// assert_eq!(WeekdaySet::EMPTY.first(), None);
207    /// ```
208    pub const fn first(self) -> Option<Weekday> {
209        if self.is_empty() {
210            return None;
211        }
212
213        // Find the first non-zero bit.
214        let bit = 1 << self.0.trailing_zeros();
215
216        Self(bit).single_day()
217    }
218
219    /// Get the last day in the collection, starting from Sunday.
220    ///
221    /// Returns `None` if the collection is empty.
222    ///
223    /// # Example
224    /// ```
225    /// # use chrono::WeekdaySet;
226    /// use chrono::Weekday::*;
227    /// assert_eq!(WeekdaySet::single(Mon).last(), Some(Mon));
228    /// assert_eq!(WeekdaySet::single(Sun).last(), Some(Sun));
229    /// assert_eq!(WeekdaySet::from_array([Mon, Tue]).last(), Some(Tue));
230    /// assert_eq!(WeekdaySet::EMPTY.last(), None);
231    /// ```
232    pub fn last(self) -> Option<Weekday> {
233        if self.is_empty() {
234            return None;
235        }
236
237        // Find the last non-zero bit.
238        let bit = 1 << (7 - self.0.leading_zeros());
239
240        Self(bit).single_day()
241    }
242
243    /// Split the collection in two at the given day.
244    ///
245    /// Returns a tuple `(before, after)`. `before` contains all days starting from Monday
246    /// up to but __not__ including `weekday`. `after` contains all days starting from `weekday`
247    /// up to and including Sunday.
248    const fn split_at(self, weekday: Weekday) -> (Self, Self) {
249        let days_after = 0b1000_0000 - Self::single(weekday).0;
250        let days_before = days_after ^ 0b0111_1111;
251        (Self(self.0 & days_before), Self(self.0 & days_after))
252    }
253
254    /// Iterate over the [`Weekday`]s in the collection starting from a given day.
255    ///
256    /// Wraps around from Sunday to Monday if necessary.
257    ///
258    /// # Example
259    /// ```
260    /// # use chrono::WeekdaySet;
261    /// use chrono::Weekday::*;
262    /// let weekdays = WeekdaySet::from_array([Mon, Wed, Fri]);
263    /// let mut iter = weekdays.iter(Wed);
264    /// assert_eq!(iter.next(), Some(Wed));
265    /// assert_eq!(iter.next(), Some(Fri));
266    /// assert_eq!(iter.next(), Some(Mon));
267    /// assert_eq!(iter.next(), None);
268    /// ```
269    pub const fn iter(self, start: Weekday) -> WeekdaySetIter {
270        WeekdaySetIter { days: self, start }
271    }
272
273    /// Returns `true` if the collection contains the given day.
274    ///
275    /// # Example
276    /// ```
277    /// # use chrono::WeekdaySet;
278    /// use chrono::Weekday::*;
279    /// assert!(WeekdaySet::single(Mon).contains(Mon));
280    /// assert!(WeekdaySet::from_array([Mon, Tue]).contains(Tue));
281    /// assert!(!WeekdaySet::single(Mon).contains(Tue));
282    /// ```
283    pub const fn contains(self, day: Weekday) -> bool {
284        self.0 & Self::single(day).0 != 0
285    }
286
287    /// Returns `true` if the collection is empty.
288    ///
289    /// # Example
290    /// ```
291    /// # use chrono::{Weekday, WeekdaySet};
292    /// assert!(WeekdaySet::EMPTY.is_empty());
293    /// assert!(!WeekdaySet::single(Weekday::Mon).is_empty());
294    /// ```
295    pub const fn is_empty(self) -> bool {
296        self.len() == 0
297    }
298    /// Returns the number of days in the collection.
299    ///
300    /// # Example
301    /// ```
302    /// # use chrono::WeekdaySet;
303    /// use chrono::Weekday::*;
304    /// assert_eq!(WeekdaySet::single(Mon).len(), 1);
305    /// assert_eq!(WeekdaySet::from_array([Mon, Wed, Fri]).len(), 3);
306    /// assert_eq!(WeekdaySet::ALL.len(), 7);
307    /// ```
308    pub const fn len(self) -> u8 {
309        self.0.count_ones() as u8
310    }
311
312    /// An empty `WeekdaySet`.
313    pub const EMPTY: Self = Self(0b000_0000);
314    /// A `WeekdaySet` containing all seven `Weekday`s.
315    pub const ALL: Self = Self(0b111_1111);
316}
317
318/// Print the underlying bitmask, padded to 7 bits.
319///
320/// # Example
321/// ```
322/// # use chrono::WeekdaySet;
323/// use chrono::Weekday::*;
324/// assert_eq!(format!("{:?}", WeekdaySet::single(Mon)), "WeekdaySet(0000001)");
325/// assert_eq!(format!("{:?}", WeekdaySet::single(Tue)), "WeekdaySet(0000010)");
326/// assert_eq!(format!("{:?}", WeekdaySet::ALL), "WeekdaySet(1111111)");
327/// ```
328impl Debug for WeekdaySet {
329    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
330        write!(f, "WeekdaySet({:0>7b})", self.0)
331    }
332}
333
334#[cfg(feature = "defmt")]
335impl defmt::Format for WeekdaySet {
336    fn format(&self, f: defmt::Formatter<'_>) {
337        defmt::write!(
338            f,
339            "WeekdaySet({}{}{}{}{}{}{})",
340            0x1 & (self.0 >> 6),
341            0x1 & (self.0 >> 5),
342            0x1 & (self.0 >> 4),
343            0x1 & (self.0 >> 3),
344            0x1 & (self.0 >> 2),
345            0x1 & (self.0 >> 1),
346            0x1 & (self.0 >> 0),
347        )
348    }
349}
350
351/// An iterator over a collection of weekdays, starting from a given day.
352///
353/// See [`WeekdaySet::iter()`].
354#[derive(Debug, Clone)]
355pub struct WeekdaySetIter {
356    days: WeekdaySet,
357    start: Weekday,
358}
359
360impl Iterator for WeekdaySetIter {
361    type Item = Weekday;
362
363    fn next(&mut self) -> Option<Self::Item> {
364        if self.days.is_empty() {
365            return None;
366        }
367
368        // Split the collection in two at `start`.
369        // Look for the first day among the days after `start` first, including `start` itself.
370        // If there are no days after `start`, look for the first day among the days before `start`.
371        let (before, after) = self.days.split_at(self.start);
372        let days = if after.is_empty() { before } else { after };
373
374        let next = days.first().expect("the collection is not empty");
375        self.days.remove(next);
376        Some(next)
377    }
378}
379
380impl DoubleEndedIterator for WeekdaySetIter {
381    fn next_back(&mut self) -> Option<Self::Item> {
382        if self.days.is_empty() {
383            return None;
384        }
385
386        // Split the collection in two at `start`.
387        // Look for the last day among the days before `start` first, NOT including `start` itself.
388        // If there are no days before `start`, look for the last day among the days after `start`.
389        let (before, after) = self.days.split_at(self.start);
390        let days = if before.is_empty() { after } else { before };
391
392        let next_back = days.last().expect("the collection is not empty");
393        self.days.remove(next_back);
394        Some(next_back)
395    }
396}
397
398impl ExactSizeIterator for WeekdaySetIter {
399    fn len(&self) -> usize {
400        self.days.len().into()
401    }
402}
403
404impl FusedIterator for WeekdaySetIter {}
405
406/// Print the collection as a slice-like list of weekdays.
407///
408/// # Example
409/// ```
410/// # use chrono::WeekdaySet;
411/// use chrono::Weekday::*;
412/// assert_eq!("[]", WeekdaySet::EMPTY.to_string());
413/// assert_eq!("[Mon]", WeekdaySet::single(Mon).to_string());
414/// assert_eq!("[Mon, Fri, Sun]", WeekdaySet::from_array([Mon, Fri, Sun]).to_string());
415/// ```
416impl fmt::Display for WeekdaySet {
417    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
418        write!(f, "[")?;
419        let mut iter = self.iter(Weekday::Mon);
420        if let Some(first) = iter.next() {
421            write!(f, "{first}")?;
422        }
423        for weekday in iter {
424            write!(f, ", {weekday}")?;
425        }
426        write!(f, "]")
427    }
428}
429
430impl FromIterator<Weekday> for WeekdaySet {
431    fn from_iter<T: IntoIterator<Item = Weekday>>(iter: T) -> Self {
432        iter.into_iter().map(Self::single).fold(Self::EMPTY, Self::union)
433    }
434}
435
436#[cfg(test)]
437mod tests {
438    use crate::Weekday;
439
440    use super::WeekdaySet;
441
442    impl WeekdaySet {
443        /// Iterate over all 128 possible sets, from `EMPTY` to `ALL`.
444        fn iter_all() -> impl Iterator<Item = Self> {
445            (0b0000_0000..0b1000_0000).map(Self)
446        }
447    }
448
449    /// Panics if the 8-th bit of `self` is not 0.
450    fn assert_8th_bit_invariant(days: WeekdaySet) {
451        assert!(days.0 & 0b1000_0000 == 0, "the 8-th bit of {days:?} is not 0");
452    }
453
454    #[test]
455    fn debug_prints_8th_bit_if_not_zero() {
456        assert_eq!(format!("{:?}", WeekdaySet(0b1000_0000)), "WeekdaySet(10000000)");
457    }
458
459    #[test]
460    fn bitwise_set_operations_preserve_8th_bit_invariant() {
461        for set1 in WeekdaySet::iter_all() {
462            for set2 in WeekdaySet::iter_all() {
463                assert_8th_bit_invariant(set1.union(set2));
464                assert_8th_bit_invariant(set1.intersection(set2));
465                assert_8th_bit_invariant(set1.symmetric_difference(set2));
466            }
467        }
468    }
469
470    /// Test `split_at` on all possible arguments.
471    #[test]
472    fn split_at_is_equivalent_to_iterating() {
473        use Weekday::*;
474
475        // `split_at()` is used in `iter()`, so we must not iterate
476        // over all days with `WeekdaySet::ALL.iter(Mon)`.
477        const WEEK: [Weekday; 7] = [Mon, Tue, Wed, Thu, Fri, Sat, Sun];
478
479        for weekdays in WeekdaySet::iter_all() {
480            for split_day in WEEK {
481                let expected_before: WeekdaySet = WEEK
482                    .into_iter()
483                    .take_while(|&day| day != split_day)
484                    .filter(|&day| weekdays.contains(day))
485                    .collect();
486                let expected_after: WeekdaySet = WEEK
487                    .into_iter()
488                    .skip_while(|&day| day != split_day)
489                    .filter(|&day| weekdays.contains(day))
490                    .collect();
491
492                assert_eq!(
493                    (expected_before, expected_after),
494                    weekdays.split_at(split_day),
495                    "split_at({split_day}) failed for {weekdays}",
496                );
497            }
498        }
499    }
500}