use crate::{Date, InvalidDayOfYear, Month, YearMonth};
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(transparent))]
pub struct Year {
year: i16,
}
impl Year {
pub const fn new(year: i16) -> Self {
Self { year }
}
pub const fn to_number(self) -> i16 {
self.year
}
pub const fn has_leap_day(self) -> bool {
self.year % 4 == 0 && (self.year % 100 != 0 || self.year % 400 == 0)
}
pub const fn total_days(self) -> u16 {
if self.has_leap_day() {
366
} else {
365
}
}
pub const fn next(self) -> Self {
Self { year: self.year + 1 }
}
pub const fn prev(self) -> Self {
Self { year: self.year - 1 }
}
pub const fn with_month(self, month: Month) -> YearMonth {
YearMonth::new_const(self, month)
}
pub const fn with_day_of_year(self, day_of_year: u16) -> Result<Date, InvalidDayOfYear> {
let (month, day_of_month) = match crate::raw::month_and_day_from_day_of_year(day_of_year, self.has_leap_day()) {
Ok(x) => x,
Err(()) => return Err(InvalidDayOfYear { year: self, day_of_year }),
};
Ok(unsafe { self.with_month(month).with_day_unchecked(day_of_month) })
}
pub const fn first_month(self) -> YearMonth {
self.with_month(Month::January)
}
pub const fn last_month(self) -> YearMonth {
self.with_month(Month::December)
}
pub const fn months(self) -> [YearMonth; 12] {
[
self.with_month(Month::January),
self.with_month(Month::February),
self.with_month(Month::March),
self.with_month(Month::April),
self.with_month(Month::May),
self.with_month(Month::June),
self.with_month(Month::July),
self.with_month(Month::August),
self.with_month(Month::September),
self.with_month(Month::October),
self.with_month(Month::November),
self.with_month(Month::December),
]
}
pub const fn first_day(self) -> Date {
Date {
year: self,
month: Month::January,
day: 1,
}
}
pub const fn last_day(self) -> Date {
Date {
year: self,
month: Month::December,
day: 31,
}
}
}
impl From<i16> for Year {
fn from(other: i16) -> Self {
Self::new(other)
}
}
impl From<Year> for i16 {
fn from(other: Year) -> i16 {
other.to_number()
}
}
impl PartialEq<i16> for Year {
fn eq(&self, other: &i16) -> bool {
self.to_number() == *other
}
}
impl PartialOrd<i16> for Year {
fn partial_cmp(&self, other: &i16) -> Option<core::cmp::Ordering> {
Some(self.to_number().cmp(other))
}
}
impl core::ops::Add<i16> for Year {
type Output = Self;
fn add(self, other: i16) -> Self {
Self::new(self.to_number() + other)
}
}
impl core::ops::Sub<i16> for Year {
type Output = Self;
fn sub(self, other: i16) -> Self {
Self::new(self.to_number() - other)
}
}
impl core::ops::AddAssign<i16> for Year {
fn add_assign(&mut self, other: i16) {
self.year += other
}
}
impl core::ops::SubAssign<i16> for Year {
fn sub_assign(&mut self, other: i16) {
self.year -= other
}
}
impl core::fmt::Display for Year {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "{:04}", self.year)
}
}
impl core::fmt::Debug for Year {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "Year({:04})", self.year)
}
}
#[cfg(test)]
mod test {
use super::*;
use assert2::{assert, let_assert};
#[test]
fn has_leap_day() {
assert!(Year::new(2020).has_leap_day() == true);
assert!(Year::new(2021).has_leap_day() == false);
assert!(Year::new(1900).has_leap_day() == false);
assert!(Year::new(2000).has_leap_day() == true);
}
#[test]
fn with_day_of_year() {
let mut date = Date::new(2020, 1, 1).unwrap();
for i in 1..=366 {
assert!(Year::new(2020).with_day_of_year(i).unwrap() == date);
date = date.next();
}
let mut date = Date::new(2021, 1, 1).unwrap();
for i in 1..=365 {
assert!(Year::new(2021).with_day_of_year(i).unwrap() == date);
date = date.next();
}
assert!(let Err(_) = Year::new(2020).with_day_of_year(0));
assert!(let Err(_) = Year::new(2021).with_day_of_year(0));
assert!(let Err(_) = Year::new(2020).with_day_of_year(367));
assert!(let Err(_) = Year::new(2021).with_day_of_year(368));
}
#[test]
fn months() {
let year = Year::new(2020);
let months = year.months();
assert!(months[0] == year.with_month(Month::January));
assert!(months[1] == year.with_month(Month::February));
assert!(months[2] == year.with_month(Month::March));
assert!(months[3] == year.with_month(Month::April));
assert!(months[4] == year.with_month(Month::May));
assert!(months[5] == year.with_month(Month::June));
assert!(months[6] == year.with_month(Month::July));
assert!(months[7] == year.with_month(Month::August));
assert!(months[8] == year.with_month(Month::September));
assert!(months[9] == year.with_month(Month::October));
assert!(months[10] == year.with_month(Month::November));
assert!(months[11] == year.with_month(Month::December));
}
#[test]
#[cfg(feature = "std")]
fn format_year() {
assert!(format!("{}", Year::new(2020)) == "2020");
assert!(format!("{:?}", Year::new(2020)) == "Year(2020)");
}
#[test]
fn serde() {
#[derive(Debug, serde::Deserialize, serde::Serialize)]
struct Container {
year: Year,
}
let_assert!(Ok(serialized) = serde_yaml::to_string(&Container {
year: Year::new(2020),
}));
assert!(serialized == "year: 2020\n");
let_assert!(Ok(parsed) = serde_yaml::from_str::<Container>("year: 2020"));
assert!(parsed.year == Year::new(2020));
}
}