use super::{
EarthlyBranch, HeavenlyStems, LunarMonth, LunarYear, SolarYear, Zodiac, BIG_MONTHS,
LEAP_MONTHS, MAX_YEAR_IN_SOLAR_CALENDAR, MIN_YEAR_IN_SOLAR_CALENDAR,
};
use std::fmt::{self, Display, Formatter};
#[derive(Debug, PartialOrd, Ord, PartialEq, Clone, Eq, Hash, Copy)]
pub struct LunisolarYear {
solar_year: SolarYear,
}
impl LunisolarYear {
#[allow(clippy::missing_safety_doc)]
#[inline]
pub unsafe fn from_solar_year_unsafe<Y: Into<SolarYear>>(solar_year: Y) -> LunisolarYear {
let solar_year = solar_year.into();
LunisolarYear {
solar_year,
}
}
#[inline]
pub fn from_solar_year<Y: Into<SolarYear>>(solar_year: Y) -> Option<LunisolarYear> {
let solar_year = solar_year.into();
let year = solar_year.to_u16();
if (MIN_YEAR_IN_SOLAR_CALENDAR..=MAX_YEAR_IN_SOLAR_CALENDAR).contains(&year) {
Some(LunisolarYear {
solar_year,
})
} else {
None
}
}
#[inline]
pub fn get_heavenly_stems(self) -> HeavenlyStems {
let index = (7 + (self.solar_year.to_u16() - MIN_YEAR_IN_SOLAR_CALENDAR)) % 10;
unsafe { HeavenlyStems::from_ordinal_unsafe(index as i8) }
}
#[inline]
pub fn get_earthly_branch(self) -> EarthlyBranch {
let index = (self.solar_year.to_u16() - MIN_YEAR_IN_SOLAR_CALENDAR + 1) % 12;
unsafe { EarthlyBranch::from_ordinal_unsafe(index as i8) }
}
#[inline]
pub fn get_zodiac(self) -> Zodiac {
let index = (self.solar_year.to_u16() - MIN_YEAR_IN_SOLAR_CALENDAR + 1) % 12;
unsafe { Zodiac::from_ordinal_unsafe(index as i8) }
}
#[inline]
pub fn get_leap_lunar_month(self) -> Option<LunarMonth> {
let year = self.to_u16();
let month = LEAP_MONTHS[((year - MIN_YEAR_IN_SOLAR_CALENDAR) / 2) as usize];
let index = if year % 2 == 1 {
(month & 0xf0) >> 4
} else {
month & 0x0f
};
if index == 0 {
None
} else {
Some(unsafe { LunarMonth::from_u8_unsafe(index, true) })
}
}
#[inline]
pub fn get_total_days_in_leap_month(self) -> u16 {
let leap_lunar_month = self.get_leap_lunar_month();
match leap_lunar_month {
Some(leap_lunar_month) => self.get_total_days_in_leap_month_inner(leap_lunar_month),
None => 0,
}
}
#[inline]
pub(crate) fn get_total_days_in_leap_month_inner(self, leap_lunar_month: LunarMonth) -> u16 {
let year = self.to_u16();
let leap_month = leap_lunar_month.to_u8();
if (BIG_MONTHS[(year - MIN_YEAR_IN_SOLAR_CALENDAR) as usize]
& (0x8000 >> u16::from(leap_month)))
== 0
{
29
} else {
30
}
}
pub fn get_total_days(self) -> u16 {
let leap_lunar_month = self.get_leap_lunar_month();
let (leap_month, mut n) = match leap_lunar_month {
Some(leap_lunar_month) => {
(
leap_lunar_month.to_u8(),
self.get_total_days_in_leap_month_inner(leap_lunar_month),
)
}
None => (0, 0),
};
let year = self.to_u16();
if leap_month > 0 {
for month in 1..=leap_month {
if (BIG_MONTHS[(year - MIN_YEAR_IN_SOLAR_CALENDAR) as usize]
& (0x8000 >> u16::from(month - 1)))
== 0
{
n += 29;
} else {
n += 30;
}
}
for month in (leap_month + 1)..=12 {
if (BIG_MONTHS[(year - MIN_YEAR_IN_SOLAR_CALENDAR) as usize]
& (0x8000 >> u16::from(month)))
== 0
{
n += 29;
} else {
n += 30;
}
}
} else {
for month in 1..=12 {
if (BIG_MONTHS[(year - MIN_YEAR_IN_SOLAR_CALENDAR) as usize]
& (0x8000 >> (month - 1)))
== 0
{
n += 29;
} else {
n += 30;
}
}
}
n
}
#[inline]
pub fn get_total_days_in_a_month(self, lunar_month: LunarMonth) -> Option<u8> {
lunar_month.get_total_days(self)
}
#[inline]
pub fn to_lunar_year(self) -> LunarYear {
let heavenly_stems = self.get_heavenly_stems();
let earthly_branch = self.get_earthly_branch();
LunarYear::from_era(heavenly_stems, earthly_branch)
}
#[inline]
pub fn to_solar_year(self) -> SolarYear {
self.solar_year
}
#[inline]
pub fn to_u16(self) -> u16 {
self.solar_year.to_u16()
}
}
impl Display for LunisolarYear {
#[inline]
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
Display::fmt(&self.solar_year, f)
}
}