1use time::{OffsetDateTime, Date};
2use std::fmt;
3
4pub fn gdate(year: i32, month: u8, day: u8) -> Option<Date> {
10 return Date::from_calendar_date(year, month.try_into().unwrap(), day).ok();
11}
12
13#[derive(Debug, Clone, Copy, PartialEq)]
15pub struct JDate {
16 year: i32,
17 month: u8, day: u8, }
20
21impl JDate {
22 pub fn new(year: i32, month: u8, day: u8) -> Option<JDate> {
25 if !date_is_valid(year, month, day) {
26 return None;
27 }
28 Some(JDate{year, month, day})
29 }
30
31 pub fn from_jd(jd: i32) -> JDate {
33 let ed = jd - 347997; let mut year = ed * 100 / 36525;
35 while year_start(year) < ed {year += 1;}
36 while year_start(year) > ed {year -= 1;}
37 let mut days = year_start(year);
38 let days_in_month = year_months(year);
39 let mut month = 7;
40 loop {
41 let length = days_in_month[month] as i32;
42 if days + length > ed {break}
43 month += 1;
44 if month == 14 {month = 1}
45 if month == 7 {unreachable!()}
46 days += length;
47 }
48 return JDate{
49 year: year,
50 month: month as u8,
51 day: (ed-days+1) as u8
52 };
53 }
54
55 pub fn to_jd(self: Self) -> i32 {
57 let mut ed = year_start(self.year) - 1;
58 let days_in_month = year_months(self.year);
59 let mut month: u8 = 7;
60 loop {
61 let length = days_in_month[month as usize];
62 if month == self.month {
63 ed += self.day as i32;
64 break;
65 }
66 ed += length as i32;
67 month += 1;
68 if month == 14 {month = 1}
69 if month == 7 {unreachable!()}
70 }
71 return ed + 347997;
72 }
73
74
75 pub fn year(self: Self) -> i32 {return self.year}
77 pub fn month(self: Self) -> u8 {return self.month}
79 pub fn day(self: Self) -> u8 {return self.day}
81
82 pub fn month_name(self: Self) -> &'static str {
84 const NAMES: [&str; 13] = [
85 "Nisan", "Iyar", "Sivan", "Tamuz", "Av", "Elul",
86 "Tishrei", "Cheshvan", "Kislev", "Tevet", "Shvat", "Adar", "Adar2"];
87 if self.month == 12 && is_leap_year(self.year) {
88 return "Adar1"
89 }
90 return NAMES[self.month as usize - 1];
91
92 }
93}
94
95impl fmt::Display for JDate {
96 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
97 write!(f, "{:0>4}-{}-{:0>2}", self.year, self.month_name(), self.day)
98 }
99}
100
101impl From<Date> for JDate {
102 fn from(d: Date) -> Self {
104 let jd = d.to_julian_day();
105 return JDate::from_jd(jd);
106 }
107}
108
109impl From<JDate> for Date {
110 fn from(d: JDate) -> Self {
112 let jd = d.to_jd();
113 return Date::from_julian_day(jd).unwrap();
114 }
115}
116
117pub fn today() -> Date {
119 let now = OffsetDateTime::now_local().unwrap_or(OffsetDateTime::now_utc());
120 return now.date();
121}
122
123pub fn is_leap_year(year: i32) -> bool {
125 match year % 19 {
126 0|3|6|8|11|14|17 => true,
127 _ => false
128 }
129}
130
131pub fn date_is_valid(year: i32, month: u8, day: u8) -> bool {
133 if month < 1 || month > 13 || day < 1 || day > 30 {
134 return false;
135 }
136 if month == 13 {
137 return day <= 29 && is_leap_year(year);
138 }
139 if day == 30 {
140 return match month {
141 1|3|5|7|11 => true,
142 2|4|6|10 => false,
143 12 => is_leap_year(year),
144 8 => {
145 let len = year_length(year);
146 len % 10 == 5 },
148 9 => {
149 let len = year_length(year);
150 len % 10 >= 4 },
152 _ => unreachable!()
153 }
154 }
155 true
156}
157
158pub fn molad(year: i32) -> i64 {
162 let parts_month = (29*24+12)*1080+793;
163 let parts_year = 12 * parts_month;
164 let parts_lyear = 13 * parts_month;
165 let parts_cycle = 12 * parts_year + 7 * parts_lyear;
166 let total_cycles = (year-1).div_euclid(19);
167 let year_in_cycle = (year-1).rem_euclid(19);
168 let mut molad: i64 = (24+5)*1080+204; molad += total_cycles as i64 * parts_cycle as i64;
170 for year in 0..year_in_cycle {
171 if is_leap_year(year+1) {
172 molad += parts_lyear as i64;
173 } else {
174 molad += parts_year as i64;
175 }
176 }
177 return molad;
178}
179
180pub fn molad_components(year: i32) -> (i32, u8, u16) {
185 let molad = molad(year);
186 return ((molad.div_euclid(1080*24)) as i32,
187 (molad.div_euclid(1080).rem_euclid(24)) as u8,
188 (molad.rem_euclid(1080)) as u16);
189}
190
191pub fn molad_print(year: i32) {
193 let (day, hour, parts) = molad_components(year);
194 let day = day % 7;
195 let hour = (hour + 18) % 24;
196 let minute = parts / 18;
197 let parts = parts % 18;
198
199 let days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
200 let day_str = days[day as usize];
201 println!("Molad {year}: {day_str} {hour:0>2}:{minute:0>2} and {parts:>2} \
202 chalakim");
203}
204
205pub fn year_start(year: i32) -> i32 {
207 let molad = molad(year);
208 let day = molad.div_euclid(1080*24) as i32;
209 let parts = molad.rem_euclid(1080*24);
210 let mut rosh = day;
211 if parts >= 18*1080 {
214 rosh += 1;
215 }
216 if rosh % 7 == 0 || rosh % 7 == 3 || rosh % 7 == 5 {
218 rosh += 1;
219 }
220 if !is_leap_year(year) && day % 7 == 2 && parts >= 9*1080+204 {
222 rosh = day+2;
223 }
224 if is_leap_year(year-1) && day % 7 == 1 && parts >= 15*1080+589 {
226 rosh = day+1;
227 }
228 return rosh;
229}
230
231pub fn year_length(year: i32) -> i32 {
233 let rosh1 = year_start(year);
234 let rosh2 = year_start(year+1);
235 return rosh2-rosh1;
236}
237
238pub fn year_months(year: i32) -> [u8; 14] {
240 let mut days_in_month = [0, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 0]; if is_leap_year(year) {
243 days_in_month[12] = 30; days_in_month[13] = 29;
244 }
245 let length = year_length(year);
246 if length % 10 == 3 {
247 days_in_month[9] = 29;
249 }
250 if length % 10 == 5 {
251 days_in_month[8] = 30;
253 }
254 return days_in_month;
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260
261 #[test]
262 fn test_molad_year() {
263 let tod = today();
264 println!("{:?}", tod);
265 assert_eq!(molad_components(1), (1, 5, 204));
266 assert_eq!(molad_components(5785), (2112590, 9, 391));
267 }
268
269 #[test]
270 fn test_leap_year() {
271 assert!(is_leap_year(5700));
272 assert!(!is_leap_year(5701));
273 assert!(!is_leap_year(5702));
274 assert!(is_leap_year(5703));
275 assert!(is_leap_year(5782));
276 assert!(!is_leap_year(5783));
277 assert!(is_leap_year(5784));
278 assert!(!is_leap_year(5785));
279 assert!(!is_leap_year(5786));
280 assert!(is_leap_year(5787));
281 }
282
283 #[test]
284 fn test_from_greg() {
285 assert_eq!(JDate::from(gdate(1, 1, 1).unwrap()),
286 JDate::new(3761, 10, 18).unwrap());
287 assert_eq!(JDate::from(gdate(-3760, 9, 7).unwrap()),
288 JDate::new(1, 7, 1).unwrap());
289 assert_eq!(JDate::from(gdate(2024, 12, 31).unwrap()),
290 JDate::new(5785, 9, 30).unwrap());
291 assert_eq!(JDate::from(gdate(2025, 1, 1).unwrap()),
292 JDate::new(5785, 10, 1).unwrap());
293 assert_eq!(JDate::from(gdate(2025, 2, 1).unwrap()),
294 JDate::new(5785, 11, 3).unwrap());
295 assert_eq!(JDate::from(gdate(2025, 3, 1).unwrap()),
296 JDate::new(5785, 12, 1).unwrap());
297 assert_eq!(JDate::from(gdate(2024, 2, 10).unwrap()),
298 JDate::new(5784, 12, 1).unwrap());
299 assert_eq!(JDate::from(gdate(2024, 3, 11).unwrap()),
300 JDate::new(5784, 13, 1).unwrap());
301 assert_eq!(JDate::from(gdate(2024, 4, 9).unwrap()),
302 JDate::new(5784, 1, 1).unwrap());
303 assert_eq!(JDate::from(gdate(2024, 10, 2).unwrap()),
304 JDate::new(5784, 6, 29).unwrap());
305 assert_eq!(JDate::from(gdate(2024, 10, 3).unwrap()),
306 JDate::new(5785, 7, 1).unwrap());
307 }
308
309 #[test]
310 fn test_from_jdate() {
311 assert_eq!(Date::from(JDate::new(3761, 10, 18).unwrap()),
312 gdate(1, 1, 1).unwrap());
313 assert_eq!(Date::from(JDate::new(1, 7, 1).unwrap()),
314 gdate(-3760, 9, 7).unwrap());
315 assert_eq!(Date::from(JDate::new(5785, 9, 30).unwrap()),
316 gdate(2024, 12, 31).unwrap());
317 assert_eq!(Date::from(JDate::new(5785, 10, 1).unwrap()),
318 gdate(2025, 1, 1).unwrap());
319 assert_eq!(Date::from(JDate::new(5785, 11, 3).unwrap()),
320 gdate(2025, 2, 1).unwrap());
321 assert_eq!(Date::from(JDate::new(5785, 12, 1).unwrap()),
322 gdate(2025, 3, 1).unwrap());
323 assert_eq!(Date::from(JDate::new(5784, 12, 1).unwrap()),
324 gdate(2024, 2, 10).unwrap());
325 assert_eq!(Date::from(JDate::new(5784, 13, 1).unwrap()),
326 gdate(2024, 3, 11).unwrap());
327 assert_eq!(Date::from(JDate::new(5784, 1, 1).unwrap()),
328 gdate(2024, 4, 9).unwrap());
329 assert_eq!(Date::from(JDate::new(5784, 6, 29).unwrap()),
330 gdate(2024, 10, 2).unwrap());
331 assert_eq!(Date::from(JDate::new(5785, 7, 1).unwrap()),
332 gdate(2024, 10, 3).unwrap());
333 }
334}