summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/date.rs58
-rw-r--r--src/month.rs52
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 {