use icu_calendar::{
Date,
cal::Hebrew,
types::{Month, Weekday},
};
use crate::{
calendar::{
HebrewHolidayCalendar,
month::{AV, NISAN, SIVAN, TISHREI},
},
limudim::{
HebrewDateExt, Limud,
cycle::Cycle,
interval::Interval,
limud::{CycleFinder, InternalLimud},
},
};
#[allow(clippy::expect_used)]
fn from_hebrew_date(year: i32, month: Month, day: u8) -> Date<Hebrew> {
Date::try_new_hebrew_v2(year, month, day).expect("hard-coded Hebrew date should be valid")
}
fn day_of_week_number(date: Date<Hebrew>) -> i32 {
match date.weekday() {
Weekday::Sunday => 1,
Weekday::Monday => 2,
Weekday::Tuesday => 3,
Weekday::Wednesday => 4,
Weekday::Thursday => 5,
Weekday::Friday => 6,
Weekday::Saturday => 7,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PirkeiAvosUnit {
Single(u8),
Combined(u8, u8),
}
#[derive(Default)]
pub struct PirkeiAvos {
pub in_israel: bool,
}
impl InternalLimud<PirkeiAvosUnit> for PirkeiAvos {
fn cycle_finder(&self) -> CycleFinder {
if self.in_israel {
CycleFinder::Perpetual(Self::find_yearly_cycle_israel)
} else {
CycleFinder::Perpetual(Self::find_yearly_cycle_diaspora)
}
}
fn unit_for_interval(&self, interval: &Interval, _limud_date: &Date<Hebrew>) -> Option<PirkeiAvosUnit> {
let iteration = interval.iteration;
if iteration < 19 {
let chapter = ((iteration - 1) % 6) + 1;
return Some(PirkeiAvosUnit::Single(chapter as u8));
}
let days_until_end = interval.end_date.days_until(&interval.cycle.end_date)?;
let weeks_remain = days_until_end.div_ceil(7);
match weeks_remain {
0 => Some(PirkeiAvosUnit::Combined(5, 6)),
1 => Some(PirkeiAvosUnit::Combined(3, 4)),
2 => {
if (iteration - 1) % 6 == 0 {
Some(PirkeiAvosUnit::Combined(1, 2))
} else {
Some(PirkeiAvosUnit::Single(((iteration - 1) % 6 + 1) as u8))
}
}
3 => Some(PirkeiAvosUnit::Single(1)),
_ => {
let chapter = ((iteration - 1) % 6) + 1;
Some(PirkeiAvosUnit::Single(chapter as u8))
}
}
}
fn interval_end_calculation(_cycle: Cycle, hebrew_date: Date<Hebrew>) -> Option<Date<Hebrew>> {
let day_number = day_of_week_number(hebrew_date);
hebrew_date.add_days(7 - day_number)
}
fn is_skip_interval(&self, interval: &Interval) -> bool {
let end_month = interval.end_date.input_month();
let end_day = interval.end_date.day_of_month().0;
if end_month == AV && end_day == 8 {
return true;
}
if end_month == AV && end_day == 9 {
return true;
}
if !self.in_israel && end_month == SIVAN && end_day == 7 {
return true;
}
false
}
}
impl Limud<PirkeiAvosUnit> for PirkeiAvos {}
impl PirkeiAvos {
pub fn new(in_israel: bool) -> Self {
Self { in_israel }
}
fn find_yearly_cycle_israel(date: Date<Hebrew>) -> Option<(Date<Hebrew>, Date<Hebrew>)> {
Some(Self::find_yearly_cycle(true, date))
}
fn find_yearly_cycle_diaspora(date: Date<Hebrew>) -> Option<(Date<Hebrew>, Date<Hebrew>)> {
Some(Self::find_yearly_cycle(false, date))
}
fn find_yearly_cycle(in_israel: bool, date: Date<Hebrew>) -> (Date<Hebrew>, Date<Hebrew>) {
let year = date.year().extended_year();
let anchor_day = if in_israel { 22 } else { 23 };
let cycle_start_this_year = from_hebrew_date(year, NISAN, anchor_day);
let (start_date, cycle_year) = if date >= cycle_start_this_year {
(cycle_start_this_year, year)
} else {
let prev_year_start = from_hebrew_date(year - 1, NISAN, anchor_day);
(prev_year_start, year - 1)
};
let rosh_hashana = from_hebrew_date(cycle_year + 1, TISHREI, 1);
let day_number = day_of_week_number(rosh_hashana);
let end_date = rosh_hashana.add_days(-day_number).unwrap_or(rosh_hashana);
(start_date, end_date)
}
}
#[cfg(test)]
#[allow(clippy::expect_used)]
mod tests {
use crate::calendar::month::ELUL;
use super::*;
#[test]
fn test_simple_date() {
let test_date = from_hebrew_date(5778, SIVAN, 1);
let calculator = PirkeiAvos::new(false);
let limud = calculator.limud(test_date).expect("limud exists");
assert_eq!(limud, PirkeiAvosUnit::Single(6));
}
#[test]
fn test_near_end_of_cycle() {
let test_date = from_hebrew_date(5778, ELUL, 20);
let calculator = PirkeiAvos::new(false);
let limud = calculator.limud(test_date).expect("limud exists");
assert_eq!(limud, PirkeiAvosUnit::Combined(3, 4));
}
#[test]
fn test_after_cycle_completes() {
let test_date = from_hebrew_date(5778, ELUL, 29);
let calculator = PirkeiAvos::new(false);
let limud = calculator.limud(test_date);
assert!(limud.is_none());
}
#[test]
fn test_before_cycle_starts() {
let test_date = from_hebrew_date(5778, NISAN, 20);
let calculator = PirkeiAvos::new(false);
let limud = calculator.limud(test_date);
assert!(limud.is_none());
}
#[test]
fn test_8th_day_pesach_outside_israel() {
let test_date = from_hebrew_date(5778, NISAN, 22);
let calculator = PirkeiAvos::new(false);
let limud = calculator.limud(test_date);
assert!(limud.is_none());
}
#[test]
fn test_day_after_pesach_outside_israel() {
let test_date = from_hebrew_date(5778, NISAN, 23);
let calculator = PirkeiAvos::new(false);
let limud = calculator.limud(test_date).expect("limud exists");
assert_eq!(limud, PirkeiAvosUnit::Single(1));
}
#[test]
fn test_compounding_before_cycle_end_outside_israel() {
let test_date = from_hebrew_date(5778, ELUL, 14);
let calculator = PirkeiAvos::new(false);
let limud = calculator.limud(test_date).expect("limud exists");
assert_eq!(limud, PirkeiAvosUnit::Combined(1, 2));
let test_date2 = from_hebrew_date(5778, ELUL, 15);
let limud2 = calculator.limud(test_date2).expect("limud exists");
assert_eq!(limud2, PirkeiAvosUnit::Combined(3, 4));
}
#[test]
fn test_8th_day_pesach_in_israel() {
let test_date = from_hebrew_date(5778, NISAN, 22);
let calculator = PirkeiAvos::new(true);
let limud = calculator.limud(test_date).expect("limud exists");
assert_eq!(limud, PirkeiAvosUnit::Single(1));
}
#[test]
fn test_day_after_pesach_in_israel() {
let test_date = from_hebrew_date(5778, NISAN, 23);
let calculator = PirkeiAvos::new(true);
let limud = calculator.limud(test_date).expect("limud exists");
assert_eq!(limud, PirkeiAvosUnit::Single(2));
}
#[test]
fn test_compounding_before_cycle_end_in_israel() {
let test_date = from_hebrew_date(5778, ELUL, 21);
let calculator = PirkeiAvos::new(true);
let limud = calculator.limud(test_date).expect("limud exists");
assert_eq!(limud, PirkeiAvosUnit::Combined(3, 4));
}
#[test]
fn test_7_sivan_on_shabbos_outside_israel() {
let test_date = from_hebrew_date(5769, SIVAN, 3);
let calculator = PirkeiAvos::new(false);
let limud = calculator.limud(test_date);
assert!(limud.is_none());
}
#[test]
fn test_iteration_following_7_sivan_on_shabbos_outside_israel() {
let test_date = from_hebrew_date(5769, SIVAN, 8);
let calculator = PirkeiAvos::new(false);
let limud = calculator.limud(test_date).expect("limud exists");
assert_eq!(limud, PirkeiAvosUnit::Single(1));
}
#[test]
fn test_7_sivan_on_shabbos_in_israel() {
let test_date = from_hebrew_date(5769, SIVAN, 3);
let calculator = PirkeiAvos::new(true);
let limud = calculator.limud(test_date).expect("limud exists");
assert_eq!(limud, PirkeiAvosUnit::Single(1));
}
#[test]
fn test_iteration_following_7_sivan_on_shabbos_in_israel() {
let test_date = from_hebrew_date(5769, SIVAN, 8);
let calculator = PirkeiAvos::new(true);
let limud = calculator.limud(test_date).expect("limud exists");
assert_eq!(limud, PirkeiAvosUnit::Single(2));
}
}