/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 | | } |