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
21const 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)]
28pub 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 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}