//! This module contains types and implementations for the ISO calendar. //! //! ```rust //! use icu::calendar::{Date, DateTime}; //! //! // `Date` type //! let date_iso = Date::try_new_iso_date(1970, 1, 2) //! .expect("Failed to initialize ISO Date instance."); //! //! // `DateTime` type //! let datetime_iso = DateTime::try_new_iso_datetime(1970, 1, 2, 13, 1, 0) //! .expect("Failed to initialize ISO DateTime instance."); //! //! // `Date` checks //! assert_eq!(date_iso.year().number, 1970); //! assert_eq!(date_iso.month().ordinal, 1); //! assert_eq!(date_iso.day_of_month().0, 2); //! //! // `DateTime` type //! assert_eq!(datetime_iso.date.year().number, 1970); //! assert_eq!(datetime_iso.date.month().ordinal, 1); //! assert_eq!(datetime_iso.date.day_of_month().0, 2); //! assert_eq!(datetime_iso.time.hour.number(), 13); //! assert_eq!(datetime_iso.time.minute.number(), 1); //! assert_eq!(datetime_iso.time.second.number(), 0); //! ```
usecrate::any_calendar::AnyCalendarKind; usecrate::calendar_arithmetic::{ArithmeticDate, CalendarArithmetic}; usecrate::{types, Calendar, CalendarError, Date, DateDuration, DateDurationUnit, DateTime, Time}; use calendrical_calculations::helpers::{i64_to_saturated_i32, I32CastError}; use calendrical_calculations::rata_die::RataDie; use tinystr::tinystr;
/// The [ISO Calendar] /// /// The [ISO Calendar] is a standardized solar calendar with twelve months. /// It is identical to the Gregorian calendar, except it uses negative years for years before 1 CE, /// and may have differing formatting data for a given locale. /// /// This type can be used with [`Date`] or [`DateTime`] to represent dates in this calendar. /// /// [ISO Calendar]: https://en.wikipedia.org/wiki/ISO_8601#Dates /// /// # Era codes /// /// This calendar supports one era, `"default"`
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] #[allow(clippy::exhaustive_structs)] // this type is stable pubstruct Iso;
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)] /// The inner date type used for representing [`Date`]s of [`Iso`]. See [`Date`] and [`Iso`] for more details. pubstruct IsoDateInner(pub(crate) ArithmeticDate<Iso>);
impl CalendarArithmetic for Iso { type YearInfo = ();
fn day_of_week(&self, date: &Self::DateInner) -> types::IsoWeekday { // For the purposes of the calculation here, Monday is 0, Sunday is 6 // ISO has Monday=1, Sunday=7, which we transform in the last step
// The days of the week are the same every 400 years // so we normalize to the nearest multiple of 400 let years_since_400 = date.0.year.rem_euclid(400);
debug_assert!(years_since_400 >= 0); // rem_euclid returns positive numbers let years_since_400 = years_since_400 as u32; let leap_years_since_400 = years_since_400 / 4 - years_since_400 / 100; // The number of days to the current year // Can never cause an overflow because years_since_400 has a maximum value of 399. let days_to_current_year = 365 * years_since_400 + leap_years_since_400; // The weekday offset from January 1 this year and January 1 2000 let year_offset = days_to_current_year % 7;
impl Date<Iso> { /// Construct a new ISO date from integers. /// /// ```rust /// use icu::calendar::Date; /// /// let date_iso = Date::try_new_iso_date(1970, 1, 2) /// .expect("Failed to initialize ISO Date instance."); /// /// assert_eq!(date_iso.year().number, 1970); /// assert_eq!(date_iso.month().ordinal, 1); /// assert_eq!(date_iso.day_of_month().0, 2); /// ``` pubfn try_new_iso_date(year: i32, month: u8, day: u8) -> Result<Date<Iso>, CalendarError> {
ArithmeticDate::new_from_ordinals(year, month, day)
.map(IsoDateInner)
.map(|inner| Date::from_raw(inner, Iso))
}
/// Constructs an ISO date representing the UNIX epoch on January 1, 1970. pubfn unix_epoch() -> Self {
Date::from_raw(IsoDateInner(ArithmeticDate::new_unchecked(1970, 1, 1)), Iso)
}
}
/// Constructs an ISO datetime representing the UNIX epoch on January 1, 1970 /// at midnight. pubfn local_unix_epoch() -> Self {
DateTime {
date: Date::unix_epoch(),
time: Time::midnight(),
}
}
/// Minute count representation of calendars starting from 00:00:00 on Jan 1st, 1970. /// /// ```rust /// use icu::calendar::DateTime; /// /// let today = DateTime::try_new_iso_datetime(2020, 2, 29, 0, 0, 0).unwrap(); /// /// assert_eq!(today.minutes_since_local_unix_epoch(), 26382240); /// assert_eq!( /// DateTime::from_minutes_since_local_unix_epoch(26382240), /// today /// ); /// /// let today = DateTime::try_new_iso_datetime(1970, 1, 1, 0, 0, 0).unwrap(); /// /// assert_eq!(today.minutes_since_local_unix_epoch(), 0); /// assert_eq!(DateTime::from_minutes_since_local_unix_epoch(0), today); /// ``` pubfn minutes_since_local_unix_epoch(&self) -> i32 { let minutes_a_hour = 60; let hours_a_day = 24; let minutes_a_day = minutes_a_hour * hours_a_day; let unix_epoch = Iso::fixed_from_iso(Date::unix_epoch().inner); let result = (Iso::fixed_from_iso(*self.date.inner()) - unix_epoch) * minutes_a_day
+ i64::from(self.time.hour.number()) * minutes_a_hour
+ i64::from(self.time.minute.number());
i64_to_saturated_i32(result)
}
/// Convert minute count since 00:00:00 on Jan 1st, 1970 to ISO Date. /// /// # Examples /// /// ```rust /// use icu::calendar::DateTime; /// /// // After Unix Epoch /// let today = DateTime::try_new_iso_datetime(2020, 2, 29, 0, 0, 0).unwrap(); /// /// assert_eq!(today.minutes_since_local_unix_epoch(), 26382240); /// assert_eq!( /// DateTime::from_minutes_since_local_unix_epoch(26382240), /// today /// ); /// /// // Unix Epoch /// let today = DateTime::try_new_iso_datetime(1970, 1, 1, 0, 0, 0).unwrap(); /// /// assert_eq!(today.minutes_since_local_unix_epoch(), 0); /// assert_eq!(DateTime::from_minutes_since_local_unix_epoch(0), today); /// /// // Before Unix Epoch /// let today = DateTime::try_new_iso_datetime(1967, 4, 6, 20, 40, 0).unwrap(); /// /// assert_eq!(today.minutes_since_local_unix_epoch(), -1440200); /// assert_eq!( /// DateTime::from_minutes_since_local_unix_epoch(-1440200), /// today /// ); /// ``` pubfn from_minutes_since_local_unix_epoch(minute: i32) -> DateTime<Iso> { let (time, extra_days) = Time::from_minute_with_remainder_days(minute); let unix_epoch = Date::unix_epoch(); let unix_epoch_days = Iso::fixed_from_iso(unix_epoch.inner); let date = Iso::iso_from_fixed(unix_epoch_days + extra_days as i64);
DateTime { date, time }
}
}
impl Iso { /// Construct a new ISO Calendar pubfn new() -> Self { Self
}
/// Count the number of days in a given month/year combo fn days_in_month(year: i32, month: u8) -> u8 { match month { 4 | 6 | 9 | 11 => 30, 2ifSelf::is_leap_year(year, ()) => 29, 2 => 28,
_ => 31,
}
}
// Fixed is day count representation of calendars starting from Jan 1st of year 1. // The fixed calculations algorithms are from the Calendrical Calculations book. pub(crate) fn fixed_from_iso(date: IsoDateInner) -> RataDie {
calendrical_calculations::iso::fixed_from_iso(date.0.year, date.0.month, date.0.day)
}
pub(crate) fn iso_from_year_day(year: i32, year_day: u16) -> Date<Iso> { letmut month = 1; letmut day = year_day as i32; while month <= 12 { let month_days = Self::days_in_month(year, month) as i32; if day <= month_days { break;
} else {
debug_assert!(month < 12); // don't try going to month 13
day -= month_days;
month += 1;
}
} let day = day as u8; // day <= month_days < u8::MAX
#[allow(clippy::unwrap_used)] // month in 1..=12, day <= month_days
Date::try_new_iso_date(year, month, day).unwrap()
} pub(crate) fn iso_from_fixed(date: RataDie) -> Date<Iso> { let (year, month, day) = match calendrical_calculations::iso::iso_from_fixed(date) {
Err(I32CastError::BelowMin) => { return Date::from_raw(IsoDateInner(ArithmeticDate::min_date()), Iso)
}
Err(I32CastError::AboveMax) => { return Date::from_raw(IsoDateInner(ArithmeticDate::max_date()), Iso)
}
Ok(ymd) => ymd,
}; #[allow(clippy::unwrap_used)] // valid day and month
Date::try_new_iso_date(year, month, day).unwrap()
}
pub(crate) fn day_of_year(date: IsoDateInner) -> u16 { // Cumulatively how much are dates in each month // offset from "30 days in each month" (in non leap years) let month_offset = [0, 1, -1, 0, 0, 1, 1, 2, 3, 3, 4, 4]; #[allow(clippy::indexing_slicing)] // date.0.month in 1..=12 letmut offset = month_offset[date.0.month as usize - 1]; ifSelf::is_leap_year(date.0.year, ()) && date.0.month > 2 { // Months after February in a leap year are offset by one less
offset += 1;
} let prev_month_days = (30 * (date.0.month as i32 - 1) + offset) as u16;
prev_month_days + date.0.day as u16
}
/// Wrap the year in the appropriate era code fn year_as_iso(year: i32) -> types::FormattableYear {
types::FormattableYear {
era: types::Era(tinystr!(16, "default")),
number: year,
cyclic: None,
related_iso: None,
}
}
}
#[cfg(test)] mod test { usesuper::*; usecrate::types::IsoWeekday;
#[test] fn iso_overflow() { #[derive(Debug)] struct TestCase {
year: i32,
month: u8,
day: u8,
fixed: RataDie,
saturating: bool,
} // Calculates the max possible year representable using i32::MAX as the fixed date let max_year = Iso::iso_from_fixed(RataDie::new(i32::MAX as i64))
.year()
.number;
// Calculates the minimum possible year representable using i32::MIN as the fixed date // *Cannot be tested yet due to hard coded date not being available yet (see line 436) let min_year = -5879610;
for case in cases { let date = Date::try_new_iso_date(case.year, case.month, case.day).unwrap(); if !case.saturating {
assert_eq!(Iso::fixed_from_iso(date.inner), case.fixed, "{case:?}");
}
assert_eq!(Iso::iso_from_fixed(case.fixed), date, "{case:?}");
}
}
// Calculates the minimum possible year representable using a large negative fixed date #[test] fn min_year() {
assert_eq!(
Iso::iso_from_fixed(RataDie::big_negative()).year().number,
i32::MIN
);
}
#[test] fn test_day_of_week() { // June 23, 2021 is a Wednesday
assert_eq!(
Date::try_new_iso_date(2021, 6, 23).unwrap().day_of_week(),
IsoWeekday::Wednesday,
); // Feb 2, 1983 was a Wednesday
assert_eq!(
Date::try_new_iso_date(1983, 2, 2).unwrap().day_of_week(),
IsoWeekday::Wednesday,
); // Jan 21, 2021 was a Tuesday
assert_eq!(
Date::try_new_iso_date(2020, 1, 21).unwrap().day_of_week(),
IsoWeekday::Tuesday,
);
}
#[test] fn test_day_of_year() { // June 23, 2021 was day 174
assert_eq!(
Date::try_new_iso_date(2021, 6, 23)
.unwrap()
.day_of_year_info()
.day_of_year, 174,
); // June 23, 2020 was day 175
assert_eq!(
Date::try_new_iso_date(2020, 6, 23)
.unwrap()
.day_of_year_info()
.day_of_year, 175,
); // Feb 2, 1983 was a Wednesday
assert_eq!(
Date::try_new_iso_date(1983, 2, 2)
.unwrap()
.day_of_year_info()
.day_of_year, 33,
);
}
fn simple_subtract(a: &Date<Iso>, b: &Date<Iso>) -> DateDuration<Iso> { let a = a.inner(); let b = b.inner();
DateDuration::new(
a.0.year - b.0.year,
a.0.month as i32 - b.0.month as i32, 0,
a.0.day as i32 - b.0.day as i32,
)
}
#[test] fn test_offset() { let today = Date::try_new_iso_date(2021, 6, 23).unwrap(); let today_plus_5000 = Date::try_new_iso_date(2035, 3, 2).unwrap(); let offset = today.added(DateDuration::new(0, 0, 0, 5000));
assert_eq!(offset, today_plus_5000); let offset = today.added(simple_subtract(&today_plus_5000, &today));
assert_eq!(offset, today_plus_5000);
let today = Date::try_new_iso_date(2021, 6, 23).unwrap(); let today_minus_5000 = Date::try_new_iso_date(2007, 10, 15).unwrap(); let offset = today.added(DateDuration::new(0, 0, 0, -5000));
assert_eq!(offset, today_minus_5000); let offset = today.added(simple_subtract(&today_minus_5000, &today));
assert_eq!(offset, today_minus_5000);
}
#[test] fn test_offset_at_month_boundary() { let today = Date::try_new_iso_date(2020, 2, 28).unwrap(); let today_plus_2 = Date::try_new_iso_date(2020, 3, 1).unwrap(); let offset = today.added(DateDuration::new(0, 0, 0, 2));
assert_eq!(offset, today_plus_2);
let today = Date::try_new_iso_date(2020, 2, 28).unwrap(); let today_plus_3 = Date::try_new_iso_date(2020, 3, 2).unwrap(); let offset = today.added(DateDuration::new(0, 0, 0, 3));
assert_eq!(offset, today_plus_3);
let today = Date::try_new_iso_date(2020, 2, 28).unwrap(); let today_plus_1 = Date::try_new_iso_date(2020, 2, 29).unwrap(); let offset = today.added(DateDuration::new(0, 0, 0, 1));
assert_eq!(offset, today_plus_1);
let today = Date::try_new_iso_date(2019, 2, 28).unwrap(); let today_plus_2 = Date::try_new_iso_date(2019, 3, 2).unwrap(); let offset = today.added(DateDuration::new(0, 0, 0, 2));
assert_eq!(offset, today_plus_2);
let today = Date::try_new_iso_date(2019, 2, 28).unwrap(); let today_plus_1 = Date::try_new_iso_date(2019, 3, 1).unwrap(); let offset = today.added(DateDuration::new(0, 0, 0, 1));
assert_eq!(offset, today_plus_1);
let today = Date::try_new_iso_date(2020, 3, 1).unwrap(); let today_minus_1 = Date::try_new_iso_date(2020, 2, 29).unwrap(); let offset = today.added(DateDuration::new(0, 0, 0, -1));
assert_eq!(offset, today_minus_1);
}
#[test] fn test_offset_handles_negative_month_offset() { let today = Date::try_new_iso_date(2020, 3, 1).unwrap(); let today_minus_2_months = Date::try_new_iso_date(2020, 1, 1).unwrap(); let offset = today.added(DateDuration::new(0, -2, 0, 0));
assert_eq!(offset, today_minus_2_months);
let today = Date::try_new_iso_date(2020, 3, 1).unwrap(); let today_minus_4_months = Date::try_new_iso_date(2019, 11, 1).unwrap(); let offset = today.added(DateDuration::new(0, -4, 0, 0));
assert_eq!(offset, today_minus_4_months);
let today = Date::try_new_iso_date(2020, 3, 1).unwrap(); let today_minus_24_months = Date::try_new_iso_date(2018, 3, 1).unwrap(); let offset = today.added(DateDuration::new(0, -24, 0, 0));
assert_eq!(offset, today_minus_24_months);
let today = Date::try_new_iso_date(2020, 3, 1).unwrap(); let today_minus_27_months = Date::try_new_iso_date(2017, 12, 1).unwrap(); let offset = today.added(DateDuration::new(0, -27, 0, 0));
assert_eq!(offset, today_minus_27_months);
}
#[test] fn test_offset_handles_out_of_bound_month_offset() { let today = Date::try_new_iso_date(2021, 1, 31).unwrap(); // since 2021/02/31 isn't a valid date, `offset_date` auto-adjusts by adding 3 days to 2021/02/28 let today_plus_1_month = Date::try_new_iso_date(2021, 3, 3).unwrap(); let offset = today.added(DateDuration::new(0, 1, 0, 0));
assert_eq!(offset, today_plus_1_month);
let today = Date::try_new_iso_date(2021, 1, 31).unwrap(); // since 2021/02/31 isn't a valid date, `offset_date` auto-adjusts by adding 3 days to 2021/02/28 let today_plus_1_month_1_day = Date::try_new_iso_date(2021, 3, 4).unwrap(); let offset = today.added(DateDuration::new(0, 1, 0, 1));
assert_eq!(offset, today_plus_1_month_1_day);
}
#[test] fn test_iso_to_from_fixed() { // Reminder: ISO year 0 is Gregorian year 1 BCE. // Year 0 is a leap year due to the 400-year rule. fn check(fixed: i64, year: i32, month: u8, day: u8) { let fixed = RataDie::new(fixed);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.