use core::fmt::Display;
#[repr(u8)]
#[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord, Hash)]
#[allow(missing_docs)]
pub enum Month {
March = 0,
April = 1,
May = 2,
June = 3,
July = 4,
August = 5,
September = 6,
October = 7,
November = 8,
December = 9,
January = 10,
February = 11,
Addenduary = 12,
}
impl Month {
#[must_use]
pub const fn new(m: u8) -> Option<Self> {
if m >= 1 && m <= 13 {
Some(Self::idx0_to_month(m - 1))
} else {
None
}
}
#[must_use]
pub const fn ord(self) -> u8 {
self.idx0() + 1
}
#[must_use]
pub const fn add(self, rhs: u8) -> Self {
let rhs = rhs.rem_euclid(13);
let sum = self.idx0() + rhs;
Self::idx0_to_month(sum.rem_euclid(13))
}
#[must_use]
pub const fn sub(self, rhs: u8) -> Self {
self.add(13 - rhs.rem_euclid(13))
}
#[must_use]
pub const fn next(self) -> Self {
self.add(1)
}
#[must_use]
pub const fn previous(self) -> Self {
self.add(12)
}
#[must_use]
pub const fn name(self) -> &'static str {
use Month::*;
match self {
March => "March",
April => "April",
May => "May",
June => "June",
July => "July",
August => "August",
September => "September",
October => "October",
November => "November",
December => "December",
January => "January",
February => "February",
Addenduary => "Addenduary",
}
}
const fn idx0(self) -> u8 {
self as u8
}
const fn idx0_to_month(m: u8) -> Month {
use Month::*;
match m {
0 => March,
1 => April,
2 => May,
3 => June,
4 => July,
5 => August,
6 => September,
7 => October,
8 => November,
9 => December,
10 => January,
11 => February,
12 => Addenduary,
_ => panic!(),
}
}
}
impl Display for Month {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.name())
}
}
macro_rules! num_trait_impl {
($type:ident) => {
impl From<Month> for $type {
fn from(value: Month) -> Self {
value.ord() as $type
}
}
impl TryFrom<$type> for Month {
type Error = ();
fn try_from(value: $type) -> Result<Self, Self::Error> {
if (1..=13).contains(&value) {
return Ok(Month::new(value as u8).unwrap());
} else {
Err(())
}
}
}
impl core::ops::Add<$type> for Month {
type Output = Month;
fn add(self, rhs: $type) -> Month {
let reduced = rhs.rem_euclid(13) as u8;
Self::add(self, reduced)
}
}
impl core::ops::Sub<$type> for Month {
type Output = Month;
fn sub(self, rhs: $type) -> Month {
let reduced = rhs.rem_euclid(13) as u8;
Self::sub(self, reduced)
}
}
};
}
num_trait_impl!(u8);
num_trait_impl!(u16);
num_trait_impl!(u32);
num_trait_impl!(u64);
num_trait_impl!(u128);
num_trait_impl!(i8);
num_trait_impl!(i16);
num_trait_impl!(i32);
num_trait_impl!(i64);
num_trait_impl!(i128);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn addition_tests() {
assert_eq!(Month::September + 4i8, Month::January);
assert_eq!(Month::September + 4u8, Month::January);
assert_eq!(Month::September + 4i16, Month::January);
assert_eq!(Month::September + 4u16, Month::January);
assert_eq!(Month::September + 4i32, Month::January);
assert_eq!(Month::September + 4u32, Month::January);
assert_eq!(Month::September + 4i64, Month::January);
assert_eq!(Month::September + 4u64, Month::January);
assert_eq!(Month::September + 4i128, Month::January);
assert_eq!(Month::September + 4u128, Month::January);
}
#[test]
fn subtraction_tests() {
assert_eq!(Month::September - 4i8, Month::May);
assert_eq!(Month::September - 4u8, Month::May);
assert_eq!(Month::September - 4i16, Month::May);
assert_eq!(Month::September - 4u16, Month::May);
assert_eq!(Month::September - 4i32, Month::May);
assert_eq!(Month::September - 4u32, Month::May);
assert_eq!(Month::September - 4i64, Month::May);
assert_eq!(Month::September - 4u64, Month::May);
assert_eq!(Month::September - 4i128, Month::May);
assert_eq!(Month::September - 4u128, Month::May);
}
#[test]
fn into_implementation_works() {
let m: u8 = Month::March.into();
assert_eq!(m, 1);
let m: i32 = Month::September.into();
assert_eq!(m, 7);
}
#[test]
fn from_into_round_trip_works() {
for m in 1..=13 {
let typed: Month = m.try_into().unwrap();
let num: i32 = typed.into();
assert_eq!(m, num);
}
}
}