use crate::{
macros,
Utc2k,
Utc2kError,
};
use std::{
cmp::Ordering,
ops::{
Add,
AddAssign,
Deref,
Sub,
SubAssign,
},
};
#[allow(missing_docs)]
#[repr(u8)]
#[derive(Debug, Clone, Copy, Default, Eq, Hash, PartialEq)]
pub enum Weekday {
#[default]
Sunday = 1_u8,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
}
impl Add<u8> for Weekday {
type Output = Self;
#[inline]
fn add(self, other: u8) -> Self {
Self::from(self as u8 + other % 7)
}
}
impl AddAssign<u8> for Weekday {
#[inline]
fn add_assign(&mut self, other: u8) { *self = *self + other; }
}
macros::as_ref_borrow_cast!(Weekday: as_str str);
impl Deref for Weekday {
type Target = str;
#[inline]
fn deref(&self) -> &Self::Target { self.as_str() }
}
macros::display_str!(as_str Weekday);
impl From<u8> for Weekday {
fn from(src: u8) -> Self {
match src {
1 => Self::Sunday,
2 => Self::Monday,
3 => Self::Tuesday,
4 => Self::Wednesday,
5 => Self::Thursday,
6 => Self::Friday,
0 | 7 => Self::Saturday,
_ => Self::from(src % 7),
}
}
}
impl From<Weekday> for u8 {
#[inline]
fn from(src: Weekday) -> Self { src as Self }
}
macro_rules! impl_int {
($($ty:ty),+) => ($(
impl Add<$ty> for Weekday {
type Output = Self;
#[inline]
fn add(self, other: $ty) -> Self {
Self::from(<$ty>::from(self) + other % 7)
}
}
impl AddAssign<$ty> for Weekday {
#[inline]
fn add_assign(&mut self, other: $ty) { *self = *self + other; }
}
impl From<$ty> for Weekday {
fn from(src: $ty) -> Self {
if src <= 7 { Self::from(src as u8) }
else { Self::from((src % 7) as u8) }
}
}
impl From<Weekday> for $ty {
fn from(src: Weekday) -> Self {
match src {
Weekday::Sunday => 1,
Weekday::Monday => 2,
Weekday::Tuesday => 3,
Weekday::Wednesday => 4,
Weekday::Thursday => 5,
Weekday::Friday => 6,
Weekday::Saturday => 7,
}
}
}
impl Sub<$ty> for Weekday {
type Output = Self;
#[allow(clippy::semicolon_if_nothing_returned)] fn sub(self, other: $ty) -> Self {
let mut lhs = <$ty>::from(self);
let mut rhs = other % 7;
while rhs > 0 {
rhs -= 1;
if lhs == 1 { lhs = 7; }
else { lhs -= 1; }
}
Self::from(lhs)
}
}
impl SubAssign<$ty> for Weekday {
#[inline]
fn sub_assign(&mut self, other: $ty) { *self = *self - other; }
}
)+);
}
impl_int!(u16, u32, u64, usize);
impl From<Utc2k> for Weekday {
#[inline]
fn from(src: Utc2k) -> Self { src.weekday() }
}
impl Ord for Weekday {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
let a = *self as u8;
let b = *other as u8;
a.cmp(&b)
}
}
impl PartialEq<u8> for Weekday {
#[inline]
fn eq(&self, other: &u8) -> bool { (*self as u8).eq(other) }
}
impl PartialEq<Weekday> for u8 {
#[inline]
fn eq(&self, other: &Weekday) -> bool { (*other as Self).eq(self) }
}
macros::partial_eq_from!(Weekday: u16, u32, u64, usize);
impl PartialOrd for Weekday {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}
impl Sub<u8> for Weekday {
type Output = Self;
#[allow(clippy::semicolon_if_nothing_returned)] fn sub(self, other: u8) -> Self {
let mut lhs = self as u8;
let mut rhs = other % 7;
while rhs > 0 {
rhs -= 1;
if lhs == 1 { lhs = 7; }
else { lhs -= 1; }
}
Self::from(lhs)
}
}
impl SubAssign<u8> for Weekday {
#[inline]
fn sub_assign(&mut self, other: u8) { *self = *self - other; }
}
impl TryFrom<&str> for Weekday {
type Error = Utc2kError;
fn try_from(src: &str) -> Result<Self, Self::Error> {
Self::from_abbreviation(src.trim().as_bytes())
.ok_or(Utc2kError::Invalid)
}
}
impl TryFrom<String> for Weekday {
type Error = Utc2kError;
fn try_from(src: String) -> Result<Self, Self::Error> {
Self::from_abbreviation(src.trim().as_bytes())
.ok_or(Utc2kError::Invalid)
}
}
impl Weekday {
#[must_use]
pub const fn abbreviation(self) -> &'static str {
match self {
Self::Sunday => "Sun",
Self::Monday => "Mon",
Self::Tuesday => "Tue",
Self::Wednesday => "Wed",
Self::Thursday => "Thu",
Self::Friday => "Fri",
Self::Saturday => "Sat",
}
}
pub(crate) const fn abbreviation_bytes(self) -> [u8; 3] {
match self {
Self::Sunday => *b"Sun",
Self::Monday => *b"Mon",
Self::Tuesday => *b"Tue",
Self::Wednesday => *b"Wed",
Self::Thursday => *b"Thu",
Self::Friday => *b"Fri",
Self::Saturday => *b"Sat",
}
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Sunday => "Sunday",
Self::Monday => "Monday",
Self::Tuesday => "Tuesday",
Self::Wednesday => "Wednesday",
Self::Thursday => "Thursday",
Self::Friday => "Friday",
Self::Saturday => "Saturday",
}
}
}
impl Weekday {
#[must_use]
pub fn now() -> Self { Utc2k::now().weekday() }
#[inline]
#[must_use]
pub fn tomorrow() -> Self { Utc2k::tomorrow().weekday() }
#[inline]
#[must_use]
pub fn yesterday() -> Self { Utc2k::yesterday().weekday() }
}
impl Weekday {
#[inline]
#[must_use]
pub fn first_in_month(self, y: u16, m: u8) -> Option<u8> { self.nth_in_month(y, m, 1) }
#[inline]
#[must_use]
pub fn last_in_month(self, y: u16, m: u8) -> Option<u8> {
let first = Utc2k::new(y, m, 1, 0, 0, 0);
if (y, m, 1) != first.ymd() { return None; }
let weekday = first.weekday();
let d = match (weekday as u8).cmp(&(self as u8)) {
Ordering::Less => 1 + self as u8 - weekday as u8,
Ordering::Equal => 1,
Ordering::Greater => 8 - (weekday as u8 - self as u8),
};
let n = (first.month_size() - d).wrapping_div(7);
Some(d + n * 7)
}
#[must_use]
pub fn nth_in_month(self, y: u16, m: u8, n: u8) -> Option<u8> {
if ! (1..6).contains(&n) { return None; }
let first = Utc2k::new(y, m, 1, 0, 0, 0);
if (y, m, 1) != first.ymd() { return None; }
let weekday = first.weekday();
let d =
match (weekday as u8).cmp(&(self as u8)) {
Ordering::Less => 1 + self as u8 - weekday as u8,
Ordering::Equal => 1,
Ordering::Greater => 8 - (weekday as u8 - self as u8),
}
+ (n - 1) * 7;
if d <= first.month_size() { Some(d) }
else { None }
}
}
impl Weekday {
pub(crate) fn from_abbreviation(src: &[u8]) -> Option<Self> {
let src = src.get(..3)?;
match &[src[0].to_ascii_lowercase(), src[1].to_ascii_lowercase(), src[2].to_ascii_lowercase()] {
b"sun" => Some(Self::Sunday),
b"mon" => Some(Self::Monday),
b"tue" => Some(Self::Tuesday),
b"wed" => Some(Self::Wednesday),
b"thu" => Some(Self::Thursday),
b"fri" => Some(Self::Friday),
b"sat" => Some(Self::Saturday),
_ => None,
}
}
#[must_use]
pub(crate) const fn year_begins_on(y: u8) -> Self {
match y {
0 | 5 | 11 | 22 | 28 | 33 | 39 | 50 | 56 | 61 | 67 | 78 | 84 | 89 | 95 => Self::Saturday,
1 | 7 | 18 | 24 | 29 | 35 | 46 | 52 | 57 | 63 | 74 | 80 | 85 | 91 => Self::Monday,
2 | 8 | 13 | 19 | 30 | 36 | 41 | 47 | 58 | 64 | 69 | 75 | 86 | 92 | 97 => Self::Tuesday,
3 | 14 | 20 | 25 | 31 | 42 | 48 | 53 | 59 | 70 | 76 | 81 | 87 | 98 => Self::Wednesday,
4 | 9 | 15 | 26 | 32 | 37 | 43 | 54 | 60 | 65 | 71 | 82 | 88 | 93 | 99 => Self::Thursday,
6 | 12 | 17 | 23 | 34 | 40 | 45 | 51 | 62 | 68 | 73 | 79 | 90 | 96 => Self::Sunday,
_ => Self::Friday,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const ALL_DAYS: &[Weekday] = &[
Weekday::Sunday,
Weekday::Monday,
Weekday::Tuesday,
Weekday::Wednesday,
Weekday::Thursday,
Weekday::Friday,
Weekday::Saturday,
];
#[test]
fn t_year_start() {
for y in 2000..=2099 {
let c = time::Date::from_calendar_date(y, time::Month::January, 1)
.expect("Unable to create time::Date.");
assert_eq!(
Weekday::year_begins_on((y - 2000) as u8).as_ref(),
c.weekday().to_string(),
"Failed with year {}", y
);
}
}
#[test]
fn t_abbr() {
for d in ALL_DAYS {
assert_eq!(d.abbreviation(), &d.as_str()[..3]);
}
}
#[test]
fn t_from() {
for i in 1..=7_u8 {
let weekday = Weekday::from(i);
assert_eq!(weekday as u8, i);
assert_eq!(weekday.abbreviation().as_bytes(), weekday.abbreviation_bytes());
}
for i in 1..=7_u64 {
assert_eq!(u64::from(Weekday::from(i)), i);
}
assert_eq!(Weekday::from(0_u64), Weekday::Saturday);
let many: Vec<Weekday> = (1..=35_u32).into_iter()
.map(Weekday::from)
.collect();
let mut when = 0;
for days in many.as_slice().chunks_exact(7) {
when += 1;
assert_eq!(days, ALL_DAYS, "Round #{}", when);
}
}
#[test]
fn t_math() {
let days: Vec<Weekday> = std::iter::repeat(ALL_DAYS)
.take(6)
.flatten()
.copied()
.collect();
for idx in 0..7 {
for a in 0..36 {
let b = days[idx] + a;
assert_eq!(b, days[idx + a]);
assert_eq!(b - a, days[idx]);
let mut c = days[idx];
c += a;
assert_eq!(c, b);
c -= a;
assert_eq!(c, days[idx]);
}
}
}
#[test]
fn t_nth_in_month() {
for (weekday, dates) in [
(Weekday::Sunday, vec![1, 8, 15, 22, 29]),
(Weekday::Monday, vec![2, 9, 16, 23, 30]),
(Weekday::Tuesday, vec![3, 10, 17, 24, 31]),
(Weekday::Wednesday, vec![4, 11, 18, 25]),
(Weekday::Thursday, vec![5, 12, 19, 26]),
(Weekday::Friday, vec![6, 13, 20, 27]),
(Weekday::Saturday, vec![7, 14, 21, 28]),
] {
for (k, v) in dates.iter().copied().enumerate() {
let tmp = weekday.nth_in_month(2023, 10, k as u8 + 1);
assert_eq!(
tmp,
Some(v),
"Expected {} {weekday} to be {v}, not {tmp:?}.",
k + 1,
);
if k == 0 { assert_eq!(weekday.first_in_month(2023, 10), tmp); }
else if k + 1 == dates.len() {
assert_eq!(
weekday.last_in_month(2023, 10),
Some(v),
"Expected {weekday} to end on {v}, not {tmp:?}.",
);
}
}
assert_eq!(weekday.nth_in_month(2023, 10, dates.len() as u8 + 1), None);
}
}
#[test]
fn t_str() {
for &d in ALL_DAYS {
assert_eq!(Ok(d), Weekday::try_from(d.abbreviation()));
assert_eq!(Ok(d), Weekday::try_from(d.as_str()));
assert_eq!(Ok(d), Weekday::try_from(d.as_str().to_ascii_uppercase()));
}
assert!(Weekday::try_from("Hello").is_err());
}
}