1use crate::cal::iso::{Iso, IsoDateInner};
19use crate::calendar_arithmetic::PrecomputedDataSource;
20use crate::calendar_arithmetic::{ArithmeticDate, CalendarArithmetic};
21use crate::error::DateError;
22use crate::types::MonthInfo;
23use crate::RangeError;
24use crate::{types, Calendar, Date, DateDuration, DateDurationUnit};
25use ::tinystr::tinystr;
26use calendrical_calculations::hebrew_keviyah::{Keviyah, YearInfo};
27use calendrical_calculations::rata_die::RataDie;
28
29#[derive(Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord, Default)]
49#[allow(clippy::exhaustive_structs)] pub struct Hebrew;
51
52#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
54pub struct HebrewDateInner(ArithmeticDate<Hebrew>);
55
56impl Hebrew {
57 pub fn new() -> Self {
59 Hebrew
60 }
61}
62
63#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
64pub(crate) struct HebrewYearInfo {
65 keviyah: Keviyah,
66 prev_keviyah: Keviyah,
67 value: i32,
68}
69
70impl From<HebrewYearInfo> for i32 {
71 fn from(value: HebrewYearInfo) -> Self {
72 value.value
73 }
74}
75
76impl HebrewYearInfo {
77 #[inline]
82 fn compute(h_year: i32) -> Self {
83 let keviyah = YearInfo::compute_for(h_year).keviyah;
84 Self::compute_with_keviyah(keviyah, h_year)
85 }
86 #[inline]
88 fn compute_with_keviyah(keviyah: Keviyah, h_year: i32) -> Self {
89 let prev_keviyah = YearInfo::compute_for(h_year - 1).keviyah;
90 Self {
91 keviyah,
92 prev_keviyah,
93 value: h_year,
94 }
95 }
96}
97impl CalendarArithmetic for Hebrew {
100 type YearInfo = HebrewYearInfo;
101
102 fn days_in_provided_month(info: HebrewYearInfo, ordinal_month: u8) -> u8 {
103 info.keviyah.month_len(ordinal_month)
104 }
105
106 fn months_in_provided_year(info: HebrewYearInfo) -> u8 {
107 if info.keviyah.is_leap() {
108 13
109 } else {
110 12
111 }
112 }
113
114 fn days_in_provided_year(info: HebrewYearInfo) -> u16 {
115 info.keviyah.year_length()
116 }
117
118 fn provided_year_is_leap(info: HebrewYearInfo) -> bool {
119 info.keviyah.is_leap()
120 }
121
122 fn last_month_day_in_provided_year(info: HebrewYearInfo) -> (u8, u8) {
123 info.keviyah.last_month_day_in_year()
124 }
125}
126
127impl PrecomputedDataSource<HebrewYearInfo> for () {
128 fn load_or_compute_info(&self, h_year: i32) -> HebrewYearInfo {
129 HebrewYearInfo::compute(h_year)
130 }
131}
132
133impl crate::cal::scaffold::UnstableSealed for Hebrew {}
134impl Calendar for Hebrew {
135 type DateInner = HebrewDateInner;
136 type Year = types::EraYear;
137
138 fn from_codes(
139 &self,
140 era: Option<&str>,
141 year: i32,
142 month_code: types::MonthCode,
143 day: u8,
144 ) -> Result<Self::DateInner, DateError> {
145 match era {
146 Some("am") | None => {}
147 _ => return Err(DateError::UnknownEra),
148 }
149
150 let year = HebrewYearInfo::compute(year);
151
152 let is_leap_year = year.keviyah.is_leap();
153
154 let month_code_str = month_code.0.as_str();
155
156 let month_ordinal = if is_leap_year {
157 match month_code_str {
158 "M01" => 1,
159 "M02" => 2,
160 "M03" => 3,
161 "M04" => 4,
162 "M05" => 5,
163 "M05L" => 6,
164 "M06" | "M06L" => 7,
166 "M07" => 8,
167 "M08" => 9,
168 "M09" => 10,
169 "M10" => 11,
170 "M11" => 12,
171 "M12" => 13,
172 _ => {
173 return Err(DateError::UnknownMonthCode(month_code));
174 }
175 }
176 } else {
177 match month_code_str {
178 "M01" => 1,
179 "M02" => 2,
180 "M03" => 3,
181 "M04" => 4,
182 "M05" => 5,
183 "M06" => 6,
184 "M07" => 7,
185 "M08" => 8,
186 "M09" => 9,
187 "M10" => 10,
188 "M11" => 11,
189 "M12" => 12,
190 _ => {
191 return Err(DateError::UnknownMonthCode(month_code));
192 }
193 }
194 };
195
196 Ok(HebrewDateInner(ArithmeticDate::new_from_ordinals(
197 year,
198 month_ordinal,
199 day,
200 )?))
201 }
202
203 fn from_rata_die(&self, rd: RataDie) -> Self::DateInner {
204 let (year, h_year) = YearInfo::year_containing_rd(rd);
205 let day = rd - year.new_year() + 1;
207 let day = u16::try_from(day).unwrap_or(u16::MAX);
208
209 let year = HebrewYearInfo::compute_with_keviyah(year.keviyah, h_year);
210 let (month, day) = year.keviyah.month_day_for(day);
211 HebrewDateInner(ArithmeticDate::new_unchecked(year, month, day))
212 }
213
214 fn to_rata_die(&self, date: &Self::DateInner) -> RataDie {
215 let year = date.0.year.keviyah.year_info(date.0.year.value);
216
217 let ny = year.new_year();
218 let days_preceding = year.keviyah.days_preceding(date.0.month);
219
220 ny + i64::from(days_preceding) + i64::from(date.0.day) - 1
222 }
223
224 fn from_iso(&self, iso: IsoDateInner) -> Self::DateInner {
225 self.from_rata_die(Iso.to_rata_die(&iso))
226 }
227
228 fn to_iso(&self, date: &Self::DateInner) -> IsoDateInner {
229 Iso.from_rata_die(self.to_rata_die(date))
230 }
231
232 fn months_in_year(&self, date: &Self::DateInner) -> u8 {
233 date.0.months_in_year()
234 }
235
236 fn days_in_year(&self, date: &Self::DateInner) -> u16 {
237 date.0.days_in_year()
238 }
239
240 fn days_in_month(&self, date: &Self::DateInner) -> u8 {
241 date.0.days_in_month()
242 }
243
244 fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
245 date.0.offset_date(offset, &())
246 }
247
248 fn until(
249 &self,
250 date1: &Self::DateInner,
251 date2: &Self::DateInner,
252 _calendar2: &Self,
253 _largest_unit: DateDurationUnit,
254 _smallest_unit: DateDurationUnit,
255 ) -> DateDuration<Self> {
256 date1.0.until(date2.0, _largest_unit, _smallest_unit)
257 }
258
259 fn debug_name(&self) -> &'static str {
260 "Hebrew"
261 }
262
263 fn year_info(&self, date: &Self::DateInner) -> Self::Year {
264 types::EraYear {
265 era_index: Some(0),
266 era: tinystr!(16, "am"),
267 year: self.extended_year(date),
268 ambiguity: types::YearAmbiguity::CenturyRequired,
269 }
270 }
271
272 fn extended_year(&self, date: &Self::DateInner) -> i32 {
273 date.0.extended_year()
274 }
275
276 fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
277 Self::provided_year_is_leap(date.0.year)
278 }
279
280 fn month(&self, date: &Self::DateInner) -> MonthInfo {
281 let mut ordinal = date.0.month;
282 let is_leap_year = Self::provided_year_is_leap(date.0.year);
283
284 if is_leap_year {
285 if ordinal == 6 {
286 return types::MonthInfo {
287 ordinal,
288 standard_code: types::MonthCode(tinystr!(4, "M05L")),
289 formatting_code: types::MonthCode(tinystr!(4, "M05L")),
290 };
291 } else if ordinal == 7 {
292 return types::MonthInfo {
293 ordinal,
294 standard_code: types::MonthCode(tinystr!(4, "M06")),
296 formatting_code: types::MonthCode(tinystr!(4, "M06L")),
297 };
298 }
299 }
300
301 if is_leap_year && ordinal > 6 {
302 ordinal -= 1;
303 }
304
305 let code = match ordinal {
306 1 => tinystr!(4, "M01"),
307 2 => tinystr!(4, "M02"),
308 3 => tinystr!(4, "M03"),
309 4 => tinystr!(4, "M04"),
310 5 => tinystr!(4, "M05"),
311 6 => tinystr!(4, "M06"),
312 7 => tinystr!(4, "M07"),
313 8 => tinystr!(4, "M08"),
314 9 => tinystr!(4, "M09"),
315 10 => tinystr!(4, "M10"),
316 11 => tinystr!(4, "M11"),
317 12 => tinystr!(4, "M12"),
318 _ => tinystr!(4, "und"),
319 };
320
321 types::MonthInfo {
322 ordinal: date.0.month,
323 standard_code: types::MonthCode(code),
324 formatting_code: types::MonthCode(code),
325 }
326 }
327
328 fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
329 date.0.day_of_month()
330 }
331
332 fn day_of_year(&self, date: &Self::DateInner) -> types::DayOfYear {
333 date.0.day_of_year()
334 }
335
336 fn calendar_algorithm(&self) -> Option<crate::preferences::CalendarAlgorithm> {
337 Some(crate::preferences::CalendarAlgorithm::Hebrew)
338 }
339}
340
341impl Date<Hebrew> {
342 pub fn try_new_hebrew(year: i32, month: u8, day: u8) -> Result<Date<Hebrew>, RangeError> {
359 let year = HebrewYearInfo::compute(year);
360
361 ArithmeticDate::new_from_ordinals(year, month, day)
362 .map(HebrewDateInner)
363 .map(|inner| Date::from_raw(inner, Hebrew))
364 }
365}
366
367#[cfg(test)]
368mod tests {
369
370 use super::*;
371 use crate::types::MonthCode;
372 use calendrical_calculations::hebrew_keviyah::*;
373
374 const ADARI: u8 = 13;
379
380 const LEAP_YEARS_IN_TESTS: [i32; 1] = [5782];
382 #[allow(clippy::type_complexity)]
386 const ISO_HEBREW_DATE_PAIRS: [((i32, u8, u8), (i32, u8, u8)); 48] = [
387 ((2021, 1, 10), (5781, TEVET, 26)),
388 ((2021, 1, 25), (5781, SHEVAT, 12)),
389 ((2021, 2, 10), (5781, SHEVAT, 28)),
390 ((2021, 2, 25), (5781, ADAR, 13)),
391 ((2021, 3, 10), (5781, ADAR, 26)),
392 ((2021, 3, 25), (5781, NISAN, 12)),
393 ((2021, 4, 10), (5781, NISAN, 28)),
394 ((2021, 4, 25), (5781, IYYAR, 13)),
395 ((2021, 5, 10), (5781, IYYAR, 28)),
396 ((2021, 5, 25), (5781, SIVAN, 14)),
397 ((2021, 6, 10), (5781, SIVAN, 30)),
398 ((2021, 6, 25), (5781, TAMMUZ, 15)),
399 ((2021, 7, 10), (5781, AV, 1)),
400 ((2021, 7, 25), (5781, AV, 16)),
401 ((2021, 8, 10), (5781, ELUL, 2)),
402 ((2021, 8, 25), (5781, ELUL, 17)),
403 ((2021, 9, 10), (5782, TISHREI, 4)),
404 ((2021, 9, 25), (5782, TISHREI, 19)),
405 ((2021, 10, 10), (5782, ḤESHVAN, 4)),
406 ((2021, 10, 25), (5782, ḤESHVAN, 19)),
407 ((2021, 11, 10), (5782, KISLEV, 6)),
408 ((2021, 11, 25), (5782, KISLEV, 21)),
409 ((2021, 12, 10), (5782, TEVET, 6)),
410 ((2021, 12, 25), (5782, TEVET, 21)),
411 ((2022, 1, 10), (5782, SHEVAT, 8)),
412 ((2022, 1, 25), (5782, SHEVAT, 23)),
413 ((2022, 2, 10), (5782, ADARI, 9)),
414 ((2022, 2, 25), (5782, ADARI, 24)),
415 ((2022, 3, 10), (5782, ADAR, 7)),
416 ((2022, 3, 25), (5782, ADAR, 22)),
417 ((2022, 4, 10), (5782, NISAN, 9)),
418 ((2022, 4, 25), (5782, NISAN, 24)),
419 ((2022, 5, 10), (5782, IYYAR, 9)),
420 ((2022, 5, 25), (5782, IYYAR, 24)),
421 ((2022, 6, 10), (5782, SIVAN, 11)),
422 ((2022, 6, 25), (5782, SIVAN, 26)),
423 ((2022, 7, 10), (5782, TAMMUZ, 11)),
424 ((2022, 7, 25), (5782, TAMMUZ, 26)),
425 ((2022, 8, 10), (5782, AV, 13)),
426 ((2022, 8, 25), (5782, AV, 28)),
427 ((2022, 9, 10), (5782, ELUL, 14)),
428 ((2022, 9, 25), (5782, ELUL, 29)),
429 ((2022, 10, 10), (5783, TISHREI, 15)),
430 ((2022, 10, 25), (5783, TISHREI, 30)),
431 ((2022, 11, 10), (5783, ḤESHVAN, 16)),
432 ((2022, 11, 25), (5783, KISLEV, 1)),
433 ((2022, 12, 10), (5783, KISLEV, 16)),
434 ((2022, 12, 25), (5783, TEVET, 1)),
435 ];
436
437 #[test]
438 fn test_conversions() {
439 for ((iso_y, iso_m, iso_d), (y, m, d)) in ISO_HEBREW_DATE_PAIRS.into_iter() {
440 let iso_date = Date::try_new_iso(iso_y, iso_m, iso_d).unwrap();
441 let month_code = if m == ADARI {
442 MonthCode(tinystr!(4, "M05L"))
443 } else {
444 MonthCode::new_normal(m).unwrap()
445 };
446 let hebrew_date = Date::try_new_from_codes(Some("am"), y, month_code, d, Hebrew)
447 .expect("Date should parse");
448
449 let iso_to_hebrew = iso_date.to_calendar(Hebrew);
450
451 let hebrew_to_iso = hebrew_date.to_calendar(Iso);
452
453 assert_eq!(
454 hebrew_to_iso, iso_date,
455 "Failed comparing to-ISO value for {hebrew_date:?} => {iso_date:?}"
456 );
457 assert_eq!(
458 iso_to_hebrew, hebrew_date,
459 "Failed comparing to-hebrew value for {iso_date:?} => {hebrew_date:?}"
460 );
461
462 let ordinal_month = if LEAP_YEARS_IN_TESTS.contains(&y) {
463 if m == ADARI {
464 ADAR
465 } else if m >= ADAR {
466 m + 1
467 } else {
468 m
469 }
470 } else {
471 assert!(m != ADARI);
472 m
473 };
474
475 let ordinal_hebrew_date = Date::try_new_hebrew(y, ordinal_month, d)
476 .expect("Construction of date must succeed");
477
478 assert_eq!(ordinal_hebrew_date, hebrew_date, "Hebrew date construction from codes and ordinals should work the same for {hebrew_date:?}");
479 }
480 }
481
482 #[test]
483 fn test_icu_bug_22441() {
484 let yi = YearInfo::compute_for(88369);
485 assert_eq!(yi.keviyah.year_length(), 383);
486 }
487
488 #[test]
489 fn test_negative_era_years() {
490 let greg_date = Date::try_new_gregorian(-5000, 1, 1).unwrap();
491 let greg_year = greg_date.era_year();
492 assert_eq!(greg_date.inner.0 .0.year, -5000);
493 assert_eq!(greg_year.era, "bce");
494 assert_eq!(greg_year.year, 5001);
496 let hebr_date = greg_date.to_calendar(Hebrew);
497 let hebr_year = hebr_date.era_year();
498 assert_eq!(hebr_date.inner.0.year.value, -1240);
499 assert_eq!(hebr_year.era, "am");
500 assert_eq!(hebr_year.year, -1240);
502 }
503
504 #[test]
505 fn test_weekdays() {
506 let cal = Hebrew::new();
508 let era = "am";
509 let month_code = MonthCode(tinystr!(4, "M01"));
510 let dt = Date::try_new_from_codes(Some(era), 3760, month_code, 1, cal).unwrap();
511
512 assert_eq!(6, dt.day_of_week() as usize);
515 }
516}