heca_convert_lib/
lib.rs

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
13// ChalakimBetweenMolad is the amount of Chalakim between two Molads
14// See https://www.chabad.org/library/article_cdo/aid/947923/jewish/Kiddush-HaChodesh-Chapter-Six.htm#footnoteRef8a947923
15pub 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];
21// FirstMolad (of Tishrei 3673) was on Monday, September 23rd at 12:16:6 Chalakim
22const 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    //full_cycle_chalakim = (7 full years)(13 month/year)(ChalakimBetweenMolad) + (12 short years)(12 months/year)(ChalakimBetweenMolad)
67    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 the Molad is in the afternoon, postpone Rosh Hashana by a day
95    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    // Lo Adu Rosh
102
103    if dow == 0 || dow == 3 || dow == 5 {
104        amnt_days += 1;
105        reg_postpone = true;
106    }
107    dow = (amnt_days) % 7;
108
109    // See Hilchos Kiddush HaChodesh Halacha 4
110
111    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}