Coverage Report

Created: 2025-07-18 06:52

/rust/registry/src/index.crates.io-6f17d22bba15001f/chrono-0.4.41/src/weekday_set.rs
Line
Count
Source (jump to first uncovered line)
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
0
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
0
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
0
213
0
        // Find the first non-zero bit.
214
0
        let bit = 1 << self.0.trailing_zeros();
215
0
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
0
237
0
        // Find the last non-zero bit.
238
0
        let bit = 1 << (7 - self.0.leading_zeros());
239
0
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
/// An iterator over a collection of weekdays, starting from a given day.
335
///
336
/// See [`WeekdaySet::iter()`].
337
#[derive(Debug, Clone)]
338
pub struct WeekdaySetIter {
339
    days: WeekdaySet,
340
    start: Weekday,
341
}
342
343
impl Iterator for WeekdaySetIter {
344
    type Item = Weekday;
345
346
0
    fn next(&mut self) -> Option<Self::Item> {
347
0
        if self.days.is_empty() {
348
0
            return None;
349
0
        }
350
0
351
0
        // Split the collection in two at `start`.
352
0
        // Look for the first day among the days after `start` first, including `start` itself.
353
0
        // If there are no days after `start`, look for the first day among the days before `start`.
354
0
        let (before, after) = self.days.split_at(self.start);
355
0
        let days = if after.is_empty() { before } else { after };
356
357
0
        let next = days.first().expect("the collection is not empty");
358
0
        self.days.remove(next);
359
0
        Some(next)
360
0
    }
361
}
362
363
impl DoubleEndedIterator for WeekdaySetIter {
364
0
    fn next_back(&mut self) -> Option<Self::Item> {
365
0
        if self.days.is_empty() {
366
0
            return None;
367
0
        }
368
0
369
0
        // Split the collection in two at `start`.
370
0
        // Look for the last day among the days before `start` first, NOT including `start` itself.
371
0
        // If there are no days before `start`, look for the last day among the days after `start`.
372
0
        let (before, after) = self.days.split_at(self.start);
373
0
        let days = if before.is_empty() { after } else { before };
374
375
0
        let next_back = days.last().expect("the collection is not empty");
376
0
        self.days.remove(next_back);
377
0
        Some(next_back)
378
0
    }
379
}
380
381
impl ExactSizeIterator for WeekdaySetIter {
382
0
    fn len(&self) -> usize {
383
0
        self.days.len().into()
384
0
    }
385
}
386
387
impl 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
/// ```
399
impl fmt::Display for WeekdaySet {
400
0
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
401
0
        write!(f, "[")?;
402
0
        let mut iter = self.iter(Weekday::Mon);
403
0
        if let Some(first) = iter.next() {
404
0
            write!(f, "{first}")?;
405
0
        }
406
0
        for weekday in iter {
407
0
            write!(f, ", {weekday}")?;
408
        }
409
0
        write!(f, "]")
410
0
    }
411
}
412
413
impl FromIterator<Weekday> for WeekdaySet {
414
0
    fn from_iter<T: IntoIterator<Item = Weekday>>(iter: T) -> Self {
415
0
        iter.into_iter().map(Self::single).fold(Self::EMPTY, Self::union)
416
0
    }
417
}
418
419
#[cfg(test)]
420
mod 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
}