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/// An iterator over a collection of weekdays, starting from a given day.
335///
336/// See [`WeekdaySet::iter()`].
337#[derive(Debug, Clone)]
338pub struct WeekdaySetIter {
339 days: WeekdaySet,
340 start: Weekday,
341}
342
343impl Iterator for WeekdaySetIter {
344 type Item = Weekday;
345
346 fn next(&mut self) -> Option<Self::Item> {
347 if self.days.is_empty() {
348 return None;
349 }
350
351 // Split the collection in two at `start`.
352 // Look for the first day among the days after `start` first, including `start` itself.
353 // If there are no days after `start`, look for the first day among the days before `start`.
354 let (before, after) = self.days.split_at(self.start);
355 let days = if after.is_empty() { before } else { after };
356
357 let next = days.first().expect("the collection is not empty");
358 self.days.remove(next);
359 Some(next)
360 }
361}
362
363impl DoubleEndedIterator for WeekdaySetIter {
364 fn next_back(&mut self) -> Option<Self::Item> {
365 if self.days.is_empty() {
366 return None;
367 }
368
369 // Split the collection in two at `start`.
370 // Look for the last day among the days before `start` first, NOT including `start` itself.
371 // If there are no days before `start`, look for the last day among the days after `start`.
372 let (before, after) = self.days.split_at(self.start);
373 let days = if before.is_empty() { after } else { before };
374
375 let next_back = days.last().expect("the collection is not empty");
376 self.days.remove(next_back);
377 Some(next_back)
378 }
379}
380
381impl ExactSizeIterator for WeekdaySetIter {
382 fn len(&self) -> usize {
383 self.days.len().into()
384 }
385}
386
387impl FusedIterator for WeekdaySetIter {}
388
389/// Print the collection as a slice-like list of weekdays.
390///
391/// # Example
392/// ```
393/// # use chrono::WeekdaySet;
394/// use chrono::Weekday::*;
395/// assert_eq!("[]", WeekdaySet::EMPTY.to_string());
396/// assert_eq!("[Mon]", WeekdaySet::single(Mon).to_string());
397/// assert_eq!("[Mon, Fri, Sun]", WeekdaySet::from_array([Mon, Fri, Sun]).to_string());
398/// ```
399impl fmt::Display for WeekdaySet {
400 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
401 write!(f, "[")?;
402 let mut iter = self.iter(Weekday::Mon);
403 if let Some(first) = iter.next() {
404 write!(f, "{first}")?;
405 }
406 for weekday in iter {
407 write!(f, ", {weekday}")?;
408 }
409 write!(f, "]")
410 }
411}
412
413impl FromIterator<Weekday> for WeekdaySet {
414 fn from_iter<T: IntoIterator<Item = Weekday>>(iter: T) -> Self {
415 iter.into_iter().map(Self::single).fold(Self::EMPTY, Self::union)
416 }
417}
418
419#[cfg(test)]
420mod tests {
421 use crate::Weekday;
422
423 use super::WeekdaySet;
424
425 impl WeekdaySet {
426 /// Iterate over all 128 possible sets, from `EMPTY` to `ALL`.
427 fn iter_all() -> impl Iterator<Item = Self> {
428 (0b0000_0000..0b1000_0000).map(Self)
429 }
430 }
431
432 /// Panics if the 8-th bit of `self` is not 0.
433 fn assert_8th_bit_invariant(days: WeekdaySet) {
434 assert!(days.0 & 0b1000_0000 == 0, "the 8-th bit of {days:?} is not 0");
435 }
436
437 #[test]
438 fn debug_prints_8th_bit_if_not_zero() {
439 assert_eq!(format!("{:?}", WeekdaySet(0b1000_0000)), "WeekdaySet(10000000)");
440 }
441
442 #[test]
443 fn bitwise_set_operations_preserve_8th_bit_invariant() {
444 for set1 in WeekdaySet::iter_all() {
445 for set2 in WeekdaySet::iter_all() {
446 assert_8th_bit_invariant(set1.union(set2));
447 assert_8th_bit_invariant(set1.intersection(set2));
448 assert_8th_bit_invariant(set1.symmetric_difference(set2));
449 }
450 }
451 }
452
453 /// Test `split_at` on all possible arguments.
454 #[test]
455 fn split_at_is_equivalent_to_iterating() {
456 use Weekday::*;
457
458 // `split_at()` is used in `iter()`, so we must not iterate
459 // over all days with `WeekdaySet::ALL.iter(Mon)`.
460 const WEEK: [Weekday; 7] = [Mon, Tue, Wed, Thu, Fri, Sat, Sun];
461
462 for weekdays in WeekdaySet::iter_all() {
463 for split_day in WEEK {
464 let expected_before: WeekdaySet = WEEK
465 .into_iter()
466 .take_while(|&day| day != split_day)
467 .filter(|&day| weekdays.contains(day))
468 .collect();
469 let expected_after: WeekdaySet = WEEK
470 .into_iter()
471 .skip_while(|&day| day != split_day)
472 .filter(|&day| weekdays.contains(day))
473 .collect();
474
475 assert_eq!(
476 (expected_before, expected_after),
477 weekdays.split_at(split_day),
478 "split_at({split_day}) failed for {weekdays}",
479 );
480 }
481 }
482 }
483}