use crate::{Date, InvalidDayOfMonth, Month, Year};
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct YearMonth {
year: Year,
month: Month,
}
impl YearMonth {
pub fn new(year: impl Into<Year>, month: Month) -> Self {
let year = year.into();
Self { year, month }
}
pub const fn new_const(year: Year, month: Month) -> Self {
Self { year, month }
}
pub const fn year(self) -> Year {
self.year
}
pub const fn month(self) -> Month {
self.month
}
pub const fn total_days(self) -> u8 {
crate::raw::days_in_month(self.month, self.year.has_leap_day())
}
pub const fn day_of_year(self) -> u16 {
crate::raw::start_day_of_year(self.month, self.year.has_leap_day())
}
pub const fn next(self) -> Self {
if let Month::December = self.month {
Self::new_const(self.year.next(), Month::January)
} else {
Self::new_const(self.year, self.month.wrapping_next())
}
}
pub const fn prev(self) -> Self {
if let Month::January = self.month {
Self::new_const(self.year.prev(), Month::December)
} else {
Self::new_const(self.year, self.month.wrapping_prev())
}
}
pub const fn add_years(self, years: i16) -> Self {
let year = Year::new(self.year.to_number() + years);
year.with_month(self.month())
}
pub const fn sub_years(self, years: i16) -> Self {
let year = Year::new(self.year.to_number() - years);
year.with_month(self.month())
}
pub const fn add_months(self, months: i32) -> Self {
let months = (self.month().to_number() - 1) as i32 + months;
let mut year = self.year().to_number() + (months / 12) as i16;
let month = Month::January.wrapping_add((months % 12) as i8);
if months % 12 < 0 {
year -= 1;
}
Year::new(year).with_month(month)
}
pub const fn sub_months(self, months: i32) -> Self {
self.add_months(-months)
}
pub const fn with_day(self, day: u8) -> Result<Date, InvalidDayOfMonth> {
if let Err(e) = InvalidDayOfMonth::check(self.year, self.month, day) {
return Err(e);
}
unsafe { Ok(Date::new_unchecked(self.year, self.month, day)) }
}
pub const unsafe fn with_day_unchecked(self, day: u8) -> Date {
Date::new_unchecked(self.year, self.month, day)
}
pub const fn first_day(self) -> Date {
Date {
year: self.year,
month: self.month,
day: 1,
}
}
pub const fn last_day(self) -> Date {
Date {
year: self.year,
month: self.month,
day: self.total_days(),
}
}
}
impl core::fmt::Display for YearMonth {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "{:04}-{:02}", self.year.to_number(), self.month().to_number())
}
}
impl core::fmt::Debug for YearMonth {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "YearMonth({})", self)
}
}
#[cfg(test)]
mod test {
use crate::*;
use assert2::{assert, let_assert};
#[test]
fn add_months() {
for i in -200..=200 {
assert!(Year::new(2000).with_month(January).add_months(i * 12 + 1) == Year::new(2000 + i as i16).with_month(February));
assert!(Year::new(2000).with_month(January).add_months(i * 12 + 2) == Year::new(2000 + i as i16).with_month(March));
assert!(Year::new(2000).with_month(January).add_months(i * 12 + 3) == Year::new(2000 + i as i16).with_month(April));
assert!(Year::new(2000).with_month(January).add_months(i * 12 + 4) == Year::new(2000 + i as i16).with_month(May));
assert!(Year::new(2000).with_month(January).add_months(i * 12 + 5) == Year::new(2000 + i as i16).with_month(June));
assert!(Year::new(2000).with_month(January).add_months(i * 12 + 6) == Year::new(2000 + i as i16).with_month(July));
assert!(Year::new(2000).with_month(January).add_months(i * 12 + 7) == Year::new(2000 + i as i16).with_month(August));
assert!(Year::new(2000).with_month(January).add_months(i * 12 + 8) == Year::new(2000 + i as i16).with_month(September));
assert!(Year::new(2000).with_month(January).add_months(i * 12 + 9) == Year::new(2000 + i as i16).with_month(October));
assert!(Year::new(2000).with_month(January).add_months(i * 12 + 10) == Year::new(2000 + i as i16).with_month(November));
assert!(Year::new(2000).with_month(January).add_months(i * 12 + 11) == Year::new(2000 + i as i16).with_month(December));
assert!(Year::new(2000).with_month(January).add_months(i * 12 + 12) == Year::new(2001 + i as i16).with_month(January));
assert!(Year::new(2000).with_month(January).add_months(i * 12 + -1) == Year::new(1999 + i as i16).with_month(December));
assert!(Year::new(2000).with_month(January).add_months(i * 12 + -2) == Year::new(1999 + i as i16).with_month(November));
assert!(Year::new(2000).with_month(January).add_months(i * 12 + -3) == Year::new(1999 + i as i16).with_month(October));
assert!(Year::new(2000).with_month(January).add_months(i * 12 + -4) == Year::new(1999 + i as i16).with_month(September));
assert!(Year::new(2000).with_month(January).add_months(i * 12 + -5) == Year::new(1999 + i as i16).with_month(August));
assert!(Year::new(2000).with_month(January).add_months(i * 12 + -6) == Year::new(1999 + i as i16).with_month(July));
assert!(Year::new(2000).with_month(January).add_months(i * 12 + -7) == Year::new(1999 + i as i16).with_month(June));
assert!(Year::new(2000).with_month(January).add_months(i * 12 + -8) == Year::new(1999 + i as i16).with_month(May));
assert!(Year::new(2000).with_month(January).add_months(i * 12 + -9) == Year::new(1999 + i as i16).with_month(April));
assert!(Year::new(2000).with_month(January).add_months(i * 12 + -10) == Year::new(1999 + i as i16).with_month(March));
assert!(Year::new(2000).with_month(January).add_months(i * 12 + -11) == Year::new(1999 + i as i16).with_month(February));
assert!(Year::new(2000).with_month(January).add_months(i * 12 + -12) == Year::new(1999 + i as i16).with_month(January));
}
}
#[test]
fn total_days() {
assert!(Year::new(2020).with_month(January).total_days() == 31);
assert!(Year::new(2020).with_month(February).total_days() == 29);
assert!(Year::new(2020).with_month(March).total_days() == 31);
assert!(Year::new(2020).with_month(April).total_days() == 30);
assert!(Year::new(2020).with_month(May).total_days() == 31);
assert!(Year::new(2020).with_month(June).total_days() == 30);
assert!(Year::new(2020).with_month(July).total_days() == 31);
assert!(Year::new(2020).with_month(August).total_days() == 31);
assert!(Year::new(2020).with_month(September).total_days() == 30);
assert!(Year::new(2020).with_month(October).total_days() == 31);
assert!(Year::new(2020).with_month(November).total_days() == 30);
assert!(Year::new(2020).with_month(December).total_days() == 31);
assert!(Year::new(2021).with_month(January).total_days() == 31);
assert!(Year::new(2021).with_month(February).total_days() == 28);
assert!(Year::new(2021).with_month(March).total_days() == 31);
assert!(Year::new(2021).with_month(April).total_days() == 30);
assert!(Year::new(2021).with_month(May).total_days() == 31);
assert!(Year::new(2021).with_month(June).total_days() == 30);
assert!(Year::new(2021).with_month(July).total_days() == 31);
assert!(Year::new(2021).with_month(August).total_days() == 31);
assert!(Year::new(2021).with_month(September).total_days() == 30);
assert!(Year::new(2021).with_month(October).total_days() == 31);
assert!(Year::new(2021).with_month(November).total_days() == 30);
assert!(Year::new(2021).with_month(December).total_days() == 31);
}
#[test]
fn start_day_of_year() {
for year in -400..=400 {
let mut start_day = 1;
for month in &Year::new(year).months() {
assert!(month.day_of_year() == start_day);
start_day += u16::from(month.total_days());
}
}
}
#[test]
#[cfg(feature = "std")]
fn format() {
assert!(format!("{}", Year::new(2020).with_month(January)) == "2020-01");
assert!(format!("{:?}", Year::new(2020).with_month(January)) == "YearMonth(2020-01)");
}
#[test]
fn serde() {
let_assert!(Ok(serialized) = serde_yaml::to_string(&YearMonth::new(2020, Month::January)));
assert!(serialized == "year: 2020\nmonth: 1\n");
let_assert!(Ok(parsed) = serde_yaml::from_str::<YearMonth>("year: 2020\nmonth: 1\n"));
assert!(parsed.year == 2020);
assert!(parsed.month == Month::January);
}
}