icu_calendar/provider/
hijri.rs1use calendrical_calculations::rata_die::RataDie;
16use icu_provider::prelude::*;
17use zerovec::ule::AsULE;
18use zerovec::ZeroVec;
19
20icu_provider::data_marker!(
21 CalendarHijriSimulatedMeccaV1,
23 "calendar/hijri/simulated/mecca/v1",
24 HijriData<'static>,
25 is_singleton = true,
26);
27
28#[derive(Debug, PartialEq, Clone, Default, yoke::Yokeable, zerofrom::ZeroFrom)]
32#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
33#[cfg_attr(feature = "datagen", databake(path = icu_calendar::provider::hijri))]
34#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
35pub struct HijriData<'data> {
36 pub first_extended_year: i32,
38 #[cfg_attr(feature = "serde", serde(borrow))]
40 pub data: ZeroVec<'data, PackedHijriYearInfo>,
41}
42
43icu_provider::data_struct!(
44 HijriData<'_>,
45 #[cfg(feature = "datagen")]
46);
47
48#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Debug)]
67#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
68#[cfg_attr(feature = "datagen", databake(path = icu_calendar::provider))]
69#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
70pub struct PackedHijriYearInfo(pub u16);
71
72impl PackedHijriYearInfo {
73 pub(crate) const fn new(
74 extended_year: i32,
75 month_lengths: [bool; 12],
76 start_day: RataDie,
77 ) -> Self {
78 let start_offset = start_day.until(Self::mean_synodic_start_day(extended_year));
79
80 debug_assert!(
81 -8 < start_offset && start_offset < 8,
82 "Year offset too big to store"
83 );
84 let start_offset = start_offset as i8;
85
86 let mut all = 0u16; let mut i = 0;
89 while i < 12 {
90 #[allow(clippy::indexing_slicing)]
91 if month_lengths[i] {
92 all |= 1 << i;
93 }
94 i += 1;
95 }
96
97 if start_offset < 0 {
98 all |= 1 << 12;
99 }
100 all |= (start_offset.unsigned_abs() as u16) << 13;
101 Self(all)
102 }
103
104 pub(crate) fn unpack(self, extended_year: i32) -> ([bool; 12], RataDie) {
105 let month_lengths = core::array::from_fn(|i| self.0 & (1 << (i as u8) as u16) != 0);
106 let start_offset = if (self.0 & 0b1_0000_0000_0000) != 0 {
107 -((self.0 >> 13) as i64)
108 } else {
109 (self.0 >> 13) as i64
110 };
111 (
112 month_lengths,
113 Self::mean_synodic_start_day(extended_year) + start_offset,
114 )
115 }
116
117 const fn mean_synodic_start_day(extended_year: i32) -> RataDie {
118 calendrical_calculations::islamic::ISLAMIC_EPOCH_FRIDAY.add(
121 ((extended_year - 1) as f64 * calendrical_calculations::islamic::MEAN_YEAR_LENGTH)
122 as i64,
123 )
124 }
125}
126
127impl AsULE for PackedHijriYearInfo {
128 type ULE = <u16 as AsULE>::ULE;
129 fn from_unaligned(unaligned: Self::ULE) -> Self {
130 Self(<u16 as AsULE>::from_unaligned(unaligned))
131 }
132 fn to_unaligned(self) -> Self::ULE {
133 <u16 as AsULE>::to_unaligned(self.0)
134 }
135}
136
137#[test]
138fn test_hijri_packed_roundtrip() {
139 fn single_roundtrip(month_lengths: [bool; 12], year_start: RataDie) {
140 let packed = PackedHijriYearInfo::new(1600, month_lengths, year_start);
141 let (month_lengths2, year_start2) = packed.unpack(1600);
142 assert_eq!(month_lengths, month_lengths2, "Month lengths must match for testcase {month_lengths:?} / {year_start:?}, with packed repr: {packed:?}");
143 assert_eq!(year_start, year_start2, "Month lengths must match for testcase {month_lengths:?} / {year_start:?}, with packed repr: {packed:?}");
144 }
145
146 let l = true;
147 let s = false;
148 let all_short = [s; 12];
149 let all_long = [l; 12];
150 let mixed1 = [l, s, l, s, l, s, l, s, l, s, l, s];
151 let mixed2 = [s, s, l, l, l, s, l, s, s, s, l, l];
152
153 let start_1600 = PackedHijriYearInfo::mean_synodic_start_day(1600);
154 single_roundtrip(all_short, start_1600);
155 single_roundtrip(all_long, start_1600);
156 single_roundtrip(mixed1, start_1600);
157 single_roundtrip(mixed2, start_1600);
158
159 single_roundtrip(mixed1, start_1600 - 7);
160 single_roundtrip(mixed2, start_1600 + 7);
161 single_roundtrip(mixed2, start_1600 + 4);
162 single_roundtrip(mixed2, start_1600 + 1);
163 single_roundtrip(mixed2, start_1600 - 1);
164 single_roundtrip(mixed2, start_1600 - 4);
165}