use crate::{
bounds::{self as b, RangeError},
macros::{rbail, unwrapr},
};
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
#[allow(missing_docs)]
pub enum Weekday {
Monday = 1,
Tuesday = 2,
Wednesday = 3,
Thursday = 4,
Friday = 5,
Saturday = 6,
Sunday = 7,
}
impl Weekday {
#[inline]
pub const fn from_monday_zero_offset(
offset: i8,
) -> Result<Weekday, RangeError> {
Ok(match offset {
0 => Weekday::Monday,
1 => Weekday::Tuesday,
2 => Weekday::Wednesday,
3 => Weekday::Thursday,
4 => Weekday::Friday,
5 => Weekday::Saturday,
6 => Weekday::Sunday,
_ => rbail!(b::WeekdayMondayZero::error()),
})
}
#[inline]
pub const fn from_monday_one_offset(
offset: i8,
) -> Result<Weekday, RangeError> {
Ok(match offset {
1 => Weekday::Monday,
2 => Weekday::Tuesday,
3 => Weekday::Wednesday,
4 => Weekday::Thursday,
5 => Weekday::Friday,
6 => Weekday::Saturday,
7 => Weekday::Sunday,
_ => rbail!(b::WeekdayMondayOne::error()),
})
}
#[inline]
pub const fn from_sunday_zero_offset(
offset: i8,
) -> Result<Weekday, RangeError> {
Ok(match offset {
0 => Weekday::Sunday,
1 => Weekday::Monday,
2 => Weekday::Tuesday,
3 => Weekday::Wednesday,
4 => Weekday::Thursday,
5 => Weekday::Friday,
6 => Weekday::Saturday,
_ => rbail!(b::WeekdaySundayZero::error()),
})
}
#[inline]
pub const fn from_sunday_one_offset(
offset: i8,
) -> Result<Weekday, RangeError> {
Ok(match offset {
1 => Weekday::Sunday,
2 => Weekday::Monday,
3 => Weekday::Tuesday,
4 => Weekday::Wednesday,
5 => Weekday::Thursday,
6 => Weekday::Friday,
7 => Weekday::Saturday,
_ => rbail!(b::WeekdaySundayOne::error()),
})
}
#[inline]
pub const fn to_monday_zero_offset(self) -> i8 {
self.to_monday_one_offset() - 1
}
#[inline]
pub const fn to_monday_one_offset(self) -> i8 {
self as i8
}
#[inline]
pub const fn to_sunday_zero_offset(self) -> i8 {
let offset = self.to_monday_one_offset();
if offset == 7 {
0
} else {
offset
}
}
#[inline]
pub const fn to_sunday_one_offset(self) -> i8 {
self.to_sunday_zero_offset() + 1
}
#[inline]
pub const fn wrapping_add(self, days: i64) -> Weekday {
let start = self.to_monday_zero_offset() as i64;
let end = (start.wrapping_add(days.rem_euclid(7)) % 7) as i8;
unwrapr!(
Weekday::from_monday_zero_offset(end),
"weekday is always 0..=6",
)
}
#[inline]
pub const fn wrapping_sub(self, days: i64) -> Weekday {
let days = match days.checked_neg() {
Some(days) => days,
None => -6,
};
self.wrapping_add(days)
}
#[inline]
pub const fn next(self) -> Weekday {
self.wrapping_add(1)
}
#[inline]
pub const fn previous(self) -> Weekday {
self.wrapping_sub(1)
}
#[inline]
pub const fn since(self, other: Weekday) -> i8 {
(self.to_monday_zero_offset() - other.to_monday_zero_offset())
.rem_euclid(7)
}
#[inline]
pub const fn until(self, other: Weekday) -> i8 {
other.since(self)
}
#[inline]
pub const fn cycle_forward(self) -> WeekdaysForward {
WeekdaysForward { next: self }
}
#[inline]
pub const fn cycle_reverse(self) -> WeekdaysReverse {
WeekdaysReverse { next: self }
}
}
#[derive(Clone, Debug)]
pub struct WeekdaysForward {
next: Weekday,
}
impl Iterator for WeekdaysForward {
type Item = Weekday;
#[inline]
fn next(&mut self) -> Option<Weekday> {
let next = self.next;
self.next = self.next.wrapping_add(1);
Some(next)
}
}
impl core::iter::FusedIterator for WeekdaysForward {}
#[derive(Clone, Debug)]
pub struct WeekdaysReverse {
next: Weekday,
}
impl Iterator for WeekdaysReverse {
type Item = Weekday;
#[inline]
fn next(&mut self) -> Option<Weekday> {
let next = self.next;
self.next = self.next.wrapping_sub(1);
Some(next)
}
}
impl core::iter::FusedIterator for WeekdaysReverse {}
#[cfg(test)]
impl quickcheck::Arbitrary for Weekday {
fn arbitrary(g: &mut quickcheck::Gen) -> Weekday {
let offset = b::WeekdayMondayZero::arbitrary(g);
Weekday::from_monday_zero_offset(offset).unwrap()
}
fn shrink(&self) -> alloc::boxed::Box<dyn Iterator<Item = Weekday>> {
alloc::boxed::Box::new(
self.to_monday_zero_offset()
.shrink()
.filter_map(|n| Weekday::from_monday_zero_offset(n).ok()),
)
}
}
#[cfg(test)]
mod tests {
use alloc::vec::Vec;
use super::*;
static WEEKDAYS: &[Weekday] = &[
Weekday::Monday,
Weekday::Tuesday,
Weekday::Wednesday,
Weekday::Thursday,
Weekday::Friday,
Weekday::Saturday,
Weekday::Sunday,
];
#[test]
fn weekday_from_monday_zero() {
use self::Weekday::*;
assert_eq!(Weekday::from_monday_zero_offset(0), Ok(Monday));
assert_eq!(Weekday::from_monday_zero_offset(1), Ok(Tuesday));
assert_eq!(Weekday::from_monday_zero_offset(2), Ok(Wednesday));
assert_eq!(Weekday::from_monday_zero_offset(3), Ok(Thursday));
assert_eq!(Weekday::from_monday_zero_offset(4), Ok(Friday));
assert_eq!(Weekday::from_monday_zero_offset(5), Ok(Saturday));
assert_eq!(Weekday::from_monday_zero_offset(6), Ok(Sunday));
}
#[test]
fn weekday_from_monday_one() {
use self::Weekday::*;
assert_eq!(Weekday::from_monday_one_offset(1), Ok(Monday));
assert_eq!(Weekday::from_monday_one_offset(2), Ok(Tuesday));
assert_eq!(Weekday::from_monday_one_offset(3), Ok(Wednesday));
assert_eq!(Weekday::from_monday_one_offset(4), Ok(Thursday));
assert_eq!(Weekday::from_monday_one_offset(5), Ok(Friday));
assert_eq!(Weekday::from_monday_one_offset(6), Ok(Saturday));
assert_eq!(Weekday::from_monday_one_offset(7), Ok(Sunday));
}
#[test]
fn weekday_from_sunday_zero() {
use self::Weekday::*;
assert_eq!(Weekday::from_sunday_zero_offset(1), Ok(Monday));
assert_eq!(Weekday::from_sunday_zero_offset(2), Ok(Tuesday));
assert_eq!(Weekday::from_sunday_zero_offset(3), Ok(Wednesday));
assert_eq!(Weekday::from_sunday_zero_offset(4), Ok(Thursday));
assert_eq!(Weekday::from_sunday_zero_offset(5), Ok(Friday));
assert_eq!(Weekday::from_sunday_zero_offset(6), Ok(Saturday));
assert_eq!(Weekday::from_sunday_zero_offset(0), Ok(Sunday));
}
#[test]
fn weekday_from_sunday_one() {
use self::Weekday::*;
assert_eq!(Weekday::from_sunday_one_offset(2), Ok(Monday));
assert_eq!(Weekday::from_sunday_one_offset(3), Ok(Tuesday));
assert_eq!(Weekday::from_sunday_one_offset(4), Ok(Wednesday));
assert_eq!(Weekday::from_sunday_one_offset(5), Ok(Thursday));
assert_eq!(Weekday::from_sunday_one_offset(6), Ok(Friday));
assert_eq!(Weekday::from_sunday_one_offset(7), Ok(Saturday));
assert_eq!(Weekday::from_sunday_one_offset(1), Ok(Sunday));
}
#[test]
fn weekday_to_monday_zero() {
for &weekday in WEEKDAYS {
assert_eq!(
Weekday::from_monday_zero_offset(
weekday.to_monday_zero_offset()
),
Ok(weekday)
);
}
}
#[test]
fn weekday_to_monday_one() {
for &weekday in WEEKDAYS {
assert_eq!(
Weekday::from_monday_one_offset(
weekday.to_monday_one_offset()
),
Ok(weekday)
);
}
}
#[test]
fn weekday_to_sunday_zero() {
for &weekday in WEEKDAYS {
assert_eq!(
Weekday::from_sunday_zero_offset(
weekday.to_sunday_zero_offset()
),
Ok(weekday)
);
}
}
#[test]
fn weekday_to_sunday_one() {
for &weekday in WEEKDAYS {
assert_eq!(
Weekday::from_sunday_one_offset(
weekday.to_sunday_one_offset()
),
Ok(weekday)
);
}
}
#[test]
fn weekday_wrapping_add() {
use self::Weekday::*;
assert_eq!(Sunday.wrapping_add(0), Sunday);
assert_eq!(Sunday.wrapping_add(1), Monday);
assert_eq!(Sunday.wrapping_add(2), Tuesday);
assert_eq!(Sunday.wrapping_add(3), Wednesday);
assert_eq!(Sunday.wrapping_add(4), Thursday);
assert_eq!(Sunday.wrapping_add(5), Friday);
assert_eq!(Sunday.wrapping_add(6), Saturday);
assert_eq!(Sunday.wrapping_add(7), Sunday);
assert_eq!(Wednesday.wrapping_add(0), Wednesday);
assert_eq!(Wednesday.wrapping_add(1), Thursday);
assert_eq!(Wednesday.wrapping_add(2), Friday);
assert_eq!(Wednesday.wrapping_add(3), Saturday);
assert_eq!(Wednesday.wrapping_add(4), Sunday);
assert_eq!(Wednesday.wrapping_add(5), Monday);
assert_eq!(Wednesday.wrapping_add(6), Tuesday);
assert_eq!(Wednesday.wrapping_add(7), Wednesday);
assert_eq!(Sunday.wrapping_add(-1), Saturday);
assert_eq!(Sunday.wrapping_add(-2), Friday);
assert_eq!(Sunday.wrapping_add(-3), Thursday);
assert_eq!(Sunday.wrapping_add(-4), Wednesday);
assert_eq!(Sunday.wrapping_add(-5), Tuesday);
assert_eq!(Sunday.wrapping_add(-6), Monday);
assert_eq!(Sunday.wrapping_add(-7), Sunday);
assert_eq!(Wednesday.wrapping_add(-1), Tuesday);
assert_eq!(Wednesday.wrapping_add(-2), Monday);
assert_eq!(Wednesday.wrapping_add(-3), Sunday);
assert_eq!(Wednesday.wrapping_add(-4), Saturday);
assert_eq!(Wednesday.wrapping_add(-5), Friday);
assert_eq!(Wednesday.wrapping_add(-6), Thursday);
assert_eq!(Wednesday.wrapping_add(-7), Wednesday);
assert_eq!(Tuesday.wrapping_add(9223372036854775807i64), Tuesday);
}
#[test]
fn weekday_wrapping_sub() {
use self::Weekday::*;
assert_eq!(Sunday.wrapping_sub(0), Sunday);
assert_eq!(Sunday.wrapping_sub(1), Saturday);
assert_eq!(Sunday.wrapping_sub(2), Friday);
assert_eq!(Sunday.wrapping_sub(3), Thursday);
assert_eq!(Sunday.wrapping_sub(4), Wednesday);
assert_eq!(Sunday.wrapping_sub(5), Tuesday);
assert_eq!(Sunday.wrapping_sub(6), Monday);
assert_eq!(Sunday.wrapping_sub(7), Sunday);
assert_eq!(Wednesday.wrapping_sub(0), Wednesday);
assert_eq!(Wednesday.wrapping_sub(1), Tuesday);
assert_eq!(Wednesday.wrapping_sub(2), Monday);
assert_eq!(Wednesday.wrapping_sub(3), Sunday);
assert_eq!(Wednesday.wrapping_sub(4), Saturday);
assert_eq!(Wednesday.wrapping_sub(5), Friday);
assert_eq!(Wednesday.wrapping_sub(6), Thursday);
assert_eq!(Wednesday.wrapping_sub(7), Wednesday);
assert_eq!(Sunday.wrapping_sub(-1), Monday);
assert_eq!(Sunday.wrapping_sub(-2), Tuesday);
assert_eq!(Sunday.wrapping_sub(-3), Wednesday);
assert_eq!(Sunday.wrapping_sub(-4), Thursday);
assert_eq!(Sunday.wrapping_sub(-5), Friday);
assert_eq!(Sunday.wrapping_sub(-6), Saturday);
assert_eq!(Sunday.wrapping_sub(-7), Sunday);
assert_eq!(Wednesday.wrapping_sub(-1), Thursday);
assert_eq!(Wednesday.wrapping_sub(-2), Friday);
assert_eq!(Wednesday.wrapping_sub(-3), Saturday);
assert_eq!(Wednesday.wrapping_sub(-4), Sunday);
assert_eq!(Wednesday.wrapping_sub(-5), Monday);
assert_eq!(Wednesday.wrapping_sub(-6), Tuesday);
assert_eq!(Wednesday.wrapping_sub(-7), Wednesday);
assert_eq!(Monday.wrapping_sub(-9223372036854775805i64), Saturday);
assert_eq!(Monday.wrapping_sub(-9223372036854775806i64), Sunday);
assert_eq!(Monday.wrapping_sub(-9223372036854775807i64), Monday);
assert_eq!(Monday.wrapping_sub(-9223372036854775808i64), Tuesday);
}
#[test]
fn weekday_since() {
for &wd1 in WEEKDAYS {
for (distance, wd2) in wd1.cycle_forward().enumerate().take(7) {
assert_eq!(
usize::try_from(wd2.since(wd1)).unwrap(),
distance,
"{wd2:?} since {wd1:?} should be {distance}",
);
}
}
}
#[test]
fn weekday_until() {
for &wd1 in WEEKDAYS {
for (distance, wd2) in wd1.cycle_forward().enumerate().take(7) {
assert_eq!(
usize::try_from(wd1.until(wd2)).unwrap(),
distance,
"{wd1:?} until {wd2:?} should be {distance}",
);
}
}
}
#[test]
fn weekday_cycle_forward() {
assert_eq!(
WEEKDAYS,
Weekday::Monday.cycle_forward().take(7).collect::<Vec<_>>(),
);
}
#[test]
fn weekday_cycle_reverse() {
let mut got =
Weekday::Sunday.cycle_reverse().take(7).collect::<Vec<_>>();
got.reverse();
assert_eq!(WEEKDAYS, got);
}
quickcheck::quickcheck! {
fn prop_weekday_add_sub(wd: Weekday, n: i64) -> bool {
wd.wrapping_add(n).wrapping_sub(n) == wd
}
fn prop_weekday_since_until(wd1: Weekday, wd2: Weekday) -> bool {
wd1.until(wd2) == wd2.since(wd1)
}
fn prop_since_add_equals_self(wd1: Weekday, wd2: Weekday) -> bool {
let days = wd1.since(wd2);
wd2.wrapping_add(days.into()) == wd1
}
fn prop_until_add_equals_other(wd1: Weekday, wd2: Weekday) -> bool {
let days = wd1.until(wd2);
wd1.wrapping_add(days.into()) == wd2
}
}
}