use crate::cal::abstract_gregorian::{
impl_with_abstract_gregorian, AbstractGregorian, GregorianYears,
};
use crate::calendar_arithmetic::ArithmeticDate;
use crate::error::UnknownEraError;
use crate::preferences::CalendarAlgorithm;
use crate::{types, Date, RangeError};
use tinystr::tinystr;
#[derive(Copy, Clone, Debug, Default)]
#[allow(clippy::exhaustive_structs)] pub struct Roc;
impl_with_abstract_gregorian!(Roc, RocDateInner, RocEra, _x, RocEra);
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct RocEra;
impl GregorianYears for RocEra {
const EXTENDED_YEAR_OFFSET: i32 = 1911;
fn extended_from_era_year(
&self,
era: Option<&[u8]>,
year: i32,
) -> Result<i32, UnknownEraError> {
match era {
None => Ok(year),
Some(b"roc") => Ok(year),
Some(b"broc") => Ok(1 - year),
Some(_) => Err(UnknownEraError),
}
}
fn era_year_from_extended(&self, extended_year: i32, _month: u8, _day: u8) -> types::EraYear {
if extended_year > 0 {
types::EraYear {
era: tinystr!(16, "roc"),
era_index: Some(1),
year: extended_year,
extended_year,
ambiguity: types::YearAmbiguity::CenturyRequired,
}
} else {
types::EraYear {
era: tinystr!(16, "broc"),
era_index: Some(0),
year: 1 - extended_year,
extended_year,
ambiguity: types::YearAmbiguity::EraAndCenturyRequired,
}
}
}
fn debug_name(&self) -> &'static str {
"ROC"
}
fn calendar_algorithm(&self) -> Option<CalendarAlgorithm> {
Some(CalendarAlgorithm::Roc)
}
}
impl Date<Roc> {
pub fn try_new_roc(year: i32, month: u8, day: u8) -> Result<Date<Roc>, RangeError> {
ArithmeticDate::from_year_month_day(year, month, day, &AbstractGregorian(RocEra))
.map(ArithmeticDate::cast)
.map(RocDateInner)
.map(|i| Date::from_raw(i, Roc))
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::cal::Iso;
use calendrical_calculations::rata_die::RataDie;
#[derive(Debug)]
struct TestCase {
rd: RataDie,
year: i32,
era: &'static str,
month: u8,
day: u8,
}
fn check_test_case(case: TestCase) {
let roc_from_rd = Date::from_rata_die(case.rd, Roc);
assert_eq!(
roc_from_rd.era_year().year,
case.year,
"Failed year check from RD: {case:?}\nROC: {roc_from_rd:?}"
);
assert_eq!(
roc_from_rd.era_year().era,
case.era,
"Failed era check from RD: {case:?}\nROC: {roc_from_rd:?}"
);
assert_eq!(
roc_from_rd.year().extended_year(),
if case.era == "roc" {
case.year
} else {
1 - case.year
},
"Failed year check from RD: {case:?}\nROC: {roc_from_rd:?}"
);
assert_eq!(
roc_from_rd.month().ordinal,
case.month,
"Failed month check from RD: {case:?}\nROC: {roc_from_rd:?}"
);
assert_eq!(
roc_from_rd.day_of_month().0,
case.day,
"Failed day_of_month check from RD: {case:?}\nROC: {roc_from_rd:?}"
);
let roc_from_case = Date::try_new_roc(
roc_from_rd.year().extended_year(),
roc_from_rd.month().ordinal,
roc_from_rd.day_of_month().0,
)
.unwrap();
assert_eq!(roc_from_rd, roc_from_case,
"ROC date from RD not equal to ROC generated from manually-input ymd\nCase: {case:?}\nRD: {roc_from_rd:?}\nManual: {roc_from_case:?}");
}
#[test]
fn test_roc_current_era() {
let cases = [
TestCase {
rd: Date::try_new_iso(1912, 1, 1).unwrap().to_rata_die(),
year: 1,
era: "roc",
month: 1,
day: 1,
},
TestCase {
rd: Date::try_new_iso(1912, 2, 29).unwrap().to_rata_die(),
year: 1,
era: "roc",
month: 2,
day: 29,
},
TestCase {
rd: Date::try_new_iso(1913, 6, 30).unwrap().to_rata_die(),
year: 2,
era: "roc",
month: 6,
day: 30,
},
TestCase {
rd: Date::try_new_iso(2023, 7, 13).unwrap().to_rata_die(),
year: 112,
era: "roc",
month: 7,
day: 13,
},
];
for case in cases {
check_test_case(case);
}
}
#[test]
fn test_roc_prior_era() {
let cases = [
TestCase {
rd: Date::try_new_iso(1911, 12, 31).unwrap().to_rata_die(),
year: 1,
era: "broc",
month: 12,
day: 31,
},
TestCase {
rd: Date::try_new_iso(1911, 1, 1).unwrap().to_rata_die(),
year: 1,
era: "broc",
month: 1,
day: 1,
},
TestCase {
rd: Date::try_new_iso(1910, 12, 31).unwrap().to_rata_die(),
year: 2,
era: "broc",
month: 12,
day: 31,
},
TestCase {
rd: Date::try_new_iso(1908, 2, 29).unwrap().to_rata_die(),
year: 4,
era: "broc",
month: 2,
day: 29,
},
TestCase {
rd: Date::try_new_iso(1, 1, 1).unwrap().to_rata_die(),
year: 1911,
era: "broc",
month: 1,
day: 1,
},
TestCase {
rd: Date::try_new_iso(0, 12, 31).unwrap().to_rata_die(),
year: 1912,
era: "broc",
month: 12,
day: 31,
},
];
for case in cases {
check_test_case(case);
}
}
#[test]
fn test_roc_directionality_near_epoch() {
let rd_epoch_start = 697978;
for i in (rd_epoch_start - 100)..=(rd_epoch_start + 100) {
for j in (rd_epoch_start - 100)..=(rd_epoch_start + 100) {
let iso_i = Date::from_rata_die(RataDie::new(i), Iso);
let iso_j = Date::from_rata_die(RataDie::new(j), Iso);
let roc_i = Date::from_rata_die(RataDie::new(i), Roc);
let roc_j = Date::from_rata_die(RataDie::new(j), Roc);
assert_eq!(
i.cmp(&j),
iso_i.cmp(&iso_j),
"ISO directionality inconsistent with directionality for i: {i}, j: {j}"
);
assert_eq!(
i.cmp(&j),
roc_i.cmp(&roc_j),
"ROC directionality inconsistent with directionality for i: {i}, j: {j}"
);
}
}
}
#[test]
fn test_roc_directionality_near_rd_zero() {
for i in -100..=100 {
for j in -100..100 {
let iso_i = Date::from_rata_die(RataDie::new(i), Iso);
let iso_j = Date::from_rata_die(RataDie::new(j), Iso);
let roc_i = Date::from_rata_die(RataDie::new(i), Roc);
let roc_j = Date::from_rata_die(RataDie::new(j), Roc);
assert_eq!(
i.cmp(&j),
iso_i.cmp(&iso_j),
"ISO directionality inconsistent with directionality for i: {i}, j: {j}"
);
assert_eq!(
i.cmp(&j),
roc_i.cmp(&roc_j),
"ROC directionality inconsistent with directionality for i: {i}, j: {j}"
);
}
}
}
}