diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/date.rs | 58 | ||||
| -rw-r--r-- | src/month.rs | 52 |
2 files changed, 110 insertions, 0 deletions
diff --git a/src/date.rs b/src/date.rs index 194fd02..43355ab 100644 --- a/src/date.rs +++ b/src/date.rs @@ -3,6 +3,8 @@ use crate::{Month, Year}; use core::cmp::Ordering; use core::fmt::Display; +use thiserror::Error; + #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub struct Date { year: Year, @@ -10,6 +12,26 @@ pub struct Date { day: u8, } +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Error)] +#[error("Tried to construct {given_day} {month}, but {month} only has {month_max_day} days")] +pub struct DayGreaterThanMaximumForMonthError { + month: Month, + given_day: u8, + month_max_day: u8, +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Error)] +#[error("Tried to construct a leap day in {0} which is not a leap year")] +pub struct LeapDayNotInLeapYearError(Year); + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Error)] +pub enum InvalidDateError { + #[error("{0}")] + DayTooBig(DayGreaterThanMaximumForMonthError), + #[error("{0}")] + NonLeapYear(LeapDayNotInLeapYearError), +} + impl Date { /// The earliest date which can be represented pub const MIN: Self = unsafe { Self::from_ymd_unchecked(Year::MIN, Month::January, 1) }; @@ -56,6 +78,42 @@ impl Date { self.year.is_leap_year() } + // TODO overflow handling + pub const fn add_years(self, years: i16) -> Result<Self, LeapDayNotInLeapYearError> { + let year = self.year + years; + + if self.day == 29 && self.month == Month::February && !year.is_leap_year() { + Err(LeapDayNotInLeapYearError(self.year)) + } else { + Ok(Self { + year, + month: self.month, + day: self.day, + }) + } + } + + // TODO overflow handling + pub const fn add_months(self, months: i8) -> Result<Self, DayGreaterThanMaximumForMonthError> { + let (month, years_to_add) = self.month.add_overflowing(months); + let year = self.year + years_to_add; + let max_days_for_month = month.days(year.is_leap_year()); + + if self.day > max_days_for_month { + Err(DayGreaterThanMaximumForMonthError { + month, + given_day: self.day, + month_max_day: max_days_for_month, + }) + } else { + Ok(Self { + year, + month, + day: self.day, + }) + } + } + // TODO handle BCE properly pub const fn days_after_common_era(self) -> isize { let year = self.year.wrapping_sub(1); diff --git a/src/month.rs b/src/month.rs index fa19324..c5407f7 100644 --- a/src/month.rs +++ b/src/month.rs @@ -181,6 +181,7 @@ impl Month { // TODO docs + // TODO handle ordinals greater than 365 pub const fn from_ordinal_common(ordinal: u16) -> Self { if ordinal < 31 { January @@ -314,6 +315,48 @@ impl Month { } } + 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. pub const fn last_day_ordinal_leap(self) -> u16 { match self { @@ -341,6 +384,15 @@ impl Month { self.last_day_ordinal_common() } } + + pub const fn add_overflowing(self, months: u8) -> (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 = Self::from_u8((zero_indexed_month as u8) + 1).unwrap(); + + (month, wraps) + } } impl From<Month> for u8 { |
