1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
use chrono::prelude::*;
use time::Duration;

use crate::prelude::*;
use serde::ser::SerializeStruct;
use serde::Serialize;

mod year;
#[doc(inline)]
pub use year::*;

#[derive(Debug, Copy, Clone)]
pub struct HebrewDate {
    day: u8,
    month: HebrewMonth,
    year: HebrewYear,
}
impl Serialize for HebrewDate {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::ser::Serializer,
    {
        let mut state = serializer.serialize_struct("HebrewDate", 3)?;
        state.serialize_field("day", &self.day)?;
        state.serialize_field("month", &self.month)?;
        state.serialize_field("year", &self.year())?;
        state.end()
    }
}
impl Eq for HebrewDate {}
impl PartialEq for HebrewDate {
    fn eq(&self, other: &HebrewDate) -> bool {
        self.day == other.day && self.month == other.month && self.year() == other.year()
    }
}

use std::cmp::Ordering;
impl Ord for HebrewDate {
    fn cmp(&self, other: &HebrewDate) -> Ordering {
        if self.year() < other.year() {
            Ordering::Less
        } else if self.year() > other.year() {
            Ordering::Greater
        } else if (self.month as i32) < (other.month as i32) {
            Ordering::Less
        } else if (self.month as i32) > (other.month as i32) {
            Ordering::Greater
        } else if self.day < other.day {
            Ordering::Less
        } else if self.day > other.day {
            Ordering::Greater
        } else {
            Ordering::Equal
        }
    }
}

impl PartialOrd for HebrewDate {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl HebrewDate {
    /// Returns a HebrewDate on success, or a ConversionError on failure.
    ///
    /// # Arguments
    /// * `year` - The Hebrew year since creation.
    /// * `month` - The Hebrew month.
    /// * `day` - The Hebrew day of month.
    ///
    /// # Error Values
    /// * `YearTooSmall` - This algorithm won't work if the year is before 3764.
    /// * `DayIsZero` - Months start with day 1, not zero. So 0 Adar won't work.
    /// * `IsLeapYear` - I treat Adar, Adar 1 and Adar 2 as three seperate months, so if you want to
    /// convert a day in Adar 1 or Adar 2 of a leap year, specify which one.
    ///  * `IsNotLeapYear` - I treat Adar, Adar 1 and Adar 2 as three seperate months, so it won't
    ///  make sense to get the English date of the first of Adar 1 or Adar 2 if the year isn't a
    ///  leap year.
    ///  * `TooManyDaysInMonth` - There are either 29 or 30 days in a month, so it doesn't make sense
    ///  to find the 50th day of Nissan.
    pub fn from_ymd(year: u64, month: HebrewMonth, day: u8) -> Result<HebrewDate, ConversionError> {
        HebrewYear::new(year)?.get_hebrew_date(month, day)
    }

    pub fn from_ymd_unchecked(year: u64, month: HebrewMonth, day: u8) -> HebrewDate {
        HebrewYear::new(year)
            .unwrap()
            .get_hebrew_date(month, day)
            .unwrap()
    }

    pub(crate) fn from_ymd_internal(
        month: HebrewMonth,
        day: u8,
        hebrew_year: HebrewYear,
    ) -> Result<HebrewDate, ConversionError> {
        //Get a HebrewDate object from the Hebrew Year, Month, and Day. Can fail if the year is too
        //small or the day is less than one.

        if day == 0 {
            return Err(ConversionError::DayIsZero);
        }

        if !hebrew_year.is_leap_year()
            && (month == HebrewMonth::Adar1 || month == HebrewMonth::Adar2)
        {
            return Err(ConversionError::IsNotLeapYear);
        }

        if hebrew_year.is_leap_year() && month == HebrewMonth::Adar {
            return Err(ConversionError::IsLeapYear);
        }

        if day > hebrew_year.sched[month as usize] {
            return Err(ConversionError::TooManyDaysInMonth(
                hebrew_year.sched[month as usize],
            ));
        }

        Ok(HebrewDate {
            month,
            day: day as u8,
            year: hebrew_year,
        })
    }

    /// Returns a HebrewDate on success, or a ConversionError on failure.
    ///
    /// # Arguments
    /// * `date` - The Gregorian date.
    ///
    /// # Notes:
    /// Hebrew days start at sundown, not midnight, so there isn't a full 1:1 mapping between
    /// Gregorian days and Hebrew. So when you look up the date of Rosh Hashana 5779, you'll get "Monday, 10th of September 2018", while Rosh Hashana really started at sundown on the 9th of September.
    ///
    /// I'm trying to be a _bit_ more precise, so I made the date cutoff at 6:00 PM. So for
    /// example:
    /// ```
    /// extern crate heca_lib;
    ///
    /// use chrono::Utc;
    /// use chrono::offset::TimeZone;
    /// use heca_lib::prelude::*;
    /// use heca_lib::HebrewDate;
    ///
    /// assert_eq!(HebrewDate::from_gregorian(Utc.ymd(2018,9,10).and_hms(17,59,59)).unwrap(),HebrewDate::from_ymd(5779,HebrewMonth::Tishrei,1).unwrap());
    /// ```
    ///
    /// while
    ///
    /// ```
    /// extern crate heca_lib;
    ///
    /// use chrono::Utc;
    /// use chrono::offset::TimeZone;
    /// use heca_lib::prelude::*;
    /// use heca_lib::HebrewDate;
    ///
    /// assert_eq!(HebrewDate::from_gregorian(Utc.ymd(2018,9,10).and_hms(18,0,0)).unwrap(),HebrewDate::from_ymd(5779,HebrewMonth::Tishrei,2).unwrap());
    /// ```
    /// # Error Values:
    /// * YearTooSmall - This algorithm won't work if the year is before year 4.
    ///
    pub fn from_gregorian(date: DateTime<Utc>) -> Result<HebrewDate, ConversionError> {
        if date < *crate::convert::year::backend::FIRST_RH + Duration::days(2 + 365) {
            return Err(ConversionError::YearTooSmall);
        }
        let days_since_first_rh =
            ((date - *crate::convert::year::backend::FIRST_RH).num_days() + 2) as u64;

        let hebrew_year = HebrewYear::new(crate::convert::year::backend::day_of_last_rh(
            days_since_first_rh,
        ))
        .unwrap();
        Ok(hebrew_year.get_hebrewdate_from_days_after_rh(days_since_first_rh))
    }

    /// Gets the Gregorian date for the current Hebrew date.
    ///
    /// # Notes
    ///
    /// This function returns the DateTime of the given HebrewDate at nightfall.
    ///
    /// For example, Yom Kippur 5779 started at sunset of September 18, 2018. So
    /// ```
    /// extern crate heca_lib;
    ///
    /// use chrono::Utc;
    /// use chrono::offset::TimeZone;
    /// use heca_lib::prelude::*;
    /// use heca_lib::HebrewDate;
    ///
    /// assert_eq!(HebrewDate::from_ymd(5779,HebrewMonth::Tishrei,10).unwrap().to_gregorian(),Utc.ymd(2018, 9,18).and_hms(18,00,00));
    /// ```
    /// ## Algorithm:
    /// The conversion is done (at the moment) according to the calculation of the Rambam (Maimonidies), as is documented in [Hilchos Kiddush Ha'chodesh](https://www.sefaria.org/Mishneh_Torah%2C_Sanctification_of_the_New_Month.6.1?lang=bi&with=all&lang2=en).
    ///
    /// The algorithm is as follows:
    ///
    /// 1. There are exactly 1080 Chalakim (parts) in an hour.
    /// 2. There are exactly (well, not really. But it's close enough that we use that number as exact.) 29 days, 12 hours, and 793 Chalakim between new moons.
    ///
    ///  So that's the basic numbers. Regarding the calendar itself:
    ///
    /// 3. All months are either 29 or 30 days long.
    /// 4. There are either 12 or 13 months in the Hebrew calendar, depending if it's a leap year. When it's a leap year, Adar (which generally is in the late winter or early spring) is doubled into a "first Adar" (Adar1) and a "second Adar" (Adar2).
    /// 5. There is a 19 year cycle of leap years. So the first two years of the cycle are regular years, the one after that's a leap year. Then another two are regular, then a leap year. Then it's regular, leap, regular, regular, leap, regular, regular, leap.
    /// 6. Year 3763 was the first year of its 19 year cycle.
    /// 7. Now you can calculate when's the New Moon before a given Rosh Hashana.
    ///
    ///  So how to calculate Rosh Hashana:
    ///
    /// 8. If the New Moon is in the afternoon, Rosh Hashana is postponed to the next day.
    /// 9. If Rosh Hashana's starting on a Sunday (Saturday night), Wednesday (Tuesday night), or Friday (Thursday night) - postpone it by a day.
    ///
    ///  If any of the above two conditions were fulfilled. Good. You just found Rosh Hashana. If not:
    ///
    /// 10. If the New Moon is on a Tuesday after 3am+204 Chalakim and the coming year is not a leap year, Rosh Hashana is postponed to that upcoming Thursday instead.
    /// 11. If the New Moon is on a Monday after 9am+589 Chalakim, and the previous year was a leap year, then Rosh Hashana is postponed to Tuesday.
    ///
    ///
    ///  Now you have all the Rosh Hashanas.
    ///
    /// 12. In general, months alternate between “Full” (30 days long) and “Empty” (29 days long) months. So Tishrei is full, Teves is empty, Shvat is full, Adar is empty, Nissan is full.
    /// 13. When the year is a leap year, Adar 1 is full and Adar 2 is empty. (So a full Shvat is followed by a full Adar1).
    ///
    ///  Knowing this, you can calculate any other date of the year.
    ///
    ///  But wait! We're playing with the date when Rosh Hashana will start, so not every year will be the same length! How do we make up these days?
    ///
    ///  So there's a last little bit:
    ///
    /// 14. Cheshvan and Kislev are variable length months – some years both are full, some years both are empty, and some years Cheshvan is full and Kislev is empty - depending on the day Rosh Hashana starts (and the day _the next Rosh Hashana starts_) and how many days are in the year.

    pub fn to_gregorian(&self) -> chrono::DateTime<Utc> {
        let amnt_days_between_rh_and_epoch = self.year.days_since_epoch;
        let sched = self.year.sched;
        let mut amnt_days_in_month: u16 = 0;
        if self.month != HebrewMonth::Tishrei {
            for item in sched.iter().take(self.month as usize) {
                amnt_days_in_month += u16::from(*item);
            }
        }

        let amnt_days =
            amnt_days_between_rh_and_epoch + u64::from(amnt_days_in_month) + u64::from(self.day)
                - 1;
        *crate::convert::year::backend::EPOCH + Duration::days(amnt_days as i64)
    }
    ///Get the Hebrew day of month.
    #[inline]
    pub fn day(&self) -> u8 {
        self.day
    }

    ///Get the Hebrew month of year
    #[inline]
    pub fn month(&self) -> HebrewMonth {
        self.month
    }

    ///Get the Hebrew year.

    #[inline]
    pub fn year(&self) -> u64 {
        self.year.year
    }
}

mod tests {
    #[test]
    fn get_year() {
        use super::*;
        for j in 0..100 {
            let mut original_day = Utc.ymd(16 + j, 10, 4).and_hms(18, 0, 0);
            for _i in 1..366 {
                let h_day = HebrewDate::from_gregorian(original_day).unwrap();
                let ne_day = h_day.to_gregorian();
                assert_eq!(original_day, ne_day);
                original_day = original_day + Duration::days(1);
            }
        }
    }

}