use crate::cal::abstract_gregorian::{
impl_with_abstract_gregorian, AbstractGregorian, GregorianYears,
};
use crate::calendar_arithmetic::ArithmeticDate;
use crate::error::UnknownEraError;
use crate::{types, Date, RangeError};
use tinystr::tinystr;
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[allow(clippy::exhaustive_structs)] pub struct Iso;
impl_with_abstract_gregorian!(Iso, IsoDateInner, IsoEra, _x, IsoEra);
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct IsoEra;
impl GregorianYears for IsoEra {
fn extended_from_era_year(
&self,
era: Option<&[u8]>,
year: i32,
) -> Result<i32, UnknownEraError> {
match era {
Some(b"default") | None => Ok(year),
Some(_) => Err(UnknownEraError),
}
}
fn era_year_from_extended(&self, extended_year: i32, _month: u8, _day: u8) -> types::EraYear {
types::EraYear {
era_index: Some(0),
era: tinystr!(16, "default"),
year: extended_year,
extended_year,
ambiguity: types::YearAmbiguity::Unambiguous,
}
}
fn debug_name(&self) -> &'static str {
"ISO"
}
}
impl Date<Iso> {
pub fn try_new_iso(year: i32, month: u8, day: u8) -> Result<Date<Iso>, RangeError> {
ArithmeticDate::from_year_month_day(year, month, day, &AbstractGregorian(IsoEra))
.map(ArithmeticDate::cast)
.map(IsoDateInner)
.map(|i| Date::from_raw(i, Iso))
}
}
impl Iso {
pub fn new() -> Self {
Self
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{
calendar_arithmetic::{CONSTRUCTOR_YEAR_RANGE, VALID_RD_RANGE},
types::{DateDuration, RataDie, Weekday},
};
#[test]
fn iso_overflow() {
#[derive(Debug)]
struct TestCase {
year: i32,
month: u8,
day: u8,
rd: RataDie,
invalid_ymd: bool,
clamping_rd: bool,
}
let cases = [
TestCase {
year: -999999,
month: 1,
day: 1,
rd: *VALID_RD_RANGE.start() - 100000,
invalid_ymd: true,
clamping_rd: true,
},
TestCase {
year: -999999,
month: 1,
day: 1,
rd: *VALID_RD_RANGE.start(),
invalid_ymd: true,
clamping_rd: false,
},
TestCase {
year: *CONSTRUCTOR_YEAR_RANGE.start(),
month: 1,
day: 1,
rd: RataDie::new(-3652424),
invalid_ymd: false,
clamping_rd: false,
},
TestCase {
year: *CONSTRUCTOR_YEAR_RANGE.end(),
month: 12,
day: 31,
rd: RataDie::new(3652059),
invalid_ymd: false,
clamping_rd: false,
},
TestCase {
year: 999999,
month: 12,
day: 31,
rd: *VALID_RD_RANGE.end(),
invalid_ymd: true,
clamping_rd: false,
},
TestCase {
year: 999999,
month: 12,
day: 31,
rd: *VALID_RD_RANGE.end() + 100000,
invalid_ymd: true,
clamping_rd: true,
},
];
for case in cases {
let date_from_rd = Date::from_rata_die(case.rd, Iso);
let date_from_ymd = Date::try_new_iso(case.year, case.month, case.day);
if !case.clamping_rd {
assert_eq!(date_from_rd.to_rata_die(), case.rd, "{:?}", case);
} else {
assert_ne!(date_from_rd.to_rata_die(), case.rd, "{:?}", case);
}
if !case.invalid_ymd {
assert_eq!(date_from_ymd.unwrap().to_rata_die(), case.rd, "{case:?}");
} else {
assert_eq!(
date_from_ymd,
Err(RangeError {
field: "year",
value: case.year,
min: *CONSTRUCTOR_YEAR_RANGE.start(),
max: *CONSTRUCTOR_YEAR_RANGE.end()
}),
"{case:?}"
)
}
assert_eq!(
(
date_from_rd.era_year().year,
date_from_rd.month().number(),
date_from_rd.day_of_month().0
),
(case.year, case.month, case.day),
"{case:?}"
);
}
}
#[test]
fn test_weekday() {
assert_eq!(
Date::try_new_iso(2021, 6, 23).unwrap().weekday(),
Weekday::Wednesday,
);
assert_eq!(
Date::try_new_iso(1983, 2, 2).unwrap().weekday(),
Weekday::Wednesday,
);
assert_eq!(
Date::try_new_iso(2020, 1, 21).unwrap().weekday(),
Weekday::Tuesday,
);
}
#[test]
fn test_day_of_year() {
assert_eq!(Date::try_new_iso(2021, 6, 23).unwrap().day_of_year().0, 174,);
assert_eq!(Date::try_new_iso(2020, 6, 23).unwrap().day_of_year().0, 175,);
assert_eq!(Date::try_new_iso(1983, 2, 2).unwrap().day_of_year().0, 33,);
}
#[test]
fn test_offset() {
let today = Date::try_new_iso(2021, 6, 23).unwrap();
let today_plus_5000 = Date::try_new_iso(2035, 3, 2).unwrap();
let offset = today
.try_added_with_options(DateDuration::for_days(5000), Default::default())
.unwrap();
assert_eq!(offset, today_plus_5000);
let today = Date::try_new_iso(2021, 6, 23).unwrap();
let today_minus_5000 = Date::try_new_iso(2007, 10, 15).unwrap();
let offset = today
.try_added_with_options(DateDuration::for_days(-5000), Default::default())
.unwrap();
assert_eq!(offset, today_minus_5000);
}
#[test]
fn test_offset_at_month_boundary() {
let today = Date::try_new_iso(2020, 2, 28).unwrap();
let today_plus_2 = Date::try_new_iso(2020, 3, 1).unwrap();
let offset = today
.try_added_with_options(DateDuration::for_days(2), Default::default())
.unwrap();
assert_eq!(offset, today_plus_2);
let today = Date::try_new_iso(2020, 2, 28).unwrap();
let today_plus_3 = Date::try_new_iso(2020, 3, 2).unwrap();
let offset = today
.try_added_with_options(DateDuration::for_days(3), Default::default())
.unwrap();
assert_eq!(offset, today_plus_3);
let today = Date::try_new_iso(2020, 2, 28).unwrap();
let today_plus_1 = Date::try_new_iso(2020, 2, 29).unwrap();
let offset = today
.try_added_with_options(DateDuration::for_days(1), Default::default())
.unwrap();
assert_eq!(offset, today_plus_1);
let today = Date::try_new_iso(2019, 2, 28).unwrap();
let today_plus_2 = Date::try_new_iso(2019, 3, 2).unwrap();
let offset = today
.try_added_with_options(DateDuration::for_days(2), Default::default())
.unwrap();
assert_eq!(offset, today_plus_2);
let today = Date::try_new_iso(2019, 2, 28).unwrap();
let today_plus_1 = Date::try_new_iso(2019, 3, 1).unwrap();
let offset = today
.try_added_with_options(DateDuration::for_days(1), Default::default())
.unwrap();
assert_eq!(offset, today_plus_1);
let today = Date::try_new_iso(2020, 3, 1).unwrap();
let today_minus_1 = Date::try_new_iso(2020, 2, 29).unwrap();
let offset = today
.try_added_with_options(DateDuration::for_days(-1), Default::default())
.unwrap();
assert_eq!(offset, today_minus_1);
}
#[test]
fn test_offset_handles_negative_month_offset() {
let today = Date::try_new_iso(2020, 3, 1).unwrap();
let today_minus_2_months = Date::try_new_iso(2020, 1, 1).unwrap();
let offset = today
.try_added_with_options(DateDuration::for_months(-2), Default::default())
.unwrap();
assert_eq!(offset, today_minus_2_months);
let today = Date::try_new_iso(2020, 3, 1).unwrap();
let today_minus_4_months = Date::try_new_iso(2019, 11, 1).unwrap();
let offset = today
.try_added_with_options(DateDuration::for_months(-4), Default::default())
.unwrap();
assert_eq!(offset, today_minus_4_months);
let today = Date::try_new_iso(2020, 3, 1).unwrap();
let today_minus_24_months = Date::try_new_iso(2018, 3, 1).unwrap();
let offset = today
.try_added_with_options(DateDuration::for_months(-24), Default::default())
.unwrap();
assert_eq!(offset, today_minus_24_months);
let today = Date::try_new_iso(2020, 3, 1).unwrap();
let today_minus_27_months = Date::try_new_iso(2017, 12, 1).unwrap();
let offset = today
.try_added_with_options(DateDuration::for_months(-27), Default::default())
.unwrap();
assert_eq!(offset, today_minus_27_months);
}
#[test]
fn test_offset_handles_out_of_bound_month_offset() {
let today = Date::try_new_iso(2021, 1, 31).unwrap();
let today_plus_1_month = Date::try_new_iso(2021, 2, 28).unwrap();
let offset = today
.try_added_with_options(DateDuration::for_months(1), Default::default())
.unwrap();
assert_eq!(offset, today_plus_1_month);
let today = Date::try_new_iso(2021, 1, 31).unwrap();
let today_plus_1_month_1_day = Date::try_new_iso(2021, 3, 1).unwrap();
let offset = today
.try_added_with_options(
DateDuration {
months: 1,
days: 1,
..Default::default()
},
Default::default(),
)
.unwrap();
assert_eq!(offset, today_plus_1_month_1_day);
}
#[test]
fn test_iso_to_from_rd() {
fn check(rd: i64, year: i32, month: u8, day: u8) {
let rd = RataDie::new(rd);
assert_eq!(
Date::from_rata_die(rd, Iso),
Date::try_new_iso(year, month, day).unwrap(),
"RD: {rd:?}"
);
}
check(-1828, -5, 12, 30);
check(-1827, -5, 12, 31); check(-1826, -4, 1, 1);
check(-1462, -4, 12, 30);
check(-1461, -4, 12, 31);
check(-1460, -3, 1, 1);
check(-1459, -3, 1, 2);
check(-732, -2, 12, 30);
check(-731, -2, 12, 31);
check(-730, -1, 1, 1);
check(-367, -1, 12, 30);
check(-366, -1, 12, 31);
check(-365, 0, 1, 1); check(-364, 0, 1, 2);
check(-1, 0, 12, 30);
check(0, 0, 12, 31);
check(1, 1, 1, 1);
check(2, 1, 1, 2);
check(364, 1, 12, 30);
check(365, 1, 12, 31);
check(366, 2, 1, 1);
check(1459, 4, 12, 29);
check(1460, 4, 12, 30);
check(1461, 4, 12, 31); check(1462, 5, 1, 1);
}
}