1use crate::calendar_arithmetic::ArithmeticDate;
16use crate::chinese_based::ChineseBasedYearInfo;
17use crate::Iso;
18use calendrical_calculations::chinese_based::ChineseBased;
19use calendrical_calculations::rata_die::RataDie;
20use core::num::NonZeroU8;
21use icu_provider::prelude::*;
22use zerovec::ule::{AsULE, ULE};
23use zerovec::ZeroVec;
24
25#[icu_provider::data_struct(
29 marker(ChineseCacheV1Marker, "calendar/chinesecache@1", singleton),
30 marker(DangiCacheV1Marker, "calendar/dangicache@1", singleton)
31)]
32#[derive(Debug, PartialEq, Clone, Default)]
33#[cfg_attr(
34 feature = "datagen",
35 derive(serde::Serialize, databake::Bake),
36 databake(path = icu_calendar::provider::chinese_based),
37)]
38#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
39pub struct ChineseBasedCacheV1<'data> {
40 pub first_extended_year: i32,
42 #[cfg_attr(feature = "serde", serde(borrow))]
44 pub data: ZeroVec<'data, PackedChineseBasedYearInfo>,
45}
46
47impl<'data> ChineseBasedCacheV1<'data> {
48 #[cfg(feature = "datagen")]
50 pub fn compute_for<CB: ChineseBased>(extended_years: core::ops::Range<i32>) -> Self {
51 let data = crate::chinese_based::compute_many_packed::<CB>(extended_years.clone());
52 ChineseBasedCacheV1 {
53 first_extended_year: extended_years.start,
54 data: data.into(),
55 }
56 }
57
58 pub(crate) fn get_for_extended_year(&self, extended_year: i32) -> Option<ChineseBasedYearInfo> {
60 let delta = extended_year - self.first_extended_year;
61 let delta = usize::try_from(delta).ok()?;
62
63 if delta == 0 {
64 return None;
65 }
66
67 let (Some(this_packed), Some(prev_packed)) =
68 (self.data.get(delta), self.data.get(delta - 1))
69 else {
70 return None;
71 };
72
73 let days_in_prev_year = prev_packed.days_in_year();
74
75 Some(ChineseBasedYearInfo::new(days_in_prev_year, this_packed))
76 }
77 pub(crate) fn get_for_iso<CB: ChineseBased>(
81 &self,
82 iso: ArithmeticDate<Iso>,
83 ) -> Option<(ChineseBasedYearInfo, i32)> {
84 let extended_year = CB::extended_from_iso(iso.year);
85 let delta = extended_year - self.first_extended_year;
86 let delta = usize::try_from(delta).ok()?;
87 if delta <= 1 {
88 return None;
89 }
90
91 let this_packed = self.data.get(delta)?;
92 let prev_packed = self.data.get(delta - 1)?;
93
94 let iso_in_year = iso.day_of_year();
95 let fetched_data_ny_in_iso = u16::from(this_packed.ny_day_of_iso_year());
96
97 if iso_in_year >= fetched_data_ny_in_iso {
98 Some((
99 ChineseBasedYearInfo::new(prev_packed.days_in_year(), this_packed),
100 extended_year,
101 ))
102 } else {
103 if delta <= 2 {
106 return None;
107 }
108 let prev2_packed = self.data.get(delta - 2)?;
109
110 let days_in_prev_year = prev2_packed.days_in_year();
111
112 Some((
113 ChineseBasedYearInfo::new(days_in_prev_year, prev_packed),
114 extended_year - 1,
115 ))
116 }
117 }
118}
119
120#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ULE)]
141#[cfg_attr(
142 feature = "datagen",
143 derive(databake::Bake),
144 databake(path = icu_calendar::provider),
145)]
146#[repr(C, packed)]
147pub struct PackedChineseBasedYearInfo(pub u8, pub u8, pub u8);
148
149impl PackedChineseBasedYearInfo {
150 pub(crate) const FIRST_NY: u8 = 19;
159
160 pub(crate) fn new(
161 month_lengths: [bool; 13],
162 leap_month_idx: Option<NonZeroU8>,
163 ny_offset: u8,
164 ) -> Self {
165 debug_assert!(
166 !month_lengths[12] || leap_month_idx.is_some(),
167 "Last month length should not be set for non-leap years"
168 );
169 debug_assert!(ny_offset < 34, "Year offset too big to store");
170 debug_assert!(
171 leap_month_idx.map(|l| l.get() <= 13).unwrap_or(true),
172 "Leap month indices must be 1 <= i <= 13"
173 );
174 let mut all = 0u32; for (month, length_30) in month_lengths.iter().enumerate() {
177 #[allow(clippy::indexing_slicing)]
178 if *length_30 {
179 all |= 1 << month as u32;
180 }
181 }
182 let leap_month_idx = leap_month_idx.map(|x| x.get()).unwrap_or(0);
183 all |= (leap_month_idx as u32) << (8 + 5);
184 all |= (ny_offset as u32) << (16 + 1);
185 let le = all.to_le_bytes();
186 Self(le[0], le[1], le[2])
187 }
188
189 pub(crate) fn ny_offset(self) -> u8 {
191 self.2 >> 1
192 }
193
194 fn ny_day_of_iso_year(self) -> u8 {
196 let ny_offset = self.ny_offset();
197 Self::FIRST_NY + ny_offset
199 }
200
201 pub(crate) fn ny_rd(self, related_iso: i32) -> RataDie {
202 let iso_ny = calendrical_calculations::iso::fixed_from_iso(related_iso, 1, 1);
203 iso_ny + i64::from(self.ny_day_of_iso_year()) - 1
205 }
206
207 pub(crate) fn leap_month_idx(self) -> Option<NonZeroU8> {
208 let low_bits = self.1 >> 5;
209 let high_bits = (self.2 & 0b1) << 3;
210
211 NonZeroU8::new(low_bits + high_bits)
212 }
213
214 #[cfg(any(test, feature = "datagen"))]
216 pub(crate) fn month_has_30_days(self, month: u8) -> bool {
217 let months = u16::from_le_bytes([self.0, self.1]);
218 months & (1 << (month - 1) as u16) != 0
219 }
220
221 pub(crate) fn last_day_of_month(self, month: u8) -> u16 {
223 let months = u16::from_le_bytes([self.0, self.1]);
224 let mut prev_month_lengths = 29 * month as u16;
226 let long_month_bits = months & ((1 << month as u16) - 1);
230 prev_month_lengths += long_month_bits.count_ones().try_into().unwrap_or(0);
231 prev_month_lengths
232 }
233
234 pub(crate) fn days_in_year(self) -> u16 {
235 if self.leap_month_idx().is_some() {
236 self.last_day_of_month(13)
237 } else {
238 self.last_day_of_month(12)
239 }
240 }
241}
242
243impl AsULE for PackedChineseBasedYearInfo {
244 type ULE = Self;
245 fn to_unaligned(self) -> Self {
246 self
247 }
248 fn from_unaligned(other: Self) -> Self {
249 other
250 }
251}
252
253#[cfg(feature = "serde")]
254mod serialization {
255 use super::*;
256
257 #[cfg(feature = "datagen")]
258 use serde::{ser, Serialize};
259 use serde::{Deserialize, Deserializer};
260
261 #[derive(Deserialize)]
262 #[cfg_attr(feature = "datagen", derive(Serialize))]
263 struct SerdePackedChineseBasedYearInfo {
264 ny_offset: u8,
265 month_has_30_days: [bool; 13],
266 leap_month_idx: Option<NonZeroU8>,
267 }
268
269 impl<'de> Deserialize<'de> for PackedChineseBasedYearInfo {
270 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
271 where
272 D: Deserializer<'de>,
273 {
274 if deserializer.is_human_readable() {
275 SerdePackedChineseBasedYearInfo::deserialize(deserializer).map(Into::into)
276 } else {
277 let data = <(u8, u8, u8)>::deserialize(deserializer)?;
278 Ok(PackedChineseBasedYearInfo(data.0, data.1, data.2))
279 }
280 }
281 }
282
283 #[cfg(feature = "datagen")]
284 impl Serialize for PackedChineseBasedYearInfo {
285 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
286 where
287 S: ser::Serializer,
288 {
289 if serializer.is_human_readable() {
290 SerdePackedChineseBasedYearInfo::from(*self).serialize(serializer)
291 } else {
292 (self.0, self.1, self.2).serialize(serializer)
293 }
294 }
295 }
296
297 #[cfg(feature = "datagen")]
298 impl From<PackedChineseBasedYearInfo> for SerdePackedChineseBasedYearInfo {
299 fn from(other: PackedChineseBasedYearInfo) -> Self {
300 let mut month_has_30_days = [false; 13];
301 for (i, month) in month_has_30_days.iter_mut().enumerate() {
302 *month = other.month_has_30_days(i as u8 + 1)
303 }
304 Self {
305 ny_offset: other.ny_offset(),
306 month_has_30_days,
307 leap_month_idx: other.leap_month_idx(),
308 }
309 }
310 }
311
312 impl From<SerdePackedChineseBasedYearInfo> for PackedChineseBasedYearInfo {
313 fn from(other: SerdePackedChineseBasedYearInfo) -> Self {
314 Self::new(
315 other.month_has_30_days,
316 other.leap_month_idx,
317 other.ny_offset,
318 )
319 }
320 }
321}