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