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}