use crate::astro::builder::{by_lunar, by_solar};
use crate::data::types::*;
use crate::error::IztroError;
use crate::models::astrolabe::Astrolabe;
use crate::utils::fix_index;
const ZODIAC_TIME_INDEX: u8 = 0;
const MAJOR_STAR_SEPARATOR: &str = ",";
pub fn get_zodiac_by_solar_date(
solar_date: &str,
language: Language,
config: Config,
) -> Result<String, IztroError> {
Ok(by_solar(
solar_date,
ZODIAC_TIME_INDEX,
Gender::Male,
true,
language,
config,
)?
.zodiac)
}
pub fn get_sign_by_solar_date(solar_date: &str, language: Language) -> Result<String, IztroError> {
Ok(by_solar(
solar_date,
ZODIAC_TIME_INDEX,
Gender::Male,
true,
language,
Config::default(),
)?
.sign)
}
pub fn get_sign_by_lunar_date(
lunar_date: &str,
is_leap_month: bool,
language: Language,
) -> Result<String, IztroError> {
Ok(by_lunar(
lunar_date,
ZODIAC_TIME_INDEX,
Gender::Male,
LeapMonth::from_flags(is_leap_month, true),
language,
Config::default(),
)?
.sign)
}
fn major_stars_of_soul_palace(astrolabe: &Astrolabe) -> String {
let soul_index = astrolabe
.palaces
.iter()
.find(|p| p.name == Palace::Soul)
.map_or(0, |p| p.index);
let names_at = |index: usize| -> Vec<String> {
astrolabe.palaces[index]
.major_stars
.iter()
.filter(|s| s.star_type == StarType::Major)
.map(|s| s.name.clone())
.collect()
};
let names = names_at(soul_index);
if !names.is_empty() {
return names.join(MAJOR_STAR_SEPARATOR);
}
names_at(fix_index(soul_index as i32 + 6, 12)).join(MAJOR_STAR_SEPARATOR)
}
pub fn get_major_star_by_solar_date(
solar_date: &str,
time_index: u8,
fix_leap: bool,
language: Language,
config: Config,
) -> Result<String, IztroError> {
let astrolabe = by_solar(
solar_date,
time_index,
Gender::Male,
fix_leap,
language,
config,
)?;
Ok(major_stars_of_soul_palace(&astrolabe))
}
pub fn get_major_star_by_lunar_date(
lunar_date: &str,
time_index: u8,
leap: LeapMonth,
language: Language,
config: Config,
) -> Result<String, IztroError> {
let astrolabe = by_lunar(lunar_date, time_index, Gender::Male, leap, language, config)?;
Ok(major_stars_of_soul_palace(&astrolabe))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_matches_full_astrolabe() {
let chart = by_solar(
"2000-8-16",
0,
Gender::Male,
true,
Language::ZhCN,
Config::default(),
)
.unwrap();
assert_eq!(
get_zodiac_by_solar_date("2000-8-16", Language::ZhCN, Config::default()).unwrap(),
chart.zodiac
);
assert_eq!(
get_sign_by_solar_date("2000-8-16", Language::ZhCN).unwrap(),
chart.sign
);
}
#[test]
fn test_sign_by_lunar_date_equals_solar() {
assert_eq!(
get_sign_by_lunar_date("2000-7-17", false, Language::ZhCN).unwrap(),
get_sign_by_solar_date("2000-8-16", Language::ZhCN).unwrap()
);
}
#[test]
fn test_major_star_matches_soul_palace() {
let chart = by_solar(
"2000-8-16",
2,
Gender::Female,
true,
Language::ZhCN,
Config::default(),
)
.unwrap();
let soul = chart.palace(Palace::Soul).unwrap();
let expected: Vec<String> = soul
.major_stars
.iter()
.filter(|s| s.star_type == StarType::Major)
.map(|s| s.name.clone())
.collect();
assert!(!expected.is_empty(), "该盘命宫应有主星");
assert_eq!(
get_major_star_by_solar_date("2000-8-16", 2, true, Language::ZhCN, Config::default())
.unwrap(),
expected.join(",")
);
}
#[test]
fn test_major_star_borrows_from_opposite_when_empty() {
for day in 1..=28 {
let date = format!("1990-3-{day}");
let chart = by_solar(
&date,
0,
Gender::Male,
true,
Language::ZhCN,
Config::default(),
)
.unwrap();
let soul = chart.palace(Palace::Soul).unwrap();
if !soul.is_empty() {
continue;
}
let opposite = &chart.palaces[fix_index(soul.index as i32 + 6, 12)];
let expected: Vec<String> = opposite
.major_stars
.iter()
.filter(|s| s.star_type == StarType::Major)
.map(|s| s.name.clone())
.collect();
assert_eq!(
get_major_star_by_solar_date(&date, 0, true, Language::ZhCN, Config::default())
.unwrap(),
expected.join(","),
"{date} 命宫空宫时应借对宫主星"
);
return;
}
panic!("测试区间内没有命宫空宫的盘,用例失去意义");
}
#[test]
fn test_invalid_input_is_rejected() {
assert!(get_zodiac_by_solar_date("2000-2-30", Language::ZhCN, Config::default()).is_err());
assert!(
get_major_star_by_solar_date("2000-8-16", 13, true, Language::ZhCN, Config::default())
.is_err()
);
}
}