use derive_more::Display; use thiserror::Error; use self::Month::*; use core::str::FromStr; /// Months of the year #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display, PartialOrd, Ord)] #[repr(u8)] pub enum Month { January = 1, February = 2, March = 3, April = 4, May = 5, June = 6, July = 7, August = 8, September = 9, October = 10, November = 11, December = 12, } impl Month { /// Get the month based on the number. /// Returns `None` if the input is 0 or greater than 12. /// /// # Example /// /// ``` /// use botic::Month; /// /// assert_eq!(Some(Month::January), Month::from_u8(1)); /// assert_eq!(None, Month::from_u8(0)); /// assert_eq!(None, Month::from_u8(13)); #[must_use] pub const fn from_u8(num: u8) -> Option { match num { 1 => Some(January), 2 => Some(February), 3 => Some(March), 4 => Some(April), 5 => Some(May), 6 => Some(June), 7 => Some(July), 8 => Some(August), 9 => Some(September), 10 => Some(October), 11 => Some(November), 12 => Some(December), _ => None, } } /// Get the month from the given string, /// which is assumed to be the month's abbreviation. /// Returns `None` if the string is not a valid abbrevation of a month /// /// # Example /// /// ``` /// use botic::Month; /// /// assert_eq!(Some(Month::January), Month::from_abbreviation("Jan")); /// assert_eq!(None, Month::from_abbreviation("Janu")); /// ``` #[must_use] pub fn from_abbreviation(abbreviation: &str) -> Option { match abbreviation { "Jan" => Some(January), "Feb" => Some(February), "Mar" => Some(March), "Apr" => Some(April), "May" => Some(May), "Jun" => Some(June), "Jul" => Some(July), "Aug" => Some(August), "Sep" => Some(September), "Oct" => Some(October), "Nov" => Some(November), "Dec" => Some(December), _ => None, } } /// Get the month from the given string, /// which is assumed to be the month's name. /// Returns `None` if the string is not a valid month /// /// # Example /// /// ``` /// use botic::Month; /// /// assert_eq!(Some(Month::January), Month::from_name("January")); /// assert_eq!(None, Month::from_name("Janu")); /// ``` #[must_use] pub fn from_name(name: &str) -> Option { match name { "January" => Some(January), "February" => Some(February), "March" => Some(March), "April" => Some(April), "May" => Some(May), "June" => Some(June), "July" => Some(July), "August" => Some(August), "September" => Some(September), "October" => Some(October), "November" => Some(November), "December" => Some(December), _ => None, } } /// Get the number of the month /// /// # Example /// /// ``` /// use botic::Month; /// /// assert_eq!(1, Month::January.number()); /// ``` #[must_use] pub const fn number(self) -> u8 { self as u8 } /// Get the name of the month /// /// # Example /// /// ``` /// use botic::Month; /// /// assert_eq!("January", Month::January.name()); /// ``` #[must_use] pub const fn name(self) -> &'static str { match self { January => "January", February => "February", March => "March", April => "April", May => "May", June => "June", July => "July", August => "August", September => "September", October => "October", November => "November", December => "December", } } /// Get the abbreviated name of the month. This is always three letters /// /// # Example /// /// ``` /// use botic::Month; /// /// assert_eq!("Jan", Month::January.abbreviation()); /// ``` #[must_use] pub const fn abbreviation(self) -> &'static str { match self { January => "Jan", February => "Feb", March => "Mar", April => "Apr", May => "May", June => "Jun", July => "Jul", August => "Aug", September => "Sep", October => "Oct", November => "Nov", December => "Dec", } } // TODO docs // TODO handle ordinals greater than 365 #[must_use] pub const fn from_ordinal_common(ordinal: u16) -> Self { if ordinal < 31 { January } else if ordinal < 59 { February } else if ordinal < 90 { March } else if ordinal < 120 { April } else if ordinal < 151 { May } else if ordinal < 181 { June } else if ordinal < 212 { July } else if ordinal < 243 { August } else if ordinal < 273 { September } else if ordinal < 304 { October } else if ordinal < 334 { November } else { December } } #[must_use] pub const fn from_ordinal_leap(ordinal: u16) -> Self { if ordinal < 31 { January } else if ordinal < 60 { February } else if ordinal < 91 { March } else if ordinal < 121 { April } else if ordinal < 152 { May } else if ordinal < 182 { June } else if ordinal < 213 { July } else if ordinal < 244 { August } else if ordinal < 274 { September } else if ordinal < 305 { October } else if ordinal < 335 { November } else { December } } #[must_use] pub const fn from_ordinal(ordinal: u16, leap_year: bool) -> Self { if leap_year { Self::from_ordinal_leap(ordinal) } else { Self::from_ordinal_common(ordinal) } } /// Get the next month. /// /// ```rust /// use botic::Month; /// /// assert_eq!(Month::January.next(), Month::February); /// ``` #[must_use] pub const fn next(self) -> Self { match self { January => February, February => March, March => April, April => May, May => June, June => July, July => August, August => September, September => October, October => November, November => December, December => January, } } /// Get the previous month. /// /// ```rust /// use botic::Month; /// /// assert_eq!(Month::January.previous(), Month::December); /// ``` #[must_use] pub const fn previous(self) -> Self { match self { January => December, February => January, March => February, April => March, May => April, June => May, July => June, August => July, September => August, October => September, November => October, December => November, } } // TODO examples /// Returns the number of days up to the end of the month in a year. /// This doesn't account for leap day #[must_use] pub const fn last_day_ordinal_common(self) -> u16 { match self { January => 31, February => 59, March => 90, April => 120, May => 151, June => 181, July => 212, August => 243, September => 273, October => 304, November => 334, December => 365, } } pub const fn days_common_year(self) -> u8 { match self { January => 31, February => 28, March => 31, April => 30, May => 31, June => 30, July => 31, August => 31, September => 30, October => 31, November => 30, December => 31, } } pub const fn days_leap_year(self) -> u8 { match self { January => 31, February => 29, March => 31, April => 30, May => 31, June => 30, July => 31, August => 31, September => 30, October => 31, November => 30, December => 31, } } pub const fn days(self, leap_year: bool) -> u8 { if leap_year { self.days_leap_year() } else { self.days_common_year() } } /// Returns the number of days up to the end of the month in a leap year. #[must_use] pub const fn last_day_ordinal_leap(self) -> u16 { match self { January => 31, February => 60, March => 91, April => 121, May => 152, June => 182, July => 213, August => 244, September => 274, October => 305, November => 335, December => 366, } } /// Returns the number of days up to the end of the month. /// Whether or not it's a leap year must be indicated #[must_use] pub const fn last_day_ordinal(self, leap_year: bool) -> u16 { if leap_year { self.last_day_ordinal_leap() } else { self.last_day_ordinal_common() } } pub const fn add_overflowing(self, months: i8) -> (Self, u8) { let zero_indexed_num = ((self as u16) - 1) + months as u16; let wraps = (zero_indexed_num as u8) / 12; let zero_indexed_month = zero_indexed_num % 12; let month = match Self::from_u8((zero_indexed_month as u8) + 1) { Some(month) => month, None => unsafe { std::hint::unreachable_unchecked() }, }; (month, wraps) } } impl From for u8 { fn from(month: Month) -> Self { month as u8 } } #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Error)] #[error("Failed to parse the month")] // TODO Consider trying to figure out what month the user meant to use pub struct ParseMonthError; // TODO optimize to look like this: https://github.com/chronotope/chrono/blob/main/src/format/scan.rs#L102 // TODO make case-insensitive impl FromStr for Month { type Err = ParseMonthError; fn from_str(s: &str) -> Result { if let Ok(num) = u8::from_str(s) { if let Some(month) = Month::from_u8(num) { Ok(month) } else { Err(ParseMonthError) } } else if let Some(month) = Month::from_abbreviation(s) { Ok(month) } else if let Some(month) = Month::from_name(s) { Ok(month) } else { Err(ParseMonthError) } } }