1use chrono::{NaiveDate, Local, Datelike};
2use std::fmt;
3
4pub struct RD {
10 pub rd: i32
11}
12
13#[derive(Debug, Clone, Copy, PartialEq)]
15pub struct GDate {
16 pub year: i32,
17 pub month: u8, pub day: u8, }
20
21#[derive(Debug, Clone, Copy, PartialEq)]
23pub struct JDate {
24 pub year: i32,
25 pub month: u8, pub day: u8, }
28
29impl GDate {
30 pub fn new(year: i32, month: u8, day: u8) -> Option<GDate> {
31 NaiveDate::from_ymd_opt(year, month as u32, day as u32)
32 .map(|_| GDate{year: year, month: month, day: day})
33 }
34
35 pub fn today() -> GDate {
36 let date = Local::now().date_naive();
37 GDate{
38 year: date.year(),
39 month: date.month() as u8,
40 day: date.day() as u8
41 }
42 }
43}
44
45
46impl JDate {
47 pub fn new(year: i32, month: u8, day: u8) -> Option<JDate> {
49 match date_is_valid(year, month, day) {
50 true => Some(JDate{year: year, month: month, day: day}),
51 false => None
52 }
53 }
54
55 pub fn today() -> JDate {
56 JDate::from(GDate::today())
57 }
58
59 pub fn month_name(self: Self) -> &'static str {
61 const NAMES: [&str; 13] = [
62 "Nisan", "Iyar", "Sivan", "Tamuz", "Av", "Elul",
63 "Tishrei", "Cheshvan", "Kislev", "Tevet", "Shvat", "Adar", "Adar2"];
64 if self.month == 12 && is_leap_year(self.year) {
65 return "Adar1"
66 }
67 return NAMES[self.month as usize - 1];
68
69 }
70
71 pub fn dafyomi(self: Self) -> (&'static str, u8) {
72 let tractates = [
73 ("Berachos", 63),
74 ("Shabbos", 156),
75 ("Eruvin", 104),
76 ("Pesachim", 120),
77 ("Shekalim", 21),
78 ("Yoma", 87),
79 ("Sukah", 55),
80 ("Beitzah", 39),
81 ("Rosh Hashanah", 34),
82 ("Taanis", 30),
83 ("Megilah", 31),
84 ("Moed Katan", 28),
85 ("Chagigah", 26),
86 ("Yevamos", 121),
87 ("Kesuvos", 111),
88 ("Nedarim", 90),
89 ("Nazir", 65),
90 ("Sotah", 48),
91 ("Gitin", 89),
92 ("Kidushin", 81),
93 ("Bava Kama", 118),
94 ("Bava Metzia", 118),
95 ("Bava Basra", 175),
96 ("Sanhedrin", 112),
97 ("Makos", 23),
98 ("Shevuos", 48),
99 ("Avodah Zarah", 75),
100 ("Horayos", 13),
101 ("Zevachim", 119),
102 ("Menachos", 109),
103 ("Chulin", 141),
104 ("Bechoros", 60),
105 ("Erchin", 33),
106 ("Temurah", 33),
107 ("Kerisus", 27),
108 ("Meilah", 36),
109 ("Nidah", 72),
110 ];
111 let rd = RD::from(self).rd;
112 let mut day = (rd - 37).rem_euclid(2711);
113 for (trac, pages) in tractates.iter() {
114 if day <= pages - 1 {
115 return (trac, (day+2) as u8);
116 }
117 day -= pages;
118 }
119 return ("", 0);
120 }
121
122 pub fn omer(self: Self) -> u8 {
123 let day = self.day;
124 let month = self.month;
125 if month == 1 && day >= 16 {
126 return day - 15;
127 } else if month == 2 {
128 return day + 15;
129 } else if month == 3 && day < 6 {
130 return day + 44;
131 } else {
132 return 0;
133 }
134 }
135}
136
137impl fmt::Display for GDate {
138 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
139 write!(f, "{:0>4}-{:0>2}-{:0>2}", self.year, self.month, self.day)
140 }
141}
142
143impl fmt::Display for JDate {
144 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
145 write!(f, "{:0>4}-{}-{:0>2}", self.year, self.month_name(), self.day)
146 }
147}
148
149impl From<RD> for JDate {
150 fn from(rd: RD) -> JDate {
152 let ed = rd.rd + 1373428; let mut year = ed * 100 / 36525;
154 while year_start(year) < ed {year += 1;}
155 while year_start(year) > ed {year -= 1;}
156 let mut days = year_start(year);
157 let days_in_month = year_months(year);
158 let mut month = 7;
159 loop {
160 let length = days_in_month[month] as i32;
161 if days + length > ed {break}
162 month += 1;
163 if month == 14 {month = 1}
164 if month == 7 {unreachable!()}
165 days += length;
166 }
167 return JDate{
168 year: year,
169 month: month as u8,
170 day: (ed-days+1) as u8
171 };
172 }
173}
174
175impl From<JDate> for RD {
176 fn from(j: JDate) -> RD {
178 let mut ed = year_start(j.year) - 1;
179 let days_in_month = year_months(j.year);
180 let mut month: u8 = 7;
181 loop {
182 let length = days_in_month[month as usize];
183 if month == j.month {
184 ed += j.day as i32;
185 break;
186 }
187 ed += length as i32;
188 month += 1;
189 if month == 14 {month = 1}
190 if month == 7 {unreachable!()}
191 }
192 RD{rd: ed - 1373428}
193 }
194}
195
196impl From<RD> for GDate {
197 fn from(rd: RD) -> GDate {
199 let date = NaiveDate::from_epoch_days(rd.rd - 719163).unwrap();
200 GDate{
201 year: date.year(),
202 month: date.month() as u8,
203 day: date.day() as u8
204 }
205 }
206}
207
208impl From<GDate> for RD {
209 fn from(g: GDate) -> RD {
211 RD{
212 rd: NaiveDate::from_ymd_opt(g.year, g.month as u32, g.day as u32)
213 .unwrap()
214 .to_epoch_days()
215 + 719163
216 }
217 }
218}
219
220impl From<GDate> for JDate {
221 fn from(d: GDate) -> JDate {
223 JDate::from(RD::from(d))
224 }
225}
226
227impl From<JDate> for GDate {
228 fn from(j: JDate) -> Self {
230 GDate::from(RD::from(j))
231
232 }
233}
234
235pub fn is_leap_year(year: i32) -> bool {
237 match year % 19 {
238 0|3|6|8|11|14|17 => true,
239 _ => false
240 }
241}
242
243pub fn date_is_valid(year: i32, month: u8, day: u8) -> bool {
245 if month < 1 || month > 13 || day < 1 || day > 30 {
246 return false;
247 }
248 if month == 13 {
249 return day <= 29 && is_leap_year(year);
250 }
251 if day == 30 {
252 return match month {
253 1|3|5|7|11 => true,
254 2|4|6|10 => false,
255 12 => is_leap_year(year),
256 8 => {
257 year_length(year) % 10 == 5 },
259 9 => {
260 year_length(year) % 10 >= 4 },
262 _ => unreachable!()
263 }
264 }
265 true
266}
267
268pub fn molad(year: i32) -> i64 {
272 let parts_month = (29*24+12)*1080+793;
273 let parts_year = 12 * parts_month;
274 let parts_lyear = 13 * parts_month;
275 let parts_cycle = 12 * parts_year + 7 * parts_lyear;
276 let total_cycles = (year-1).div_euclid(19);
277 let year_in_cycle = (year-1).rem_euclid(19);
278 let mut molad: i64 = (24+5)*1080+204; molad += total_cycles as i64 * parts_cycle as i64;
280 for year in 0..year_in_cycle {
281 if is_leap_year(year+1) {
282 molad += parts_lyear as i64;
283 } else {
284 molad += parts_year as i64;
285 }
286 }
287 return molad;
288}
289
290pub fn molad_components(year: i32) -> (i32, u8, u16) {
295 let molad = molad(year);
296 return ((molad.div_euclid(1080*24)) as i32,
297 (molad.div_euclid(1080).rem_euclid(24)) as u8,
298 (molad.rem_euclid(1080)) as u16);
299}
300
301pub fn molad_print(year: i32) {
303 let (day, hour, parts) = molad_components(year);
304 let day = day % 7;
305 let hour = (hour + 18) % 24;
306 let minute = parts / 18;
307 let parts = parts % 18;
308
309 let days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
310 let day_str = days[day as usize];
311 println!("Molad {year}: {day_str} {hour:0>2}:{minute:0>2} and {parts:>2} \
312 chalakim");
313}
314
315pub fn year_start(year: i32) -> i32 {
317 let molad = molad(year);
318 let day = molad.div_euclid(1080*24) as i32;
319 let parts = molad.rem_euclid(1080*24) as i32;
320 let mut rosh = day;
321 if parts >= 18*1080 {
324 rosh += 1;
325 }
326 if rosh % 7 == 0 || rosh % 7 == 3 || rosh % 7 == 5 {
328 rosh += 1;
329 }
330 if !is_leap_year(year) && day % 7 == 2 && parts >= 9*1080+204 {
332 rosh = day+2;
333 }
334 if is_leap_year(year-1) && day % 7 == 1 && parts >= 15*1080+589 {
336 rosh = day+1;
337 }
338 return rosh;
339}
340
341pub fn year_length(year: i32) -> i32 {
343 let rosh1 = year_start(year);
344 let rosh2 = year_start(year+1);
345 return rosh2-rosh1;
346}
347
348pub fn year_months(year: i32) -> [u8; 14] {
350 let mut days_in_month = [0, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 0]; if is_leap_year(year) {
353 days_in_month[12] = 30; days_in_month[13] = 29;
354 }
355 let length = year_length(year);
356 if length % 10 == 3 {
357 days_in_month[9] = 29;
359 }
360 if length % 10 == 5 {
361 days_in_month[8] = 30;
363 }
364 return days_in_month;
365}
366
367#[cfg(test)]
368mod tests {
369 use super::*;
370
371 fn gdate(year: i32, month: u8, day: u8) -> GDate {
374 GDate::new(year, month, day).unwrap()
375 }
376
377 fn jdate(year: i32, month: u8, day: u8) -> JDate {
378 JDate::new(year, month, day).unwrap()
379 }
380
381 fn check_roundtrip(g: GDate, j: JDate) {
382 assert_eq!(JDate::from(g), j);
383 assert_eq!(GDate::from(j), g);
384 }
385
386
387 #[test]
388 fn test_molad_year() {
389 assert_eq!(molad_components(1), (1, 5, 204));
390 assert_eq!(molad_components(5785), (2112590, 9, 391));
391 }
392
393 #[test]
394 fn test_leap_year() {
395 assert!(is_leap_year(5700));
396 assert!(!is_leap_year(5701));
397 assert!(!is_leap_year(5702));
398 assert!(is_leap_year(5703));
399 assert!(is_leap_year(5782));
400 assert!(!is_leap_year(5783));
401 assert!(is_leap_year(5784));
402 assert!(!is_leap_year(5785));
403 assert!(!is_leap_year(5786));
404 assert!(is_leap_year(5787));
405 }
406
407 #[test]
408 fn test_jdate_new() {
409 assert!(JDate::new(5785, 1, 1).is_some());
411 assert!(JDate::new(5784, 12, 30).is_some());
412 assert!(JDate::new(5784, 13, 1).is_some());
413 assert!(JDate::new(5786, 9, 30).is_some());
414 assert!(JDate::new(5787, 9, 30).is_some());
415 assert!(JDate::new(5785, 13, 1).is_none());
417 assert!(JDate::new(5785, 0, 1).is_none());
418 assert!(JDate::new(5785, 14, 1).is_none());
419 assert!(JDate::new(5785, 1, 0).is_none());
420 assert!(JDate::new(5785, 1, 31).is_none());
421 assert!(JDate::new(5785, 2, 30).is_none());
422 assert!(JDate::new(5785, 12, 30).is_none());
423 assert!(JDate::new(5785, 13, 30).is_none());
424 assert!(JDate::new(5781, 8, 30).is_none());
425 assert!(JDate::new(5781, 9, 30).is_none());
426 assert!(JDate::new(5786, 8, 30).is_none());
427 }
428
429 #[test]
430 fn test_roundtrip() {
431 check_roundtrip(gdate( 1, 1, 1), jdate(3761, 10, 18));
432 check_roundtrip(gdate(-3760, 9, 7), jdate( 1, 7, 1));
433 check_roundtrip(gdate(2024, 12, 31), jdate(5785, 9, 30));
434 check_roundtrip(gdate(2025, 1, 1), jdate(5785, 10, 1));
435 check_roundtrip(gdate(2025, 2, 1), jdate(5785, 11, 3));
436 check_roundtrip(gdate(2025, 3, 1), jdate(5785, 12, 1));
437 check_roundtrip(gdate(2024, 2, 10), jdate(5784, 12, 1));
438 check_roundtrip(gdate(2024, 3, 11), jdate(5784, 13, 1));
439 check_roundtrip(gdate(2024, 4, 9), jdate(5784, 1, 1));
440 check_roundtrip(gdate(2024, 10, 2), jdate(5784, 6, 29));
441 check_roundtrip(gdate(2024, 10, 3), jdate(5785, 7, 1));
442 }
443
444 #[test]
445 fn test_dafyomi() {
446 assert_eq!(jdate(5772, 5, 15).dafyomi(), ("Berachos", 2));
447 assert_eq!(jdate(5780, 10, 8).dafyomi(), ("Berachos", 2));
448 assert_eq!(jdate(5780, 12, 11).dafyomi(), ("Berachos", 64));
449 assert_eq!(jdate(5780, 12, 12).dafyomi(), ("Shabbos", 2));
450 assert_eq!(jdate(5784, 7, 6).dafyomi(), ("Kidushin", 39));
451 assert_eq!(jdate(5787, 3, 2).dafyomi(), ("Nidah", 73));
452 }
453
454 #[test]
455 fn test_omer() {
456 assert_eq!(jdate(5785, 1, 15).omer(), 0);
457 assert_eq!(jdate(5785, 1, 16).omer(), 1);
458 assert_eq!(jdate(5785, 1, 30).omer(), 15);
459 assert_eq!(jdate(5785, 2, 1).omer(), 16);
460 assert_eq!(jdate(5785, 2, 29).omer(), 44);
461 assert_eq!(jdate(5785, 3, 1).omer(), 45);
462 assert_eq!(jdate(5785, 3, 5).omer(), 49);
463 assert_eq!(jdate(5785, 3, 6).omer(), 0);
464 assert_eq!(jdate(5785, 4, 1).omer(), 0);
465 assert_eq!(jdate(5785, 1, 1).omer(), 0);
466 }
467}