Coverage Report

Created: 2026-09-14 06:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/chrono-0.4.45/src/weekday_set.rs
Line
Count
Source
1
use core::{
2
    fmt::{self, Debug},
3
    iter::FusedIterator,
4
};
5
6
use 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)]
15
pub struct WeekdaySet(u8); // Invariant: the 8-th bit is always 0.
16
17
impl 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
0
    pub const fn from_array<const C: usize>(days: [Weekday; C]) -> Self {
29
0
        let mut acc = Self::EMPTY;
30
0
        let mut idx = 0;
31
0
        while idx < days.len() {
32
0
            acc.0 |= Self::single(days[idx]).0;
33
0
            idx += 1;
34
0
        }
35
0
        acc
36
0
    }
37
38
    /// Create a `WeekdaySet` from a single [`Weekday`].
39
0
    pub const fn single(weekday: Weekday) -> Self {
40
0
        match weekday {
41
0
            Weekday::Mon => Self(0b000_0001),
42
0
            Weekday::Tue => Self(0b000_0010),
43
0
            Weekday::Wed => Self(0b000_0100),
44
0
            Weekday::Thu => Self(0b000_1000),
45
0
            Weekday::Fri => Self(0b001_0000),
46
0
            Weekday::Sat => Self(0b010_0000),
47
0
            Weekday::Sun => Self(0b100_0000),
48
        }
49
0
    }
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
0
    pub const fn single_day(self) -> Option<Weekday> {
65
0
        match self {
66
0
            Self(0b000_0001) => Some(Weekday::Mon),
67
0
            Self(0b000_0010) => Some(Weekday::Tue),
68
0
            Self(0b000_0100) => Some(Weekday::Wed),
69
0
            Self(0b000_1000) => Some(Weekday::Thu),
70
0
            Self(0b001_0000) => Some(Weekday::Fri),
71
0
            Self(0b010_0000) => Some(Weekday::Sat),
72
0
            Self(0b100_0000) => Some(Weekday::Sun),
73
0
            _ => None,
74
        }
75
0
    }
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
0
    pub fn insert(&mut self, day: Weekday) -> bool {
90
0
        if self.contains(day) {
91
0
            return false;
92
0
        }
93
94
0
        self.0 |= Self::single(day).0;
95
0
        true
96
0
    }
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
0
    pub fn remove(&mut self, day: Weekday) -> bool {
111
0
        if self.contains(day) {
112
0
            self.0 &= !Self::single(day).0;
113
0
            return true;
114
0
        }
115
116
0
        false
117
0
    }
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
0
    pub const fn is_subset(self, other: Self) -> bool {
130
0
        self.intersection(other).0 == self.0
131
0
    }
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
0
    pub const fn intersection(self, other: Self) -> Self {
145
0
        Self(self.0 & other.0)
146
0
    }
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
0
    pub const fn union(self, other: Self) -> Self {
160
0
        Self(self.0 | other.0)
161
0
    }
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
0
    pub const fn symmetric_difference(self, other: Self) -> Self {
178
0
        Self(self.0 ^ other.0)
179
0
    }
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
0
    pub const fn difference(self, other: Self) -> Self {
192
0
        Self(self.0 & !other.0)
193
0
    }
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
0
    pub const fn first(self) -> Option<Weekday> {
209
0
        if self.is_empty() {
210
0
            return None;
211
0
        }
212
213
        // Find the first non-zero bit.
214
0
        let bit = 1 << self.0.trailing_zeros();
215
216
0
        Self(bit).single_day()
217
0
    }
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
0
    pub fn last(self) -> Option<Weekday> {
233
0
        if self.is_empty() {
234
0
            return None;
235
0
        }
236
237
        // Find the last non-zero bit.
238
0
        let bit = 1 << (7 - self.0.leading_zeros());
239
240
0
        Self(bit).single_day()
241
0
    }
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
0
    const fn split_at(self, weekday: Weekday) -> (Self, Self) {
249
0
        let days_after = 0b1000_0000 - Self::single(weekday).0;
250
0
        let days_before = days_after ^ 0b0111_1111;
251
0
        (Self(self.0 & days_before), Self(self.0 & days_after))
252
0
    }
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
0
    pub const fn iter(self, start: Weekday) -> WeekdaySetIter {
270
0
        WeekdaySetIter { days: self, start }
271
0
    }
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
0
    pub const fn contains(self, day: Weekday) -> bool {
284
0
        self.0 & Self::single(day).0 != 0
285
0
    }
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
0
    pub const fn is_empty(self) -> bool {
296
0
        self.len() == 0
297
0
    }
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
0
    pub const fn len(self) -> u8 {
309
0
        self.0.count_ones() as u8
310
0
    }
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
/// ```
328
impl Debug for WeekdaySet {
329
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
330
0
        write!(f, "WeekdaySet({:0>7b})", self.0)
331
0
    }
332
}
333
334
#[cfg(feature = "defmt")]
335
impl 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)]
355
pub struct WeekdaySetIter {
356
    days: WeekdaySet,
357
    start: Weekday,
358
}
359
360
impl Iterator for WeekdaySetIter {
361
    type Item = Weekday;
362
363
0
    fn next(&mut self) -> Option<Self::Item> {
364
0
        if self.days.is_empty() {
365
0
            return None;
366
0
        }
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
0
        let (before, after) = self.days.split_at(self.start);
372
0
        let days = if after.is_empty() { before } else { after };
373
374
0
        let next = days.first().expect("the collection is not empty");
375
0
        self.days.remove(next);
376
0
        Some(next)
377
0
    }
378
}
379
380
impl DoubleEndedIterator for WeekdaySetIter {
381
0
    fn next_back(&mut self) -> Option<Self::Item> {
382
0
        if self.days.is_empty() {
383
0
            return None;
384
0
        }
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
0
        let (before, after) = self.days.split_at(self.start);
390
0
        let days = if before.is_empty() { after } else { before };
391
392
0
        let next_back = days.last().expect("the collection is not empty");
393
0
        self.days.remove(next_back);
394
0
        Some(next_back)
395
0
    }
396
}
397
398
impl ExactSizeIterator for WeekdaySetIter {
399
0
    fn len(&self) -> usize {
400
0
        self.days.len().into()
401
0
    }
402
}
403
404
impl 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
/// ```
416
impl fmt::Display for WeekdaySet {
417
0
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
418
0
        write!(f, "[")?;
419
0
        let mut iter = self.iter(Weekday::Mon);
420
0
        if let Some(first) = iter.next() {
421
0
            write!(f, "{first}")?;
422
0
        }
423
0
        for weekday in iter {
424
0
            write!(f, ", {weekday}")?;
425
        }
426
0
        write!(f, "]")
427
0
    }
428
}
429
430
impl FromIterator<Weekday> for WeekdaySet {
431
0
    fn from_iter<T: IntoIterator<Item = Weekday>>(iter: T) -> Self {
432
0
        iter.into_iter().map(Self::single).fold(Self::EMPTY, Self::union)
433
0
    }
434
}
435
436
#[cfg(test)]
437
mod 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
}