use crate::calendar_arithmetic::{ArithmeticDate, CalendarArithmetic};
use crate::types::FormattableMonth;
use crate::AnyCalendarKind;
use crate::AsCalendar;
use crate::Iso;
use crate::{types, Calendar, CalendarError, Date, DateDuration, DateDurationUnit, DateTime};
use ::tinystr::tinystr;
use calendrical_calculations::hebrew::BookHebrew;
use calendrical_calculations::rata_die::RataDie;
#[derive(Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
#[non_exhaustive] pub struct Hebrew;
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
struct BookHebrewDateInner;
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
pub struct HebrewDateInner(ArithmeticDate<Hebrew>);
impl Hebrew {
pub fn new_always_calculating() -> Self {
Hebrew
}
}
impl CalendarArithmetic for Hebrew {
fn month_days(civil_year: i32, civil_month: u8) -> u8 {
Self::last_day_of_civil_hebrew_month(civil_year, civil_month)
}
fn months_for_every_year(civil_year: i32) -> u8 {
Self::last_month_of_civil_hebrew_year(civil_year)
}
fn days_in_provided_year(civil_year: i32) -> u16 {
BookHebrew::days_in_book_hebrew_year(civil_year) }
fn is_leap_year(civil_year: i32) -> bool {
BookHebrew::is_hebrew_leap_year(civil_year)
}
fn last_month_day_in_year(civil_year: i32) -> (u8, u8) {
let civil_month = Self::last_month_of_civil_hebrew_year(civil_year);
let civil_day = Self::last_day_of_civil_hebrew_month(civil_year, civil_month);
(civil_month, civil_day)
}
}
impl Calendar for Hebrew {
type DateInner = HebrewDateInner;
fn date_from_codes(
&self,
era: types::Era,
year: i32,
month_code: types::MonthCode,
day: u8,
) -> Result<Self::DateInner, CalendarError> {
let is_leap_year = Self::is_leap_year(year);
let year = if era.0 == tinystr!(16, "hebrew") || era.0 == tinystr!(16, "am") {
year
} else {
return Err(CalendarError::UnknownEra(era.0, self.debug_name()));
};
let month_code_str = month_code.0.as_str();
let month_ordinal = if is_leap_year {
match month_code_str {
"M01" => 1,
"M02" => 2,
"M03" => 3,
"M04" => 4,
"M05" => 5,
"M05L" => 6,
"M06" | "M06L" => 7,
"M07" => 8,
"M08" => 9,
"M09" => 10,
"M10" => 11,
"M11" => 12,
"M12" => 13,
_ => {
return Err(CalendarError::UnknownMonthCode(
month_code.0,
self.debug_name(),
))
}
}
} else {
match month_code_str {
"M01" => 1,
"M02" => 2,
"M03" => 3,
"M04" => 4,
"M05" => 5,
"M06" => 6,
"M07" => 7,
"M08" => 8,
"M09" => 9,
"M10" => 10,
"M11" => 11,
"M12" => 12,
_ => {
return Err(CalendarError::UnknownMonthCode(
month_code.0,
self.debug_name(),
))
}
}
};
ArithmeticDate::new_from_lunar_ordinals(year, month_ordinal, day).map(HebrewDateInner)
}
fn date_from_iso(&self, iso: Date<Iso>) -> Self::DateInner {
let fixed_iso = Iso::fixed_from_iso(*iso.inner());
Self::civil_hebrew_from_fixed(fixed_iso).inner
}
fn date_to_iso(&self, date: &Self::DateInner) -> Date<Iso> {
let fixed_hebrew = Self::fixed_from_civil_hebrew(*date);
Iso::iso_from_fixed(fixed_hebrew)
}
fn months_in_year(&self, date: &Self::DateInner) -> u8 {
date.0.months_in_year()
}
fn days_in_year(&self, date: &Self::DateInner) -> u16 {
date.0.days_in_year()
}
fn days_in_month(&self, date: &Self::DateInner) -> u8 {
date.0.days_in_month()
}
fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
date.0.offset_date(offset)
}
fn until(
&self,
date1: &Self::DateInner,
date2: &Self::DateInner,
_calendar2: &Self,
_largest_unit: DateDurationUnit,
_smallest_unit: DateDurationUnit,
) -> DateDuration<Self> {
date1.0.until(date2.0, _largest_unit, _smallest_unit)
}
fn debug_name(&self) -> &'static str {
"Hebrew"
}
fn year(&self, date: &Self::DateInner) -> types::FormattableYear {
Self::year_as_hebrew(date.0.year)
}
fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
Self::is_leap_year(date.0.year)
}
fn month(&self, date: &Self::DateInner) -> FormattableMonth {
let mut ordinal = date.0.month;
let is_leap_year = Self::is_leap_year(date.0.year);
if is_leap_year {
if ordinal == 6 {
return types::FormattableMonth {
ordinal: ordinal as u32,
code: types::MonthCode(tinystr!(4, "M05L")),
};
} else if ordinal == 7 {
return types::FormattableMonth {
ordinal: ordinal as u32,
code: types::MonthCode(tinystr!(4, "M06L")),
};
}
}
if is_leap_year && ordinal > 6 {
ordinal -= 1;
}
let code = match ordinal {
1 => tinystr!(4, "M01"),
2 => tinystr!(4, "M02"),
3 => tinystr!(4, "M03"),
4 => tinystr!(4, "M04"),
5 => tinystr!(4, "M05"),
6 => tinystr!(4, "M06"),
7 => tinystr!(4, "M07"),
8 => tinystr!(4, "M08"),
9 => tinystr!(4, "M09"),
10 => tinystr!(4, "M10"),
11 => tinystr!(4, "M11"),
12 => tinystr!(4, "M12"),
_ => tinystr!(4, "und"),
};
types::FormattableMonth {
ordinal: date.0.month as u32,
code: types::MonthCode(code),
}
}
fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
date.0.day_of_month()
}
fn day_of_year_info(&self, date: &Self::DateInner) -> types::DayOfYearInfo {
let prev_year = date.0.year.saturating_sub(1);
let next_year = date.0.year.saturating_add(1);
types::DayOfYearInfo {
day_of_year: date.0.day_of_year(),
days_in_year: date.0.days_in_year(),
prev_year: Self::year_as_hebrew(prev_year),
days_in_prev_year: Self::days_in_provided_year(prev_year),
next_year: Self::year_as_hebrew(next_year),
}
}
fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
Some(AnyCalendarKind::Hebrew)
}
}
impl Hebrew {
fn biblical_to_civil_date(biblical_date: BookHebrew) -> HebrewDateInner {
let (y, m, d) = biblical_date.to_civil_date();
debug_assert!(ArithmeticDate::<Hebrew>::new_from_lunar_ordinals(y, m, d,).is_ok());
HebrewDateInner(ArithmeticDate::new_unchecked(y, m, d))
}
fn civil_to_biblical_date(civil_date: HebrewDateInner) -> BookHebrew {
BookHebrew::from_civil_date(civil_date.0.year, civil_date.0.month, civil_date.0.day)
}
fn last_month_of_civil_hebrew_year(civil_year: i32) -> u8 {
if Self::is_leap_year(civil_year) {
13 } else {
12
}
}
fn last_day_of_civil_hebrew_month(civil_year: i32, civil_month: u8) -> u8 {
let book_date = Hebrew::civil_to_biblical_date(HebrewDateInner(
ArithmeticDate::new_unchecked(civil_year, civil_month, 1),
));
BookHebrew::last_day_of_book_hebrew_month(book_date.year, book_date.month)
}
fn fixed_from_civil_hebrew(date: HebrewDateInner) -> RataDie {
let book_date = Hebrew::civil_to_biblical_date(date);
BookHebrew::fixed_from_book_hebrew(book_date)
}
fn civil_hebrew_from_fixed(date: RataDie) -> Date<Hebrew> {
let book_hebrew = BookHebrew::book_hebrew_from_fixed(date);
Date::from_raw(Hebrew::biblical_to_civil_date(book_hebrew), Hebrew)
}
fn year_as_hebrew(civil_year: i32) -> types::FormattableYear {
types::FormattableYear {
era: types::Era(tinystr!(16, "hebrew")),
number: civil_year,
cyclic: None,
related_iso: None,
}
}
}
impl<A: AsCalendar<Calendar = Hebrew>> Date<A> {
pub fn try_new_hebrew_date_with_calendar(
year: i32,
month: u8,
day: u8,
calendar: A,
) -> Result<Date<A>, CalendarError> {
ArithmeticDate::new_from_lunar_ordinals(year, month, day)
.map(HebrewDateInner)
.map(|inner| Date::from_raw(inner, calendar))
}
}
impl<A: AsCalendar<Calendar = Hebrew>> DateTime<A> {
pub fn try_new_hebrew_datetime_with_calendar(
year: i32,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
calendar: A,
) -> Result<DateTime<A>, CalendarError> {
Ok(DateTime {
date: Date::try_new_hebrew_date_with_calendar(year, month, day, calendar)?,
time: types::Time::try_new(hour, minute, second, 0)?,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use calendrical_calculations::hebrew::*;
#[test]
fn test_conversions() {
let iso_dates: [Date<Iso>; 48] = [
Date::try_new_iso_date(2021, 1, 10).unwrap(),
Date::try_new_iso_date(2021, 1, 25).unwrap(),
Date::try_new_iso_date(2021, 2, 10).unwrap(),
Date::try_new_iso_date(2021, 2, 25).unwrap(),
Date::try_new_iso_date(2021, 3, 10).unwrap(),
Date::try_new_iso_date(2021, 3, 25).unwrap(),
Date::try_new_iso_date(2021, 4, 10).unwrap(),
Date::try_new_iso_date(2021, 4, 25).unwrap(),
Date::try_new_iso_date(2021, 5, 10).unwrap(),
Date::try_new_iso_date(2021, 5, 25).unwrap(),
Date::try_new_iso_date(2021, 6, 10).unwrap(),
Date::try_new_iso_date(2021, 6, 25).unwrap(),
Date::try_new_iso_date(2021, 7, 10).unwrap(),
Date::try_new_iso_date(2021, 7, 25).unwrap(),
Date::try_new_iso_date(2021, 8, 10).unwrap(),
Date::try_new_iso_date(2021, 8, 25).unwrap(),
Date::try_new_iso_date(2021, 9, 10).unwrap(),
Date::try_new_iso_date(2021, 9, 25).unwrap(),
Date::try_new_iso_date(2021, 10, 10).unwrap(),
Date::try_new_iso_date(2021, 10, 25).unwrap(),
Date::try_new_iso_date(2021, 11, 10).unwrap(),
Date::try_new_iso_date(2021, 11, 25).unwrap(),
Date::try_new_iso_date(2021, 12, 10).unwrap(),
Date::try_new_iso_date(2021, 12, 25).unwrap(),
Date::try_new_iso_date(2022, 1, 10).unwrap(),
Date::try_new_iso_date(2022, 1, 25).unwrap(),
Date::try_new_iso_date(2022, 2, 10).unwrap(),
Date::try_new_iso_date(2022, 2, 25).unwrap(),
Date::try_new_iso_date(2022, 3, 10).unwrap(),
Date::try_new_iso_date(2022, 3, 25).unwrap(),
Date::try_new_iso_date(2022, 4, 10).unwrap(),
Date::try_new_iso_date(2022, 4, 25).unwrap(),
Date::try_new_iso_date(2022, 5, 10).unwrap(),
Date::try_new_iso_date(2022, 5, 25).unwrap(),
Date::try_new_iso_date(2022, 6, 10).unwrap(),
Date::try_new_iso_date(2022, 6, 25).unwrap(),
Date::try_new_iso_date(2022, 7, 10).unwrap(),
Date::try_new_iso_date(2022, 7, 25).unwrap(),
Date::try_new_iso_date(2022, 8, 10).unwrap(),
Date::try_new_iso_date(2022, 8, 25).unwrap(),
Date::try_new_iso_date(2022, 9, 10).unwrap(),
Date::try_new_iso_date(2022, 9, 25).unwrap(),
Date::try_new_iso_date(2022, 10, 10).unwrap(),
Date::try_new_iso_date(2022, 10, 25).unwrap(),
Date::try_new_iso_date(2022, 11, 10).unwrap(),
Date::try_new_iso_date(2022, 11, 25).unwrap(),
Date::try_new_iso_date(2022, 12, 10).unwrap(),
Date::try_new_iso_date(2022, 12, 25).unwrap(),
];
let book_hebrew_dates: [(u8, u8, i32); 48] = [
(26, TEVET, 5781),
(12, SHEVAT, 5781),
(28, SHEVAT, 5781),
(13, ADAR, 5781),
(26, ADAR, 5781),
(12, NISAN, 5781),
(28, NISAN, 5781),
(13, IYYAR, 5781),
(28, IYYAR, 5781),
(14, SIVAN, 5781),
(30, SIVAN, 5781),
(15, TAMMUZ, 5781),
(1, AV, 5781),
(16, AV, 5781),
(2, ELUL, 5781),
(17, ELUL, 5781),
(4, TISHRI, 5782),
(19, TISHRI, 5782),
(4, MARHESHVAN, 5782),
(19, MARHESHVAN, 5782),
(6, KISLEV, 5782),
(21, KISLEV, 5782),
(6, TEVET, 5782),
(21, TEVET, 5782),
(8, SHEVAT, 5782),
(23, SHEVAT, 5782),
(9, ADAR, 5782),
(24, ADAR, 5782),
(7, ADARII, 5782),
(22, ADARII, 5782),
(9, NISAN, 5782),
(24, NISAN, 5782),
(9, IYYAR, 5782),
(24, IYYAR, 5782),
(11, SIVAN, 5782),
(26, SIVAN, 5782),
(11, TAMMUZ, 5782),
(26, TAMMUZ, 5782),
(13, AV, 5782),
(28, AV, 5782),
(14, ELUL, 5782),
(29, ELUL, 5782),
(15, TISHRI, 5783),
(30, TISHRI, 5783),
(16, MARHESHVAN, 5783),
(1, KISLEV, 5783),
(16, KISLEV, 5783),
(1, TEVET, 5783),
];
let civil_hebrew_dates: [(u8, u8, i32); 48] = [
(26, 4, 5781),
(12, 5, 5781),
(28, 5, 5781),
(13, 6, 5781),
(26, 6, 5781),
(12, 7, 5781),
(28, 7, 5781),
(13, 8, 5781),
(28, 8, 5781),
(14, 9, 5781),
(30, 9, 5781),
(15, 10, 5781),
(1, 11, 5781),
(16, 11, 5781),
(2, 12, 5781),
(17, 12, 5781),
(4, 1, 5782),
(19, 1, 5782),
(4, 2, 5782),
(19, 2, 5782),
(6, 3, 5782),
(21, 3, 5782),
(6, 4, 5782),
(21, 4, 5782),
(8, 5, 5782),
(23, 5, 5782),
(9, 6, 5782),
(24, 6, 5782),
(7, 7, 5782),
(22, 7, 5782),
(9, 8, 5782),
(24, 8, 5782),
(9, 9, 5782),
(24, 9, 5782),
(11, 10, 5782),
(26, 10, 5782),
(11, 11, 5782),
(26, 11, 5782),
(13, 12, 5782),
(28, 12, 5782),
(14, 13, 5782),
(29, 13, 5782),
(15, 1, 5783),
(30, 1, 5783),
(16, 2, 5783),
(1, 3, 5783),
(16, 3, 5783),
(1, 4, 5783),
];
for (iso_date, (book_date_nums, civil_date_nums)) in iso_dates
.iter()
.zip(book_hebrew_dates.iter().zip(civil_hebrew_dates.iter()))
{
let book_date = BookHebrew {
year: book_date_nums.2,
month: book_date_nums.1,
day: book_date_nums.0,
};
let civil_date: HebrewDateInner = HebrewDateInner(ArithmeticDate::new_unchecked(
civil_date_nums.2,
civil_date_nums.1,
civil_date_nums.0,
));
let book_to_civil = Hebrew::biblical_to_civil_date(book_date);
let civil_to_book = Hebrew::civil_to_biblical_date(civil_date);
assert_eq!(civil_date, book_to_civil);
assert_eq!(book_date, civil_to_book);
let iso_to_fixed = Iso::fixed_from_iso(iso_date.inner);
let fixed_to_hebrew = Hebrew::civil_hebrew_from_fixed(iso_to_fixed);
let hebrew_to_fixed = Hebrew::fixed_from_civil_hebrew(civil_date);
let fixed_to_iso = Iso::iso_from_fixed(hebrew_to_fixed);
assert_eq!(fixed_to_hebrew.inner, civil_date);
assert_eq!(fixed_to_iso.inner, iso_date.inner);
}
}
#[test]
fn test_icu_bug_22441() {
assert_eq!(BookHebrew::days_in_book_hebrew_year(88369), 383);
}
}