use calendrical_calculations::rata_die::RataDie;
use icu_provider::prelude::*;
use zerovec::ule::AsULE;
use zerovec::ZeroVec;
icu_provider::data_marker!(
CalendarHijriSimulatedMeccaV1,
"calendar/hijri/simulated/mecca/v1",
HijriData<'static>,
is_singleton = true,
);
#[derive(Debug, PartialEq, Clone, Default, yoke::Yokeable, zerofrom::ZeroFrom)]
#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
#[cfg_attr(feature = "datagen", databake(path = icu_calendar::provider::hijri))]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
pub struct HijriData<'data> {
pub first_extended_year: i32,
#[cfg_attr(feature = "serde", serde(borrow))]
pub data: ZeroVec<'data, PackedHijriYearInfo>,
}
icu_provider::data_struct!(
HijriData<'_>,
#[cfg(feature = "datagen")]
);
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Debug)]
#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
#[cfg_attr(feature = "datagen", databake(path = icu_calendar::provider))]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
pub struct PackedHijriYearInfo(pub u16);
impl PackedHijriYearInfo {
pub(crate) const fn new(
extended_year: i32,
month_lengths: [bool; 12],
start_day: RataDie,
) -> Self {
let start_offset = start_day.until(Self::mean_synodic_start_day(extended_year));
debug_assert!(
-8 < start_offset && start_offset < 8,
"Year offset too big to store"
);
let start_offset = start_offset as i8;
let mut all = 0u16;
let mut i = 0;
while i < 12 {
#[allow(clippy::indexing_slicing)]
if month_lengths[i] {
all |= 1 << i;
}
i += 1;
}
if start_offset < 0 {
all |= 1 << 12;
}
all |= (start_offset.unsigned_abs() as u16) << 13;
Self(all)
}
pub(crate) fn unpack(self, extended_year: i32) -> ([bool; 12], RataDie) {
let month_lengths = core::array::from_fn(|i| self.0 & (1 << (i as u8) as u16) != 0);
let start_offset = if (self.0 & 0b1_0000_0000_0000) != 0 {
-((self.0 >> 13) as i64)
} else {
(self.0 >> 13) as i64
};
(
month_lengths,
Self::mean_synodic_start_day(extended_year) + start_offset,
)
}
const fn mean_synodic_start_day(extended_year: i32) -> RataDie {
calendrical_calculations::islamic::ISLAMIC_EPOCH_FRIDAY.add(
((extended_year - 1) as f64 * calendrical_calculations::islamic::MEAN_YEAR_LENGTH)
as i64,
)
}
}
impl AsULE for PackedHijriYearInfo {
type ULE = <u16 as AsULE>::ULE;
fn from_unaligned(unaligned: Self::ULE) -> Self {
Self(<u16 as AsULE>::from_unaligned(unaligned))
}
fn to_unaligned(self) -> Self::ULE {
<u16 as AsULE>::to_unaligned(self.0)
}
}
#[test]
fn test_hijri_packed_roundtrip() {
fn single_roundtrip(month_lengths: [bool; 12], year_start: RataDie) {
let packed = PackedHijriYearInfo::new(1600, month_lengths, year_start);
let (month_lengths2, year_start2) = packed.unpack(1600);
assert_eq!(month_lengths, month_lengths2, "Month lengths must match for testcase {month_lengths:?} / {year_start:?}, with packed repr: {packed:?}");
assert_eq!(year_start, year_start2, "Month lengths must match for testcase {month_lengths:?} / {year_start:?}, with packed repr: {packed:?}");
}
let l = true;
let s = false;
let all_short = [s; 12];
let all_long = [l; 12];
let mixed1 = [l, s, l, s, l, s, l, s, l, s, l, s];
let mixed2 = [s, s, l, l, l, s, l, s, s, s, l, l];
let start_1600 = PackedHijriYearInfo::mean_synodic_start_day(1600);
single_roundtrip(all_short, start_1600);
single_roundtrip(all_long, start_1600);
single_roundtrip(mixed1, start_1600);
single_roundtrip(mixed2, start_1600);
single_roundtrip(mixed1, start_1600 - 7);
single_roundtrip(mixed2, start_1600 + 7);
single_roundtrip(mixed2, start_1600 + 4);
single_roundtrip(mixed2, start_1600 + 1);
single_roundtrip(mixed2, start_1600 - 1);
single_roundtrip(mixed2, start_1600 - 4);
}