1use crate::calendar_arithmetic::PrecomputedDataSource;
34use crate::calendar_arithmetic::{ArithmeticDate, CalendarArithmetic};
35use crate::types::FormattableMonth;
36use crate::AnyCalendarKind;
37use crate::AsCalendar;
38use crate::Iso;
39use crate::{types, Calendar, CalendarError, Date, DateDuration, DateDurationUnit, DateTime, Time};
40use ::tinystr::tinystr;
41use calendrical_calculations::hebrew_keviyah::{Keviyah, YearInfo};
42
43#[derive(Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord, Default)]
65#[allow(clippy::exhaustive_structs)] pub struct Hebrew;
67
68#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
70pub struct HebrewDateInner(ArithmeticDate<Hebrew>);
71
72impl Hebrew {
73 pub fn new() -> Self {
75 Hebrew
76 }
77
78 #[deprecated(since = "1.5.0", note = "Use Hebrew::new()")]
82 pub fn new_always_calculating() -> Self {
83 Hebrew
84 }
85}
86
87#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
88pub(crate) struct HebrewYearInfo {
89 keviyah: Keviyah,
90 prev_keviyah: Keviyah,
91}
92
93impl HebrewYearInfo {
94 #[inline]
99 fn compute(h_year: i32) -> Self {
100 let keviyah = YearInfo::compute_for(h_year).keviyah;
101 Self::compute_with_keviyah(keviyah, h_year)
102 }
103 #[inline]
105 fn compute_with_keviyah(keviyah: Keviyah, h_year: i32) -> Self {
106 let prev_keviyah = YearInfo::compute_for(h_year - 1).keviyah;
107 Self {
108 keviyah,
109 prev_keviyah,
110 }
111 }
112}
113impl CalendarArithmetic for Hebrew {
116 type YearInfo = HebrewYearInfo;
117
118 fn month_days(_h_year: i32, ordinal_month: u8, info: HebrewYearInfo) -> u8 {
119 info.keviyah.month_len(ordinal_month)
120 }
121
122 fn months_for_every_year(_h_year: i32, info: HebrewYearInfo) -> u8 {
123 if info.keviyah.is_leap() {
124 13
125 } else {
126 12
127 }
128 }
129
130 fn days_in_provided_year(_h_year: i32, info: HebrewYearInfo) -> u16 {
131 info.keviyah.year_length()
132 }
133
134 fn is_leap_year(_h_year: i32, info: HebrewYearInfo) -> bool {
135 info.keviyah.is_leap()
136 }
137
138 fn last_month_day_in_year(_h_year: i32, info: HebrewYearInfo) -> (u8, u8) {
139 info.keviyah.last_month_day_in_year()
140 }
141}
142
143impl PrecomputedDataSource<HebrewYearInfo> for () {
144 fn load_or_compute_info(&self, h_year: i32) -> HebrewYearInfo {
145 HebrewYearInfo::compute(h_year)
146 }
147}
148
149impl Calendar for Hebrew {
150 type DateInner = HebrewDateInner;
151
152 fn date_from_codes(
153 &self,
154 era: types::Era,
155 year: i32,
156 month_code: types::MonthCode,
157 day: u8,
158 ) -> Result<Self::DateInner, CalendarError> {
159 let year = if era.0 == tinystr!(16, "hebrew") || era.0 == tinystr!(16, "am") {
160 year
161 } else {
162 return Err(CalendarError::UnknownEra(era.0, self.debug_name()));
163 };
164
165 let year_info = HebrewYearInfo::compute(year);
166
167 let is_leap_year = year_info.keviyah.is_leap();
168
169 let month_code_str = month_code.0.as_str();
170
171 let month_ordinal = if is_leap_year {
172 match month_code_str {
173 "M01" => 1,
174 "M02" => 2,
175 "M03" => 3,
176 "M04" => 4,
177 "M05" => 5,
178 "M05L" => 6,
179 "M06" | "M06L" => 7,
181 "M07" => 8,
182 "M08" => 9,
183 "M09" => 10,
184 "M10" => 11,
185 "M11" => 12,
186 "M12" => 13,
187 _ => {
188 return Err(CalendarError::UnknownMonthCode(
189 month_code.0,
190 self.debug_name(),
191 ))
192 }
193 }
194 } else {
195 match month_code_str {
196 "M01" => 1,
197 "M02" => 2,
198 "M03" => 3,
199 "M04" => 4,
200 "M05" => 5,
201 "M06" => 6,
202 "M07" => 7,
203 "M08" => 8,
204 "M09" => 9,
205 "M10" => 10,
206 "M11" => 11,
207 "M12" => 12,
208 _ => {
209 return Err(CalendarError::UnknownMonthCode(
210 month_code.0,
211 self.debug_name(),
212 ))
213 }
214 }
215 };
216
217 ArithmeticDate::new_from_ordinals_with_info(year, month_ordinal, day, year_info)
218 .map(HebrewDateInner)
219 }
220
221 fn date_from_iso(&self, iso: Date<Iso>) -> Self::DateInner {
222 let fixed_iso = Iso::fixed_from_iso(*iso.inner());
223 let (year_info, h_year) = YearInfo::year_containing_rd(fixed_iso);
224 let day = fixed_iso - year_info.new_year() + 1;
226 let day = u16::try_from(day).unwrap_or(u16::MAX);
227
228 let year_info = HebrewYearInfo::compute_with_keviyah(year_info.keviyah, h_year);
229 let (month, day) = year_info.keviyah.month_day_for(day);
230 HebrewDateInner(ArithmeticDate::new_unchecked_with_info(
231 h_year, month, day, year_info,
232 ))
233 }
234
235 fn date_to_iso(&self, date: &Self::DateInner) -> Date<Iso> {
236 let year_info = date.0.year_info.keviyah.year_info(date.0.year);
237
238 let ny = year_info.new_year();
239 let days_preceding = year_info.keviyah.days_preceding(date.0.month);
240
241 Iso::iso_from_fixed(ny + i64::from(days_preceding) + i64::from(date.0.day) - 1)
243 }
244
245 fn months_in_year(&self, date: &Self::DateInner) -> u8 {
246 date.0.months_in_year()
247 }
248
249 fn days_in_year(&self, date: &Self::DateInner) -> u16 {
250 date.0.days_in_year()
251 }
252
253 fn days_in_month(&self, date: &Self::DateInner) -> u8 {
254 date.0.days_in_month()
255 }
256
257 fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
258 date.0.offset_date(offset, &())
259 }
260
261 fn until(
262 &self,
263 date1: &Self::DateInner,
264 date2: &Self::DateInner,
265 _calendar2: &Self,
266 _largest_unit: DateDurationUnit,
267 _smallest_unit: DateDurationUnit,
268 ) -> DateDuration<Self> {
269 date1.0.until(date2.0, _largest_unit, _smallest_unit)
270 }
271
272 fn debug_name(&self) -> &'static str {
273 "Hebrew"
274 }
275
276 fn year(&self, date: &Self::DateInner) -> types::FormattableYear {
277 Self::year_as_hebrew(date.0.year)
278 }
279
280 fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
281 Self::is_leap_year(date.0.year, date.0.year_info)
282 }
283
284 fn month(&self, date: &Self::DateInner) -> FormattableMonth {
285 let mut ordinal = date.0.month;
286 let is_leap_year = Self::is_leap_year(date.0.year, date.0.year_info);
287
288 if is_leap_year {
289 if ordinal == 6 {
290 return types::FormattableMonth {
291 ordinal: ordinal as u32,
292 code: types::MonthCode(tinystr!(4, "M05L")),
293 };
294 } else if ordinal == 7 {
295 return types::FormattableMonth {
296 ordinal: ordinal as u32,
297 code: types::MonthCode(tinystr!(4, "M06L")),
298 };
299 }
300 }
301
302 if is_leap_year && ordinal > 6 {
303 ordinal -= 1;
304 }
305
306 let code = match ordinal {
307 1 => tinystr!(4, "M01"),
308 2 => tinystr!(4, "M02"),
309 3 => tinystr!(4, "M03"),
310 4 => tinystr!(4, "M04"),
311 5 => tinystr!(4, "M05"),
312 6 => tinystr!(4, "M06"),
313 7 => tinystr!(4, "M07"),
314 8 => tinystr!(4, "M08"),
315 9 => tinystr!(4, "M09"),
316 10 => tinystr!(4, "M10"),
317 11 => tinystr!(4, "M11"),
318 12 => tinystr!(4, "M12"),
319 _ => tinystr!(4, "und"),
320 };
321
322 types::FormattableMonth {
323 ordinal: date.0.month as u32,
324 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_info(&self, date: &Self::DateInner) -> types::DayOfYearInfo {
333 let prev_year = date.0.year.saturating_sub(1);
334 let next_year = date.0.year.saturating_add(1);
335 types::DayOfYearInfo {
336 day_of_year: date.0.day_of_year(),
337 days_in_year: date.0.days_in_year(),
338 prev_year: Self::year_as_hebrew(prev_year),
339 days_in_prev_year: date.0.year_info.prev_keviyah.year_length(),
340 next_year: Self::year_as_hebrew(next_year),
341 }
342 }
343 fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
344 Some(AnyCalendarKind::Hebrew)
345 }
346}
347
348impl Hebrew {
349 fn year_as_hebrew(civil_year: i32) -> types::FormattableYear {
350 types::FormattableYear {
351 era: types::Era(tinystr!(16, "hebrew")),
352 number: civil_year,
353 cyclic: None,
354 related_iso: None,
355 }
356 }
357}
358
359impl Date<Hebrew> {
360 pub fn try_new_hebrew_date(
377 year: i32,
378 month: u8,
379 day: u8,
380 ) -> Result<Date<Hebrew>, CalendarError> {
381 let year_info = HebrewYearInfo::compute(year);
382
383 ArithmeticDate::new_from_ordinals_with_info(year, month, day, year_info)
384 .map(HebrewDateInner)
385 .map(|inner| Date::from_raw(inner, Hebrew))
386 }
387}
388
389impl<A: AsCalendar<Calendar = Hebrew>> Date<A> {
390 #[deprecated(since = "1.5.0", note = "Use Date::try_new_hebrew_date()")]
395 pub fn try_new_hebrew_date_with_calendar(
396 year: i32,
397 month: u8,
398 day: u8,
399 calendar: A,
400 ) -> Result<Date<A>, CalendarError> {
401 let year_info = HebrewYearInfo::compute(year);
402
403 ArithmeticDate::new_from_ordinals_with_info(year, month, day, year_info)
404 .map(HebrewDateInner)
405 .map(|inner| Date::from_raw(inner, calendar))
406 }
407}
408
409impl DateTime<Hebrew> {
410 pub fn try_new_hebrew_datetime(
427 year: i32,
428 month: u8,
429 day: u8,
430 hour: u8,
431 minute: u8,
432 second: u8,
433 ) -> Result<DateTime<Hebrew>, CalendarError> {
434 Ok(DateTime {
435 date: Date::try_new_hebrew_date(year, month, day)?,
436 time: Time::try_new(hour, minute, second, 0)?,
437 })
438 }
439}
440
441impl<A: AsCalendar<Calendar = Hebrew>> DateTime<A> {
442 #[deprecated(since = "1.5.0", note = "Use DateTime::try_new_hebrew_datetime()")]
447 pub fn try_new_hebrew_datetime_with_calendar(
448 year: i32,
449 month: u8,
450 day: u8,
451 hour: u8,
452 minute: u8,
453 second: u8,
454 calendar: A,
455 ) -> Result<DateTime<A>, CalendarError> {
456 #[allow(deprecated)]
457 Ok(DateTime {
458 date: Date::try_new_hebrew_date_with_calendar(year, month, day, calendar)?,
459 time: Time::try_new(hour, minute, second, 0)?,
460 })
461 }
462}
463
464#[cfg(test)]
465mod tests {
466
467 use super::*;
468 use crate::types::{Era, MonthCode};
469 use calendrical_calculations::hebrew_keviyah::*;
470
471 const ADARI: u8 = 13;
476
477 const LEAP_YEARS_IN_TESTS: [i32; 1] = [5782];
479 #[allow(clippy::type_complexity)]
483 const ISO_HEBREW_DATE_PAIRS: [((i32, u8, u8), (i32, u8, u8)); 48] = [
484 ((2021, 1, 10), (5781, TEVET, 26)),
485 ((2021, 1, 25), (5781, SHEVAT, 12)),
486 ((2021, 2, 10), (5781, SHEVAT, 28)),
487 ((2021, 2, 25), (5781, ADAR, 13)),
488 ((2021, 3, 10), (5781, ADAR, 26)),
489 ((2021, 3, 25), (5781, NISAN, 12)),
490 ((2021, 4, 10), (5781, NISAN, 28)),
491 ((2021, 4, 25), (5781, IYYAR, 13)),
492 ((2021, 5, 10), (5781, IYYAR, 28)),
493 ((2021, 5, 25), (5781, SIVAN, 14)),
494 ((2021, 6, 10), (5781, SIVAN, 30)),
495 ((2021, 6, 25), (5781, TAMMUZ, 15)),
496 ((2021, 7, 10), (5781, AV, 1)),
497 ((2021, 7, 25), (5781, AV, 16)),
498 ((2021, 8, 10), (5781, ELUL, 2)),
499 ((2021, 8, 25), (5781, ELUL, 17)),
500 ((2021, 9, 10), (5782, TISHREI, 4)),
501 ((2021, 9, 25), (5782, TISHREI, 19)),
502 ((2021, 10, 10), (5782, ḤESHVAN, 4)),
503 ((2021, 10, 25), (5782, ḤESHVAN, 19)),
504 ((2021, 11, 10), (5782, KISLEV, 6)),
505 ((2021, 11, 25), (5782, KISLEV, 21)),
506 ((2021, 12, 10), (5782, TEVET, 6)),
507 ((2021, 12, 25), (5782, TEVET, 21)),
508 ((2022, 1, 10), (5782, SHEVAT, 8)),
509 ((2022, 1, 25), (5782, SHEVAT, 23)),
510 ((2022, 2, 10), (5782, ADARI, 9)),
511 ((2022, 2, 25), (5782, ADARI, 24)),
512 ((2022, 3, 10), (5782, ADAR, 7)),
513 ((2022, 3, 25), (5782, ADAR, 22)),
514 ((2022, 4, 10), (5782, NISAN, 9)),
515 ((2022, 4, 25), (5782, NISAN, 24)),
516 ((2022, 5, 10), (5782, IYYAR, 9)),
517 ((2022, 5, 25), (5782, IYYAR, 24)),
518 ((2022, 6, 10), (5782, SIVAN, 11)),
519 ((2022, 6, 25), (5782, SIVAN, 26)),
520 ((2022, 7, 10), (5782, TAMMUZ, 11)),
521 ((2022, 7, 25), (5782, TAMMUZ, 26)),
522 ((2022, 8, 10), (5782, AV, 13)),
523 ((2022, 8, 25), (5782, AV, 28)),
524 ((2022, 9, 10), (5782, ELUL, 14)),
525 ((2022, 9, 25), (5782, ELUL, 29)),
526 ((2022, 10, 10), (5783, TISHREI, 15)),
527 ((2022, 10, 25), (5783, TISHREI, 30)),
528 ((2022, 11, 10), (5783, ḤESHVAN, 16)),
529 ((2022, 11, 25), (5783, KISLEV, 1)),
530 ((2022, 12, 10), (5783, KISLEV, 16)),
531 ((2022, 12, 25), (5783, TEVET, 1)),
532 ];
533
534 #[test]
535 fn test_conversions() {
536 for ((iso_y, iso_m, iso_d), (y, m, d)) in ISO_HEBREW_DATE_PAIRS.into_iter() {
537 let iso_date = Date::try_new_iso_date(iso_y, iso_m, iso_d).unwrap();
538 let month_code = if m == ADARI {
539 MonthCode(tinystr!(4, "M05L"))
540 } else {
541 MonthCode::new_normal(m).unwrap()
542 };
543 let hebrew_date =
544 Date::try_new_from_codes(tinystr!(16, "am").into(), y, month_code, d, Hebrew)
545 .expect("Date should parse");
546
547 let iso_to_hebrew = iso_date.to_calendar(Hebrew);
548
549 let hebrew_to_iso = hebrew_date.to_calendar(Iso);
550
551 assert_eq!(
552 hebrew_to_iso, iso_date,
553 "Failed comparing to-ISO value for {hebrew_date:?} => {iso_date:?}"
554 );
555 assert_eq!(
556 iso_to_hebrew, hebrew_date,
557 "Failed comparing to-hebrew value for {iso_date:?} => {hebrew_date:?}"
558 );
559
560 let ordinal_month = if LEAP_YEARS_IN_TESTS.contains(&y) {
561 if m == ADARI {
562 ADAR
563 } else if m >= ADAR {
564 m + 1
565 } else {
566 m
567 }
568 } else {
569 assert!(m != ADARI);
570 m
571 };
572
573 let ordinal_hebrew_date = Date::try_new_hebrew_date(y, ordinal_month, d)
574 .expect("Construction of date must succeed");
575
576 assert_eq!(ordinal_hebrew_date, hebrew_date, "Hebrew date construction from codes and ordinals should work the same for {hebrew_date:?}");
577 }
578 }
579
580 #[test]
581 fn test_icu_bug_22441() {
582 let yi = YearInfo::compute_for(88369);
583 assert_eq!(yi.keviyah.year_length(), 383);
584 }
585
586 #[test]
587 fn test_weekdays() {
588 let cal = Hebrew::new();
590 let era = "am".parse::<Era>().unwrap();
591 let month_code = "M01".parse::<MonthCode>().unwrap();
592 let dt = cal.date_from_codes(era, 3760, month_code, 1).unwrap();
593
594 assert_eq!(6, cal.day_of_week(&dt) as usize);
597 }
598}