use crate::core::calculation::YearBoundary;
use crate::core::error::ChartError;
use crate::core::model::calendar::{SolarDate, SolarDay, SolarMonth};
use crate::core::model::ganzhi::{EarthlyBranch, FourPillars, HeavenlyStem};
use crate::core::placement::natal::life_body::{LunarDay, LunarMonth};
use super::policy::resolve_four_pillars;
use super::tyme::{LunarDateInfo, ResolvedSolarDateTime, TymeCalendar};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct LunarConversion {
lunar_year: i32,
lunar_month: LunarMonth,
lunar_day: LunarDay,
is_leap_month: bool,
birth_year_stem: HeavenlyStem,
birth_year_branch: EarthlyBranch,
four_pillars: FourPillars,
}
impl LunarConversion {
pub(crate) const fn lunar_year(&self) -> i32 {
self.lunar_year
}
pub(crate) const fn lunar_month(&self) -> LunarMonth {
self.lunar_month
}
pub(crate) const fn lunar_day(&self) -> LunarDay {
self.lunar_day
}
pub(crate) const fn is_leap_month(&self) -> bool {
self.is_leap_month
}
pub(crate) const fn birth_year_stem(&self) -> HeavenlyStem {
self.birth_year_stem
}
pub(crate) const fn birth_year_branch(&self) -> EarthlyBranch {
self.birth_year_branch
}
pub(crate) const fn four_pillars(&self) -> FourPillars {
self.four_pillars
}
}
const fn synthesized_hour(time_index: u8) -> u8 {
let raw = time_index as i32 * 2 - 1;
if raw < 0 { 0 } else { raw as u8 }
}
pub(crate) fn solar_to_lunar(
year: i32,
month: SolarMonth,
day: SolarDay,
time_index: u8,
) -> Result<LunarConversion, ChartError> {
solar_to_lunar_with_year_boundary(
year,
month,
day,
time_index,
YearBoundary::ChineseNewYearEve,
)
}
pub(crate) fn solar_to_lunar_with_year_boundary(
year: i32,
month: SolarMonth,
day: SolarDay,
time_index: u8,
year_boundary: YearBoundary,
) -> Result<LunarConversion, ChartError> {
let resolved_time = ResolvedSolarDateTime {
year,
month: month.value(),
day: day.value(),
hour: synthesized_hour(time_index),
minute: 30,
second: 0,
};
solar_to_lunar_with_resolved_time(year, month, day, resolved_time, year_boundary)
}
pub(crate) fn solar_to_lunar_with_resolved_datetime(
year: i32,
month: SolarMonth,
day: SolarDay,
hour: u8,
minute: u8,
second: u8,
year_boundary: YearBoundary,
) -> Result<LunarConversion, ChartError> {
let resolved_time = ResolvedSolarDateTime {
year,
month: month.value(),
day: day.value(),
hour,
minute,
second,
};
solar_to_lunar_with_resolved_time(year, month, day, resolved_time, year_boundary)
}
fn solar_to_lunar_with_resolved_time(
year: i32,
month: SolarMonth,
day: SolarDay,
resolved_time: ResolvedSolarDateTime,
year_boundary: YearBoundary,
) -> Result<LunarConversion, ChartError> {
let conversion_failed = || ChartError::CalendarConversionFailed {
year,
month: month.value(),
day: day.value(),
};
let calendar = TymeCalendar;
let date = SolarDate::new(year, month.value(), day.value())?;
let lunar = calendar.lunar_from_solar(date)?;
let four_pillars = resolve_four_pillars(&calendar, resolved_time, lunar, year_boundary)?;
Ok(LunarConversion {
lunar_year: lunar.year,
lunar_month: LunarMonth::new(lunar.month).map_err(|_| conversion_failed())?,
lunar_day: LunarDay::new(lunar.day).map_err(|_| conversion_failed())?,
is_leap_month: lunar.is_leap_month,
birth_year_stem: four_pillars.yearly.stem(),
birth_year_branch: four_pillars.yearly.branch(),
four_pillars,
})
}
pub(crate) fn lunar_facts(
year: i32,
month: SolarMonth,
day: SolarDay,
) -> Result<LunarDateInfo, ChartError> {
let date = SolarDate::new(year, month.value(), day.value())?;
TymeCalendar.lunar_from_solar(date)
}
#[cfg(test)]
pub(crate) fn resolve_effective_birth_year(
year: i32,
month: SolarMonth,
day: SolarDay,
time_index: u8,
policy: YearBoundary,
) -> Result<crate::core::model::ganzhi::StemBranch, ChartError> {
Ok(
solar_to_lunar_with_year_boundary(year, month, day, time_index, policy)?
.four_pillars()
.yearly,
)
}
#[cfg(test)]
mod tests;