1#[macro_use]
2extern crate lazy_static;
3extern crate chrono;
4extern crate time;
5use chrono::prelude::*;
6use std::fmt;
7use time::Duration;
8#[macro_use]
9extern crate enum_primitive;
10extern crate num;
11use num::FromPrimitive;
12
13pub const CHALAKIM_PER_HOUR: i64 = 1080;
16pub const CHALAKIM_BETWEEN_MOLAD: i64 = 29 * 24 * CHALAKIM_PER_HOUR + 12 * CHALAKIM_PER_HOUR + 793;
17const LEAP_YEARS: [bool; 19] = [
18 false, false, true, false, false, true, false, true, false, false, true, false, false, true,
19 false, false, true, false, true,
20];
21const FIRST_MOLAD: i64 = 1 * 24 * 1080 + 18 * 1080 + (16 * 1080 / 60) + 6;
23const FIRST_YEAR: i64 = 3763;
24lazy_static! {
25 static ref EPOCH: chrono::DateTime<Utc> = Utc.ymd(2, 9, 21).and_hms(18, 0, 0);
26}
27
28fn return_year_sched(days: i64) -> usize {
29 match days {
30 354 => 0,
31 355 => 1,
32 356 => 2,
33
34 383 => 3,
35 384 => 4,
36 385 => 5,
37 _ => panic!(format!("Wrong amount of days {}",days))
38 }
39}
40const YEAR_SCHED: [[i8; 14]; 6] = [
41 [30, 29, 29, 29, 30, 29, 0, 0, 30, 29, 30, 29, 30, 29],
42 [30, 29, 30, 29, 30, 29, 0, 0, 30, 29, 30, 29, 30, 29],
43 [30, 30, 30, 29, 30, 29, 0, 0, 30, 29, 30, 29, 30, 29],
44 [30, 29, 29, 29, 30, 0, 30, 29, 30, 29, 30, 29, 30, 29],
45 [30, 29, 30, 29, 30, 0, 30, 29, 30, 29, 30, 29, 30, 29],
46 [30, 30, 30, 29, 30, 0, 30, 29, 30, 29, 30, 29, 30, 29],
47];
48
49lazy_static! {
50 static ref AMNT_CHALAKIM_PER_CYCLE: i64 = {
51 let mut chalakim = 0;
52 for i in LEAP_YEARS.iter() {
53 if *i {
54 chalakim += CHALAKIM_BETWEEN_MOLAD * 13;
55 } else {
56 chalakim += CHALAKIM_BETWEEN_MOLAD * 12;
57 }
58 }
59 chalakim
60 };
61}
62
63fn get_molad_for_year(year: i64) -> i64 {
64 let amnt_of_cycles = (year - FIRST_YEAR) / 19;
65
66 let full_cycle_chalakim = 7 * 13 * CHALAKIM_BETWEEN_MOLAD + 12 * 12 * CHALAKIM_BETWEEN_MOLAD;
68
69 let mut amnt_chalakim = full_cycle_chalakim * amnt_of_cycles;
70 let cur_year_in_cycle = (year - FIRST_YEAR) % 19;
71 for i in 0..cur_year_in_cycle {
72 amnt_chalakim += if LEAP_YEARS[i as usize] { 13 } else { 12 } * CHALAKIM_BETWEEN_MOLAD;
73 }
74
75 return amnt_chalakim;
76}
77
78fn months_per_year(year: i64) -> i64 {
79 let year_in_cycle = ((year - FIRST_YEAR) % 19) as usize;
80 if LEAP_YEARS[year_in_cycle] {
81 13
82 } else {
83 12
84 }
85}
86
87fn get_rosh_hashana(year: i64) -> (i64, Day) {
88 let amnt_chalakim_since_first_molad = get_molad_for_year(year);
89 let amnt_chalakim_since_epoch = amnt_chalakim_since_first_molad + FIRST_MOLAD;
90
91 let mut amnt_days = amnt_chalakim_since_epoch / (CHALAKIM_PER_HOUR * 24);
92 let amnt_chalakim = amnt_chalakim_since_epoch % (CHALAKIM_PER_HOUR * 24);
93 let mut reg_postpone = false;
94 if amnt_chalakim >= 18 * CHALAKIM_PER_HOUR {
96 amnt_days += 1;
97 reg_postpone = true;
98 }
99
100 let mut dow = (amnt_days) % 7;
101 if dow == 0 || dow == 3 || dow == 5 {
104 amnt_days += 1;
105 reg_postpone = true;
106 }
107 dow = (amnt_days) % 7;
108
109 if !reg_postpone
112 && dow == 2
113 && amnt_chalakim > 9 * CHALAKIM_PER_HOUR + 204
114 && months_per_year(year) == 12
115 {
116 amnt_days += 2;
117 }
118
119 if !reg_postpone
120 && months_per_year(year - 1) == 13
121 && dow == 1
122 && amnt_chalakim > 12 * CHALAKIM_PER_HOUR + 3 * CHALAKIM_PER_HOUR + 589
123 {
124 amnt_days += 1;
125 }
126
127 (amnt_days + 1, Day::from_i64(dow).unwrap())
128}
129
130fn get_english_date(h: HebrewDate) -> Result<chrono::DateTime<Utc>, ConversionError> {
131 let months_per_year = months_per_year(h.year);
132 if months_per_year == 12 && (h.month == HebrewMonth::Adar1 || h.month == HebrewMonth::Adar2) {
133 return Err(ConversionError::IsNotLeapYear);
134 }
135 if months_per_year == 13 && h.month == HebrewMonth::Adar {
136 return Err(ConversionError::IsLeapYear);
137 }
138
139 let amnt_days_between_rh_and_epoch = get_rosh_hashana(h.year).0;
140 let amnt_days_in_year = get_rosh_hashana(h.year + 1).0 - amnt_days_between_rh_and_epoch;
141 let sched = &YEAR_SCHED[return_year_sched(amnt_days_in_year)];
142
143 if h.day > sched[h.month as usize] {
144 return Err(ConversionError::TooManyDaysInMonth(sched[h.month as usize]));
145 }
146 let mut amnt_days_in_month: i16 = 0;
147 if h.month != HebrewMonth::Tishrei {
148 for i in 0..h.month as usize {
149 amnt_days_in_month += sched[i] as i16;
150 }
151 }
152 let amnt_days =
153 amnt_days_between_rh_and_epoch + amnt_days_in_month as i64 + h.day as i64 - 1 - 1;
154 Ok(*EPOCH + Duration::days(amnt_days))
155}
156
157#[derive(Debug)]
158pub struct HebrewDate {
159 day: i8,
160 month: HebrewMonth,
161 year: i64,
162 molads_of_month: [i64; 14],
163 months_length: &'static [i8; 14],
164 rosh_hashana_dow: Day,
165}
166
167impl HebrewDate {
168 pub fn day(&self) -> i8 {
169 return self.day;
170 }
171 pub fn month(&self) -> HebrewMonth {
172 return self.month;
173 }
174 pub fn year(&self) -> i64 {
175 return self.year;
176 }
177
178 pub fn from_ymd(
179 year: i64,
180 month: HebrewMonth,
181 day: i64,
182 ) -> Result<HebrewDate, ConversionError> {
183 if year < FIRST_YEAR + 1 {
184 return Err(ConversionError::YearTooSmall(format!(
185 "We cannot work with Hebrew years before year {}",
186 FIRST_YEAR + 1
187 )));
188 }
189
190 let amnt_days = get_rosh_hashana(year+1).0-get_rosh_hashana(year).0;
191 let sched = &YEAR_SCHED[return_year_sched(amnt_days)];
192
193 let mut molads_of_month = [0; 14];
194 for i in 0..14 {
195 molads_of_month[i] = 0;
196 }
197 Ok(HebrewDate {
198 year: year,
199 month: month,
200 day: day as i8,
201 rosh_hashana_dow: get_rosh_hashana(year).1,
202 months_length: sched,
203 molads_of_month:molads_of_month
204 })
205 }
206
207 pub fn from_eng(time: chrono::DateTime<Utc>) -> Result<HebrewDate, ConversionError> {
208 if time.year() < (*EPOCH + Duration::days(365)).year() {
209 return Err(ConversionError::YearTooSmall(format!(
210 "We cannot work with Gregorian years before year {}",
211 (*EPOCH + Duration::days(365)).year()
212 )));
213 }
214
215 let amnt_chalakim_per_cycle = *AMNT_CHALAKIM_PER_CYCLE;
216 let diff_sec = (time - *EPOCH).num_seconds();
217 let diff_chalakim = diff_sec * CHALAKIM_PER_HOUR / 60 / 60;
218 let amnt_cycles = diff_chalakim / amnt_chalakim_per_cycle;
219
220 let mut remainder_chalakim = diff_chalakim % amnt_chalakim_per_cycle;
221 let mut year = FIRST_YEAR + amnt_cycles * 19;
222 for i in LEAP_YEARS.iter() {
223 let chalakim_this_year = if *i { 13 } else { 12 } * CHALAKIM_BETWEEN_MOLAD;
224
225 if remainder_chalakim - chalakim_this_year < 0 {
226 break;
227 }
228 year += 1;
229 remainder_chalakim -= chalakim_this_year;
230 }
231
232 let mut month = 0;
233 let current_rh = get_rosh_hashana(year);
234 let amnt_days_in_year = get_rosh_hashana(year + 1).0 - current_rh.0;
235 let sched = &YEAR_SCHED[return_year_sched(amnt_days_in_year)];
236 for amnt_days in sched.iter() {
237 let chalakim_this_month = *amnt_days as i64 * CHALAKIM_PER_HOUR * 24;
238 if (remainder_chalakim - chalakim_this_month as i64) < 0 {
239 break;
240 }
241 month += 1;
242 remainder_chalakim -= chalakim_this_month as i64;
243 }
244
245 let day = if time.hour() <= 18 { 0 } else { 1 };
246
247 let molads_of_month = [0; 14];
248 Ok(HebrewDate {
249 month: HebrewMonth::from_i32(month).unwrap(),
250 day: (day + remainder_chalakim / (CHALAKIM_PER_HOUR * 24) ) as i8,
251 year: year,
252 molads_of_month: molads_of_month,
253 months_length: sched,
254 rosh_hashana_dow: current_rh.1
255 })
256 }
257 pub fn to_eng(self) -> Result<chrono::DateTime<Utc>, ConversionError> {
258 get_english_date(self)
259 }
260}
261
262enum_from_primitive! {
263#[derive(Debug, PartialEq, Copy, Clone)]
264enum Day{
265 Sunday,
266 Monday,
267 Tuesday,
268 Wedneday,
269 Thurday,
270 Friday,
271 Shabbos
272}
273}
274enum_from_primitive! {
275 #[derive(Debug, PartialEq, Copy, Clone)]
276 pub enum HebrewMonth {
277 Tishrei = 0,
278 Cheshvan = 1,
279 Kislev = 2,
280 Teves = 3,
281 Shvat = 4,
282 Adar = 5,
283 Adar1 = 6,
284 Adar2 = 7,
285 Nissan = 8,
286 Iyar = 9,
287 Sivan = 10,
288 Tammuz = 11,
289 Av = 12,
290 Elul = 13
291 }
292}
293
294impl HebrewMonth {
295 pub fn month_list() -> Vec<&'static str> {
296 vec![
297 "Tishrei", "Cheshvan", "Kislev", "Teves", "Shvat", "Adar", "Adar1", "Adar2", "Nissan",
298 "Iyar", "Sivan", "Tammuz", "Av", "Elul",
299 ]
300 }
301 pub fn try_from(s: &str) -> Result<HebrewMonth, ConversionError> {
302 match s {
303 "Tishrei" => Ok(HebrewMonth::Tishrei),
304 "Cheshvan" => Ok(HebrewMonth::Cheshvan),
305 "Kislev" => Ok(HebrewMonth::Kislev),
306 "Teves" => Ok(HebrewMonth::Teves),
307 "Shvat" => Ok(HebrewMonth::Shvat),
308 "Adar" => Ok(HebrewMonth::Adar),
309 "Adar1" => Ok(HebrewMonth::Adar1),
310 "Adar 1" => Ok(HebrewMonth::Adar1),
311 "Adar Aleph" => Ok(HebrewMonth::Adar1),
312 "Adar2" => Ok(HebrewMonth::Adar2),
313 "Adar 2" => Ok(HebrewMonth::Adar2),
314 "Adar Beis" => Ok(HebrewMonth::Adar2),
315 "Nissan" => Ok(HebrewMonth::Nissan),
316 "Iyar" => Ok(HebrewMonth::Iyar),
317 "Sivan" => Ok(HebrewMonth::Sivan),
318 "Tammuz" => Ok(HebrewMonth::Tammuz),
319 "Av" => Ok(HebrewMonth::Av),
320 "Elul" => Ok(HebrewMonth::Elul),
321 _ => Err(ConversionError::MonthDoesntExist),
322 }
323 }
324
325 pub fn as_str(&self) -> &str {
326 match self {
327 HebrewMonth::Tishrei => "Tishrei",
328 HebrewMonth::Cheshvan => "Cheshvan",
329 HebrewMonth::Kislev => "Kislev",
330 HebrewMonth::Teves => "Teves",
331 HebrewMonth::Shvat => "Shvat",
332 HebrewMonth::Adar => "Adar",
333 HebrewMonth::Adar1 => "Adar 1",
334 HebrewMonth::Adar2 => "Adar 2",
335 HebrewMonth::Nissan => "Nissan",
336 HebrewMonth::Iyar => "Iyar",
337 HebrewMonth::Sivan => "Sivan",
338 HebrewMonth::Tammuz => "Tammuz",
339 HebrewMonth::Av => "Av",
340 HebrewMonth::Elul => "Elul",
341 }
342 }
343}
344
345impl std::fmt::Display for HebrewMonth {
346 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
347 let string = self.as_str();
348 write!(f, "{}", string)
349 }
350}
351
352#[derive(Debug)]
353
354pub enum ConversionError {
355 IsNotLeapYear,
356 TooManyDaysInMonth(i8),
357 IsLeapYear,
358 MonthDoesntExist,
359 YearTooSmall(String),
360}
361
362impl std::fmt::Display for ConversionError {
363 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
364 use crate::ConversionError::*;
365 match self {
366 IsNotLeapYear => write!(f, "The year you specified is not a leap year, yet you're trying to convert from an Adar1 or Adar2. Use the regular Adar for a regular year"),
367 TooManyDaysInMonth(d) => write!(f,"There aren't {} days in this month",d),
368 IsLeapYear => write!(f, "The year you specified is a leap year, yet you're trying to convert from a Regular Adar. Use Adar1 or Adar2 on a leap year"),
369 MonthDoesntExist => write!(f, "This month doesn't exist. Please specify another one."),
370 YearTooSmall(s) => write!(f, "{}",s)
371 }
372 }
373}