pub mod month;
pub mod holiday;
pub mod parsha;
pub mod prelude {
pub use super::holiday::{Holiday, HolidayIterator};
pub use super::month;
pub use super::month::HebrewMonthExt;
pub use super::parsha::Parsha;
pub use super::{HebrewCalendar, HebrewCalendarDate, HebrewHolidayCalendar, YearLengthType};
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod tests;
use holiday::{Holiday, HolidayIterator};
use icu_calendar::options::DateAddOptions;
use icu_calendar::types::{DateDuration, Month, Weekday};
use icu_calendar::{AsCalendar, Date, Iso, cal::Hebrew};
use jiff_icu::ConvertFrom;
use month::*;
use num_enum::{IntoPrimitive, TryFromPrimitive};
pub use parsha::Parsha;
use parsha::get_parsha_list;
pub trait HebrewCalendarDate {
fn hebrew_date(&self) -> Date<Hebrew>;
}
impl<C> HebrewCalendarDate for Date<C>
where
C: AsCalendar,
{
#[inline]
fn hebrew_date(&self) -> Date<Hebrew> {
self.to_calendar(Hebrew)
}
}
impl HebrewCalendarDate for jiff::civil::Date {
#[inline]
fn hebrew_date(&self) -> Date<Hebrew> {
let iso_date = Date::<Iso>::convert_from(*self);
iso_date.to_calendar(Hebrew)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, IntoPrimitive, TryFromPrimitive)]
#[repr(u8)]
pub enum YearLengthType {
Chaserim = 0,
Kesidran = 1,
Shelaimim = 2,
}
pub trait HebrewHolidayCalendar: HebrewCalendarDate {
type HolidayIter: Iterator<Item = Holiday>;
fn input_month(&self) -> Month;
fn holidays(&self, in_israel: bool, use_modern_holidays: bool) -> Self::HolidayIter;
fn is_assur_bemelacha(&self, in_israel: bool) -> bool;
fn has_candle_lighting(&self, in_israel: bool) -> bool;
fn is_aseres_yemei_teshuva(&self) -> bool;
fn todays_parsha(&self, in_israel: bool) -> Option<Parsha>;
fn special_parsha(&self, in_israel: bool) -> Option<Parsha>;
fn upcoming_parsha(&self, in_israel: bool) -> Option<Parsha>;
}
impl<T> HebrewHolidayCalendar for T
where
T: HebrewCalendarDate,
{
type HolidayIter = HolidayIterator;
#[inline]
fn is_assur_bemelacha(&self, in_israel: bool) -> bool {
self.hebrew_date().weekday() == Weekday::Saturday
|| self.holidays(in_israel, false).any(|i| i.is_assur_bemelacha())
}
#[inline]
fn has_candle_lighting(&self, in_israel: bool) -> bool {
self.hebrew_date()
.try_added_with_options(DateDuration::for_days(1), DateAddOptions::default())
.map(|next_day| next_day.is_assur_bemelacha(in_israel))
.unwrap_or(false)
}
#[inline]
fn input_month(&self) -> Month {
self.hebrew_date().month().to_input()
}
#[inline]
fn holidays(&self, in_israel: bool, use_modern_holidays: bool) -> Self::HolidayIter {
HolidayIterator {
iter: holiday::all_rules().iter(),
date: self.hebrew_date(),
in_israel,
use_modern_holidays,
}
}
fn is_aseres_yemei_teshuva(&self) -> bool {
let date = self.hebrew_date();
date.input_month() == TISHREI && date.day_of_month().0 <= 10
}
fn todays_parsha(&self, in_israel: bool) -> Option<Parsha> {
let date = self.hebrew_date();
if date.weekday() != Weekday::Saturday {
return None;
}
let parsha_list = get_parsha_list(&date, in_israel)?;
let rosh_hashana_day_of_week = get_hebrew_elapsed_days(date.year().extended_year())? % 7;
let day = rosh_hashana_day_of_week + date.day_of_year().0 as i32;
let week_index = usize::try_from(day / 7).ok()?;
parsha_list.get(week_index).copied().flatten()
}
fn special_parsha(&self, in_israel: bool) -> Option<Parsha> {
let date = self.hebrew_date();
if date.weekday() != Weekday::Saturday {
return None;
}
let month = date.input_month();
let day = date.day_of_month().0;
let is_leap = Hebrew::is_hebrew_leap_year(date.year().extended_year());
if ((month == SHEVAT && !is_leap) || (month == ADARI && is_leap)) && (day == 25 || day == 27 || day == 29) {
return Some(Parsha::Shekalim);
}
if month == ADAR {
if day == 1 {
return Some(Parsha::Shekalim);
}
if day == 8 || day == 9 || day == 11 || day == 13 {
return Some(Parsha::Zachor);
}
if day == 18 || day == 20 || day == 22 || day == 23 {
return Some(Parsha::Parah);
}
if day == 25 || day == 27 || day == 29 {
return Some(Parsha::Hachodesh);
}
}
if month == NISAN {
if day == 1 {
return Some(Parsha::Hachodesh);
}
if (8..=14).contains(&day) {
return Some(Parsha::Hagadol);
}
}
if month == AV {
if (4..=9).contains(&day) {
return Some(Parsha::Chazon);
}
if (10..=16).contains(&day) {
return Some(Parsha::Nachamu);
}
}
if month == TISHREI {
if (3..=8).contains(&day) {
return Some(Parsha::Shuva);
}
}
if self.todays_parsha(in_israel) == Some(Parsha::Beshalach) {
return Some(Parsha::Shira);
}
None
}
fn upcoming_parsha(&self, in_israel: bool) -> Option<Parsha> {
let days_to_shabbos = match self.hebrew_date().weekday() {
Weekday::Monday => 5,
Weekday::Tuesday => 4,
Weekday::Wednesday => 3,
Weekday::Thursday => 2,
Weekday::Friday => 1,
Weekday::Saturday => 7,
Weekday::Sunday => 6,
};
let mut date = self
.hebrew_date()
.try_added_with_options(DateDuration::for_days(days_to_shabbos), DateAddOptions::default())
.ok()?;
for _ in 0..60 {
if let Some(parshah) = date.todays_parsha(in_israel) {
return Some(parshah);
}
date = date
.try_added_with_options(DateDuration::for_days(7), DateAddOptions::default())
.ok()?;
}
None
}
}
const CHALAKIM_MOLAD_TOHU: i64 = 31524;
const CHALAKIM_PER_MONTH: i64 = 765433;
const CHALAKIM_PER_DAY: i64 = 25920;
pub(crate) fn chalakim_since_molad_tohu(year: i32, month: Month) -> Option<i64> {
let month_of_year = month.hebrew_month_of_year(year)?;
let months_elapsed = (235 * ((year - 1) / 19))
+ (12 * ((year - 1) % 19))
+ ((7 * ((year - 1) % 19) + 1) / 19)
+ (month_of_year as i32 - 1);
Some(CHALAKIM_MOLAD_TOHU + (CHALAKIM_PER_MONTH * months_elapsed as i64))
}
pub(super) fn get_hebrew_elapsed_days(year: i32) -> Option<i32> {
let chalakim_since = chalakim_since_molad_tohu(year, TISHREI)?;
let molad_day = chalakim_since / CHALAKIM_PER_DAY;
let molad_parts = chalakim_since - molad_day * CHALAKIM_PER_DAY;
let mut rosh_hashana_day = molad_day;
if (molad_parts >= 19440)
|| (((molad_day % 7) == 2) && (molad_parts >= 9924) && !Hebrew::is_hebrew_leap_year(year))
|| (((molad_day % 7) == 1) && (molad_parts >= 16789) && (Hebrew::is_hebrew_leap_year(year - 1)))
{
rosh_hashana_day += 1;
}
if ((rosh_hashana_day % 7) == 0) || ((rosh_hashana_day % 7) == 3) || ((rosh_hashana_day % 7) == 5) {
rosh_hashana_day += 1;
}
Some(rosh_hashana_day as i32)
}
pub trait HebrewCalendar {
fn days_in_hebrew_year(year: i32) -> Option<i32>;
fn days_in_hebrew_month(year: i32, month: Month) -> Option<u8>;
fn is_cheshvan_long(year: i32) -> Option<bool>;
fn is_kislev_short(year: i32) -> Option<bool>;
fn is_hebrew_leap_year(year: i32) -> bool;
fn cheshvan_kislev_kviah(year: i32) -> Option<YearLengthType>;
}
impl HebrewCalendar for Hebrew {
#[inline]
fn days_in_hebrew_year(year: i32) -> Option<i32> {
Some(get_hebrew_elapsed_days(year + 1)? - get_hebrew_elapsed_days(year)?)
}
#[inline]
fn days_in_hebrew_month(year: i32, month: Month) -> Option<u8> {
month.hebrew_month_of_year(year)?;
Some(match month {
IYYAR | TAMMUZ | ELUL | TEVET => 29,
ḤESHVAN if Self::is_cheshvan_long(year)? => 30,
ḤESHVAN => 29,
KISLEV if Self::is_kislev_short(year)? => 29,
KISLEV => 30,
ADARI => 30,
ADAR => 29,
TISHREI | SHEVAT | NISAN | SIVAN | AV => 30,
_ => return None,
})
}
#[inline]
fn is_cheshvan_long(year: i32) -> Option<bool> {
Some(Self::days_in_hebrew_year(year)? % 10 == 5)
}
#[inline]
fn is_kislev_short(year: i32) -> Option<bool> {
Some(Self::days_in_hebrew_year(year)? % 10 == 3)
}
#[inline]
fn is_hebrew_leap_year(year: i32) -> bool {
let year_in_cycle = ((year - 1) % 19) + 1;
matches!(year_in_cycle, 3 | 6 | 8 | 11 | 14 | 17 | 19)
}
#[inline]
fn cheshvan_kislev_kviah(year: i32) -> Option<YearLengthType> {
Some(if Self::is_cheshvan_long(year)? && !Self::is_kislev_short(year)? {
YearLengthType::Shelaimim
} else if !Self::is_cheshvan_long(year)? && Self::is_kislev_short(year)? {
YearLengthType::Chaserim
} else {
YearLengthType::Kesidran
})
}
}