1use crate::helpers::{final_func, i64_to_i32, next_u8};
11use crate::rata_die::{Moment, RataDie};
12#[allow(unused_imports)]
13use core_maths::*;
14
15pub const HEBREW_EPOCH: RataDie = crate::julian::fixed_from_julian_book_version(-3761, 10, 7);
17
18#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq, PartialOrd, Ord)]
21#[allow(clippy::exhaustive_structs)]
22pub struct BookHebrew {
23 pub year: i32,
25 pub month: u8,
27 pub day: u8,
29}
30
31pub const NISAN: u8 = 1;
34pub const IYYAR: u8 = 2;
36pub const SIVAN: u8 = 3;
38pub const TAMMUZ: u8 = 4;
40pub const AV: u8 = 5;
42pub const ELUL: u8 = 6;
44pub const TISHRI: u8 = 7;
46pub const MARHESHVAN: u8 = 8;
48pub const KISLEV: u8 = 9;
50pub const TEVET: u8 = 10;
52pub const SHEVAT: u8 = 11;
54pub const ADAR: u8 = 12;
56pub const ADARII: u8 = 13;
58
59impl BookHebrew {
62 pub fn to_civil_date(self) -> (i32, u8, u8) {
65 let biblical_month = self.month;
66 let biblical_year = self.year;
67 let mut civil_month;
68 civil_month = (biblical_month + 6) % 12;
69
70 if civil_month == 0 {
71 civil_month = 12;
72 }
73
74 if Self::is_hebrew_leap_year(biblical_year) && biblical_month < TISHRI {
75 civil_month += 1;
76 }
77 (biblical_year, civil_month, self.day)
78 }
79
80 pub fn from_civil_date(civil_year: i32, civil_month: u8, civil_day: u8) -> Self {
83 let mut biblical_month;
84
85 if civil_month <= 6 {
86 biblical_month = civil_month + 6; } else {
88 biblical_month = civil_month - 6; if Self::is_hebrew_leap_year(civil_year) {
90 biblical_month -= 1
91 }
92 if biblical_month == 0 {
93 biblical_month = 13;
95 }
96 }
97
98 BookHebrew {
99 year: civil_year,
100 month: biblical_month,
101 day: civil_day,
102 }
103 }
104 #[allow(dead_code)]
107 pub(crate) fn molad(book_year: i32, book_month: u8) -> Moment {
108 let y = if book_month < TISHRI {
109 book_year + 1
110 } else {
111 book_year
112 }; let months_elapsed = (book_month as f64 - TISHRI as f64) + ((235.0 * y as f64 - 234.0) / 19.0).floor(); Moment::new(
118 HEBREW_EPOCH.to_f64_date() - (876.0 / 25920.0)
119 + months_elapsed * (29.0 + (1.0 / 2.0) + (793.0 / 25920.0)),
120 )
121 }
122
123 #[allow(dead_code)]
126 fn last_month_of_book_hebrew_year(book_year: i32) -> u8 {
127 if Self::is_hebrew_leap_year(book_year) {
128 ADARII
129 } else {
130 ADAR
131 }
132 }
133
134 fn book_hebrew_calendar_elapsed_days(book_year: i32) -> i32 {
137 let months_elapsed = ((235.0 * book_year as f64 - 234.0) / 19.0).floor() as i64;
138 let parts_elapsed = 12084 + 13753 * months_elapsed;
139 let days = 29 * months_elapsed + (parts_elapsed as f64 / 25920.0).floor() as i64;
140
141 if (3 * (days + 1)).rem_euclid(7) < 3 {
142 days as i32 + 1
143 } else {
144 days as i32
145 }
146 }
147
148 fn book_hebrew_year_length_correction(book_year: i32) -> u8 {
151 let ny0 = Self::book_hebrew_calendar_elapsed_days(book_year - 1);
152 let ny1 = Self::book_hebrew_calendar_elapsed_days(book_year);
153 let ny2 = Self::book_hebrew_calendar_elapsed_days(book_year + 1);
154
155 if (ny2 - ny1) == 356 {
156 2
157 } else if (ny1 - ny0) == 382 {
158 1
159 } else {
160 0
161 }
162 }
163
164 pub fn book_hebrew_new_year(book_year: i32) -> RataDie {
167 RataDie::new(
168 HEBREW_EPOCH.to_i64_date()
169 + Self::book_hebrew_calendar_elapsed_days(book_year) as i64
170 + Self::book_hebrew_year_length_correction(book_year) as i64,
171 )
172 }
173
174 pub fn days_in_book_hebrew_year(book_year: i32) -> u16 {
176 (Self::book_hebrew_new_year(1 + book_year) - Self::book_hebrew_new_year(book_year)) as u16
177 }
178
179 pub fn is_hebrew_leap_year(book_year: i32) -> bool {
181 (7 * book_year + 1).rem_euclid(19) < 7
182 }
183
184 #[allow(dead_code)]
187 fn is_long_marheshvan(book_year: i32) -> bool {
188 let long_marheshavan_year_lengths = [355, 385];
189 long_marheshavan_year_lengths.contains(&Self::days_in_book_hebrew_year(book_year))
190 }
191
192 #[allow(dead_code)]
195 fn is_short_kislev(book_year: i32) -> bool {
196 let short_kislev_year_lengths = [353, 383];
197 short_kislev_year_lengths.contains(&Self::days_in_book_hebrew_year(book_year))
198 }
199
200 pub fn last_day_of_book_hebrew_month(book_year: i32, book_month: u8) -> u8 {
203 match book_month {
204 IYYAR | TAMMUZ | ELUL | TEVET | ADARII => 29,
205 ADAR => {
206 if !Self::is_hebrew_leap_year(book_year) {
207 29
208 } else {
209 30
210 }
211 }
212 MARHESHVAN => {
213 if !Self::is_long_marheshvan(book_year) {
214 29
215 } else {
216 30
217 }
218 }
219 KISLEV => {
220 if Self::is_short_kislev(book_year) {
221 29
222 } else {
223 30
224 }
225 }
226 _ => 30,
227 }
228 }
229
230 pub fn fixed_from_book_hebrew(date: BookHebrew) -> RataDie {
232 let book_year = date.year;
233 let book_month = date.month;
234 let book_day = date.day;
235
236 let mut total_days = Self::book_hebrew_new_year(book_year) + book_day.into() - 1; if book_month < TISHRI {
239 for m in
241 (TISHRI..=Self::last_month_of_book_hebrew_year(book_year)).chain(NISAN..book_month)
242 {
243 total_days += Self::last_day_of_book_hebrew_month(book_year, m).into();
244 }
245 } else {
246 for m in TISHRI..book_month {
248 total_days += Self::last_day_of_book_hebrew_month(book_year, m).into();
249 }
250 }
251
252 total_days
253 }
254
255 pub fn book_hebrew_from_fixed(date: RataDie) -> BookHebrew {
257 let approx = i64_to_i32(
258 1 + ((date - HEBREW_EPOCH) as f64).div_euclid(35975351.0 / 98496.0) as i64, )
260 .unwrap_or_else(|e| e.saturate());
261
262 let year_condition = |year: i32| Self::book_hebrew_new_year(year) <= date;
264 let year = final_func(approx - 1, year_condition);
265
266 let start = if date
268 < Self::fixed_from_book_hebrew(BookHebrew {
269 year,
270 month: NISAN,
271 day: 1,
272 }) {
273 TISHRI
274 } else {
275 NISAN
276 };
277
278 let month_condition = |m: u8| {
279 date <= Self::fixed_from_book_hebrew(BookHebrew {
280 year,
281 month: m,
282 day: Self::last_day_of_book_hebrew_month(year, m),
283 })
284 };
285 let month = next_u8(start, month_condition);
287
288 let day = (date
290 - Self::fixed_from_book_hebrew(BookHebrew {
291 year,
292 month,
293 day: 1,
294 }))
295 + 1;
296
297 BookHebrew {
298 year,
299 month,
300 day: day as u8,
301 }
302 }
303}
304
305#[cfg(test)]
306mod tests {
307 use super::*;
308
309 #[derive(Debug)]
310 struct DateCase {
311 year: i32,
312 month: u8,
313 day: u8,
314 }
315
316 static TEST_FIXED_DATE: [i64; 33] = [
317 -214193, -61387, 25469, 49217, 171307, 210155, 253427, 369740, 400085, 434355, 452605,
318 470160, 473837, 507850, 524156, 544676, 567118, 569477, 601716, 613424, 626596, 645554,
319 664224, 671401, 694799, 704424, 708842, 709409, 709580, 727274, 728714, 744313, 764652,
320 ];
321
322 static HEBREW_DATES: [DateCase; 33] = [
323 DateCase {
324 year: 3174,
325 month: 5,
326 day: 10,
327 },
328 DateCase {
329 year: 3593,
330 month: 9,
331 day: 25,
332 },
333 DateCase {
334 year: 3831,
335 month: 7,
336 day: 3,
337 },
338 DateCase {
339 year: 3896,
340 month: 7,
341 day: 9,
342 },
343 DateCase {
344 year: 4230,
345 month: 10,
346 day: 18,
347 },
348 DateCase {
349 year: 4336,
350 month: 3,
351 day: 4,
352 },
353 DateCase {
354 year: 4455,
355 month: 8,
356 day: 13,
357 },
358 DateCase {
359 year: 4773,
360 month: 2,
361 day: 6,
362 },
363 DateCase {
364 year: 4856,
365 month: 2,
366 day: 23,
367 },
368 DateCase {
369 year: 4950,
370 month: 1,
371 day: 7,
372 },
373 DateCase {
374 year: 5000,
375 month: 13,
376 day: 8,
377 },
378 DateCase {
379 year: 5048,
380 month: 1,
381 day: 21,
382 },
383 DateCase {
384 year: 5058,
385 month: 2,
386 day: 7,
387 },
388 DateCase {
389 year: 5151,
390 month: 4,
391 day: 1,
392 },
393 DateCase {
394 year: 5196,
395 month: 11,
396 day: 7,
397 },
398 DateCase {
399 year: 5252,
400 month: 1,
401 day: 3,
402 },
403 DateCase {
404 year: 5314,
405 month: 7,
406 day: 1,
407 },
408 DateCase {
409 year: 5320,
410 month: 12,
411 day: 27,
412 },
413 DateCase {
414 year: 5408,
415 month: 3,
416 day: 20,
417 },
418 DateCase {
419 year: 5440,
420 month: 4,
421 day: 3,
422 },
423 DateCase {
424 year: 5476,
425 month: 5,
426 day: 5,
427 },
428 DateCase {
429 year: 5528,
430 month: 4,
431 day: 4,
432 },
433 DateCase {
434 year: 5579,
435 month: 5,
436 day: 11,
437 },
438 DateCase {
439 year: 5599,
440 month: 1,
441 day: 12,
442 },
443 DateCase {
444 year: 5663,
445 month: 1,
446 day: 22,
447 },
448 DateCase {
449 year: 5689,
450 month: 5,
451 day: 19,
452 },
453 DateCase {
454 year: 5702,
455 month: 7,
456 day: 8,
457 },
458 DateCase {
459 year: 5703,
460 month: 1,
461 day: 14,
462 },
463 DateCase {
464 year: 5704,
465 month: 7,
466 day: 8,
467 },
468 DateCase {
469 year: 5752,
470 month: 13,
471 day: 12,
472 },
473 DateCase {
474 year: 5756,
475 month: 12,
476 day: 5,
477 },
478 DateCase {
479 year: 5799,
480 month: 8,
481 day: 12,
482 },
483 DateCase {
484 year: 5854,
485 month: 5,
486 day: 5,
487 },
488 ];
489
490 static EXPECTED_MOLAD_DATES: [f64; 33] = [
491 -1850718767f64 / 8640f64,
492 -1591805959f64 / 25920f64,
493 660097927f64 / 25920f64,
494 1275506059f64 / 25920f64,
495 4439806081f64 / 25920f64,
496 605235101f64 / 2880f64,
497 3284237627f64 / 12960f64,
498 9583515841f64 / 25920f64,
499 2592403883f64 / 6480f64,
500 2251656649f64 / 5184f64,
501 11731320839f64 / 25920f64,
502 12185988041f64 / 25920f64,
503 6140833583f64 / 12960f64,
504 6581722991f64 / 12960f64,
505 6792982499f64 / 12960f64,
506 4705980311f64 / 8640f64,
507 14699670013f64 / 25920f64,
508 738006961f64 / 1296f64,
509 1949499007f64 / 3240f64,
510 5299956319f64 / 8640f64,
511 3248250415f64 / 5184f64,
512 16732660061f64 / 25920f64,
513 17216413717f64 / 25920f64,
514 1087650871f64 / 1620f64,
515 2251079609f64 / 3240f64,
516 608605601f64 / 864f64,
517 306216383f64 / 432f64,
518 18387526207f64 / 25920f64,
519 3678423761f64 / 5184f64,
520 1570884431f64 / 2160f64,
521 18888119389f64 / 25920f64,
522 19292268013f64 / 25920f64,
523 660655045f64 / 864f64,
524 ];
525
526 static EXPECTED_LAST_HEBREW_MONTH: [u8; 33] = [
527 12, 12, 12, 12, 12, 12, 12, 12, 13, 12, 13, 12, 12, 12, 12, 13, 12, 13, 12, 13, 12, 12, 12,
528 12, 12, 13, 12, 13, 12, 13, 12, 12, 12,
529 ];
530
531 static EXPECTED_HEBREW_ELASPED_CALENDAR_DAYS: [i32; 33] = [
532 1158928, 1311957, 1398894, 1422636, 1544627, 1583342, 1626812, 1742956, 1773254, 1807597,
533 1825848, 1843388, 1847051, 1881010, 1897460, 1917895, 1940545, 1942729, 1974889, 1986554,
534 1999723, 2018712, 2037346, 2044640, 2068027, 2077507, 2082262, 2082617, 2083000, 2100511,
535 2101988, 2117699, 2137779,
536 ];
537
538 static EXPECTED_FIXED_HEBREW_NEW_YEAR: [i64; 33] = [
539 -214497, -61470, 25467, 49209, 171200, 209915, 253385, 369529, 399827, 434172, 452421,
540 469963, 473624, 507583, 524033, 544468, 567118, 569302, 601462, 613127, 626296, 645285,
541 663919, 671213, 694600, 704080, 708835, 709190, 709573, 727084, 728561, 744272, 764352,
542 ];
543
544 static EXPECTED_DAYS_IN_HEBREW_YEAR: [u16; 33] = [
545 354, 354, 355, 355, 355, 355, 355, 353, 383, 354, 383, 354, 354, 355, 353, 383, 353, 385,
546 353, 383, 355, 354, 354, 354, 355, 385, 355, 383, 354, 385, 355, 354, 355,
547 ];
548
549 static EXPECTED_MARHESHVAN_VALUES: [bool; 33] = [
550 false, false, true, true, true, true, true, false, false, false, false, false, false, true,
551 false, false, false, true, false, false, true, false, false, false, true, true, true,
552 false, false, true, true, false, true,
553 ];
554
555 static EXPECTED_KISLEV_VALUES: [bool; 33] = [
556 false, false, false, false, false, false, false, true, true, false, true, false, false,
557 false, true, true, true, false, true, true, false, false, false, false, false, false,
558 false, true, false, false, false, false, false,
559 ];
560
561 static EXPECTED_DAY_IN_MONTH: [u8; 33] = [
562 30, 30, 30, 30, 29, 30, 30, 29, 29, 30, 29, 30, 29, 29, 30, 30, 30, 30, 30, 29, 30, 29, 30,
563 30, 30, 30, 30, 30, 30, 29, 29, 29, 30,
564 ];
565
566 #[allow(dead_code)]
567 static CIVIL_EXPECTED_DAY_IN_MONTH: [u8; 33] = [
568 30, 30, 30, 30, 29, 30, 29, 29, 29, 30, 30, 30, 29, 29, 30, 29, 30, 29, 29, 30, 30, 29, 30,
569 30, 30, 30, 30, 29, 30, 30, 29, 29, 30,
570 ];
571
572 #[test]
573 fn test_hebrew_epoch() {
574 assert_eq!(HEBREW_EPOCH, RataDie::new(-1373427));
576 }
577
578 #[test]
579 fn test_hebrew_molad() {
580 let precision = 1_00000f64;
581 for (case, expected) in HEBREW_DATES.iter().zip(EXPECTED_MOLAD_DATES.iter()) {
582 let molad =
583 (BookHebrew::molad(case.year, case.month).inner() * precision).round() / precision;
584 let final_expected = (expected * precision).round() / precision;
585 assert_eq!(molad, final_expected, "{case:?}");
586 }
587 }
588
589 #[test]
590 fn test_last_book_hebrew_month() {
591 for (case, expected) in HEBREW_DATES.iter().zip(EXPECTED_LAST_HEBREW_MONTH.iter()) {
592 let last_month = BookHebrew::last_month_of_book_hebrew_year(case.year);
593 assert_eq!(last_month, *expected);
594 }
595 }
596
597 #[test]
598 fn test_book_hebrew_calendar_elapsed_days() {
599 for (case, expected) in HEBREW_DATES
600 .iter()
601 .zip(EXPECTED_HEBREW_ELASPED_CALENDAR_DAYS.iter())
602 {
603 let elapsed_days = BookHebrew::book_hebrew_calendar_elapsed_days(case.year);
604 assert_eq!(elapsed_days, *expected);
605 }
606 }
607
608 #[test]
609 fn test_book_hebrew_year_length_correction() {
610 let year_length_correction: [u8; 33] = [
611 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
612 0, 0, 0, 0,
613 ];
614 for (case, expected) in HEBREW_DATES.iter().zip(year_length_correction.iter()) {
615 let correction = BookHebrew::book_hebrew_year_length_correction(case.year);
616 assert_eq!(correction, *expected);
617 }
618 }
619
620 #[test]
621 fn test_book_hebrew_new_year() {
622 for (case, expected) in HEBREW_DATES
623 .iter()
624 .zip(EXPECTED_FIXED_HEBREW_NEW_YEAR.iter())
625 {
626 let f_date = BookHebrew::book_hebrew_new_year(case.year);
627 assert_eq!(f_date.to_i64_date(), *expected);
628 }
629 }
630
631 #[test]
632 fn test_days_in_book_hebrew_year() {
633 for (case, expected) in HEBREW_DATES.iter().zip(EXPECTED_DAYS_IN_HEBREW_YEAR.iter()) {
634 let days_in_year = BookHebrew::days_in_book_hebrew_year(case.year);
635 assert_eq!(days_in_year, *expected);
636 }
637 }
638
639 #[test]
640 fn test_long_marheshvan() {
641 for (case, expected) in HEBREW_DATES.iter().zip(EXPECTED_MARHESHVAN_VALUES.iter()) {
642 let marsheshvan = BookHebrew::is_long_marheshvan(case.year);
643 assert_eq!(marsheshvan, *expected);
644 }
645 }
646
647 #[test]
648 fn test_short_kislev() {
649 for (case, expected) in HEBREW_DATES.iter().zip(EXPECTED_KISLEV_VALUES.iter()) {
650 let kislev = BookHebrew::is_short_kislev(case.year);
651 assert_eq!(kislev, *expected);
652 }
653 }
654
655 #[test]
656 fn test_last_day_in_book_hebrew_month() {
657 for (case, expected) in HEBREW_DATES.iter().zip(EXPECTED_DAY_IN_MONTH.iter()) {
658 let days_in_month = BookHebrew::last_day_of_book_hebrew_month(case.year, case.month);
659 assert_eq!(days_in_month, *expected);
660 }
661 }
662
663 #[test]
664 fn test_fixed_from_book_hebrew() {
665 for (case, f_date) in HEBREW_DATES.iter().zip(TEST_FIXED_DATE.iter()) {
666 assert_eq!(
667 BookHebrew::fixed_from_book_hebrew(BookHebrew {
668 year: case.year,
669 month: case.month,
670 day: case.day
671 }),
672 RataDie::new(*f_date),
673 "{case:?}"
674 );
675 }
676 }
677
678 #[test]
679 fn test_book_hebrew_from_fixed() {
680 for (case, f_date) in HEBREW_DATES.iter().zip(TEST_FIXED_DATE.iter()) {
681 assert_eq!(
682 BookHebrew::book_hebrew_from_fixed(RataDie::new(*f_date)),
683 BookHebrew {
684 year: case.year,
685 month: case.month,
686 day: case.day
687 },
688 "{case:?}"
689 );
690 }
691 }
692
693 #[test]
694 fn test_civil_to_book_conversion() {
695 for (f_date, case) in TEST_FIXED_DATE.iter().zip(HEBREW_DATES.iter()) {
696 let book_hebrew = BookHebrew::book_hebrew_from_fixed(RataDie::new(*f_date));
697 let (y, m, d) = book_hebrew.to_civil_date();
698 let book_hebrew = BookHebrew::from_civil_date(y, m, d);
699
700 assert_eq!(
701 (case.year, case.month),
702 (book_hebrew.year, book_hebrew.month)
703 )
704 }
705 }
706}