use crate::core::model::zodiac::{WesternZodiac, western_zodiac};
const YEAR_DIGITS: [&str; 10] = ["〇", "一", "二", "三", "四", "五", "六", "七", "八", "九"];
const LUNAR_MONTH_LABELS: [&str; 12] = [
"正月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "冬月", "腊月",
];
const LUNAR_DAY_LABELS: [&str; 30] = [
"初一", "初二", "初三", "初四", "初五", "初六", "初七", "初八", "初九", "初十", "十一", "十二",
"十三", "十四", "十五", "十六", "十七", "十八", "十九", "二十", "廿一", "廿二", "廿三", "廿四",
"廿五", "廿六", "廿七", "廿八", "廿九", "三十",
];
pub fn chinese_year_digits(year: i32) -> String {
if year < 0 {
return format!("公元前{}", chinese_year_digits(-year));
}
year.to_string()
.bytes()
.map(|byte| YEAR_DIGITS[(byte - b'0') as usize])
.collect()
}
pub fn lunar_month_zh(month: u8) -> String {
match month {
1..=12 => LUNAR_MONTH_LABELS[(month - 1) as usize].to_owned(),
other => format!("{other}月"),
}
}
pub fn lunar_day_zh(day: u8) -> String {
match day {
1..=30 => LUNAR_DAY_LABELS[(day - 1) as usize].to_owned(),
other => other.to_string(),
}
}
pub fn solar_date_label_padded(year: i32, month: u8, day: u8) -> String {
format!("{year}-{month:02}-{day:02}")
}
pub fn solar_date_label_unpadded(year: i32, month: u8, day: u8) -> String {
format!("{year}-{month}-{day}")
}
pub fn lunar_date_label(year: i32, month: u8, day: u8, is_leap_month: bool) -> String {
let leap = if is_leap_month { "闰" } else { "" };
format!(
"{}年{}{}{}",
chinese_year_digits(year),
leap,
lunar_month_zh(month),
lunar_day_zh(day)
)
}
pub fn birth_time_label(time_index: u8) -> String {
let (branch, range) = match time_index {
0 => ("子", "00:00~01:00"),
1 => ("丑", "01:00~03:00"),
2 => ("寅", "03:00~05:00"),
3 => ("卯", "05:00~07:00"),
4 => ("辰", "07:00~09:00"),
5 => ("巳", "09:00~11:00"),
6 => ("午", "11:00~13:00"),
7 => ("未", "13:00~15:00"),
8 => ("申", "15:00~17:00"),
9 => ("酉", "17:00~19:00"),
10 => ("戌", "19:00~21:00"),
11 => ("亥", "21:00~23:00"),
12 => ("子", "23:00~24:00"),
_ => ("未知", "--:--~--:--"),
};
format!("{branch}时({range})")
}
pub fn constellation_zh(solar_month: u8, solar_day: u8) -> &'static str {
match western_zodiac(solar_month, solar_day) {
Some(sign) => western_zodiac_zh(sign),
None => "未知",
}
}
pub const fn western_zodiac_zh(sign: WesternZodiac) -> &'static str {
match sign {
WesternZodiac::Aries => "白羊座",
WesternZodiac::Taurus => "金牛座",
WesternZodiac::Gemini => "双子座",
WesternZodiac::Cancer => "巨蟹座",
WesternZodiac::Leo => "狮子座",
WesternZodiac::Virgo => "处女座",
WesternZodiac::Libra => "天秤座",
WesternZodiac::Scorpio => "天蝎座",
WesternZodiac::Sagittarius => "射手座",
WesternZodiac::Capricorn => "摩羯座",
WesternZodiac::Aquarius => "水瓶座",
WesternZodiac::Pisces => "双鱼座",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn year_renders_digit_by_digit() {
assert_eq!(chinese_year_digits(1993), "一九九三");
assert_eq!(chinese_year_digits(2008), "二〇〇八");
assert_eq!(chinese_year_digits(2020), "二〇二〇");
}
#[test]
fn padded_solar_date_label_keeps_birth_date_style() {
assert_eq!(solar_date_label_padded(1993, 5, 27), "1993-05-27");
assert_eq!(solar_date_label_padded(2008, 2, 10), "2008-02-10");
}
#[test]
fn unpadded_solar_date_label_matches_temporal_style() {
assert_eq!(solar_date_label_unpadded(2008, 2, 10), "2008-2-10");
assert_eq!(solar_date_label_unpadded(2026, 11, 28), "2026-11-28");
}
#[test]
fn lunar_date_label_matches_almanac_form() {
assert_eq!(lunar_date_label(1993, 4, 7, false), "一九九三年四月初七");
assert_eq!(lunar_date_label(2008, 1, 4, false), "二〇〇八年正月初四");
assert_eq!(lunar_date_label(2020, 4, 15, true), "二〇二〇年闰四月十五");
}
#[test]
fn birth_time_label_includes_hour_range() {
assert_eq!(birth_time_label(9), "酉时(17:00~19:00)");
assert_eq!(birth_time_label(0), "子时(00:00~01:00)");
assert_eq!(birth_time_label(12), "子时(23:00~24:00)");
}
#[test]
fn constellation_matches_western_zodiac() {
assert_eq!(constellation_zh(5, 27), "双子座");
assert_eq!(constellation_zh(5, 20), "金牛座");
assert_eq!(constellation_zh(5, 21), "双子座");
assert_eq!(constellation_zh(1, 1), "摩羯座");
assert_eq!(constellation_zh(12, 25), "摩羯座");
}
}