diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/date.rs | 47 | ||||
| -rw-r--r-- | src/month.rs | 115 | ||||
| -rw-r--r-- | src/year.rs | 34 |
3 files changed, 180 insertions, 16 deletions
diff --git a/src/date.rs b/src/date.rs index 49a7642..194fd02 100644 --- a/src/date.rs +++ b/src/date.rs @@ -12,12 +12,10 @@ pub struct Date { impl Date { /// The earliest date which can be represented - pub const MIN: Self = - unsafe { Self::from_calendar_date_unchecked(Year::MIN, Month::January, 1) }; + pub const MIN: Self = unsafe { Self::from_ymd_unchecked(Year::MIN, Month::January, 1) }; /// The latest date which can be represented - pub const MAX: Self = - unsafe { Self::from_calendar_date_unchecked(Year::MAX, Month::December, 31) }; + pub const MAX: Self = unsafe { Self::from_ymd_unchecked(Year::MAX, Month::December, 31) }; // TODO validated from_calendar_date @@ -26,17 +24,17 @@ impl Date { /// # Example /// /// ``` - /// use botic::Date; + /// use botic::{Date, Month, Year}; /// /// let y2k = unsafe { - /// Date::from_calendar_date_unchecked(Year::from(2000), Month::January, 1) + /// Date::from_ymd_unchecked(Year::from(2000), Month::January, 1) /// }; /// ``` /// /// # Safety /// /// This function results in undefined behavior if the given date is not a real date - pub const unsafe fn from_calendar_date_unchecked(year: Year, month: Month, day: u8) -> Self { + pub const unsafe fn from_ymd_unchecked(year: Year, month: Month, day: u8) -> Self { Self { year, month, day } } @@ -53,6 +51,41 @@ impl Date { pub const fn day(self) -> u8 { self.day } + + pub const fn is_leap_year(self) -> bool { + self.year.is_leap_year() + } + + // TODO handle BCE properly + pub const fn days_after_common_era(self) -> isize { + let year = self.year.wrapping_sub(1); + let leap_years = (year.as_i16() / 4 - year.as_i16() / 100 + year.as_i16() / 400) as isize; + let month_last_day_ordinal = self.month.last_day_ordinal(self.is_leap_year()) as isize; + + year.as_i16() as isize * 365 + leap_years + month_last_day_ordinal + self.day as isize - 1 + } + + // TODO test + pub const fn from_days_after_common_era(days: isize) -> Self { + let era = days / 146097; // an era is a period of 400 year + let day_of_era = days - (era * 146097); + let year_of_era = day_of_era / 365; + let year = year_of_era + (era * 400); + let ordinal = day_of_era - (365 * year + year / 4 - year / 100); + // TODO look at as's + let year = Year::from_i16(year as i16); + let month = Month::from_ordinal(ordinal as u16, year.is_leap_year()); + let day = ordinal as u16 - month.previous().last_day_ordinal(year.is_leap_year()); + let day = day as u8; + + unsafe { Self::from_ymd_unchecked(year, month, day) } + } + + #[must_use] + pub const fn add_days(self, days: isize) -> Self { + let total_days_since_ce = self.days_after_common_era() + days; + Self::from_days_after_common_era(total_days_since_ce) + } } impl PartialOrd for Date { diff --git a/src/month.rs b/src/month.rs index 4d988b8..fa19324 100644 --- a/src/month.rs +++ b/src/month.rs @@ -179,6 +179,72 @@ impl Month { } } + // TODO docs + + pub const fn from_ordinal_common(ordinal: u16) -> Self { + if ordinal < 31 { + January + } else if ordinal < 59 { + February + } else if ordinal < 90 { + March + } else if ordinal < 120 { + April + } else if ordinal < 151 { + May + } else if ordinal < 181 { + June + } else if ordinal < 212 { + July + } else if ordinal < 243 { + August + } else if ordinal < 273 { + September + } else if ordinal < 304 { + October + } else if ordinal < 334 { + November + } else { + December + } + } + + pub const fn from_ordinal_leap(ordinal: u16) -> Self { + if ordinal < 31 { + January + } else if ordinal < 60 { + February + } else if ordinal < 91 { + March + } else if ordinal < 121 { + April + } else if ordinal < 152 { + May + } else if ordinal < 182 { + June + } else if ordinal < 213 { + July + } else if ordinal < 244 { + August + } else if ordinal < 274 { + September + } else if ordinal < 305 { + October + } else if ordinal < 335 { + November + } else { + December + } + } + + pub const fn from_ordinal(ordinal: u16, leap_year: bool) -> Self { + if leap_year { + Self::from_ordinal_leap(ordinal) + } else { + Self::from_ordinal_common(ordinal) + } + } + /// Get the next month. /// /// ```rust @@ -226,6 +292,55 @@ impl Month { December => November, } } + + // TODO examples + + /// Returns the number of days up to the end of the month in a year. + /// This doesn't account for leap day + pub const fn last_day_ordinal_common(self) -> u16 { + match self { + January => 31, + February => 59, + March => 90, + April => 120, + May => 151, + June => 181, + July => 212, + August => 243, + September => 273, + October => 304, + November => 334, + December => 365, + } + } + + /// 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 { + January => 31, + February => 60, + March => 91, + April => 121, + May => 152, + June => 182, + July => 213, + August => 244, + September => 274, + October => 305, + November => 335, + December => 366, + } + } + + /// Returns the number of days up to the end of the month. + /// Whether or not it's a leap year must be indicated + pub const fn last_day_ordinal(self, leap_year: bool) -> u16 { + if leap_year { + self.last_day_ordinal_leap() + } else { + self.last_day_ordinal_common() + } + } } impl From<Month> for u8 { diff --git a/src/year.rs b/src/year.rs index 76a42fe..284821c 100644 --- a/src/year.rs +++ b/src/year.rs @@ -13,17 +13,17 @@ impl Year { /// The earliest year that can be represented pub const MIN: Self = Self(i16::MIN); - /// An equivalent of `Year::from(i32)`, which can be run at compile-time + /// An equivalent of `Year::from(i16)`, which can be run at compile-time /// /// # Example /// /// ``` /// use botic::Year; /// - /// const YEAR: Year = Year::from_i32(2021); - /// assert_eq!(2021, YEAR.as_i32()); + /// const YEAR: Year = Year::from_i16(2021); + /// assert_eq!(2021, YEAR.as_i16()); /// ``` - pub const fn from_i32(i: i16) -> Self { + pub const fn from_i16(i: i16) -> Self { Self(i) } @@ -34,11 +34,11 @@ impl Year { /// ``` /// use botic::Year; /// - /// const YEAR: Year = Year::from_i32(2021); - /// const YEAR_INT: i32 = YEAR.as_i32(); + /// const YEAR: Year = Year::from_i16(2021); + /// const YEAR_INT: i16 = YEAR.as_i16(); /// assert_eq!(2021, YEAR_INT); /// ``` - pub const fn as_i32(self) -> i16 { + pub const fn as_i16(self) -> i16 { self.0 } @@ -50,7 +50,7 @@ impl Year { /// ``` /// use botic::Year; /// - /// assert_eq!(Some(Year::from(2022)), Year::from_i32(2021).checked_add(1)); + /// assert_eq!(Some(Year::from(2022)), Year::from_i16(2021).checked_add(1)); /// assert_eq!(None, Year::MAX.checked_add(1)); /// ``` pub const fn checked_add(self, rhs: i16) -> Option<Year> { @@ -116,7 +116,7 @@ impl Year { /// ``` /// use botic::Year; /// - /// assert_eq!(Some(Year::from(2020)), Year::from_i32(2021).checked_sub(1)); + /// assert_eq!(Some(Year::from(2020)), Year::from_i16(2021).checked_sub(1)); /// assert_eq!(None, Year::MIN.checked_sub(1)); /// ``` pub const fn checked_sub(self, rhs: i16) -> Option<Year> { @@ -173,6 +173,22 @@ impl Year { pub const fn wrapping_sub(self, rhs: i16) -> Year { Year(self.0.wrapping_sub(rhs)) } + + /// Checks if the year is a leap year + /// + /// # Example + /// + /// ``` + /// use botic::Year; + /// + /// assert!(!Year::from(2022).is_leap_year()); + /// assert!(Year::from(2020).is_leap_year()); + /// assert!(Year::from(2000).is_leap_year()); + /// assert!(!Year::from(2100).is_leap_year()); + /// ``` + pub const fn is_leap_year(self) -> bool { + (self.0 % 4 == 0) && ((self.0 % 100 != 0) || (self.0 % 400 == 0)) + } } impl From<i16> for Year { |
