1use crate::{
2 bounds::{self as b, RangeError},
3 macros::{rbail, unwrapr},
4};
5
6#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
8#[cfg_attr(feature = "defmt", derive(defmt::Format))]
9#[repr(u8)]
10#[allow(missing_docs)]
11pub enum Weekday {
12 Monday = 1,
13 Tuesday = 2,
14 Wednesday = 3,
15 Thursday = 4,
16 Friday = 5,
17 Saturday = 6,
18 Sunday = 7,
19}
20
21impl Weekday {
22 #[inline]
25 pub const fn from_monday_zero_offset(
26 offset: i8,
27 ) -> Result<Weekday, RangeError> {
28 Ok(match offset {
29 0 => Weekday::Monday,
30 1 => Weekday::Tuesday,
31 2 => Weekday::Wednesday,
32 3 => Weekday::Thursday,
33 4 => Weekday::Friday,
34 5 => Weekday::Saturday,
35 6 => Weekday::Sunday,
36 _ => rbail!(b::WeekdayMondayZero::error()),
37 })
38 }
39
40 #[inline]
43 pub const fn from_monday_one_offset(
44 offset: i8,
45 ) -> Result<Weekday, RangeError> {
46 Ok(match offset {
47 1 => Weekday::Monday,
48 2 => Weekday::Tuesday,
49 3 => Weekday::Wednesday,
50 4 => Weekday::Thursday,
51 5 => Weekday::Friday,
52 6 => Weekday::Saturday,
53 7 => Weekday::Sunday,
54 _ => rbail!(b::WeekdayMondayOne::error()),
55 })
56 }
57
58 #[inline]
61 pub const fn from_sunday_zero_offset(
62 offset: i8,
63 ) -> Result<Weekday, RangeError> {
64 Ok(match offset {
65 0 => Weekday::Sunday,
66 1 => Weekday::Monday,
67 2 => Weekday::Tuesday,
68 3 => Weekday::Wednesday,
69 4 => Weekday::Thursday,
70 5 => Weekday::Friday,
71 6 => Weekday::Saturday,
72 _ => rbail!(b::WeekdaySundayZero::error()),
73 })
74 }
75
76 #[inline]
79 pub const fn from_sunday_one_offset(
80 offset: i8,
81 ) -> Result<Weekday, RangeError> {
82 Ok(match offset {
83 1 => Weekday::Sunday,
84 2 => Weekday::Monday,
85 3 => Weekday::Tuesday,
86 4 => Weekday::Wednesday,
87 5 => Weekday::Thursday,
88 6 => Weekday::Friday,
89 7 => Weekday::Saturday,
90 _ => rbail!(b::WeekdaySundayOne::error()),
91 })
92 }
93
94 #[inline]
97 pub const fn to_monday_zero_offset(self) -> i8 {
98 self.to_monday_one_offset() - 1
99 }
100
101 #[inline]
104 pub const fn to_monday_one_offset(self) -> i8 {
105 self as i8
106 }
107
108 #[inline]
111 pub const fn to_sunday_zero_offset(self) -> i8 {
112 let offset = self.to_monday_one_offset();
113 if offset == 7 {
114 0
115 } else {
116 offset
117 }
118 }
119
120 #[inline]
123 pub const fn to_sunday_one_offset(self) -> i8 {
124 self.to_sunday_zero_offset() + 1
125 }
126
127 #[inline]
133 pub const fn wrapping_add(self, days: i64) -> Weekday {
134 let start = self.to_monday_zero_offset() as i64;
135 let end = (start.wrapping_add(days.rem_euclid(7)) % 7) as i8;
141 unwrapr!(
143 Weekday::from_monday_zero_offset(end),
144 "weekday is always 0..=6",
145 )
146 }
147
148 #[inline]
154 pub const fn wrapping_sub(self, days: i64) -> Weekday {
155 let days = match days.checked_neg() {
157 Some(days) => days,
158 None => -6,
159 };
160 self.wrapping_add(days)
161 }
162
163 #[inline]
169 pub const fn next(self) -> Weekday {
170 self.wrapping_add(1)
171 }
172
173 #[inline]
179 pub const fn previous(self) -> Weekday {
180 self.wrapping_sub(1)
181 }
182
183 #[inline]
189 pub const fn since(self, other: Weekday) -> i8 {
190 (self.to_monday_zero_offset() - other.to_monday_zero_offset())
191 .rem_euclid(7)
192 }
193
194 #[inline]
200 pub const fn until(self, other: Weekday) -> i8 {
201 other.since(self)
202 }
203
204 #[inline]
207 pub const fn cycle_forward(self) -> WeekdaysForward {
208 WeekdaysForward { next: self }
209 }
210
211 #[inline]
214 pub const fn cycle_reverse(self) -> WeekdaysReverse {
215 WeekdaysReverse { next: self }
216 }
217}
218
219#[derive(Clone, Debug)]
223pub struct WeekdaysForward {
224 next: Weekday,
225}
226
227impl Iterator for WeekdaysForward {
228 type Item = Weekday;
229
230 #[inline]
231 fn next(&mut self) -> Option<Weekday> {
232 let next = self.next;
233 self.next = self.next.wrapping_add(1);
234 Some(next)
235 }
236}
237
238impl core::iter::FusedIterator for WeekdaysForward {}
239
240#[derive(Clone, Debug)]
244pub struct WeekdaysReverse {
245 next: Weekday,
246}
247
248impl Iterator for WeekdaysReverse {
249 type Item = Weekday;
250
251 #[inline]
252 fn next(&mut self) -> Option<Weekday> {
253 let next = self.next;
254 self.next = self.next.wrapping_sub(1);
255 Some(next)
256 }
257}
258
259impl core::iter::FusedIterator for WeekdaysReverse {}
260
261#[cfg(test)]
262impl quickcheck::Arbitrary for Weekday {
263 fn arbitrary(g: &mut quickcheck::Gen) -> Weekday {
264 let offset = b::WeekdayMondayZero::arbitrary(g);
265 Weekday::from_monday_zero_offset(offset).unwrap()
266 }
267
268 fn shrink(&self) -> alloc::boxed::Box<dyn Iterator<Item = Weekday>> {
269 alloc::boxed::Box::new(
270 self.to_monday_zero_offset()
271 .shrink()
272 .filter_map(|n| Weekday::from_monday_zero_offset(n).ok()),
273 )
274 }
275}
276
277#[cfg(test)]
278mod tests {
279 use alloc::vec::Vec;
280
281 use super::*;
282
283 static WEEKDAYS: &[Weekday] = &[
284 Weekday::Monday,
285 Weekday::Tuesday,
286 Weekday::Wednesday,
287 Weekday::Thursday,
288 Weekday::Friday,
289 Weekday::Saturday,
290 Weekday::Sunday,
291 ];
292
293 #[test]
294 fn weekday_from_monday_zero() {
295 use self::Weekday::*;
296
297 assert_eq!(Weekday::from_monday_zero_offset(0), Ok(Monday));
298 assert_eq!(Weekday::from_monday_zero_offset(1), Ok(Tuesday));
299 assert_eq!(Weekday::from_monday_zero_offset(2), Ok(Wednesday));
300 assert_eq!(Weekday::from_monday_zero_offset(3), Ok(Thursday));
301 assert_eq!(Weekday::from_monday_zero_offset(4), Ok(Friday));
302 assert_eq!(Weekday::from_monday_zero_offset(5), Ok(Saturday));
303 assert_eq!(Weekday::from_monday_zero_offset(6), Ok(Sunday));
304 }
305
306 #[test]
307 fn weekday_from_monday_one() {
308 use self::Weekday::*;
309
310 assert_eq!(Weekday::from_monday_one_offset(1), Ok(Monday));
311 assert_eq!(Weekday::from_monday_one_offset(2), Ok(Tuesday));
312 assert_eq!(Weekday::from_monday_one_offset(3), Ok(Wednesday));
313 assert_eq!(Weekday::from_monday_one_offset(4), Ok(Thursday));
314 assert_eq!(Weekday::from_monday_one_offset(5), Ok(Friday));
315 assert_eq!(Weekday::from_monday_one_offset(6), Ok(Saturday));
316 assert_eq!(Weekday::from_monday_one_offset(7), Ok(Sunday));
317 }
318
319 #[test]
320 fn weekday_from_sunday_zero() {
321 use self::Weekday::*;
322
323 assert_eq!(Weekday::from_sunday_zero_offset(1), Ok(Monday));
324 assert_eq!(Weekday::from_sunday_zero_offset(2), Ok(Tuesday));
325 assert_eq!(Weekday::from_sunday_zero_offset(3), Ok(Wednesday));
326 assert_eq!(Weekday::from_sunday_zero_offset(4), Ok(Thursday));
327 assert_eq!(Weekday::from_sunday_zero_offset(5), Ok(Friday));
328 assert_eq!(Weekday::from_sunday_zero_offset(6), Ok(Saturday));
329 assert_eq!(Weekday::from_sunday_zero_offset(0), Ok(Sunday));
330 }
331
332 #[test]
333 fn weekday_from_sunday_one() {
334 use self::Weekday::*;
335
336 assert_eq!(Weekday::from_sunday_one_offset(2), Ok(Monday));
337 assert_eq!(Weekday::from_sunday_one_offset(3), Ok(Tuesday));
338 assert_eq!(Weekday::from_sunday_one_offset(4), Ok(Wednesday));
339 assert_eq!(Weekday::from_sunday_one_offset(5), Ok(Thursday));
340 assert_eq!(Weekday::from_sunday_one_offset(6), Ok(Friday));
341 assert_eq!(Weekday::from_sunday_one_offset(7), Ok(Saturday));
342 assert_eq!(Weekday::from_sunday_one_offset(1), Ok(Sunday));
343 }
344
345 #[test]
346 fn weekday_to_monday_zero() {
347 for &weekday in WEEKDAYS {
348 assert_eq!(
349 Weekday::from_monday_zero_offset(
350 weekday.to_monday_zero_offset()
351 ),
352 Ok(weekday)
353 );
354 }
355 }
356
357 #[test]
358 fn weekday_to_monday_one() {
359 for &weekday in WEEKDAYS {
360 assert_eq!(
361 Weekday::from_monday_one_offset(
362 weekday.to_monday_one_offset()
363 ),
364 Ok(weekday)
365 );
366 }
367 }
368
369 #[test]
370 fn weekday_to_sunday_zero() {
371 for &weekday in WEEKDAYS {
372 assert_eq!(
373 Weekday::from_sunday_zero_offset(
374 weekday.to_sunday_zero_offset()
375 ),
376 Ok(weekday)
377 );
378 }
379 }
380
381 #[test]
382 fn weekday_to_sunday_one() {
383 for &weekday in WEEKDAYS {
384 assert_eq!(
385 Weekday::from_sunday_one_offset(
386 weekday.to_sunday_one_offset()
387 ),
388 Ok(weekday)
389 );
390 }
391 }
392
393 #[test]
394 fn weekday_wrapping_add() {
395 use self::Weekday::*;
396
397 assert_eq!(Sunday.wrapping_add(0), Sunday);
398 assert_eq!(Sunday.wrapping_add(1), Monday);
399 assert_eq!(Sunday.wrapping_add(2), Tuesday);
400 assert_eq!(Sunday.wrapping_add(3), Wednesday);
401 assert_eq!(Sunday.wrapping_add(4), Thursday);
402 assert_eq!(Sunday.wrapping_add(5), Friday);
403 assert_eq!(Sunday.wrapping_add(6), Saturday);
404 assert_eq!(Sunday.wrapping_add(7), Sunday);
405
406 assert_eq!(Wednesday.wrapping_add(0), Wednesday);
407 assert_eq!(Wednesday.wrapping_add(1), Thursday);
408 assert_eq!(Wednesday.wrapping_add(2), Friday);
409 assert_eq!(Wednesday.wrapping_add(3), Saturday);
410 assert_eq!(Wednesday.wrapping_add(4), Sunday);
411 assert_eq!(Wednesday.wrapping_add(5), Monday);
412 assert_eq!(Wednesday.wrapping_add(6), Tuesday);
413 assert_eq!(Wednesday.wrapping_add(7), Wednesday);
414
415 assert_eq!(Sunday.wrapping_add(-1), Saturday);
416 assert_eq!(Sunday.wrapping_add(-2), Friday);
417 assert_eq!(Sunday.wrapping_add(-3), Thursday);
418 assert_eq!(Sunday.wrapping_add(-4), Wednesday);
419 assert_eq!(Sunday.wrapping_add(-5), Tuesday);
420 assert_eq!(Sunday.wrapping_add(-6), Monday);
421 assert_eq!(Sunday.wrapping_add(-7), Sunday);
422
423 assert_eq!(Wednesday.wrapping_add(-1), Tuesday);
424 assert_eq!(Wednesday.wrapping_add(-2), Monday);
425 assert_eq!(Wednesday.wrapping_add(-3), Sunday);
426 assert_eq!(Wednesday.wrapping_add(-4), Saturday);
427 assert_eq!(Wednesday.wrapping_add(-5), Friday);
428 assert_eq!(Wednesday.wrapping_add(-6), Thursday);
429 assert_eq!(Wednesday.wrapping_add(-7), Wednesday);
430
431 assert_eq!(Tuesday.wrapping_add(9223372036854775807i64), Tuesday);
434 }
435
436 #[test]
437 fn weekday_wrapping_sub() {
438 use self::Weekday::*;
439
440 assert_eq!(Sunday.wrapping_sub(0), Sunday);
441 assert_eq!(Sunday.wrapping_sub(1), Saturday);
442 assert_eq!(Sunday.wrapping_sub(2), Friday);
443 assert_eq!(Sunday.wrapping_sub(3), Thursday);
444 assert_eq!(Sunday.wrapping_sub(4), Wednesday);
445 assert_eq!(Sunday.wrapping_sub(5), Tuesday);
446 assert_eq!(Sunday.wrapping_sub(6), Monday);
447 assert_eq!(Sunday.wrapping_sub(7), Sunday);
448
449 assert_eq!(Wednesday.wrapping_sub(0), Wednesday);
450 assert_eq!(Wednesday.wrapping_sub(1), Tuesday);
451 assert_eq!(Wednesday.wrapping_sub(2), Monday);
452 assert_eq!(Wednesday.wrapping_sub(3), Sunday);
453 assert_eq!(Wednesday.wrapping_sub(4), Saturday);
454 assert_eq!(Wednesday.wrapping_sub(5), Friday);
455 assert_eq!(Wednesday.wrapping_sub(6), Thursday);
456 assert_eq!(Wednesday.wrapping_sub(7), Wednesday);
457
458 assert_eq!(Sunday.wrapping_sub(-1), Monday);
459 assert_eq!(Sunday.wrapping_sub(-2), Tuesday);
460 assert_eq!(Sunday.wrapping_sub(-3), Wednesday);
461 assert_eq!(Sunday.wrapping_sub(-4), Thursday);
462 assert_eq!(Sunday.wrapping_sub(-5), Friday);
463 assert_eq!(Sunday.wrapping_sub(-6), Saturday);
464 assert_eq!(Sunday.wrapping_sub(-7), Sunday);
465
466 assert_eq!(Wednesday.wrapping_sub(-1), Thursday);
467 assert_eq!(Wednesday.wrapping_sub(-2), Friday);
468 assert_eq!(Wednesday.wrapping_sub(-3), Saturday);
469 assert_eq!(Wednesday.wrapping_sub(-4), Sunday);
470 assert_eq!(Wednesday.wrapping_sub(-5), Monday);
471 assert_eq!(Wednesday.wrapping_sub(-6), Tuesday);
472 assert_eq!(Wednesday.wrapping_sub(-7), Wednesday);
473
474 assert_eq!(Monday.wrapping_sub(-9223372036854775805i64), Saturday);
477 assert_eq!(Monday.wrapping_sub(-9223372036854775806i64), Sunday);
478 assert_eq!(Monday.wrapping_sub(-9223372036854775807i64), Monday);
479 assert_eq!(Monday.wrapping_sub(-9223372036854775808i64), Tuesday);
480 }
481
482 #[test]
483 fn weekday_since() {
484 for &wd1 in WEEKDAYS {
485 for (distance, wd2) in wd1.cycle_forward().enumerate().take(7) {
486 assert_eq!(
487 usize::try_from(wd2.since(wd1)).unwrap(),
488 distance,
489 "{wd2:?} since {wd1:?} should be {distance}",
490 );
491 }
492 }
493 }
494
495 #[test]
496 fn weekday_until() {
497 for &wd1 in WEEKDAYS {
498 for (distance, wd2) in wd1.cycle_forward().enumerate().take(7) {
499 assert_eq!(
500 usize::try_from(wd1.until(wd2)).unwrap(),
501 distance,
502 "{wd1:?} until {wd2:?} should be {distance}",
503 );
504 }
505 }
506 }
507
508 #[test]
509 fn weekday_cycle_forward() {
510 assert_eq!(
511 WEEKDAYS,
512 Weekday::Monday.cycle_forward().take(7).collect::<Vec<_>>(),
513 );
514 }
515
516 #[test]
517 fn weekday_cycle_reverse() {
518 let mut got =
519 Weekday::Sunday.cycle_reverse().take(7).collect::<Vec<_>>();
520 got.reverse();
521 assert_eq!(WEEKDAYS, got);
522 }
523
524 quickcheck::quickcheck! {
525 fn prop_weekday_add_sub(wd: Weekday, n: i64) -> bool {
526 wd.wrapping_add(n).wrapping_sub(n) == wd
527 }
528
529 fn prop_weekday_since_until(wd1: Weekday, wd2: Weekday) -> bool {
530 wd1.until(wd2) == wd2.since(wd1)
531 }
532
533 fn prop_since_add_equals_self(wd1: Weekday, wd2: Weekday) -> bool {
534 let days = wd1.since(wd2);
535 wd2.wrapping_add(days.into()) == wd1
536 }
537
538 fn prop_until_add_equals_other(wd1: Weekday, wd2: Weekday) -> bool {
539 let days = wd1.until(wd2);
540 wd1.wrapping_add(days.into()) == wd2
541 }
542 }
543}