use std::fmt;
use std::ops::{BitOr, BitOrAssign};
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, Serialize)]
#[cfg_attr(any(), pyo3::prelude::pyclass(from_py_object, eq, hash, frozen))]
#[serde(transparent)]
pub struct DaysOfWeek(u8);
impl DaysOfWeek {
pub const NONE: Self = Self(0);
pub const SUN: Self = Self(1 << 0);
pub const MON: Self = Self(1 << 1);
pub const TUE: Self = Self(1 << 2);
pub const WED: Self = Self(1 << 3);
pub const THU: Self = Self(1 << 4);
pub const FRI: Self = Self(1 << 5);
pub const SAT: Self = Self(1 << 6);
pub const WEEKDAYS: Self =
Self(Self::MON.0 | Self::TUE.0 | Self::WED.0 | Self::THU.0 | Self::FRI.0);
pub const WEEKEND: Self = Self(Self::SUN.0 | Self::SAT.0);
pub const EVERY_DAY: Self = Self(Self::WEEKDAYS.0 | Self::WEEKEND.0);
pub const fn from_bits_truncate(bits: u8) -> Self {
Self(bits & Self::EVERY_DAY.0)
}
}
#[cfg_attr(any(), pyo3::pymethods)]
impl DaysOfWeek {
pub const fn bits(&self) -> u8 {
self.0
}
pub const fn contains(&self, other: Self) -> bool {
self.0 & other.0 == other.0
}
pub const fn is_empty(&self) -> bool {
self.0 == 0
}
}
impl DaysOfWeek {
const fn all_days() -> [(Self, &'static str); 7] {
[
(Self::SUN, "SUN"),
(Self::MON, "MON"),
(Self::TUE, "TUE"),
(Self::WED, "WED"),
(Self::THU, "THU"),
(Self::FRI, "FRI"),
(Self::SAT, "SAT"),
]
}
}
#[cfg(any())]
#[pyo3::pymethods]
impl DaysOfWeek {
#[classattr]
#[pyo3(name = "NONE")]
const PY_NONE: DaysOfWeek = DaysOfWeek::NONE;
#[classattr]
#[pyo3(name = "SUN")]
const PY_SUN: DaysOfWeek = DaysOfWeek::SUN;
#[classattr]
#[pyo3(name = "MON")]
const PY_MON: DaysOfWeek = DaysOfWeek::MON;
#[classattr]
#[pyo3(name = "TUE")]
const PY_TUE: DaysOfWeek = DaysOfWeek::TUE;
#[classattr]
#[pyo3(name = "WED")]
const PY_WED: DaysOfWeek = DaysOfWeek::WED;
#[classattr]
#[pyo3(name = "THU")]
const PY_THU: DaysOfWeek = DaysOfWeek::THU;
#[classattr]
#[pyo3(name = "FRI")]
const PY_FRI: DaysOfWeek = DaysOfWeek::FRI;
#[classattr]
#[pyo3(name = "SAT")]
const PY_SAT: DaysOfWeek = DaysOfWeek::SAT;
#[classattr]
#[pyo3(name = "WEEKDAYS")]
const PY_WEEKDAYS: DaysOfWeek = DaysOfWeek::WEEKDAYS;
#[classattr]
#[pyo3(name = "WEEKEND")]
const PY_WEEKEND: DaysOfWeek = DaysOfWeek::WEEKEND;
#[classattr]
#[pyo3(name = "EVERY_DAY")]
const PY_EVERY_DAY: DaysOfWeek = DaysOfWeek::EVERY_DAY;
#[staticmethod]
#[pyo3(name = "from_bits_truncate")]
fn py_from_bits_truncate(bits: u8) -> Self {
Self::from_bits_truncate(bits)
}
fn __or__(&self, other: &Self) -> Self {
*self | *other
}
fn __repr__(&self) -> String {
format!("{self:?}")
}
}
impl fmt::Debug for DaysOfWeek {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("DaysOfWeek(")?;
if self.is_empty() {
f.write_str("NONE")?;
} else {
let mut first = true;
for (day, name) in Self::all_days() {
if self.contains(day) {
if !first {
f.write_str(" | ")?;
}
f.write_str(name)?;
first = false;
}
}
}
f.write_str(")")
}
}
impl<'de> Deserialize<'de> for DaysOfWeek {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Ok(Self::from_bits_truncate(u8::deserialize(deserializer)?))
}
}
impl BitOr for DaysOfWeek {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
Self(self.0 | rhs.0)
}
}
impl BitOrAssign for DaysOfWeek {
fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn combine_and_contain() {
let midweek = DaysOfWeek::MON | DaysOfWeek::WED;
assert_eq!(midweek.bits(), 0b0000_1010);
assert!(midweek.contains(DaysOfWeek::MON));
assert!(!midweek.contains(DaysOfWeek::TUE));
assert!(!midweek.is_empty());
assert!(DaysOfWeek::NONE.is_empty());
let mut acc = DaysOfWeek::SUN;
acc |= DaysOfWeek::SAT;
assert_eq!(acc, DaysOfWeek::WEEKEND);
assert_eq!(DaysOfWeek::WEEKDAYS.bits(), 0b0011_1110);
assert_eq!(DaysOfWeek::EVERY_DAY.bits(), 0b0111_1111);
}
#[test]
fn from_device_ignores_high_bits() {
assert_eq!(
DaysOfWeek::from_bits_truncate(0b1000_1010),
DaysOfWeek::MON | DaysOfWeek::WED
);
}
#[test]
fn deserialize_cannot_smuggle_high_bits() {
let days: DaysOfWeek = serde_json::from_str("255").expect("deserialize");
assert_eq!(days, DaysOfWeek::EVERY_DAY);
}
#[test]
fn equal_values_hash_equally() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(DaysOfWeek::MON | DaysOfWeek::WED);
set.insert(DaysOfWeek::WED | DaysOfWeek::MON);
assert_eq!(set.len(), 1);
assert!(set.contains(&DaysOfWeek::from_bits_truncate(0b0000_1010)));
}
#[test]
fn debug_names_the_days() {
assert_eq!(
format!("{:?}", DaysOfWeek::MON | DaysOfWeek::WED),
"DaysOfWeek(MON | WED)"
);
assert_eq!(format!("{:?}", DaysOfWeek::NONE), "DaysOfWeek(NONE)");
}
}