Skip to main content

calendrical_calculations/
hebrew.rs

1// This file is part of ICU4X.
2//
3// The contents of this file implement algorithms from Calendrical Calculations
4// by Reingold & Dershowitz, Cambridge University Press, 4th edition (2018),
5// which have been released as Lisp code at <https://github.com/EdReingold/calendar-code2/>
6// under the Apache-2.0 license. Accordingly, this file is released under
7// the Apache License, Version 2.0 which can be found at the calendrical_calculations
8// package root or at http://www.apache.org/licenses/LICENSE-2.0.
9
10use crate::helpers::{final_func, i64_to_i32, next_u8};
11use crate::rata_die::{Moment, RataDie};
12#[allow(unused_imports)]
13use core_maths::*;
14
15/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2206>
16pub const HEBREW_EPOCH: RataDie = crate::julian::fixed_from_julian_book_version(-3761, 10, 7);
17
18/// Biblical Hebrew dates. The months are reckoned a bit strangely, with the new year occurring on
19/// Tishri (as in the civil calendar) but the months being numbered in a different order
20#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq, PartialOrd, Ord)]
21#[allow(clippy::exhaustive_structs)]
22pub struct BookHebrew {
23    /// The year
24    pub year: i32,
25    /// The month
26    pub month: u8,
27    /// The day
28    pub day: u8,
29}
30
31// The BookHebrew Months
32/// The biblical month number used for the month of Nisan
33pub const NISAN: u8 = 1;
34/// The biblical month number used for the month of Iyyar
35pub const IYYAR: u8 = 2;
36/// The biblical month number used for the month of Sivan
37pub const SIVAN: u8 = 3;
38/// The biblical month number used for the month of Tammuz
39pub const TAMMUZ: u8 = 4;
40/// The biblical month number used for the month of Av
41pub const AV: u8 = 5;
42/// The biblical month number used for the month of Elul
43pub const ELUL: u8 = 6;
44/// The biblical month number used for the month of Tishri
45pub const TISHRI: u8 = 7;
46/// The biblical month number used for the month of Marheshvan
47pub const MARHESHVAN: u8 = 8;
48/// The biblical month number used for the month of Kislev
49pub const KISLEV: u8 = 9;
50/// The biblical month number used for the month of Tevet
51pub const TEVET: u8 = 10;
52/// The biblical month number used for the month of Shevat
53pub const SHEVAT: u8 = 11;
54/// The biblical month number used for the month of Adar (and Adar I)
55pub const ADAR: u8 = 12;
56/// The biblical month number used for the month of Adar II
57pub const ADARII: u8 = 13;
58
59// BIBLICAL HEBREW CALENDAR FUNCTIONS
60
61impl BookHebrew {
62    /// The civil calendar has the same year and day numbering as the book one, but the months are numbered
63    /// differently
64    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    /// The civil calendar has the same year and day numbering as the book one, but the months are numbered
81    /// differently
82    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; //  months 1-6 correspond to biblical months 7-12
87        } else {
88            biblical_month = civil_month - 6; //  months 7-12 correspond to biblical months 1-6
89            if Self::is_hebrew_leap_year(civil_year) {
90                biblical_month -= 1
91            }
92            if biblical_month == 0 {
93                // Special case for Adar II in a leap year
94                biblical_month = 13;
95            }
96        }
97
98        BookHebrew {
99            year: civil_year,
100            month: biblical_month,
101            day: civil_day,
102        }
103    }
104    // Moment of mean conjunction (New Moon) of h_month in BookHebrew
105    /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2244>
106    #[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        }; // Treat Nisan as start of year
113
114        let months_elapsed = (book_month as f64 - TISHRI as f64) // Months this year
115            + ((235.0 * y as f64 - 234.0) / 19.0).floor(); // Months until New Year.
116
117        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    // ADAR = 12, ADARII = 13
124    /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2217>
125    #[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    // Number of days elapsed from the (Sunday) noon prior to the epoch of the BookHebrew Calendar to the molad of Tishri of BookHebrew year (h_year) or one day later
135    /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2261>
136    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    // Delays to start of BookHebrew year to keep ordinary year in range 353-356 and leap year in range 383-386
149    /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2301>
150    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    // Fixed date of BookHebrew new year
165    /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2294>
166    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    /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2315>
175    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    /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/1ee51ecfaae6f856b0d7de3e36e9042100b4f424/calendar.l#L2275-L2278>
180    pub fn is_hebrew_leap_year(book_year: i32) -> bool {
181        (7 * book_year + 1).rem_euclid(19) < 7
182    }
183
184    // True if the month Marheshvan is going to be long in given BookHebrew year
185    /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2321>
186    #[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    // True if the month Kislve is going to be short in given BookHebrew year
193    /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2326>
194    #[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    // Last day of month (h_month) in BookHebrew year (book_year)
201    /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2230>
202    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    /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2331>
231    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; // (day - 1) Days so far this month.
237
238        if book_month < TISHRI {
239            // Then add days in prior months this year before
240            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            // Else add days in prior months this year
247            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    /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2352>
256    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, //  The value 35975351/98496, the average length of a BookHebrew year, can be approximated by 365.25
259        )
260        .unwrap_or_else(|e| e.saturate());
261
262        // Search forward for the year
263        let year_condition = |year: i32| Self::book_hebrew_new_year(year) <= date;
264        let year = final_func(approx - 1, year_condition);
265
266        // Starting month for search for month.
267        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        // Search forward from either Tishri or Nisan.
286        let month = next_u8(start, month_condition);
287
288        // Calculate the day by subtraction.
289        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        // page 119 of the Calendrical Calculations book
575        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}