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