use crate::sun::{solar_term_jd, sun_apparent_longitude};
use crate::{
civil_day_number, jd_ut_to_jde, local_civil_day_of,
moon::{new_moon_jd_ut, new_moon_k_near},
};
#[cfg(feature = "serde")]
use serde::Serialize;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct LunarDate {
pub year: i32,
pub month: u32,
pub leap: bool,
pub day: u32,
}
fn new_moon_on_or_before(cdn: i64, tz: f64) -> (i64, i64) {
let mut k = new_moon_k_near(cdn as f64 - 0.5);
loop {
let c = local_civil_day_of(new_moon_jd_ut(k), tz);
let c_next = local_civil_day_of(new_moon_jd_ut(k + 1), tz);
if c <= cdn && cdn < c_next {
return (c, k);
} else if c > cdn {
k -= 1;
} else {
k += 1;
}
}
}
fn month11(year: i32, tz: f64) -> (i64, i64) {
let ws_cdn = local_civil_day_of(solar_term_jd(year, 270.0), tz);
new_moon_on_or_before(ws_cdn, tz)
}
#[must_use]
pub fn solar_to_lunar(year: i32, month: u32, day: u32, tz: f64) -> LunarDate {
let cdn = civil_day_number(year, month, day);
let (m11_y_cdn, _) = month11(year, tz);
let start_year = if cdn >= m11_y_cdn { year } else { year - 1 };
let (_start_cdn, start_k) = month11(start_year, tz);
let (next11_cdn, _) = month11(start_year + 1, tz);
let mut nm_cdn: Vec<i64> = Vec::new();
let mut k = start_k;
loop {
let c = local_civil_day_of(new_moon_jd_ut(k), tz);
nm_cdn.push(c);
if c >= next11_cdn {
break;
}
k += 1;
}
let n_months = nm_cdn.len() - 1; let is_leap_year = n_months == 13;
let lam_at_day = |cdn: i64| -> f64 {
let jd_ut = (cdn as f64 - 0.5) - tz / 24.0; sun_apparent_longitude(jd_ut_to_jde(jd_ut))
};
let has_zhongqi = |i: usize| -> bool {
let a = lam_at_day(nm_cdn[i]);
let b = lam_at_day(nm_cdn[i + 1]);
let na = (a / 30.0).floor() as i64;
let mut nb = (b / 30.0).floor() as i64;
if b < a {
nb += 12;
}
nb > na
};
let mut num: u32 = 11;
let mut last: u32 = 11;
let mut leap_done = false;
let mut info: Vec<(u32, bool)> = Vec::with_capacity(n_months);
for i in 0..n_months {
let is_leap = is_leap_year && !leap_done && i >= 1 && !has_zhongqi(i);
if is_leap {
info.push((last, true));
leap_done = true;
} else {
info.push((num, false));
last = num;
num = if num == 12 { 1 } else { num + 1 };
}
}
let mut idx = 0usize;
for i in 0..n_months {
if nm_cdn[i] <= cdn && cdn < nm_cdn[i + 1] {
idx = i;
break;
}
}
let (mnum, leap) = info[idx];
let lday = (cdn - nm_cdn[idx] + 1) as u32;
let lyear = if mnum >= 11 { start_year } else { start_year + 1 };
LunarDate {
year: lyear,
month: mnum,
leap,
day: lday,
}
}