use std::fmt;
use std::str::FromStr;
use crate::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Month {
year: u16,
month: u8,
}
impl Month {
pub fn new(year: u16, month: u8) -> Result<Self, Error> {
if year > 9999 || !(1..=12).contains(&month) {
return Err(Error::invalid_argument(format!(
"{year}-{month} is not a month: the year must be 0 to 9999 and the month 1 to 12"
)));
}
Ok(Month { year, month })
}
pub fn year(self) -> u16 {
self.year
}
pub fn month(self) -> u8 {
self.month
}
}
impl fmt::Display for Month {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:04}-{:02}", self.year, self.month)
}
}
impl FromStr for Month {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
let invalid = || Error::invalid_argument(format!("{s:?} is not a month: use YYYY-MM"));
let (year, month) = s.split_once('-').ok_or_else(invalid)?;
let digits = |p: &str, n: usize| p.len() == n && p.bytes().all(|b| b.is_ascii_digit());
if !digits(year, 4) || !digits(month, 2) {
return Err(invalid());
}
let year = year.parse().map_err(|_| invalid())?;
let month = month.parse().map_err(|_| invalid())?;
Month::new(year, month)
}
}
impl TryFrom<&str> for Month {
type Error = Error;
fn try_from(s: &str) -> Result<Self, Error> {
s.parse()
}
}