Skip to main content

kosher_rust/limudim/
daf_yomi_yerushalmi.rs

1use icu_calendar::{Date, cal::Hebrew, types::Weekday};
2
3use crate::{
4    calendar::prelude::*,
5    limudim::{
6        HebrewDateExt, Limud, YERUSHALMI_DAF_COUNT,
7        interval::Interval,
8        limud::{CycleFinder, InternalLimud},
9        units::{Daf, YERUSHALMI_TRACTATES},
10    },
11};
12
13fn initial_cycle_date() -> Date<Hebrew> {
14    #[allow(clippy::expect_used)]
15    let date = Date::try_new_gregorian(1980, 2, 2)
16        .expect("hard-coded Gregorian date should be valid")
17        .to_calendar(Hebrew);
18    date
19}
20
21/// Pages per masechta in the Vilna Yerushalmi, matching KosherJava's `BLATT_PER_MASECHTA`.
22const YERUSHALMI_BLATT_PER_MASECHTA: [u16; 39] = [
23    68, 37, 34, 44, 31, 59, 26, 33, 28, 20, 13, 92, 65, 71, 22, 22, 42, 26, 26, 33, 34, 22, 19, 85, 72, 47, 40, 47, 54,
24    48, 44, 37, 34, 44, 9, 57, 37, 19, 13,
25];
26
27#[derive(Default)]
28/// Calculates the Daf Yomi Yerushalmi schedule using the Vilna Edition of the Jerusalem Talmud.
29pub struct DafYomiYerushalmiVilna {}
30
31impl DafYomiYerushalmiVilna {
32    fn cycle_start_date() -> Date<Hebrew> {
33        initial_cycle_date()
34    }
35}
36
37impl InternalLimud<Daf> for DafYomiYerushalmiVilna {
38    fn limud(&self, limud_date: Date<Hebrew>) -> Option<Daf> {
39        daf_yomi_yerushalmi_for_date(limud_date)
40    }
41
42    fn cycle_finder(&self) -> CycleFinder {
43        CycleFinder::Initial(Self::cycle_start_date())
44    }
45
46    fn cycle_end_calculation(hebrew_date: Date<Hebrew>, _iteration: Option<i32>) -> Option<Date<Hebrew>> {
47        cycle_end_date(hebrew_date)
48    }
49
50    fn unit_for_interval(&self, _interval: &Interval, limud_date: &Date<Hebrew>) -> Option<Daf> {
51        daf_yomi_yerushalmi_for_date(*limud_date)
52    }
53
54    fn is_skip_interval(&self, interval: &Interval) -> bool {
55        is_skip_day(&interval.start_date)
56    }
57}
58
59fn daf_yomi_yerushalmi_for_date(limud_date: Date<Hebrew>) -> Option<Daf> {
60    let cycle_start = DafYomiYerushalmiVilna::cycle_start_date();
61    if limud_date < cycle_start {
62        return None;
63    }
64    if is_skip_day(&limud_date) {
65        return None;
66    }
67
68    let mut prev_cycle = cycle_start;
69    let mut next_cycle = cycle_end_date(prev_cycle)?.add_days(1)?;
70    while limud_date >= next_cycle {
71        prev_cycle = next_cycle;
72        next_cycle = cycle_end_date(prev_cycle)?.add_days(1)?;
73    }
74    let daf_no = prev_cycle.days_until(&limud_date)?;
75    let special_days = count_special_days(prev_cycle, limud_date)?;
76    let mut total = daf_no.checked_sub(special_days)?;
77
78    for (&tractate, &blatt) in YERUSHALMI_TRACTATES.iter().zip(YERUSHALMI_BLATT_PER_MASECHTA.iter()) {
79        let blatt = u32::from(blatt);
80        if total < blatt {
81            return Some(Daf {
82                tractate,
83                page: (total + 1) as u16,
84            });
85        }
86        total -= blatt;
87    }
88
89    None
90}
91
92impl Limud<Daf> for DafYomiYerushalmiVilna {}
93
94fn cycle_end_date(cycle_start: Date<Hebrew>) -> Option<Date<Hebrew>> {
95    let mut end_date = cycle_start.add_days(YERUSHALMI_DAF_COUNT - 1)?;
96    let mut found_days = count_special_days(cycle_start, end_date)?;
97    while found_days > 0 {
98        let new_start_date = end_date.add_days(1)?;
99        end_date = end_date.add_days(found_days as i32)?;
100        found_days = count_special_days(new_start_date, end_date)?;
101    }
102    Some(end_date)
103}
104
105fn tisha_bav_date(year: i32) -> Option<Date<Hebrew>> {
106    let date = Date::try_new_hebrew_v2(year, month::AV, 9).ok()?;
107    if date.weekday() == Weekday::Saturday {
108        date.add_days(1)
109    } else {
110        Some(date)
111    }
112}
113
114fn count_special_days(start: Date<Hebrew>, end: Date<Hebrew>) -> Option<u32> {
115    let start_year = start.year().extended_year();
116    let end_year = end.year().extended_year();
117    let mut special_days = 0;
118
119    for year in start_year..=end_year {
120        let yom_kippur = Date::try_new_hebrew_v2(year, month::TISHREI, 10).ok()?;
121        if start < yom_kippur && yom_kippur <= end {
122            special_days += 1;
123        }
124
125        let tisha_bav = tisha_bav_date(year);
126        if start < tisha_bav? && tisha_bav? <= end {
127            special_days += 1;
128        }
129    }
130
131    Some(special_days)
132}
133
134fn is_skip_day(date: &Date<Hebrew>) -> bool {
135    date.holidays(false, false)
136        .any(|h| h == Holiday::TishahBav || h == Holiday::YomKippur)
137}
138
139#[cfg(test)]
140#[allow(clippy::expect_used)]
141mod tests {
142    use icu_calendar::{Date, cal::Hebrew};
143
144    use crate::{calendar::month::TISHREI, limudim::from_gregorian_date};
145
146    use super::*;
147    use crate::limudim::Tractate;
148
149    #[test]
150    fn daf_yomi_yerushalmi_simple_date() {
151        let test_date = from_gregorian_date(2017, 12, 28);
152        let limud = DafYomiYerushalmiVilna::default()
153            .limud(test_date)
154            .expect("limud exists");
155        assert_eq!(limud.page, 33);
156        assert_eq!(limud.tractate, Tractate::BavaMetzia);
157    }
158
159    #[test]
160    fn daf_yomi_yerushalmi_before_cycle_began() {
161        let test_date = from_gregorian_date(1980, 1, 1);
162        let limud = DafYomiYerushalmiVilna::default().limud(test_date);
163        assert!(limud.is_none());
164    }
165
166    #[test]
167    fn daf_yomi_yerushalmi_first_day_of_cycle() {
168        let test_date = from_gregorian_date(2005, 10, 3);
169        let limud = DafYomiYerushalmiVilna::default()
170            .limud(test_date)
171            .expect("limud exists");
172        assert_eq!(limud.page, 1);
173        assert_eq!(limud.tractate, Tractate::Berachos);
174    }
175
176    #[test]
177    fn daf_yomi_yerushalmi_last_day_of_cycle() {
178        let test_date = from_gregorian_date(2010, 1, 12);
179        let limud = DafYomiYerushalmiVilna::default()
180            .limud(test_date)
181            .expect("limud exists");
182        assert_eq!(limud.page, 13);
183        assert_eq!(limud.tractate, Tractate::Niddah);
184    }
185
186    #[test]
187    fn daf_yomi_yerushalmi_last_skip_day() {
188        // JewishDate(5778, 7, 10) is Tishrei 10 (Yom Kippur) - a skip day
189        let test_date = Date::<Hebrew>::try_new_hebrew_v2(5778, TISHREI, 10).expect("valid hebrew date");
190        let limud = DafYomiYerushalmiVilna::default().limud(test_date);
191
192        assert!(limud.is_none());
193    }
194
195    #[test]
196    fn daf_yomi_yerushalmi_1980_02_02() {
197        let test_date = from_gregorian_date(1980, 2, 2);
198        let limud = DafYomiYerushalmiVilna::default()
199            .limud(test_date)
200            .expect("limud exists");
201        assert_eq!(limud.page, 1);
202        assert_eq!(limud.tractate, Tractate::Berachos);
203    }
204
205    #[test]
206    fn daf_yomi_yerushalmi_1982_05_15() {
207        let test_date = from_gregorian_date(1982, 5, 15);
208        let limud = DafYomiYerushalmiVilna::default()
209            .limud(test_date)
210            .expect("limud exists");
211        assert_eq!(limud.page, 4);
212        assert_eq!(limud.tractate, Tractate::Chagigah);
213    }
214
215    #[test]
216    fn daf_yomi_yerushalmi_1984_05_12() {
217        let test_date = from_gregorian_date(1984, 5, 12);
218        let limud = DafYomiYerushalmiVilna::default()
219            .limud(test_date)
220            .expect("limud exists");
221        assert_eq!(limud.page, 13);
222        assert_eq!(limud.tractate, Tractate::Niddah);
223    }
224
225    #[test]
226    fn daf_yomi_yerushalmi_1984_05_13() {
227        let test_date = from_gregorian_date(1984, 5, 13);
228        let limud = DafYomiYerushalmiVilna::default()
229            .limud(test_date)
230            .expect("limud exists");
231        assert_eq!(limud.page, 1);
232        assert_eq!(limud.tractate, Tractate::Berachos);
233    }
234
235    #[test]
236    fn daf_yomi_yerushalmi_1990_08_01() {
237        let test_date = from_gregorian_date(1990, 8, 1);
238        let limud = DafYomiYerushalmiVilna::default()
239            .limud(test_date)
240            .expect("limud exists");
241        assert_eq!(limud.page, 40);
242        assert_eq!(limud.tractate, Tractate::Yoma);
243    }
244
245    #[test]
246    fn daf_yomi_yerushalmi_2000_01_01() {
247        let test_date = from_gregorian_date(2000, 1, 1);
248        let limud = DafYomiYerushalmiVilna::default()
249            .limud(test_date)
250            .expect("limud exists");
251        assert_eq!(limud.page, 66);
252        assert_eq!(limud.tractate, Tractate::Kesubos);
253    }
254
255    #[test]
256    fn daf_yomi_yerushalmi_2005_10_02() {
257        let test_date = from_gregorian_date(2005, 10, 2);
258        let limud = DafYomiYerushalmiVilna::default()
259            .limud(test_date)
260            .expect("limud exists");
261        assert_eq!(limud.page, 13);
262        assert_eq!(limud.tractate, Tractate::Niddah);
263    }
264
265    #[test]
266    fn daf_yomi_yerushalmi_2007_06_15() {
267        let test_date = from_gregorian_date(2007, 6, 15);
268        let limud = DafYomiYerushalmiVilna::default()
269            .limud(test_date)
270            .expect("limud exists");
271        assert_eq!(limud.page, 68);
272        assert_eq!(limud.tractate, Tractate::Pesachim);
273    }
274
275    #[test]
276    fn daf_yomi_yerushalmi_2010_01_11() {
277        let test_date = from_gregorian_date(2010, 1, 11);
278        let limud = DafYomiYerushalmiVilna::default()
279            .limud(test_date)
280            .expect("limud exists");
281        assert_eq!(limud.page, 12);
282        assert_eq!(limud.tractate, Tractate::Niddah);
283    }
284
285    #[test]
286    fn daf_yomi_yerushalmi_2015_04_23() {
287        let test_date = from_gregorian_date(2015, 4, 23);
288        let limud = DafYomiYerushalmiVilna::default()
289            .limud(test_date)
290            .expect("limud exists");
291        assert_eq!(limud.page, 3);
292        assert_eq!(limud.tractate, Tractate::Orlah);
293    }
294
295    #[test]
296    fn daf_yomi_yerushalmi_2020_01_01() {
297        let test_date = from_gregorian_date(2020, 1, 1);
298        let limud = DafYomiYerushalmiVilna::default()
299            .limud(test_date)
300            .expect("limud exists");
301        assert_eq!(limud.page, 28);
302        assert_eq!(limud.tractate, Tractate::Eruvin);
303    }
304
305    #[test]
306    fn daf_yomi_yerushalmi_2025_10_02() {
307        let test_date = from_gregorian_date(2025, 10, 2);
308        let limud = DafYomiYerushalmiVilna::default().limud(test_date);
309        assert!(limud.is_none());
310    }
311
312    #[test]
313    fn daf_yomi_yerushalmi_1980_09_20() {
314        let test_date = from_gregorian_date(1980, 9, 20);
315        let limud = DafYomiYerushalmiVilna::default().limud(test_date);
316        assert!(limud.is_none());
317    }
318
319    #[test]
320    fn daf_yomi_yerushalmi_1990_09_29() {
321        let test_date = from_gregorian_date(1990, 9, 29);
322        let limud = DafYomiYerushalmiVilna::default().limud(test_date);
323        assert!(limud.is_none());
324    }
325
326    #[test]
327    fn daf_yomi_yerushalmi_2000_10_09() {
328        let test_date = from_gregorian_date(2000, 10, 9);
329        let limud = DafYomiYerushalmiVilna::default().limud(test_date);
330        assert!(limud.is_none());
331    }
332
333    #[test]
334    fn daf_yomi_yerushalmi_2010_09_18() {
335        let test_date = from_gregorian_date(2010, 9, 18);
336        let limud = DafYomiYerushalmiVilna::default().limud(test_date);
337        assert!(limud.is_none());
338    }
339
340    #[test]
341    fn daf_yomi_yerushalmi_1980_07_21() {
342        let test_date = from_gregorian_date(1980, 7, 21);
343        let limud = DafYomiYerushalmiVilna::default()
344            .limud(test_date)
345            .expect("limud exists");
346        assert_eq!(limud.page, 32);
347        assert_eq!(limud.tractate, Tractate::Kilayim);
348    }
349
350    #[test]
351    fn daf_yomi_yerushalmi_1990_07_31() {
352        let test_date = from_gregorian_date(1990, 7, 31);
353        let limud = DafYomiYerushalmiVilna::default().limud(test_date);
354        assert!(limud.is_none());
355    }
356
357    #[test]
358    fn daf_yomi_yerushalmi_2000_08_10() {
359        let test_date = from_gregorian_date(2000, 8, 10);
360        let limud = DafYomiYerushalmiVilna::default().limud(test_date);
361        assert!(limud.is_none());
362    }
363
364    #[test]
365    fn daf_yomi_yerushalmi_2010_07_20() {
366        let test_date = from_gregorian_date(2010, 7, 20);
367        let limud = DafYomiYerushalmiVilna::default().limud(test_date);
368        assert!(limud.is_none());
369    }
370
371    #[test]
372    fn daf_yomi_yerushalmi_1980_03_01() {
373        let test_date = from_gregorian_date(1980, 3, 1);
374        let limud = DafYomiYerushalmiVilna::default()
375            .limud(test_date)
376            .expect("limud exists");
377        assert_eq!(limud.page, 29);
378        assert_eq!(limud.tractate, Tractate::Berachos);
379    }
380
381    #[test]
382    fn daf_yomi_yerushalmi_1982_01_01() {
383        let test_date = from_gregorian_date(1982, 1, 1);
384        let limud = DafYomiYerushalmiVilna::default()
385            .limud(test_date)
386            .expect("limud exists");
387        assert_eq!(limud.page, 31);
388        assert_eq!(limud.tractate, Tractate::Yoma);
389    }
390
391    #[test]
392    fn daf_yomi_yerushalmi_1984_04_01() {
393        let test_date = from_gregorian_date(1984, 4, 1);
394        let limud = DafYomiYerushalmiVilna::default()
395            .limud(test_date)
396            .expect("limud exists");
397        assert_eq!(limud.page, 28);
398        assert_eq!(limud.tractate, Tractate::AvodahZarah);
399    }
400}