use std::convert::{TryFrom, TryInto};
use smallvec::*;
use crate::convert::year::backend::{CHALAKIM_BETWEEN_MOLAD, FIRST_MOLAD};
use crate::convert::*;
use crate::holidays::get_chol_list;
use crate::holidays::get_shabbos_list;
use crate::holidays::get_special_parsha_list;
use crate::holidays::get_yt_list;
use chrono::{Duration, Utc};
use std::num::NonZeroI8;
pub(crate) mod backend;
use crate::convert::year::backend::{
get_rosh_hashana, months_per_year, return_year_sched, CHALAKIM_PER_HOUR, EPOCH, FIRST_YEAR,
YEAR_SCHED,
};
use crate::prelude::HebrewMonth::{Adar, Adar1, Adar2};
use crate::prelude::{ConversionError, HebrewMonth, Molad};
#[derive(Copy, Clone, Debug)]
pub struct HebrewYear {
pub(crate) year: u64,
pub(crate) day_of_rh: Day,
pub(crate) day_of_next_rh: Day,
pub(crate) months_per_year: u64,
pub(crate) sched: [u8; 14],
pub(crate) year_len: u64,
pub(crate) days_since_epoch: u64,
pub(crate) chalakim_since_epoch: u64,
}
impl HebrewYear {
#[inline]
pub fn new(year: u64) -> Result<HebrewYear, ConversionError> {
if year < FIRST_YEAR + 1 {
Err(ConversionError::YearTooSmall)
} else {
let cur_rh = get_rosh_hashana(year);
let next_rh = get_rosh_hashana(year + 1);
let days_since_epoch = cur_rh.0;
let chalakim_since_epoch = cur_rh.2;
let year_len = next_rh.0 - cur_rh.0;
let months_per_year = months_per_year(year);
let sched = &YEAR_SCHED[return_year_sched(year_len)];
Ok(HebrewYear {
day_of_rh: get_rosh_hashana(year).1,
year,
day_of_next_rh: get_rosh_hashana(year + 1).1,
months_per_year,
sched: sched.clone(),
days_since_epoch,
year_len,
chalakim_since_epoch,
})
}
}
#[inline]
pub fn is_leap_year(&self) -> bool {
self.months_per_year == 13
}
#[inline]
pub fn year_type(&self) -> MonthSchedule {
if self.months_per_year == 12 {
match self.day_of_rh {
Day::Monday => {
if self.sched[1] == 30 && self.sched[2] == 30 {
MonthSchedule::BaShaH
} else if self.sched[1] == 29 && self.sched[2] == 29 {
MonthSchedule::BaChaG
} else {
panic!(format!(
"Year {} is 12 months, stars on Monday, yet has Cheshvan {} days and Kislev {} days",
self.year, self.sched[1], self.sched[2]
))
}
}
Day::Tuesday => {
if self.sched[1] == 29 && self.sched[2] == 30 {
MonthSchedule::GaChaH
} else {
panic!(format!(
"Year {} is 12 months, starts on Tuesday, yet has Cheshvan {} days and Kislev {} days",
self.year, self.sched[1], self.sched[2]
))
}
}
Day::Thursday => {
if self.sched[1] == 29 && self.sched[2] == 30 {
MonthSchedule::HaKaZ
} else if self.sched[1] == 30 && self.sched[2] == 30 {
MonthSchedule::HaShA
} else {
panic!(format!(
"Year {} is 12 months, starts on Thursday, yet has Cheshvan {} days and Kislev {} days",
self.year, self.sched[1], self.sched[2]
))
}
}
Day::Shabbos => {
if self.sched[1] == 30 && self.sched[2] == 30 {
MonthSchedule::ZaShaG
} else if self.sched[1] == 29 && self.sched[2] == 29 {
MonthSchedule::ZaChA
} else {
panic!(format!(
"Year {} is 12 months, stars on Shabbos, yet has Cheshvan {} days and Kislev {} days",
self.year, self.sched[1], self.sched[2]
))
}
}
x => panic!(format!("Rosh Hashana should never fall out on {:?}", x)),
}
} else {
match self.day_of_rh {
Day::Monday => {
if self.sched[1] == 30 && self.sched[2] == 30 {
MonthSchedule::BaShaZ
} else if self.sched[1] == 29 && self.sched[2] == 29 {
MonthSchedule::BaChaH
} else {
panic!(format!(
"Year {} is 13 months, stars on Monday, yet has Cheshvan {} days and Kislev {} days",
self.year, self.sched[1], self.sched[2]
))
}
}
Day::Tuesday => {
if self.sched[1] == 29 && self.sched[2] == 30 {
MonthSchedule::GaKaZ
} else {
panic!(format!(
"Year {} is 13 months, starts on Tuesday, yet has Cheshvan {} days and Kislev {} days",
self.year, self.sched[1], self.sched[2]
))
}
}
Day::Thursday => {
if self.sched[1] == 30 && self.sched[2] == 30 {
MonthSchedule::HaShaG
} else if self.sched[1] == 29 && self.sched[2] == 29 {
MonthSchedule::HaChA
} else {
panic!(format!(
"Year {} is 13 months, starts on Thursday, yet has Cheshvan {} days and Kislev {} days",
self.year, self.sched[1], self.sched[2]
))
}
}
Day::Shabbos => {
if self.sched[1] == 30 && self.sched[2] == 30 {
MonthSchedule::ZaShaH
} else if self.sched[1] == 29 && self.sched[2] == 29 {
MonthSchedule::ZaChaG
} else {
panic!(format!(
"Year {} is 13 months, stars on Shabbos, yet has Cheshvan {} days and Kislev {} days",
self.year, self.sched[1], self.sched[2]
))
}
}
x => panic!(format!("Rosh Hashana should never fall out on {:?}", x)),
}
}
}
#[inline]
pub fn year(&self) -> u64 {
self.year
}
#[inline]
pub fn get_hebrew_date(
self,
month: HebrewMonth,
day: NonZeroI8,
) -> Result<HebrewDate, ConversionError> {
HebrewDate::from_ymd_internal(month, day, self)
}
pub(crate) fn get_hebrewdate_from_days_after_rh(self, amnt_days: u64) -> HebrewDate {
let mut remainder = amnt_days - self.days_since_epoch;
let mut month: u64 = 0;
for days_in_month in self.sched.iter() {
if remainder < u64::from(*days_in_month) {
break;
}
month += 1;
remainder -= u64::from(*days_in_month);
}
HebrewDate {
year: self,
month: HebrewMonth::from(month),
day: NonZeroI8::new((remainder + 1) as i8).unwrap(),
}
}
pub fn get_holidays(
&self,
location: Location,
yt_types: &[TorahReadingType],
) -> SmallVec<[TorahReadingDay; 256]> {
let mut return_vec: SmallVec<[TorahReadingDay; 256]> = SmallVec::new();
if yt_types.contains(&TorahReadingType::YomTov) {
return_vec.extend_from_slice(&get_yt_list(self.clone(), location));
}
if yt_types.contains(&TorahReadingType::Chol) {
return_vec.extend_from_slice(&get_chol_list(self.clone()));
}
if yt_types.contains(&TorahReadingType::Shabbos) {
return_vec.extend_from_slice(&get_shabbos_list(self.clone(), location));
}
if yt_types.contains(&TorahReadingType::SpecialParsha) {
return_vec.extend_from_slice(&get_special_parsha_list(self.clone()));
}
return_vec
}
pub fn get_molad(&self, month: HebrewMonth) -> Result<Molad, ConversionError> {
let chalakim_since_epoch = if self.is_leap_year() {
match month {
HebrewMonth::Tishrei => 0,
HebrewMonth::Cheshvan => 1,
HebrewMonth::Kislev => 2,
HebrewMonth::Teves => 3,
HebrewMonth::Shvat => 4,
Adar1 => 5,
Adar2 => 6,
HebrewMonth::Nissan => 7,
HebrewMonth::Iyar => 8,
HebrewMonth::Sivan => 9,
HebrewMonth::Tammuz => 10,
HebrewMonth::Av => 11,
HebrewMonth::Elul => 12,
Adar => {
return Err(ConversionError::IsLeapYear);
}
}
} else {
match month {
HebrewMonth::Tishrei => 0,
HebrewMonth::Cheshvan => 1,
HebrewMonth::Kislev => 2,
HebrewMonth::Teves => 3,
HebrewMonth::Shvat => 4,
HebrewMonth::Adar => 5,
HebrewMonth::Nissan => 6,
HebrewMonth::Iyar => 7,
HebrewMonth::Sivan => 8,
HebrewMonth::Tammuz => 9,
HebrewMonth::Av => 10,
HebrewMonth::Elul => 11,
Adar1 => return Err(ConversionError::IsNotLeapYear),
Adar2 => return Err(ConversionError::IsNotLeapYear),
}
} * CHALAKIM_BETWEEN_MOLAD
+ self.chalakim_since_epoch
+ FIRST_MOLAD;
let minutes_since_epoch = (chalakim_since_epoch / (CHALAKIM_PER_HOUR / 60))
.try_into()
.unwrap();
let remainder = (chalakim_since_epoch % (CHALAKIM_PER_HOUR / 60))
.try_into()
.unwrap();
let day = EPOCH.clone() + Duration::minutes(minutes_since_epoch);
Ok(Molad { day, remainder })
}
}
#[test]
fn test_get_molad() {
use chrono::prelude::*;
let hebrew_year = HebrewYear::new(5780).unwrap();
let p = hebrew_year.get_molad(HebrewMonth::Tishrei).unwrap();
assert_eq!(
p,
Molad {
day: Utc.ymd(2019, 9, 29).and_hms(5, 50, 0),
remainder: 5
}
);
let p = hebrew_year.get_molad(HebrewMonth::Cheshvan).unwrap();
assert_eq!(
p,
Molad {
day: Utc.ymd(2019, 10, 28).and_hms(18, 34, 0),
remainder: 6
}
);
let hebrew_year = HebrewYear::new(5781).unwrap();
let p = hebrew_year.get_molad(HebrewMonth::Elul).unwrap();
assert_eq!(
p,
Molad {
day: Utc.ymd(2021, 8, 8).and_hms(10, 43, 0),
remainder: 10
}
);
let hebrew_year = HebrewYear::new(5780).unwrap();
assert_eq!(
hebrew_year.get_molad(HebrewMonth::Adar1),
Err(ConversionError::IsNotLeapYear)
);
let hebrew_year = HebrewYear::new(5779).unwrap();
assert_eq!(
hebrew_year.get_molad(HebrewMonth::Adar),
Err(ConversionError::IsLeapYear)
);
}
impl TryFrom<DateTime<Utc>> for HebrewDate {
type Error = ConversionError;
fn try_from(original_day: DateTime<Utc>) -> Result<HebrewDate, ConversionError> {
HebrewDate::from_gregorian(original_day)
}
}
impl From<HebrewDate> for DateTime<Utc> {
fn from(h: HebrewDate) -> Self {
h.to_gregorian()
}
}
mod test {
#[test]
fn make_new_year() {
use super::*;
for i in 4000..5000 {
println!("{}", i);
HebrewYear::new(i).unwrap();
}
}
#[test]
fn check_year_type() {
use super::*;
use chrono::prelude::*;
for i in 3765..9999 {
println!("{}", i);
let y = HebrewYear::new(i).unwrap();
let t = y.year_type();
match t {
MonthSchedule::GaChaH
| MonthSchedule::BaShaH
| MonthSchedule::BaChaH
| MonthSchedule::ZaShaH => assert_eq!(
y.get_hebrew_date(HebrewMonth::Nissan, NonZeroI8::new(16).unwrap())
.unwrap()
.to_gregorian()
.weekday(),
Weekday::Thu
),
MonthSchedule::HaShaG
| MonthSchedule::ZaShaG
| MonthSchedule::ZaChaG
| MonthSchedule::BaChaG => assert_eq!(
y.get_hebrew_date(HebrewMonth::Nissan, NonZeroI8::new(16).unwrap())
.unwrap()
.to_gregorian()
.weekday(),
Weekday::Tue
),
MonthSchedule::HaShA | MonthSchedule::ZaChA | MonthSchedule::HaChA => assert_eq!(
y.get_hebrew_date(HebrewMonth::Nissan, NonZeroI8::new(16).unwrap())
.unwrap()
.to_gregorian()
.weekday(),
Weekday::Sun
),
MonthSchedule::HaKaZ | MonthSchedule::BaShaZ | MonthSchedule::GaKaZ => assert_eq!(
y.get_hebrew_date(HebrewMonth::Nissan, NonZeroI8::new(16).unwrap())
.unwrap()
.to_gregorian()
.weekday(),
Weekday::Sat
),
}
}
}
}