Skip to main content

kosher_rust/limudim/
daf_yomi_bavli.rs

1use icu_calendar::{Date, cal::Hebrew};
2
3use crate::limudim::{
4    BAVLI_DAF_COUNT_EARLY, BAVLI_DAF_COUNT_MODERN, HebrewDateExt, Limud, SHEKALIM_EXPANSION_CYCLE,
5    cycle::Cycle,
6    interval::Interval,
7    limud::{CycleFinder, InternalLimud},
8    units::{BAVLI_TRACTATES, Daf, Tractate},
9};
10
11fn initial_cycle_date() -> Date<Hebrew> {
12    #[allow(clippy::expect_used)]
13    let date = Date::try_new_gregorian(1923, 9, 11)
14        .expect("hard-coded Gregorian date should be valid")
15        .to_calendar(Hebrew);
16    date
17}
18
19pub const fn start_daf(tractate: Tractate) -> u16 {
20    match tractate {
21        Tractate::Kinnim => 23,
22        Tractate::Tamid => 26,
23        Tractate::Midos => 34,
24        _ => 2,
25    }
26}
27
28/// Last daf number (Berachos ends at 64)
29pub const fn end_daf(tractate: Tractate, iteration: i32) -> Option<u16> {
30    let daf = match tractate {
31        Tractate::Berachos => 64,
32        Tractate::Shabbos => 157,
33        Tractate::Eruvin => 105,
34        Tractate::Pesachim => 121,
35        Tractate::Shekalim => {
36            if iteration < SHEKALIM_EXPANSION_CYCLE {
37                13
38            } else {
39                22
40            }
41        }
42        Tractate::Yoma => 88,
43        Tractate::Sukkah => 56,
44        Tractate::Beitzah => 40,
45        Tractate::RoshHashanah => 35,
46        Tractate::Taanis => 31,
47        Tractate::Megillah => 32,
48        Tractate::MoedKatan => 29,
49        Tractate::Chagigah => 27,
50        Tractate::Yevamos => 122,
51        Tractate::Kesubos => 112,
52        Tractate::Nedarim => 91,
53        Tractate::Nazir => 66,
54        Tractate::Sotah => 49,
55        Tractate::Gitin => 90,
56        Tractate::Kiddushin => 82,
57        Tractate::BavaKamma => 119,
58        Tractate::BavaMetzia => 119,
59        Tractate::BavaBasra => 176,
60        Tractate::Sanhedrin => 113,
61        Tractate::Makkos => 24,
62        Tractate::Shevuos => 49,
63        Tractate::AvodahZarah => 76,
64        Tractate::Horiyos => 14,
65        Tractate::Zevachim => 120,
66        Tractate::Menachos => 110,
67        Tractate::Chullin => 142,
68        Tractate::Bechoros => 61,
69        Tractate::Arachin => 34,
70        Tractate::Temurah => 34,
71        Tractate::Kerisos => 28,
72        Tractate::Meilah => 22,
73        Tractate::Kinnim => 25,
74        Tractate::Tamid => 33,
75        Tractate::Midos => 37,
76        Tractate::Niddah => 73,
77        _ => 0,
78    };
79    if daf == 0 { None } else { Some(daf) }
80}
81
82#[derive(Default)]
83/// Calculates the Daf Yomi Bavli schedule.
84pub struct DafYomiBavli {}
85
86fn daf_yomi_bavli_for_date(limud_date: Date<Hebrew>) -> Option<Daf> {
87    let cycle = Cycle::from_cycle_initiation(initial_cycle_date(), DafYomiBavli::cycle_end_calculation, limud_date)?;
88    if limud_date > cycle.end_date {
89        return None;
90    }
91    let offset = cycle.start_date.days_until(&limud_date)? as usize;
92    daf_at_offset(cycle.iteration?, offset)
93}
94
95impl InternalLimud<Daf> for DafYomiBavli {
96    fn limud(&self, limud_date: Date<Hebrew>) -> Option<Daf> {
97        daf_yomi_bavli_for_date(limud_date)
98    }
99
100    fn cycle_finder(&self) -> CycleFinder {
101        CycleFinder::Initial(initial_cycle_date())
102    }
103    fn cycle_end_calculation(hebrew_date: Date<Hebrew>, _iteration: Option<i32>) -> Option<Date<Hebrew>> {
104        let days = _iteration
105            .map(|i| {
106                if i < SHEKALIM_EXPANSION_CYCLE {
107                    BAVLI_DAF_COUNT_EARLY
108                } else {
109                    BAVLI_DAF_COUNT_MODERN
110                }
111            })
112            .unwrap_or(BAVLI_DAF_COUNT_EARLY);
113        hebrew_date.add_days(days - 1)
114    }
115
116    fn unit_for_interval(&self, _interval: &Interval, limud_date: &Date<Hebrew>) -> Option<Daf> {
117        daf_yomi_bavli_for_date(*limud_date)
118    }
119}
120
121impl Limud<Daf> for DafYomiBavli {}
122
123fn daf_at_offset(cycle_iteration: i32, offset: usize) -> Option<Daf> {
124    let mut remaining = offset;
125    for tractate in BAVLI_TRACTATES {
126        let start = start_daf(tractate);
127        let end = end_daf(tractate, cycle_iteration)?;
128        let count = usize::from(end - start + 1);
129        if remaining < count {
130            return Some(Daf {
131                tractate,
132                page: start + remaining as u16,
133            });
134        }
135        remaining -= count;
136    }
137    None
138}
139#[cfg(test)]
140#[allow(clippy::expect_used)]
141mod tests {
142    use crate::limudim::from_gregorian_date;
143
144    use super::*;
145
146    #[test]
147    fn daf_yomi_bavli_simple_date() {
148        let test_date = from_gregorian_date(2017, 12, 28);
149        let limud = DafYomiBavli::default().limud(test_date).expect("limud exists");
150        assert_eq!(limud.page, 30);
151        assert_eq!(limud.tractate, Tractate::Shevuos);
152    }
153
154    #[test]
155    fn daf_yomi_bavli_before_cycle_began() {
156        let test_date = from_gregorian_date(1920, 1, 1);
157        let limud = DafYomiBavli::default().limud(test_date);
158        assert!(limud.is_none());
159    }
160
161    #[test]
162    fn daf_yomi_bavli_first_day_of_cycle() {
163        let test_date = from_gregorian_date(2012, 8, 3);
164        let limud = DafYomiBavli::default().limud(test_date).expect("limud exists");
165        assert_eq!(limud.page, 2);
166        assert_eq!(limud.tractate, Tractate::Berachos);
167    }
168
169    #[test]
170    fn daf_yomi_bavli_last_day_of_cycle() {
171        let test_date = from_gregorian_date(2020, 1, 4);
172        let limud = DafYomiBavli::default().limud(test_date).expect("limud exists");
173        assert_eq!(limud.page, 73);
174        assert_eq!(limud.tractate, Tractate::Niddah);
175    }
176
177    #[test]
178    fn daf_yomi_bavli_before_shekalim_transition_end_of_shekalim() {
179        let test_date = from_gregorian_date(1969, 4, 28);
180        let limud = DafYomiBavli::default().limud(test_date).expect("limud exists");
181        assert_eq!(limud.page, 13);
182        assert_eq!(limud.tractate, Tractate::Shekalim);
183    }
184
185    #[test]
186    fn daf_yomi_bavli_before_shekalim_transition_beginning_of_yoma() {
187        let test_date = from_gregorian_date(1969, 4, 29);
188        let limud = DafYomiBavli::default().limud(test_date).expect("limud exists");
189        assert_eq!(limud.page, 2);
190        assert_eq!(limud.tractate, Tractate::Yoma);
191    }
192
193    #[test]
194    fn daf_yomi_bavli_end_of_meilah() {
195        let test_date = from_gregorian_date(2019, 10, 9);
196        let limud = DafYomiBavli::default().limud(test_date).expect("limud exists");
197        assert_eq!(limud.page, 22);
198        assert_eq!(limud.tractate, Tractate::Meilah);
199    }
200
201    #[test]
202    fn daf_yomi_bavli_beginning_of_kinnim() {
203        let test_date = from_gregorian_date(2019, 10, 10);
204        let limud = DafYomiBavli::default().limud(test_date).expect("limud exists");
205        assert_eq!(limud.page, 23);
206        assert_eq!(limud.tractate, Tractate::Kinnim);
207    }
208
209    #[test]
210    fn daf_yomi_bavli_beginning_of_tamid() {
211        let test_date = from_gregorian_date(2019, 10, 13);
212        let limud = DafYomiBavli::default().limud(test_date).expect("limud exists");
213        assert_eq!(limud.page, 26);
214        assert_eq!(limud.tractate, Tractate::Tamid);
215    }
216
217    #[test]
218    fn daf_yomi_bavli_second_day_of_midos() {
219        // Note: Midos starts at page 34 on 2019-10-21. This tests the second day.
220        let test_date = from_gregorian_date(2019, 10, 22);
221        let limud = DafYomiBavli::default().limud(test_date).expect("limud exists");
222        assert_eq!(limud.page, 35);
223        assert_eq!(limud.tractate, Tractate::Midos);
224    }
225
226    #[test]
227    fn daf_yomi_bavli_after_midos() {
228        let test_date = from_gregorian_date(2019, 10, 25);
229        let limud = DafYomiBavli::default().limud(test_date).expect("limud exists");
230        assert_eq!(limud.page, 2);
231        assert_eq!(limud.tractate, Tractate::Niddah);
232    }
233}