├── .github ├── dependabot.yml └── workflows │ ├── ci-version.yml │ └── ci.yml ├── src ├── lunar │ ├── mod.rs │ ├── month │ │ ├── built_in_traits.rs │ │ ├── ba_zi_weight.rs │ │ ├── chinese.rs │ │ ├── parse.rs │ │ └── mod.rs │ ├── day │ │ ├── chinese.rs │ │ ├── parse.rs │ │ ├── ba_zi_weight.rs │ │ ├── built_in_traits.rs │ │ └── mod.rs │ ├── year │ │ ├── chinese.rs │ │ ├── ba_zi_weight.rs │ │ ├── parse.rs │ │ ├── built_in_traits.rs │ │ └── mod.rs │ └── errors.rs ├── lunisolar │ ├── mod.rs │ ├── year │ │ ├── built_in_traits.rs │ │ └── mod.rs │ ├── errors.rs │ ├── date │ │ ├── ba_zi_weight.rs │ │ ├── built_in_traits.rs │ │ ├── parse.rs │ │ └── mod.rs │ └── constants.rs ├── solar │ ├── mod.rs │ ├── year │ │ ├── chinese.rs │ │ ├── built_in_traits.rs │ │ ├── parse.rs │ │ └── mod.rs │ ├── month │ │ ├── chinese.rs │ │ ├── parse.rs │ │ ├── built_in_traits.rs │ │ └── mod.rs │ ├── day │ │ ├── chinese.rs │ │ ├── parse.rs │ │ ├── built_in_traits.rs │ │ └── mod.rs │ ├── date │ │ ├── built_in_traits.rs │ │ ├── parse.rs │ │ └── mod.rs │ └── errors.rs ├── heavenly_stems │ ├── chinese.rs │ ├── parse.rs │ ├── built_in_traits.rs │ └── mod.rs ├── earthly_branch │ ├── chinese.rs │ ├── ba_zi_weight.rs │ ├── parse.rs │ ├── built_in_traits.rs │ └── mod.rs ├── zodiac │ ├── built_in_traits.rs │ ├── chinese.rs │ ├── parse.rs │ └── mod.rs └── lib.rs ├── tests ├── ba_zi_weight.rs ├── loopback.rs ├── solar_year.rs ├── lunar_day.rs ├── solar_day.rs ├── heavenly_stems.rs ├── lunar_year.rs ├── solar_month.rs ├── zodiac.rs ├── earthly_branch.rs ├── lunisolar_year.rs ├── solar_date.rs ├── lunar_month.rs └── lunisolar_date.rs ├── Cargo.toml ├── LICENSE ├── rustfmt.toml ├── README.md └── .gitignore /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" -------------------------------------------------------------------------------- /src/lunar/mod.rs: -------------------------------------------------------------------------------- 1 | mod day; 2 | mod errors; 3 | mod month; 4 | mod year; 5 | 6 | pub use day::*; 7 | pub use errors::*; 8 | pub use month::*; 9 | pub use year::*; 10 | -------------------------------------------------------------------------------- /src/lunisolar/mod.rs: -------------------------------------------------------------------------------- 1 | mod constants; 2 | mod date; 3 | mod errors; 4 | mod year; 5 | 6 | use constants::*; 7 | pub use date::*; 8 | pub use errors::*; 9 | pub use year::*; 10 | -------------------------------------------------------------------------------- /src/solar/mod.rs: -------------------------------------------------------------------------------- 1 | mod date; 2 | mod day; 3 | mod errors; 4 | mod month; 5 | mod year; 6 | 7 | pub use date::*; 8 | pub use day::*; 9 | pub use errors::*; 10 | pub use month::*; 11 | pub use year::*; 12 | -------------------------------------------------------------------------------- /src/solar/year/chinese.rs: -------------------------------------------------------------------------------- 1 | /// 列舉西曆年用到的數字:〇、一、二、...、九。 2 | pub(super) const THE_SOLAR_YEAR_NUMBERS_CHAR: [char; 10] = 3 | ['〇', '一', '二', '三', '四', '五', '六', '七', '八', '九']; 4 | 5 | pub(super) const ALTERNATIVE_ZERO: char = '零'; 6 | -------------------------------------------------------------------------------- /src/lunar/month/built_in_traits.rs: -------------------------------------------------------------------------------- 1 | use core::str::FromStr; 2 | 3 | use super::{LunarMonth, LunarMonthError}; 4 | 5 | impl FromStr for LunarMonth { 6 | type Err = LunarMonthError; 7 | 8 | #[inline] 9 | fn from_str(s: &str) -> Result { 10 | Self::parse_str(s) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/solar/month/chinese.rs: -------------------------------------------------------------------------------- 1 | /// 列舉西曆十二個月份名稱:一月、二月、三月、四月、五月、六月、七月、八月、九月、十月、十一月、十二月。 2 | pub(super) const THE_SOLAR_MONTHS: [&str; 12] = [ 3 | "一月", 4 | "二月", 5 | "三月", 6 | "四月", 7 | "五月", 8 | "六月", 9 | "七月", 10 | "八月", 11 | "九月", 12 | "十月", 13 | "十一月", 14 | "十二月", 15 | ]; 16 | -------------------------------------------------------------------------------- /src/heavenly_stems/chinese.rs: -------------------------------------------------------------------------------- 1 | /// 列舉中國十天干:甲、乙、丙、丁、戊、己、更、辛、壬、葵。 2 | pub(super) const THE_HEAVENLY_STEMS: [&str; 10] = 3 | ["甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"]; 4 | 5 | /// 列舉中國十天干:甲、乙、丙、丁、戊、己、更、辛、壬、葵。 6 | pub(super) const THE_HEAVENLY_STEMS_CHAR: [char; 10] = 7 | ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸']; 8 | -------------------------------------------------------------------------------- /src/lunar/day/chinese.rs: -------------------------------------------------------------------------------- 1 | /// 列舉農曆三十個天數名稱:初一、初二、...、十一、十二、...、廿一、廿二、...、三十。 2 | #[rustfmt::skip] 3 | pub(super) const THE_LUNAR_DAYS: [&str; 30] = [ 4 | "初一", "初二", "初三", "初四", "初五", "初六", "初七", "初八", "初九", "初十", 5 | "十一", "十二", "十三", "十四", "十五", "十六", "十七", "十八", "十九", "二十", 6 | "廿一", "廿二", "廿三", "廿四", "廿五", "廿六", "廿七", "廿八", "廿九", "三十", 7 | ]; 8 | -------------------------------------------------------------------------------- /src/earthly_branch/chinese.rs: -------------------------------------------------------------------------------- 1 | /// 列舉中國十二地支:子、丑、寅、卯、辰、巳、午、未、申、酉、戌、亥。 2 | pub(super) const THE_EARTHLY_BRANCHES: [&str; 12] = 3 | ["子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"]; 4 | 5 | /// 列舉中國十二地支:子、丑、寅、卯、辰、巳、午、未、申、酉、戌、亥。 6 | pub(super) const THE_EARTHLY_BRANCHES_CHAR: [char; 12] = 7 | ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥']; 8 | -------------------------------------------------------------------------------- /tests/ba_zi_weight.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "ba-zi-weight")] 2 | 3 | use chinese_lunisolar_calendar::LunisolarDate; 4 | use chrono::prelude::*; 5 | 6 | #[test] 7 | fn get_ba_zi_weight_by_time() { 8 | assert_eq!( 9 | 48, 10 | LunisolarDate::from_ymd(1993, 6, false, 23) 11 | .unwrap() 12 | .get_ba_zi_weight_by_time(NaiveTime::from_hms_opt(23, 30, 0).unwrap()) 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/lunar/day/parse.rs: -------------------------------------------------------------------------------- 1 | use super::{LunarDay, LunarDayError, THE_LUNAR_DAYS}; 2 | 3 | /// 用以解析字串的關聯函數。 4 | impl LunarDay { 5 | /// 透過農曆日期字串來取得 `LunarDay` 列舉實體。 6 | #[inline] 7 | pub fn parse_str>(s: S) -> Result { 8 | let s = s.as_ref(); 9 | 10 | for (i, &t) in THE_LUNAR_DAYS.iter().enumerate() { 11 | if s == t { 12 | return Ok(unsafe { Self::from_u8_unsafe(i as u8 + 1) }); 13 | } 14 | } 15 | 16 | Err(LunarDayError) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/lunar/year/chinese.rs: -------------------------------------------------------------------------------- 1 | /// 列舉農曆六十個年份名稱:甲子、乙丑、丙寅、...、癸亥。 2 | #[rustfmt::skip] 3 | pub(super) const THE_LUNAR_YEARS: [&str; 60] = [ 4 | "甲子", "乙丑", "丙寅", "丁卯", "戊辰", "己巳", "庚午", "辛未", "壬申", "癸酉", // 0 / 12 5 | "甲戌", "乙亥", "丙子", "丁丑", "戊寅", "己卯", "庚辰", "辛巳", "壬午", "癸未", // 10 6 | "甲申", "乙酉", "丙戌", "丁亥", "戊子", "己丑", "庚寅", "辛卯", "壬辰", "癸巳", // 8 7 | "甲午", "乙未", "丙申", "丁酉", "戊戌", "己亥", "庚子", "辛丑", "壬寅", "癸卯", // 6 8 | "甲辰", "乙巳", "丙午", "丁未", "戊申", "己酉", "庚戌", "辛亥", "壬子", "癸丑", // 4 9 | "甲寅", "乙卯", "丙辰", "丁巳", "戊午", "己未", "庚申", "辛酉", "壬戌", "癸亥", // 2 10 | ]; 11 | -------------------------------------------------------------------------------- /src/solar/day/chinese.rs: -------------------------------------------------------------------------------- 1 | /// 列舉西曆三十一個天數名稱:一、二、...、十一、十二、...、二十一、二十二、...、三十、三十一。 2 | pub(super) const THE_SOLAR_DAYS: [&str; 31] = [ 3 | "一", 4 | "二", 5 | "三", 6 | "四", 7 | "五", 8 | "六", 9 | "七", 10 | "八", 11 | "九", 12 | "十", // 10 13 | "十一", 14 | "十二", 15 | "十三", 16 | "十四", 17 | "十五", 18 | "十六", 19 | "十七", 20 | "十八", 21 | "十九", 22 | "二十", // 20 23 | "二十一", 24 | "二十二", 25 | "二十三", 26 | "二十四", 27 | "二十五", 28 | "二十六", 29 | "二十七", 30 | "二十八", 31 | "二十九", 32 | "三十", // 30 33 | "三十一", 34 | ]; 35 | -------------------------------------------------------------------------------- /src/lunar/month/ba_zi_weight.rs: -------------------------------------------------------------------------------- 1 | use super::LunarMonth; 2 | 3 | /// 從正月到臘月共十二個月的八字重量。 4 | const BA_ZI_WEIGHT_MONTHS: [u8; 12] = [6, 7, 18, 9, 5, 16, 9, 15, 18, 8, 9, 5]; 5 | 6 | impl LunarMonth { 7 | /// 取得八字重量。 8 | /// 9 | /// # Examples 10 | /// 11 | /// ``` 12 | /// # #[cfg(feature = "ba-zi-weight")] 13 | /// # { 14 | /// # use chinese_lunisolar_calendar::LunarMonth; 15 | /// assert_eq!(5, LunarMonth::Fifth.get_ba_zi_weight()); 16 | /// # } 17 | /// ``` 18 | #[inline] 19 | pub const fn get_ba_zi_weight(self) -> u8 { 20 | BA_ZI_WEIGHT_MONTHS[(self.to_u8() - 1) as usize] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/earthly_branch/ba_zi_weight.rs: -------------------------------------------------------------------------------- 1 | use super::EarthlyBranch; 2 | 3 | /// 從子時到亥時共十二地支的八字重量。 4 | const BA_ZI_WEIGHT_TIME: [u8; 12] = [16, 6, 7, 10, 9, 16, 10, 8, 8, 9, 6, 6]; 5 | 6 | impl EarthlyBranch { 7 | /// 取得八字重量。 8 | /// 9 | /// # Examples 10 | /// 11 | /// ``` 12 | /// # #[cfg(feature = "ba-zi-weight")] 13 | /// # { 14 | /// # use chinese_lunisolar_calendar::EarthlyBranch; 15 | /// assert_eq!(9, EarthlyBranch::Fifth.get_ba_zi_weight()); 16 | /// # } 17 | /// ``` 18 | #[inline] 19 | pub const fn get_ba_zi_weight(self) -> u8 { 20 | let i = (self.ordinal() - 1) as usize; 21 | 22 | BA_ZI_WEIGHT_TIME[i] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/loopback.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Add; 2 | 3 | use chinese_lunisolar_calendar::{ 4 | chrono::Duration, LunisolarDate, MAX_LUNISOLAR_DATE_IN_SOLAR_DATE, 5 | MIN_LUNISOLAR_DATE_IN_SOLAR_DATE, 6 | }; 7 | 8 | #[test] 9 | fn loopback() { 10 | let mut current = MIN_LUNISOLAR_DATE_IN_SOLAR_DATE; 11 | 12 | while current <= MAX_LUNISOLAR_DATE_IN_SOLAR_DATE { 13 | let lunar_date = LunisolarDate::from_solar_date(current).unwrap(); 14 | 15 | assert_eq!(lunar_date, current.to_lunisolar_date().unwrap()); 16 | assert_eq!(current, lunar_date.to_solar_date()); 17 | 18 | current = current.to_naive_date().add(Duration::days(1)).try_into().unwrap(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/zodiac/built_in_traits.rs: -------------------------------------------------------------------------------- 1 | use core::str::FromStr; 2 | 3 | use super::Zodiac; 4 | use crate::EarthlyBranch; 5 | 6 | impl TryFrom for Zodiac { 7 | type Error = (); 8 | 9 | #[inline] 10 | fn try_from(value: char) -> Result { 11 | Self::from_char(value).ok_or(()) 12 | } 13 | } 14 | 15 | impl From for Zodiac { 16 | #[inline] 17 | fn from(value: EarthlyBranch) -> Self { 18 | Self::from_earthly_branch(value) 19 | } 20 | } 21 | 22 | impl FromStr for Zodiac { 23 | type Err = (); 24 | 25 | #[inline] 26 | fn from_str(s: &str) -> Result { 27 | Self::parse_str(s).ok_or(()) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/zodiac/chinese.rs: -------------------------------------------------------------------------------- 1 | /// 列舉中國十二生肖:鼠、牛、虎、兔、龍、蛇、馬、羊、猴、雞、狗、豬。 2 | pub(super) const THE_ZODIAC_SIGNS: [(&str, &str); 12] = [ 3 | ("鼠", "鼠"), 4 | ("牛", "牛"), 5 | ("虎", "虎"), 6 | ("兔", "兔"), 7 | ("龍", "龙"), 8 | ("蛇", "蛇"), 9 | ("馬", "马"), 10 | ("羊", "羊"), 11 | ("猴", "猴"), 12 | ("雞", "鸡"), 13 | ("狗", "狗"), 14 | ("豬", "猪"), 15 | ]; 16 | 17 | /// 列舉中國十二生肖:鼠、牛、虎、兔、龍、蛇、馬、羊、猴、雞、狗、豬。 18 | pub(super) const THE_ZODIAC_SIGNS_CHAR: [(char, char); 12] = [ 19 | ('鼠', '鼠'), 20 | ('牛', '牛'), 21 | ('虎', '虎'), 22 | ('兔', '兔'), 23 | ('龍', '龙'), 24 | ('蛇', '蛇'), 25 | ('馬', '马'), 26 | ('羊', '羊'), 27 | ('猴', '猴'), 28 | ('雞', '鸡'), 29 | ('狗', '狗'), 30 | ('豬', '猪'), 31 | ]; 32 | -------------------------------------------------------------------------------- /src/solar/day/parse.rs: -------------------------------------------------------------------------------- 1 | use super::{SolarDay, SolarDayError, THE_SOLAR_DAYS}; 2 | 3 | /// 用以解析字串的關聯函數。 4 | impl SolarDay { 5 | /// 透過西曆日期字串來取得 `SolarDay` 列舉實體。 6 | /// 7 | /// # Examples 8 | /// 9 | /// ``` 10 | /// # use chinese_lunisolar_calendar::SolarDay; 11 | /// assert_eq!(SolarDay::Fifteen, SolarDay::parse_str("十五").unwrap()); 12 | /// ``` 13 | #[inline] 14 | pub fn parse_str>(s: S) -> Result { 15 | let s = s.as_ref(); 16 | 17 | for (i, &t) in THE_SOLAR_DAYS.iter().enumerate() { 18 | if s == t { 19 | return Ok(unsafe { Self::from_u8_unsafe(i as u8 + 1) }); 20 | } 21 | } 22 | 23 | Err(SolarDayError) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/lunar/day/ba_zi_weight.rs: -------------------------------------------------------------------------------- 1 | use super::LunarDay; 2 | 3 | /// 從初一到三十共三十天的八字重量。 4 | #[rustfmt::skip] 5 | const BA_ZI_WEIGHT_DAYS: [u8; 30] = [ 6 | 5, 10, 8, 15, 16, 15, 8, 16, 8, 16, 7 | 9, 17, 8, 17, 10, 8, 9, 18, 5, 15, 8 | 10, 9, 8, 9, 15, 18, 7, 8, 16, 6, 9 | ]; 10 | 11 | impl LunarDay { 12 | /// 取得八字重量。 13 | /// 14 | /// # Examples 15 | /// 16 | /// ``` 17 | /// # #[cfg(feature = "ba-zi-weight")] 18 | /// # { 19 | /// # use chinese_lunisolar_calendar::LunarDay; 20 | /// assert_eq!(8, LunarDay::Seventh.get_ba_zi_weight()); 21 | /// # } 22 | /// ``` 23 | #[inline] 24 | pub const fn get_ba_zi_weight(self) -> u8 { 25 | BA_ZI_WEIGHT_DAYS[(self.to_u8() - 1) as usize] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/solar/month/parse.rs: -------------------------------------------------------------------------------- 1 | use super::{SolarMonth, SolarMonthError, THE_SOLAR_MONTHS}; 2 | 3 | /// 用以解析字串的關聯函數。 4 | impl SolarMonth { 5 | /// 透過西曆月份字串來取得 `SolarMonth` 列舉實體。 6 | /// 7 | /// # Examples 8 | /// 9 | /// ``` 10 | /// # use chinese_lunisolar_calendar::SolarMonth; 11 | /// assert_eq!(SolarMonth::May, SolarMonth::parse_str("五月").unwrap()); 12 | /// ``` 13 | pub fn parse_str>(s: S) -> Result { 14 | let s = s.as_ref(); 15 | 16 | for (i, &t) in THE_SOLAR_MONTHS.iter().enumerate() { 17 | if s == t { 18 | return Ok(unsafe { Self::from_u8_unsafe(i as u8 + 1) }); 19 | } 20 | } 21 | 22 | Err(SolarMonthError) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/heavenly_stems/parse.rs: -------------------------------------------------------------------------------- 1 | use super::{HeavenlyStems, THE_HEAVENLY_STEMS}; 2 | 3 | /// 用以解析字串的關聯函數。 4 | impl HeavenlyStems { 5 | /// 透過甲、乙、丙、丁、戊、己、更、辛、壬、葵等字串來取得 `HeavenlyStems` 列舉實體。 6 | /// 7 | /// # Examples 8 | /// 9 | /// ``` 10 | /// # use chinese_lunisolar_calendar::HeavenlyStems; 11 | /// assert_eq!(HeavenlyStems::Fifth, HeavenlyStems::parse_str("戊").unwrap()); 12 | /// ``` 13 | #[inline] 14 | pub fn parse_str>(s: S) -> Option { 15 | let s = s.as_ref(); 16 | 17 | for (i, &t) in THE_HEAVENLY_STEMS.iter().enumerate() { 18 | if s == t { 19 | return Some(unsafe { Self::from_ordinal_unsafe(i as u8 + 1) }); 20 | } 21 | } 22 | 23 | None 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/lunar/day/built_in_traits.rs: -------------------------------------------------------------------------------- 1 | use core::str::FromStr; 2 | 3 | use super::{LunarDay, LunarDayError}; 4 | 5 | impl TryFrom for LunarDay { 6 | type Error = LunarDayError; 7 | 8 | fn try_from(value: u8) -> Result { 9 | Self::from_u8(value) 10 | } 11 | } 12 | 13 | impl FromStr for LunarDay { 14 | type Err = LunarDayError; 15 | 16 | #[inline] 17 | fn from_str(s: &str) -> Result { 18 | Self::parse_str(s) 19 | } 20 | } 21 | 22 | impl From for u8 { 23 | #[inline] 24 | fn from(value: LunarDay) -> Self { 25 | value.to_u8() 26 | } 27 | } 28 | 29 | impl AsRef for LunarDay { 30 | #[inline] 31 | fn as_ref(&self) -> &str { 32 | self.to_str() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/earthly_branch/parse.rs: -------------------------------------------------------------------------------- 1 | use super::{EarthlyBranch, THE_EARTHLY_BRANCHES}; 2 | 3 | /// 用以解析字串的關聯函數。 4 | impl EarthlyBranch { 5 | /// 透過子、丑、寅、卯、辰、巳、午、未、申、酉、戌、亥等字串來取得 `EarthlyBranch` 列舉實體。 6 | /// 7 | /// # Examples 8 | /// 9 | /// ``` 10 | /// # use chinese_lunisolar_calendar::EarthlyBranch; 11 | /// assert_eq!(EarthlyBranch::Fifth, EarthlyBranch::parse_str("辰").unwrap()); 12 | /// ``` 13 | #[inline] 14 | pub fn parse_str>(s: S) -> Option { 15 | let s = s.as_ref(); 16 | 17 | for (i, &t) in THE_EARTHLY_BRANCHES.iter().enumerate() { 18 | if s == t { 19 | return Some(unsafe { Self::from_ordinal_unsafe(i as u8 + 1) }); 20 | } 21 | } 22 | 23 | None 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/zodiac/parse.rs: -------------------------------------------------------------------------------- 1 | use super::{Zodiac, THE_ZODIAC_SIGNS}; 2 | 3 | /// 用以解析字串的關聯函數。 4 | impl Zodiac { 5 | /// 透過鼠、牛、虎、兔、龍、蛇、馬、羊、猴、雞、狗、豬等字串來取得 `Zodiac` 列舉實體。 6 | /// 7 | /// # Examples 8 | /// 9 | /// ``` 10 | /// # use chinese_lunisolar_calendar::Zodiac; 11 | /// assert_eq!(Zodiac::Dragon, Zodiac::parse_str("龍").unwrap()); 12 | /// assert_eq!(Zodiac::Dragon, Zodiac::parse_str("龙").unwrap()); 13 | /// ``` 14 | #[inline] 15 | pub fn parse_str>(s: S) -> Option { 16 | let s = s.as_ref(); 17 | 18 | for (i, t) in THE_ZODIAC_SIGNS.iter().enumerate() { 19 | if s == t.0 || s == t.1 { 20 | return Some(unsafe { Self::from_ordinal_unsafe(i as u8 + 1) }); 21 | } 22 | } 23 | 24 | None 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/heavenly_stems/built_in_traits.rs: -------------------------------------------------------------------------------- 1 | use core::str::FromStr; 2 | 3 | use super::HeavenlyStems; 4 | 5 | impl TryFrom for HeavenlyStems { 6 | type Error = (); 7 | 8 | #[inline] 9 | fn try_from(value: char) -> Result { 10 | Self::from_char(value).ok_or(()) 11 | } 12 | } 13 | 14 | impl FromStr for HeavenlyStems { 15 | type Err = (); 16 | 17 | #[inline] 18 | fn from_str(s: &str) -> Result { 19 | Self::parse_str(s).ok_or(()) 20 | } 21 | } 22 | 23 | impl From for char { 24 | #[inline] 25 | fn from(value: HeavenlyStems) -> Self { 26 | value.to_char() 27 | } 28 | } 29 | 30 | impl AsRef for HeavenlyStems { 31 | #[inline] 32 | fn as_ref(&self) -> &str { 33 | self.to_str() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/lunar/year/ba_zi_weight.rs: -------------------------------------------------------------------------------- 1 | use super::LunarYear; 2 | 3 | /// 從甲子年到癸亥年共六十年的八字重量。 4 | #[rustfmt::skip] 5 | const BA_ZI_WEIGHT_YEARS: [u8; 60] = [ 6 | 12, 9, 6, 7, 12, 5, 9, 8, 7, 8, 7 | 15, 9, 16, 8, 8, 19, 12, 6, 8, 7, 8 | 5, 15, 6, 16, 15, 7, 9, 12, 10, 7, 9 | 15, 6, 5, 14, 14, 9, 7, 7, 9, 12, 10 | 8, 7, 13, 5, 14, 5, 9, 17, 5, 7, 11 | 12, 8, 8, 6, 19, 6, 8, 16, 10, 7, 12 | ]; 13 | 14 | impl LunarYear { 15 | /// 取得八字重量。 16 | /// 17 | /// # Examples 18 | /// 19 | /// ``` 20 | /// # #[cfg(feature = "ba-zi-weight")] 21 | /// # { 22 | /// # use chinese_lunisolar_calendar::LunarYear; 23 | /// assert_eq!(14, LunarYear::parse_str("戊戌").unwrap().get_ba_zi_weight()); 24 | /// # } 25 | /// ``` 26 | #[inline] 27 | pub const fn get_ba_zi_weight(self) -> u8 { 28 | BA_ZI_WEIGHT_YEARS[self.0 as usize] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/lunar/year/parse.rs: -------------------------------------------------------------------------------- 1 | use super::{LunarYear, LunarYearError, THE_LUNAR_YEARS}; 2 | 3 | /// 用以解析字串的關聯函數。 4 | impl LunarYear { 5 | /// 透過農曆年份字串來取得 `LunarYear` 實體。 6 | /// 7 | /// # Examples 8 | /// 9 | /// ``` 10 | /// use chinese_lunisolar_calendar::{EarthlyBranch, HeavenlyStems, LunarYear}; 11 | /// 12 | /// let lunar_year = LunarYear::parse_str("戊戌").unwrap(); 13 | /// 14 | /// assert_eq!(HeavenlyStems::Fifth, lunar_year.to_heavenly_stems()); 15 | /// assert_eq!(EarthlyBranch::Eleventh, lunar_year.to_earthly_branch()); 16 | /// ``` 17 | #[inline] 18 | pub fn parse_str>(s: S) -> Result { 19 | let s = s.as_ref(); 20 | 21 | for (i, &t) in THE_LUNAR_YEARS.iter().enumerate() { 22 | if s == t { 23 | return Ok(LunarYear(i as u8)); 24 | } 25 | } 26 | 27 | Err(LunarYearError) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/lunar/month/chinese.rs: -------------------------------------------------------------------------------- 1 | /// 列舉農曆十二個月份名稱:正月、二月、三月、四月、五月、六月、七月、八月、九月、十月、冬月、臘月。閏月緊接其後。 2 | pub(super) const THE_LUNAR_MONTHS: [(&str, &str); 31] = [ 3 | ("正月", "正月"), 4 | ("二月", "二月"), 5 | ("三月", "三月"), 6 | ("四月", "四月"), 7 | ("五月", "五月"), 8 | ("六月", "六月"), 9 | ("七月", "七月"), 10 | ("八月", "八月"), 11 | ("九月", "九月"), 12 | ("十月", "十月"), 13 | ("冬月", "冬月"), 14 | ("臘月", "腊月"), 15 | ("閏正月", "闰正月"), 16 | ("閏二月", "闰二月"), 17 | ("閏三月", "闰三月"), 18 | ("閏四月", "闰四月"), 19 | ("閏五月", "闰五月"), 20 | ("閏六月", "闰六月"), 21 | ("閏七月", "闰七月"), 22 | ("閏八月", "闰八月"), 23 | ("閏九月", "闰九月"), 24 | ("閏十月", "闰十月"), 25 | ("閏冬月", "闰冬月"), 26 | ("閏臘月", "闰腊月"), 27 | ("一月", "一月"), // 補充 28 | ("十一月", "十一月"), // 補充 29 | ("十二月", "十二月"), // 補充 30 | ("闰一月", "閏一月"), // 補充 31 | ("闰十一月", "閏十一月"), // 補充 32 | ("闰十二月", "閏十二月"), // 補充 33 | ("闰臘月", "閏腊月"), // 排列組合補充 34 | ]; 35 | -------------------------------------------------------------------------------- /src/lunar/year/built_in_traits.rs: -------------------------------------------------------------------------------- 1 | use core::str::FromStr; 2 | 3 | use super::{LunarYear, LunarYearError}; 4 | use crate::{EarthlyBranch, HeavenlyStems, Zodiac}; 5 | 6 | impl FromStr for LunarYear { 7 | type Err = LunarYearError; 8 | 9 | #[inline] 10 | fn from_str(s: &str) -> Result { 11 | Self::parse_str(s) 12 | } 13 | } 14 | 15 | impl From for HeavenlyStems { 16 | #[inline] 17 | fn from(value: LunarYear) -> Self { 18 | value.to_heavenly_stems() 19 | } 20 | } 21 | 22 | impl From for EarthlyBranch { 23 | #[inline] 24 | fn from(value: LunarYear) -> Self { 25 | value.to_earthly_branch() 26 | } 27 | } 28 | 29 | impl From for Zodiac { 30 | #[inline] 31 | fn from(value: LunarYear) -> Self { 32 | value.to_zodiac() 33 | } 34 | } 35 | 36 | impl AsRef for LunarYear { 37 | #[inline] 38 | fn as_ref(&self) -> &str { 39 | self.to_str() 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/solar/year/built_in_traits.rs: -------------------------------------------------------------------------------- 1 | use core::str::FromStr; 2 | 3 | use super::{SolarOutOfRangeError, SolarYear, SolarYearError}; 4 | 5 | impl From for SolarYear { 6 | #[inline] 7 | fn from(value: u16) -> Self { 8 | SolarYear::from_u16(value) 9 | } 10 | } 11 | 12 | impl TryFrom for SolarYear { 13 | type Error = SolarOutOfRangeError; 14 | 15 | #[inline] 16 | fn try_from(value: i32) -> Result { 17 | Self::from_i32(value) 18 | } 19 | } 20 | 21 | impl FromStr for SolarYear { 22 | type Err = SolarYearError; 23 | 24 | #[inline] 25 | fn from_str(s: &str) -> Result { 26 | Self::parse_str(s) 27 | } 28 | } 29 | 30 | impl From for u16 { 31 | #[inline] 32 | fn from(value: SolarYear) -> Self { 33 | value.to_u16() 34 | } 35 | } 36 | 37 | impl From for i32 { 38 | #[inline] 39 | fn from(value: SolarYear) -> Self { 40 | value.to_i32() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chinese-lunisolar-calendar" 3 | version = "0.2.1" 4 | authors = ["Magic Len "] 5 | edition = "2021" 6 | rust-version = "1.71" 7 | repository = "https://github.com/magiclen/chinese-lunisolar-calendar" 8 | homepage = "https://magiclen.org/chinese-lunisolar-calendar" 9 | keywords = ["chinese", "calendar", "lunar", "leap", "solar"] 10 | categories = ["no-std", "localization", "date-and-time"] 11 | description = "The traditional Chinese Calendar, known as 農曆 or 陰曆 in Chinese, is based on the moon, and is commonly referred to as the Lunar Calendar." 12 | license = "MIT" 13 | include = ["src/**/*", "Cargo.toml", "README.md", "LICENSE"] 14 | 15 | [dependencies] 16 | chrono = { version = "0.4", default-features = false, features = ["clock"] } 17 | year-helper = "0.2" 18 | 19 | chinese-variant = "1" 20 | 21 | enum-ordinalize = { version = "4.2", default-features = false, features = ["derive", "traits"] } 22 | 23 | [features] 24 | default = ["std"] 25 | 26 | std = ["chrono/std"] 27 | 28 | ba-zi-weight = [] 29 | 30 | [package.metadata.docs.rs] 31 | all-features = true 32 | rustdoc-args = ["--cfg", "docsrs_1_92"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 magiclen.org (Ron Li) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/solar/day/built_in_traits.rs: -------------------------------------------------------------------------------- 1 | use core::str::FromStr; 2 | 3 | use super::{SolarDay, SolarDayError}; 4 | 5 | impl TryFrom for SolarDay { 6 | type Error = SolarDayError; 7 | 8 | #[inline] 9 | fn try_from(value: u8) -> Result { 10 | Self::from_u8(value) 11 | } 12 | } 13 | 14 | impl TryFrom for SolarDay { 15 | type Error = SolarDayError; 16 | 17 | #[inline] 18 | fn try_from(value: u32) -> Result { 19 | Self::from_u32(value) 20 | } 21 | } 22 | 23 | impl FromStr for SolarDay { 24 | type Err = SolarDayError; 25 | 26 | #[inline] 27 | fn from_str(s: &str) -> Result { 28 | Self::parse_str(s) 29 | } 30 | } 31 | 32 | impl From for u8 { 33 | #[inline] 34 | fn from(value: SolarDay) -> Self { 35 | value.to_u8() 36 | } 37 | } 38 | 39 | impl From for u32 { 40 | #[inline] 41 | fn from(value: SolarDay) -> Self { 42 | value.to_u32() 43 | } 44 | } 45 | 46 | impl AsRef for SolarDay { 47 | #[inline] 48 | fn as_ref(&self) -> &str { 49 | self.to_str() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/solar_year.rs: -------------------------------------------------------------------------------- 1 | use chinese_lunisolar_calendar::SolarYear; 2 | 3 | #[test] 4 | fn parse_str_to_u16() { 5 | let solar_year = SolarYear::parse_str("二零一八").unwrap(); 6 | assert_eq!(2018, solar_year.to_u16()); 7 | 8 | let solar_year = SolarYear::parse_str("二〇一八").unwrap(); 9 | assert_eq!(2018, solar_year.to_u16()); 10 | } 11 | 12 | #[test] 13 | fn is_leap() { 14 | assert!(SolarYear::from_u16(2000).is_leap()); 15 | assert!(!SolarYear::from_u16(2001).is_leap()); 16 | assert!(!SolarYear::from_u16(2002).is_leap()); 17 | assert!(SolarYear::from_u16(2004).is_leap()); 18 | assert!(!SolarYear::from_u16(2100).is_leap()); 19 | assert!(SolarYear::from_u16(2104).is_leap()); 20 | } 21 | 22 | #[test] 23 | fn get_total_days() { 24 | assert_eq!(366, SolarYear::from_u16(2000).get_total_days()); 25 | assert_eq!(365, SolarYear::from_u16(2001).get_total_days()); 26 | assert_eq!(365, SolarYear::from_u16(2002).get_total_days()); 27 | assert_eq!(366, SolarYear::from_u16(2004).get_total_days()); 28 | assert_eq!(365, SolarYear::from_u16(2100).get_total_days()); 29 | assert_eq!(366, SolarYear::from_u16(2104).get_total_days()); 30 | } 31 | -------------------------------------------------------------------------------- /src/lunar/errors.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::{self, Display, Formatter}; 2 | #[cfg(feature = "std")] 3 | use std::error::Error; 4 | 5 | /// 錯誤的農曆年。 6 | #[derive(Debug, Eq, PartialEq)] 7 | pub struct LunarYearError; 8 | 9 | impl Display for LunarYearError { 10 | #[inline] 11 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 12 | f.write_str("the lunar year is incorrect") 13 | } 14 | } 15 | 16 | #[cfg(feature = "std")] 17 | impl Error for LunarYearError {} 18 | 19 | /// 錯誤的農曆月。 20 | #[derive(Debug, Eq, PartialEq)] 21 | pub struct LunarMonthError; 22 | 23 | impl Display for LunarMonthError { 24 | #[inline] 25 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 26 | f.write_str("the lunar date is incorrect") 27 | } 28 | } 29 | 30 | #[cfg(feature = "std")] 31 | impl Error for LunarMonthError {} 32 | 33 | /// 錯誤的農曆日。 34 | #[derive(Debug, Eq, PartialEq)] 35 | pub struct LunarDayError; 36 | 37 | impl Display for LunarDayError { 38 | #[inline] 39 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 40 | f.write_str("the lunar day is incorrect") 41 | } 42 | } 43 | 44 | #[cfg(feature = "std")] 45 | impl Error for LunarDayError {} 46 | -------------------------------------------------------------------------------- /src/solar/month/built_in_traits.rs: -------------------------------------------------------------------------------- 1 | use core::str::FromStr; 2 | 3 | use super::{SolarMonth, SolarMonthError}; 4 | 5 | impl TryFrom for SolarMonth { 6 | type Error = SolarMonthError; 7 | 8 | #[inline] 9 | fn try_from(value: u8) -> Result { 10 | Self::from_u8(value) 11 | } 12 | } 13 | 14 | impl TryFrom for SolarMonth { 15 | type Error = SolarMonthError; 16 | 17 | #[inline] 18 | fn try_from(value: u32) -> Result { 19 | Self::from_u32(value) 20 | } 21 | } 22 | 23 | impl FromStr for SolarMonth { 24 | type Err = SolarMonthError; 25 | 26 | #[inline] 27 | fn from_str(s: &str) -> Result { 28 | Self::parse_str(s) 29 | } 30 | } 31 | 32 | impl From for u8 { 33 | #[inline] 34 | fn from(value: SolarMonth) -> Self { 35 | value.to_u8() 36 | } 37 | } 38 | 39 | impl From for u32 { 40 | #[inline] 41 | fn from(value: SolarMonth) -> Self { 42 | value.to_u32() 43 | } 44 | } 45 | 46 | impl AsRef for SolarMonth { 47 | #[inline] 48 | fn as_ref(&self) -> &str { 49 | self.to_str() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/lunar_day.rs: -------------------------------------------------------------------------------- 1 | use chinese_lunisolar_calendar::LunarDay; 2 | 3 | #[test] 4 | fn parse_str() { 5 | assert_eq!(LunarDay::First, LunarDay::parse_str("初一").unwrap()); 6 | assert_eq!(LunarDay::Second, LunarDay::parse_str("初二").unwrap()); 7 | assert_eq!(LunarDay::Fifth, LunarDay::parse_str("初五").unwrap()); 8 | assert_eq!(LunarDay::Thirty, LunarDay::parse_str("三十").unwrap()); 9 | } 10 | 11 | #[test] 12 | fn to_str() { 13 | assert_eq!("初一", LunarDay::First.to_str()); 14 | assert_eq!("初二", LunarDay::Second.to_str()); 15 | assert_eq!("初五", LunarDay::Fifth.to_str()); 16 | assert_eq!("三十", LunarDay::Thirty.to_str()); 17 | } 18 | 19 | #[test] 20 | fn from_u8() { 21 | assert_eq!(LunarDay::First, LunarDay::from_u8(1).unwrap()); 22 | assert_eq!(LunarDay::Second, LunarDay::from_u8(2).unwrap()); 23 | assert_eq!(LunarDay::Fifth, LunarDay::from_u8(5).unwrap()); 24 | assert_eq!(LunarDay::Thirty, LunarDay::from_u8(30).unwrap()); 25 | } 26 | 27 | #[test] 28 | fn to_u8() { 29 | assert_eq!(1, LunarDay::First.to_u8()); 30 | assert_eq!(2, LunarDay::Second.to_u8()); 31 | assert_eq!(5, LunarDay::Fifth.to_u8()); 32 | assert_eq!(30, LunarDay::Thirty.to_u8()); 33 | } 34 | -------------------------------------------------------------------------------- /src/earthly_branch/built_in_traits.rs: -------------------------------------------------------------------------------- 1 | use core::str::FromStr; 2 | 3 | use chrono::prelude::*; 4 | 5 | use super::EarthlyBranch; 6 | use crate::Zodiac; 7 | 8 | impl TryFrom for EarthlyBranch { 9 | type Error = (); 10 | 11 | #[inline] 12 | fn try_from(value: char) -> Result { 13 | Self::from_char(value).ok_or(()) 14 | } 15 | } 16 | 17 | impl From for EarthlyBranch { 18 | #[inline] 19 | fn from(value: Zodiac) -> Self { 20 | Self::from_zodiac(value) 21 | } 22 | } 23 | 24 | impl From for EarthlyBranch { 25 | #[inline] 26 | fn from(value: NaiveTime) -> Self { 27 | Self::from_time(value) 28 | } 29 | } 30 | 31 | impl FromStr for EarthlyBranch { 32 | type Err = (); 33 | 34 | #[inline] 35 | fn from_str(s: &str) -> Result { 36 | Self::parse_str(s).ok_or(()) 37 | } 38 | } 39 | 40 | impl From for char { 41 | #[inline] 42 | fn from(value: EarthlyBranch) -> Self { 43 | value.to_char() 44 | } 45 | } 46 | 47 | impl AsRef for EarthlyBranch { 48 | #[inline] 49 | fn as_ref(&self) -> &str { 50 | self.to_str() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/solar_day.rs: -------------------------------------------------------------------------------- 1 | use chinese_lunisolar_calendar::SolarDay; 2 | 3 | #[test] 4 | fn parse_str() { 5 | assert_eq!(SolarDay::First, SolarDay::parse_str("一").unwrap()); 6 | assert_eq!(SolarDay::Second, SolarDay::parse_str("二").unwrap()); 7 | assert_eq!(SolarDay::Fifth, SolarDay::parse_str("五").unwrap()); 8 | assert_eq!(SolarDay::ThirtyFirst, SolarDay::parse_str("三十一").unwrap()); 9 | } 10 | 11 | #[test] 12 | fn to_str() { 13 | assert_eq!("一", SolarDay::First.to_str()); 14 | assert_eq!("二", SolarDay::Second.to_str()); 15 | assert_eq!("五", SolarDay::Fifth.to_str()); 16 | assert_eq!("三十一", SolarDay::ThirtyFirst.to_str()); 17 | } 18 | 19 | #[test] 20 | fn from_u8() { 21 | assert_eq!(SolarDay::First, SolarDay::from_u8(1).unwrap()); 22 | assert_eq!(SolarDay::Second, SolarDay::from_u8(2).unwrap()); 23 | assert_eq!(SolarDay::Fifth, SolarDay::from_u8(5).unwrap()); 24 | assert_eq!(SolarDay::ThirtyFirst, SolarDay::from_u8(31).unwrap()); 25 | } 26 | 27 | #[test] 28 | fn to_u8() { 29 | assert_eq!(1, SolarDay::First.to_u8()); 30 | assert_eq!(2, SolarDay::Second.to_u8()); 31 | assert_eq!(5, SolarDay::Fifth.to_u8()); 32 | assert_eq!(31, SolarDay::ThirtyFirst.to_u8()); 33 | } 34 | -------------------------------------------------------------------------------- /src/solar/date/built_in_traits.rs: -------------------------------------------------------------------------------- 1 | use core::str::FromStr; 2 | 3 | use chrono::prelude::*; 4 | 5 | use super::{SolarDate, SolarDateError, SolarOutOfRangeError}; 6 | use crate::{SolarDay, SolarMonth, SolarYear}; 7 | 8 | impl TryFrom for SolarDate { 9 | type Error = SolarOutOfRangeError; 10 | 11 | #[inline] 12 | fn try_from(value: NaiveDate) -> Result { 13 | Self::from_date(value) 14 | } 15 | } 16 | 17 | impl FromStr for SolarDate { 18 | type Err = SolarDateError; 19 | 20 | #[inline] 21 | fn from_str(s: &str) -> Result { 22 | SolarDate::parse_str(s) 23 | } 24 | } 25 | 26 | impl From for NaiveDate { 27 | #[inline] 28 | fn from(value: SolarDate) -> Self { 29 | value.to_naive_date() 30 | } 31 | } 32 | 33 | impl From for SolarYear { 34 | #[inline] 35 | fn from(value: SolarDate) -> Self { 36 | value.to_solar_year() 37 | } 38 | } 39 | 40 | impl From for SolarMonth { 41 | #[inline] 42 | fn from(value: SolarDate) -> Self { 43 | value.to_solar_month() 44 | } 45 | } 46 | 47 | impl From for SolarDay { 48 | #[inline] 49 | fn from(value: SolarDate) -> Self { 50 | value.to_solar_day() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/heavenly_stems.rs: -------------------------------------------------------------------------------- 1 | use chinese_lunisolar_calendar::HeavenlyStems; 2 | 3 | #[test] 4 | fn parse_str() { 5 | assert_eq!(HeavenlyStems::First, HeavenlyStems::parse_str("甲").unwrap()); 6 | assert_eq!(HeavenlyStems::Second, HeavenlyStems::parse_str("乙").unwrap()); 7 | assert_eq!(HeavenlyStems::Fifth, HeavenlyStems::parse_str("戊").unwrap()); 8 | assert_eq!(HeavenlyStems::Tenth, HeavenlyStems::parse_str("癸").unwrap()); 9 | } 10 | 11 | #[test] 12 | fn to_str() { 13 | assert_eq!("甲", HeavenlyStems::First.to_str()); 14 | assert_eq!("乙", HeavenlyStems::Second.to_str()); 15 | assert_eq!("戊", HeavenlyStems::Fifth.to_str()); 16 | assert_eq!("癸", HeavenlyStems::Tenth.to_str()); 17 | } 18 | 19 | #[test] 20 | fn from_char() { 21 | assert_eq!(HeavenlyStems::First, HeavenlyStems::from_char('甲').unwrap()); 22 | assert_eq!(HeavenlyStems::Second, HeavenlyStems::from_char('乙').unwrap()); 23 | assert_eq!(HeavenlyStems::Fifth, HeavenlyStems::from_char('戊').unwrap()); 24 | assert_eq!(HeavenlyStems::Tenth, HeavenlyStems::from_char('癸').unwrap()); 25 | } 26 | 27 | #[test] 28 | fn to_char() { 29 | assert_eq!('甲', HeavenlyStems::First.to_char()); 30 | assert_eq!('乙', HeavenlyStems::Second.to_char()); 31 | assert_eq!('戊', HeavenlyStems::Fifth.to_char()); 32 | assert_eq!('癸', HeavenlyStems::Tenth.to_char()); 33 | } 34 | -------------------------------------------------------------------------------- /src/lunisolar/year/built_in_traits.rs: -------------------------------------------------------------------------------- 1 | use super::{LunisolarOutOfRangeError, LunisolarYear}; 2 | use crate::{EarthlyBranch, HeavenlyStems, LunarYear, SolarYear, Zodiac}; 3 | 4 | impl TryFrom for LunisolarYear { 5 | type Error = LunisolarOutOfRangeError; 6 | 7 | #[inline] 8 | fn try_from(value: SolarYear) -> Result { 9 | Self::from_solar_year(value) 10 | } 11 | } 12 | 13 | impl From for HeavenlyStems { 14 | #[inline] 15 | fn from(value: LunisolarYear) -> Self { 16 | value.to_heavenly_stems() 17 | } 18 | } 19 | 20 | impl From for EarthlyBranch { 21 | #[inline] 22 | fn from(value: LunisolarYear) -> Self { 23 | value.to_earthly_branch() 24 | } 25 | } 26 | 27 | impl From for Zodiac { 28 | #[inline] 29 | fn from(value: LunisolarYear) -> Self { 30 | value.to_zodiac() 31 | } 32 | } 33 | 34 | impl From for LunarYear { 35 | #[inline] 36 | fn from(value: LunisolarYear) -> Self { 37 | value.to_lunar_year() 38 | } 39 | } 40 | 41 | impl From for SolarYear { 42 | #[inline] 43 | fn from(value: LunisolarYear) -> Self { 44 | value.to_solar_year() 45 | } 46 | } 47 | 48 | impl From for u16 { 49 | #[inline] 50 | fn from(value: LunisolarYear) -> Self { 51 | value.to_u16() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/lunar/month/parse.rs: -------------------------------------------------------------------------------- 1 | use super::{LunarMonth, LunarMonthError, THE_LUNAR_MONTHS}; 2 | 3 | /// 用以解析字串的關聯函數。 4 | impl LunarMonth { 5 | /// 透過農曆月份字串來取得 `LunarMonth` 列舉實體。 6 | /// 7 | /// # Examples 8 | /// 9 | /// ``` 10 | /// use chinese_lunisolar_calendar::LunarMonth; 11 | /// 12 | /// assert_eq!(LunarMonth::Twelfth, LunarMonth::parse_str("臘月").unwrap()); 13 | /// assert_eq!( 14 | /// LunarMonth::LeapTwelfth, 15 | /// LunarMonth::parse_str("闰腊月").unwrap() 16 | /// ); 17 | /// ``` 18 | #[inline] 19 | pub fn parse_str>(s: S) -> Result { 20 | let s = s.as_ref(); 21 | 22 | for (i, t) in THE_LUNAR_MONTHS.iter().enumerate().take(24) { 23 | if s == t.0 || s == t.1 { 24 | return if i >= 12 { 25 | Ok(unsafe { Self::from_u8_raw_unsafe((i as u8 - 12) + 101) }) 26 | } else { 27 | Ok(unsafe { Self::from_u8_raw_unsafe(i as u8 + 1) }) 28 | }; 29 | } 30 | } 31 | 32 | macro_rules! extra { 33 | ($i:expr, $variant:ident) => {{ 34 | let t = THE_LUNAR_MONTHS[$i]; 35 | 36 | if s == t.0 || s == t.1 { 37 | return Ok(LunarMonth::$variant); 38 | } 39 | }}; 40 | } 41 | 42 | extra!(24, First); 43 | extra!(25, Eleventh); 44 | extra!(26, Twelfth); 45 | extra!(27, LeapFirst); 46 | extra!(28, LeapEleventh); 47 | extra!(29, LeapTwelfth); 48 | extra!(30, LeapTwelfth); 49 | 50 | Err(LunarMonthError) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /.github/workflows/ci-version.yml: -------------------------------------------------------------------------------- 1 | name: CI-version 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | 11 | jobs: 12 | tests: 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | os: 17 | - ubuntu-latest 18 | - macos-latest 19 | - windows-latest 20 | toolchain: 21 | - stable 22 | - nightly 23 | features: 24 | - 25 | - --features ba-zi-weight 26 | - --no-default-features 27 | - --no-default-features --features ba-zi-weight 28 | name: Test ${{ matrix.toolchain }} on ${{ matrix.os }} (${{ matrix.features }}) 29 | runs-on: ${{ matrix.os }} 30 | steps: 31 | - uses: actions/checkout@v6 32 | - uses: actions-rust-lang/setup-rust-toolchain@v1 33 | with: 34 | toolchain: ${{ matrix.toolchain }} 35 | - run: cargo test --release ${{ matrix.features }} 36 | - run: cargo doc --release ${{ matrix.features }} 37 | 38 | MSRV: 39 | strategy: 40 | fail-fast: false 41 | matrix: 42 | os: 43 | - ubuntu-latest 44 | - macos-latest 45 | - windows-latest 46 | toolchain: 47 | - 1.71 48 | features: 49 | - 50 | - --features ba-zi-weight 51 | - --no-default-features 52 | - --no-default-features --features ba-zi-weight 53 | name: Test ${{ matrix.toolchain }} on ${{ matrix.os }} (${{ matrix.features }}) 54 | runs-on: ${{ matrix.os }} 55 | steps: 56 | - uses: actions/checkout@v6 57 | - uses: actions-rust-lang/setup-rust-toolchain@v1 58 | with: 59 | toolchain: ${{ matrix.toolchain }} 60 | - run: cargo test --release --lib --bins ${{ matrix.features }} -------------------------------------------------------------------------------- /src/solar/date/parse.rs: -------------------------------------------------------------------------------- 1 | use super::{SolarDate, SolarDateError, SolarDay, SolarMonth, SolarYear}; 2 | 3 | /// 用以解析字串的關聯函數。 4 | impl SolarDate { 5 | /// 用中文西曆年月日字串來產生 `SolarDate` 實體。 6 | /// 7 | /// # Examples 8 | /// 9 | /// ``` 10 | /// use chinese_lunisolar_calendar::SolarDate; 11 | /// 12 | /// let solar_date = SolarDate::parse_str("二〇二四年一月一日").unwrap(); 13 | /// 14 | /// assert_eq!("二〇二四年一月一日", solar_date.to_string()); 15 | /// ``` 16 | pub fn parse_str>(s: S) -> Result { 17 | let s = s.as_ref(); 18 | 19 | let year_index = { 20 | match s.find('年') { 21 | Some(index) => index, 22 | None => match s.find(' ') { 23 | Some(index) => index, 24 | None => return Err(SolarDateError::YearIncorrect), 25 | }, 26 | } 27 | }; 28 | 29 | let year_str = s[..year_index].trim(); 30 | 31 | let solar_year = SolarYear::parse_str(year_str)?; 32 | 33 | let s = &s[year_index + 3..]; 34 | 35 | let month_index = { 36 | match s.find('月') { 37 | Some(index) => index, 38 | None => match s.find(' ') { 39 | Some(index) => index, 40 | None => return Err(SolarDateError::MonthIncorrect), 41 | }, 42 | } 43 | }; 44 | 45 | let month_str = s[..month_index + 3].trim(); 46 | 47 | let solar_month = SolarMonth::parse_str(month_str)?; 48 | 49 | let mut day_str = s[month_index + 3..].trim(); 50 | 51 | if let Some(day) = day_str.strip_suffix('日') { 52 | day_str = day; 53 | } 54 | 55 | let solar_day = SolarDay::parse_str(day_str)?; 56 | 57 | Self::from_solar_year_month_day(solar_year, solar_month, solar_day) 58 | .map_err(|err| err.into()) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/lunisolar/errors.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::{self, Display, Formatter}; 2 | #[cfg(feature = "std")] 3 | use std::error::Error; 4 | 5 | use crate::{LunarDayError, LunarMonthError, LunarYearError}; 6 | 7 | /// 超出農曆支援的日期範圍。 8 | #[derive(Debug, Eq, PartialEq)] 9 | pub struct LunisolarOutOfRangeError; 10 | 11 | impl Display for LunisolarOutOfRangeError { 12 | #[inline] 13 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 14 | f.write_str("the lunisolar date is out of range") 15 | } 16 | } 17 | 18 | #[cfg(feature = "std")] 19 | impl Error for LunisolarOutOfRangeError {} 20 | 21 | /// 錯誤的農曆年月日。 22 | #[derive(Debug, Eq, PartialEq)] 23 | pub enum LunisolarDateError { 24 | OutOfRange, 25 | YearIncorrect, 26 | MonthIncorrect, 27 | DayIncorrect, 28 | } 29 | 30 | impl Display for LunisolarDateError { 31 | #[inline] 32 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 33 | match self { 34 | Self::OutOfRange => Display::fmt(&LunisolarOutOfRangeError, f), 35 | Self::YearIncorrect => Display::fmt(&LunarYearError, f), 36 | Self::MonthIncorrect => Display::fmt(&LunarMonthError, f), 37 | Self::DayIncorrect => Display::fmt(&LunarDayError, f), 38 | } 39 | } 40 | } 41 | 42 | impl From for LunisolarDateError { 43 | #[inline] 44 | fn from(_: LunisolarOutOfRangeError) -> Self { 45 | Self::OutOfRange 46 | } 47 | } 48 | 49 | impl From for LunisolarDateError { 50 | #[inline] 51 | fn from(_: LunarYearError) -> Self { 52 | Self::YearIncorrect 53 | } 54 | } 55 | 56 | impl From for LunisolarDateError { 57 | #[inline] 58 | fn from(_: LunarMonthError) -> Self { 59 | Self::MonthIncorrect 60 | } 61 | } 62 | 63 | impl From for LunisolarDateError { 64 | #[inline] 65 | fn from(_: LunarDayError) -> Self { 66 | Self::DayIncorrect 67 | } 68 | } 69 | 70 | #[cfg(feature = "std")] 71 | impl Error for LunisolarDateError {} 72 | -------------------------------------------------------------------------------- /tests/lunar_year.rs: -------------------------------------------------------------------------------- 1 | use chinese_lunisolar_calendar::{EarthlyBranch, HeavenlyStems, LunarYear}; 2 | 3 | #[test] 4 | fn parse_str() { 5 | let lunar_year = LunarYear::parse_str("甲午").unwrap(); 6 | 7 | assert_eq!(HeavenlyStems::First, lunar_year.to_heavenly_stems()); 8 | assert_eq!(EarthlyBranch::Seventh, lunar_year.to_earthly_branch()); 9 | } 10 | 11 | #[test] 12 | fn to_str() { 13 | let lunar_year = LunarYear::from_era(HeavenlyStems::First, EarthlyBranch::First).unwrap(); 14 | assert_eq!("甲子", lunar_year.to_str()); 15 | 16 | let lunar_year = LunarYear::from_era(HeavenlyStems::First, EarthlyBranch::Eleventh).unwrap(); 17 | assert_eq!("甲戌", lunar_year.to_str()); 18 | 19 | let lunar_year = LunarYear::from_era(HeavenlyStems::Third, EarthlyBranch::First).unwrap(); 20 | assert_eq!("丙子", lunar_year.to_str()); 21 | 22 | let lunar_year = LunarYear::from_era(HeavenlyStems::First, EarthlyBranch::Ninth).unwrap(); 23 | assert_eq!("甲申", lunar_year.to_str()); 24 | 25 | let lunar_year = LunarYear::from_era(HeavenlyStems::Fifth, EarthlyBranch::First).unwrap(); 26 | assert_eq!("戊子", lunar_year.to_str()); 27 | 28 | let lunar_year = LunarYear::from_era(HeavenlyStems::First, EarthlyBranch::Seventh).unwrap(); 29 | assert_eq!("甲午", lunar_year.to_str()); 30 | 31 | let lunar_year = LunarYear::from_era(HeavenlyStems::Sixth, EarthlyBranch::Twelfth).unwrap(); 32 | assert_eq!("己亥", lunar_year.to_str()); 33 | 34 | let lunar_year = LunarYear::from_era(HeavenlyStems::Seventh, EarthlyBranch::First).unwrap(); 35 | assert_eq!("庚子", lunar_year.to_str()); 36 | 37 | let lunar_year = LunarYear::from_era(HeavenlyStems::First, EarthlyBranch::Fifth).unwrap(); 38 | assert_eq!("甲辰", lunar_year.to_str()); 39 | 40 | let lunar_year = LunarYear::from_era(HeavenlyStems::Ninth, EarthlyBranch::First).unwrap(); 41 | assert_eq!("壬子", lunar_year.to_str()); 42 | 43 | let lunar_year = LunarYear::from_era(HeavenlyStems::First, EarthlyBranch::Third).unwrap(); 44 | assert_eq!("甲寅", lunar_year.to_str()); 45 | } 46 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # array_width = 60 2 | # attr_fn_like_width = 70 3 | binop_separator = "Front" 4 | blank_lines_lower_bound = 0 5 | blank_lines_upper_bound = 1 6 | brace_style = "PreferSameLine" 7 | # chain_width = 60 8 | color = "Auto" 9 | # comment_width = 100 10 | condense_wildcard_suffixes = true 11 | control_brace_style = "AlwaysSameLine" 12 | empty_item_single_line = true 13 | enum_discrim_align_threshold = 80 14 | error_on_line_overflow = false 15 | error_on_unformatted = false 16 | # fn_call_width = 60 17 | fn_params_layout = "Tall" 18 | fn_single_line = false 19 | force_explicit_abi = true 20 | force_multiline_blocks = false 21 | format_code_in_doc_comments = true 22 | doc_comment_code_block_width = 80 23 | format_generated_files = true 24 | format_macro_matchers = true 25 | format_macro_bodies = true 26 | skip_macro_invocations = [] 27 | format_strings = true 28 | hard_tabs = false 29 | hex_literal_case = "Upper" 30 | imports_indent = "Block" 31 | imports_layout = "Mixed" 32 | indent_style = "Block" 33 | inline_attribute_width = 0 34 | match_arm_blocks = true 35 | match_arm_leading_pipes = "Never" 36 | match_block_trailing_comma = true 37 | max_width = 100 38 | merge_derives = true 39 | imports_granularity = "Crate" 40 | newline_style = "Unix" 41 | normalize_comments = false 42 | normalize_doc_attributes = true 43 | overflow_delimited_expr = true 44 | remove_nested_parens = true 45 | reorder_impl_items = true 46 | reorder_imports = true 47 | group_imports = "StdExternalCrate" 48 | reorder_modules = true 49 | short_array_element_width_threshold = 10 50 | # single_line_if_else_max_width = 50 51 | space_after_colon = true 52 | space_before_colon = false 53 | spaces_around_ranges = false 54 | struct_field_align_threshold = 80 55 | struct_lit_single_line = false 56 | # struct_lit_width = 18 57 | # struct_variant_width = 35 58 | tab_spaces = 4 59 | trailing_comma = "Vertical" 60 | trailing_semicolon = true 61 | type_punctuation_density = "Wide" 62 | use_field_init_shorthand = true 63 | use_small_heuristics = "Max" 64 | use_try_shorthand = true 65 | where_single_line = false 66 | wrap_comments = false -------------------------------------------------------------------------------- /src/lunisolar/date/ba_zi_weight.rs: -------------------------------------------------------------------------------- 1 | use chrono::Timelike; 2 | 3 | use super::LunisolarDate; 4 | use crate::EarthlyBranch; 5 | 6 | impl LunisolarDate { 7 | /// 搭配出生時間(地支),來計算八字有幾錢重。(10錢 = 1兩) 8 | /// 9 | /// * 子:23~1 10 | /// * 丑:1~3 11 | /// * 寅:3~5 12 | /// * 卯:5~7 13 | /// * 辰:7~9 14 | /// * 巳:9~11 15 | /// * 午:11~13 16 | /// * 未:13~15 17 | /// * 申:15~17 18 | /// * 酉:17~19 19 | /// * 戌:19~21 20 | /// * 亥:21~23 21 | /// 22 | /// # Examples 23 | /// 24 | /// ``` 25 | /// use chinese_lunisolar_calendar::{EarthlyBranch, LunisolarDate, SolarDate}; 26 | /// 27 | /// let solar_date = SolarDate::from_ymd(2024, 2, 10).unwrap(); 28 | /// 29 | /// let lunisolar_date = LunisolarDate::from_solar_date(solar_date).unwrap(); 30 | /// 31 | /// assert_eq!(35, lunisolar_date.get_ba_zi_weight(EarthlyBranch::First)); 32 | /// ``` 33 | #[inline] 34 | pub const fn get_ba_zi_weight(self, earthly_branch: EarthlyBranch) -> u8 { 35 | self.lunisolar_year.to_lunar_year().get_ba_zi_weight() 36 | + self.lunar_month.get_ba_zi_weight() 37 | + self.lunar_day.get_ba_zi_weight() 38 | + earthly_branch.get_ba_zi_weight() 39 | } 40 | 41 | /// 搭配出生時間,來計算八字有幾錢重。(10錢 = 1兩) 42 | /// 43 | /// # Examples 44 | /// 45 | /// ``` 46 | /// use chinese_lunisolar_calendar::{LunisolarDate, SolarDate}; 47 | /// use chrono::prelude::*; 48 | /// 49 | /// let solar_date = SolarDate::from_ymd(2024, 2, 10).unwrap(); 50 | /// 51 | /// let lunisolar_date = LunisolarDate::from_solar_date(solar_date).unwrap(); 52 | /// 53 | /// assert_eq!( 54 | /// 29, 55 | /// lunisolar_date.get_ba_zi_weight_by_time( 56 | /// NaiveTime::from_hms_opt(12, 30, 0).unwrap() 57 | /// ) 58 | /// ); 59 | /// ``` 60 | #[inline] 61 | pub fn get_ba_zi_weight_by_time(self, time: T) -> u8 { 62 | let earthly_branch = EarthlyBranch::from_time(time); 63 | 64 | self.get_ba_zi_weight(earthly_branch) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/lunisolar/date/built_in_traits.rs: -------------------------------------------------------------------------------- 1 | use core::str::FromStr; 2 | 3 | use chrono::prelude::*; 4 | 5 | use super::{LunisolarDate, LunisolarDateError, LunisolarOutOfRangeError}; 6 | use crate::{LunarDay, LunarMonth, LunarYear, LunisolarYear, SolarDate, SolarYear}; 7 | 8 | impl TryFrom for LunisolarDate { 9 | type Error = LunisolarOutOfRangeError; 10 | 11 | #[inline] 12 | fn try_from(value: SolarDate) -> Result { 13 | Self::from_solar_date(value) 14 | } 15 | } 16 | 17 | impl TryFrom for LunisolarDate { 18 | type Error = LunisolarOutOfRangeError; 19 | 20 | #[inline] 21 | fn try_from(value: NaiveDate) -> Result { 22 | Self::from_date(value) 23 | } 24 | } 25 | 26 | impl FromStr for LunisolarDate { 27 | type Err = LunisolarDateError; 28 | 29 | #[inline] 30 | fn from_str(s: &str) -> Result { 31 | LunisolarDate::parse_str(s) 32 | } 33 | } 34 | 35 | impl From for SolarDate { 36 | #[inline] 37 | fn from(value: LunisolarDate) -> Self { 38 | value.to_solar_date() 39 | } 40 | } 41 | 42 | impl From for NaiveDate { 43 | #[inline] 44 | fn from(value: LunisolarDate) -> Self { 45 | value.to_naive_date() 46 | } 47 | } 48 | 49 | impl From for SolarYear { 50 | #[inline] 51 | fn from(value: LunisolarDate) -> Self { 52 | value.to_solar_year() 53 | } 54 | } 55 | 56 | impl From for LunisolarYear { 57 | #[inline] 58 | fn from(value: LunisolarDate) -> Self { 59 | value.to_lunisolar_year() 60 | } 61 | } 62 | 63 | impl From for LunarYear { 64 | #[inline] 65 | fn from(value: LunisolarDate) -> Self { 66 | value.to_lunar_year() 67 | } 68 | } 69 | 70 | impl From for LunarMonth { 71 | #[inline] 72 | fn from(value: LunisolarDate) -> Self { 73 | value.to_lunar_month() 74 | } 75 | } 76 | 77 | impl From for LunarDay { 78 | #[inline] 79 | fn from(value: LunisolarDate) -> Self { 80 | value.to_lunar_day() 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /tests/solar_month.rs: -------------------------------------------------------------------------------- 1 | use chinese_lunisolar_calendar::SolarMonth; 2 | 3 | #[test] 4 | fn parse_str() { 5 | assert_eq!(SolarMonth::January, SolarMonth::parse_str("一月").unwrap()); 6 | assert_eq!(SolarMonth::February, SolarMonth::parse_str("二月").unwrap()); 7 | assert_eq!(SolarMonth::May, SolarMonth::parse_str("五月").unwrap()); 8 | assert_eq!(SolarMonth::December, SolarMonth::parse_str("十二月").unwrap()); 9 | } 10 | 11 | #[test] 12 | fn to_str() { 13 | assert_eq!("一月", SolarMonth::January.to_str()); 14 | assert_eq!("二月", SolarMonth::February.to_str()); 15 | assert_eq!("五月", SolarMonth::May.to_str()); 16 | assert_eq!("十二月", SolarMonth::December.to_str()); 17 | } 18 | 19 | #[test] 20 | fn from_u8() { 21 | assert_eq!(SolarMonth::January, SolarMonth::from_u8(1).unwrap()); 22 | assert_eq!(SolarMonth::February, SolarMonth::from_u8(2).unwrap()); 23 | assert_eq!(SolarMonth::May, SolarMonth::from_u8(5).unwrap()); 24 | assert_eq!(SolarMonth::December, SolarMonth::from_u8(12).unwrap()); 25 | } 26 | 27 | #[test] 28 | fn to_u8() { 29 | assert_eq!(1, SolarMonth::January.to_u8()); 30 | assert_eq!(2, SolarMonth::February.to_u8()); 31 | assert_eq!(5, SolarMonth::May.to_u8()); 32 | assert_eq!(12, SolarMonth::December.to_u8()); 33 | } 34 | 35 | #[test] 36 | fn get_total_days() { 37 | assert_eq!(31, SolarMonth::January.get_total_days(2000.into())); 38 | assert_eq!(29, SolarMonth::February.get_total_days(2000.into())); 39 | assert_eq!(31, SolarMonth::March.get_total_days(2000.into())); 40 | assert_eq!(30, SolarMonth::April.get_total_days(2000.into())); 41 | assert_eq!(30, SolarMonth::November.get_total_days(2000.into())); 42 | assert_eq!(31, SolarMonth::December.get_total_days(2000.into())); 43 | assert_eq!(31, SolarMonth::January.get_total_days(2100.into())); 44 | assert_eq!(28, SolarMonth::February.get_total_days(2100.into())); 45 | assert_eq!(31, SolarMonth::March.get_total_days(2100.into())); 46 | assert_eq!(30, SolarMonth::April.get_total_days(2100.into())); 47 | assert_eq!(30, SolarMonth::November.get_total_days(2100.into())); 48 | assert_eq!(31, SolarMonth::December.get_total_days(2100.into())); 49 | } 50 | -------------------------------------------------------------------------------- /src/solar/year/parse.rs: -------------------------------------------------------------------------------- 1 | use super::{SolarYear, SolarYearError, ALTERNATIVE_ZERO, THE_SOLAR_YEAR_NUMBERS_CHAR}; 2 | 3 | /// 用以解析字串的關聯函數。 4 | impl SolarYear { 5 | /// 透過西曆年份字串來取得 `SolarYear` 實體。 6 | /// 7 | /// # Examples 8 | /// 9 | /// ``` 10 | /// # use chinese_lunisolar_calendar::SolarYear; 11 | /// assert_eq!( 12 | /// SolarYear::from_u16(2008), 13 | /// SolarYear::parse_str("二〇〇八").unwrap() 14 | /// ); 15 | /// assert_eq!( 16 | /// SolarYear::from_u16(2008), 17 | /// SolarYear::parse_str("二零零八").unwrap() 18 | /// ); 19 | /// ``` 20 | pub fn parse_str>(s: S) -> Result { 21 | let s = s.as_ref(); 22 | 23 | let mut year = 0u16; 24 | 25 | for (count, c) in s.chars().enumerate() { 26 | if count == 5 { 27 | return Err(SolarYearError); 28 | } 29 | 30 | if c.is_ascii_digit() { 31 | year = year 32 | .checked_mul(10) 33 | .ok_or(SolarYearError)? 34 | .checked_add((c as u8 - b'0') as u16) 35 | .ok_or(SolarYearError)?; 36 | } else { 37 | let mut failed = true; 38 | 39 | for (i, cc) in THE_SOLAR_YEAR_NUMBERS_CHAR.iter().copied().enumerate() { 40 | if c == cc { 41 | year = year 42 | .checked_mul(10) 43 | .ok_or(SolarYearError)? 44 | .checked_add(i as u16) 45 | .ok_or(SolarYearError)?; 46 | 47 | failed = false; 48 | 49 | break; 50 | } 51 | } 52 | 53 | if failed { 54 | match c { 55 | ALTERNATIVE_ZERO => { 56 | year = year.checked_mul(10).ok_or(SolarYearError)?; 57 | }, 58 | _ => return Err(SolarYearError), 59 | } 60 | } 61 | } 62 | } 63 | 64 | Ok(SolarYear(year)) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | rustfmt: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v6 13 | - uses: actions-rust-lang/setup-rust-toolchain@v1 14 | with: 15 | toolchain: nightly 16 | components: rustfmt 17 | - uses: actions-rust-lang/rustfmt@v1 18 | 19 | clippy: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v6 23 | - uses: actions-rust-lang/setup-rust-toolchain@v1 24 | with: 25 | components: clippy 26 | - run: cargo clippy --all-targets --all-features -- -D warnings 27 | 28 | tests: 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | os: 33 | - ubuntu-latest 34 | - macos-latest 35 | - windows-latest 36 | toolchain: 37 | - stable 38 | - nightly 39 | features: 40 | - 41 | - --features ba-zi-weight 42 | - --no-default-features 43 | - --no-default-features --features ba-zi-weight 44 | name: Test ${{ matrix.toolchain }} on ${{ matrix.os }} (${{ matrix.features }}) 45 | runs-on: ${{ matrix.os }} 46 | steps: 47 | - uses: actions/checkout@v6 48 | - uses: actions-rust-lang/setup-rust-toolchain@v1 49 | with: 50 | toolchain: ${{ matrix.toolchain }} 51 | - run: cargo test ${{ matrix.features }} 52 | - run: cargo doc ${{ matrix.features }} 53 | 54 | MSRV: 55 | strategy: 56 | fail-fast: false 57 | matrix: 58 | os: 59 | - ubuntu-latest 60 | - macos-latest 61 | - windows-latest 62 | toolchain: 63 | - 1.71 64 | features: 65 | - 66 | - --features ba-zi-weight 67 | - --no-default-features 68 | - --no-default-features --features ba-zi-weight 69 | name: Test ${{ matrix.toolchain }} on ${{ matrix.os }} (${{ matrix.features }}) 70 | runs-on: ${{ matrix.os }} 71 | steps: 72 | - uses: actions/checkout@v6 73 | - uses: actions-rust-lang/setup-rust-toolchain@v1 74 | with: 75 | toolchain: ${{ matrix.toolchain }} 76 | - run: cargo test --lib --bins ${{ matrix.features }} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Chinese Lunisolar Calendar 2 | ==================== 3 | 4 | [![CI](https://github.com/magiclen/chinese-lunisolar-calendar/actions/workflows/ci.yml/badge.svg)](https://github.com/magiclen/chinese-lunisolar-calendar/actions/workflows/ci.yml) 5 | 6 | The traditional Chinese Calendar, known as 農曆 or 陰曆 in Chinese, is based on the moon, and is commonly referred to as the Lunar Calendar. 7 | 8 | Due to the Lunar Calendar's 60-year cycle and the absence of regular rules for the days in each lunar month, as well as the variable number of months in a lunar year, using the Lunar Calendar without referencing other calendars can be challenging. The Lunisolar Calendar is designed to address this issue by combining both the Solar Calendar (Gregorian Calendar) and the Lunar Calendar, ensuring accuracy, predictability, and practicality. 9 | 10 | This library allows you to seamlessly convert dates between the Lunisolar Calendar and the Solar Calendar, compute the Ba Zi (八字) weight, and also convert dates to Chinese text strings. Furthermore, it enables parsing of Chinese text strings into dates in both Simplified Chinese and Traditional Chinese. 11 | 12 | ## Examples 13 | 14 | ```rust 15 | use chinese_lunisolar_calendar::SolarDate; 16 | 17 | let solar_date = SolarDate::from_ymd(2019, 1, 15).unwrap(); 18 | 19 | assert_eq!("二〇一九年一月十五日", solar_date.to_string()); 20 | ``` 21 | 22 | ```rust 23 | use chinese_lunisolar_calendar::{LunisolarDate, SolarDate}; 24 | 25 | let lunisolar_date = LunisolarDate::from_solar_date(SolarDate::from_ymd(2019, 1, 15).unwrap()).unwrap(); 26 | 27 | assert_eq!("二〇一八 戊戌、狗年 臘月 初十", lunisolar_date.to_string()); 28 | 29 | assert_eq!("二〇一八 戊戌、狗年 臘月 初十", format!("{lunisolar_date}")); 30 | assert_eq!("二〇一八 戊戌、狗年 腊月 初十", format!("{lunisolar_date:#}")); 31 | 32 | use chinese_lunisolar_calendar::EarthlyBranch; 33 | assert_eq!(43, lunisolar_date.get_ba_zi_weight(EarthlyBranch::Ninth)); 34 | ``` 35 | 36 | To calculate the Ba Zi weight, the `ba-zi-weight` feature must be enabled. 37 | 38 | ## No Std 39 | 40 | Disable the default features to compile this crate without std. 41 | 42 | ```toml 43 | [dependencies.chinese-lunisolar-calendar] 44 | version = "*" 45 | default-features = false 46 | ``` 47 | 48 | ## Crates.io 49 | 50 | https://crates.io/crates/chinese-lunisolar-calendar 51 | 52 | ## Documentation 53 | 54 | https://docs.rs/chinese-lunisolar-calendar 55 | 56 | ## License 57 | 58 | [MIT](LICENSE) -------------------------------------------------------------------------------- /src/lunisolar/date/parse.rs: -------------------------------------------------------------------------------- 1 | use super::{LunarDay, LunarMonth, LunisolarDate, LunisolarDateError, LunisolarYear, SolarYear}; 2 | 3 | /// 用以解析字串的關聯函數。 4 | impl LunisolarDate { 5 | /// 用中文農曆西曆年和農曆月日字串來產生 `LunisolarDate` 實體。 6 | /// 7 | /// # Examples 8 | /// 9 | /// ``` 10 | /// use chinese_lunisolar_calendar::LunisolarDate; 11 | /// 12 | /// let lunisolar_date = LunisolarDate::parse_str("二〇二四 甲辰、龍年 正月 初一").unwrap(); 13 | /// let lunisolar_date = LunisolarDate::parse_str("二零二四年正月初一").unwrap(); 14 | /// ``` 15 | pub fn parse_str>(s: S) -> Result { 16 | let s = s.as_ref(); 17 | 18 | let year_index = { 19 | match s.find(' ') { 20 | Some(index) => index, 21 | None => match s.find('年') { 22 | Some(index) => index, 23 | None => return Err(LunisolarDateError::YearIncorrect), 24 | }, 25 | } 26 | }; 27 | 28 | let year_str = s[..year_index].trim(); 29 | 30 | let lunisolar_year = LunisolarYear::from_solar_year( 31 | SolarYear::parse_str(year_str).map_err(|_| LunisolarDateError::YearIncorrect)?, 32 | )?; 33 | 34 | let s = &s[year_index + 3..]; 35 | 36 | let month_index = { 37 | match s.find('月') { 38 | Some(index) => index, 39 | None => match s.find(' ') { 40 | Some(index) => index, 41 | None => return Err(LunisolarDateError::MonthIncorrect), 42 | }, 43 | } 44 | }; 45 | 46 | let month_str = s[..month_index + 3].trim(); 47 | 48 | let month_str = { 49 | match month_str.find('年') { 50 | Some(index) => month_str[index + 3..].trim(), 51 | None => match month_str.find(' ') { 52 | Some(index) => month_str[index + 3..].trim(), 53 | None => month_str, 54 | }, 55 | } 56 | }; 57 | 58 | let lunar_month = LunarMonth::parse_str(month_str)?; 59 | 60 | let mut day_str = s[month_index + 3..].trim(); 61 | 62 | if day_str.ends_with('日') { 63 | day_str = &day_str[..day_str.len() - 3]; 64 | } 65 | 66 | let lunar_day = LunarDay::parse_str(day_str)?; 67 | 68 | Self::from_lunisolar_year_lunar_month_day(lunisolar_year, lunar_month, lunar_day) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | # Chinese Lunisolar Calendar 3 | 4 | The traditional Chinese Calendar, known as 農曆 or 陰曆 in Chinese, is based on the moon, and is commonly referred to as the Lunar Calendar. 5 | 6 | Due to the Lunar Calendar's 60-year cycle and the absence of regular rules for the days in each lunar month, as well as the variable number of months in a lunar year, using the Lunar Calendar without referencing other calendars can be challenging. The Lunisolar Calendar is designed to address this issue by combining both the Solar Calendar (Gregorian Calendar) and the Lunar Calendar, ensuring accuracy, predictability, and practicality. 7 | 8 | This library allows you to seamlessly convert dates between the Lunisolar Calendar and the Solar Calendar, compute the Ba Zi (八字) weight, and also convert dates to Chinese text strings. Furthermore, it enables parsing of Chinese text strings into dates in both Simplified Chinese and Traditional Chinese. 9 | 10 | ## Examples 11 | 12 | ```rust 13 | use chinese_lunisolar_calendar::SolarDate; 14 | 15 | let solar_date = SolarDate::from_ymd(2019, 1, 15).unwrap(); 16 | 17 | assert_eq!("二〇一九年一月十五日", solar_date.to_string()); 18 | ``` 19 | 20 | ```rust 21 | use chinese_lunisolar_calendar::{LunisolarDate, SolarDate}; 22 | 23 | let lunisolar_date = LunisolarDate::from_solar_date(SolarDate::from_ymd(2019, 1, 15).unwrap()).unwrap(); 24 | 25 | assert_eq!("二〇一八 戊戌、狗年 臘月 初十", lunisolar_date.to_string()); 26 | 27 | assert_eq!("二〇一八 戊戌、狗年 臘月 初十", format!("{lunisolar_date}")); 28 | assert_eq!("二〇一八 戊戌、狗年 腊月 初十", format!("{lunisolar_date:#}")); 29 | 30 | # #[cfg(feature = "ba-zi-weight")] 31 | # { 32 | use chinese_lunisolar_calendar::EarthlyBranch; 33 | assert_eq!(43, lunisolar_date.get_ba_zi_weight(EarthlyBranch::Ninth)); 34 | # } 35 | ``` 36 | 37 | To calculate the Ba Zi weight, the `ba-zi-weight` feature must be enabled. 38 | 39 | ## No Std 40 | 41 | Disable the default features to compile this crate without std. 42 | 43 | ```toml 44 | [dependencies.chinese-lunisolar-calendar] 45 | version = "*" 46 | default-features = false 47 | ``` 48 | */ 49 | 50 | #![cfg_attr(not(feature = "std"), no_std)] 51 | #![allow(unexpected_cfgs)] 52 | #![cfg_attr(docsrs_1_92, feature(doc_cfg))] 53 | 54 | pub extern crate chrono; 55 | 56 | mod earthly_branch; 57 | mod heavenly_stems; 58 | mod lunar; 59 | mod lunisolar; 60 | mod solar; 61 | mod zodiac; 62 | 63 | pub use chinese_variant::ChineseVariant; 64 | pub use earthly_branch::*; 65 | pub use heavenly_stems::*; 66 | pub use lunar::*; 67 | pub use lunisolar::*; 68 | pub use solar::*; 69 | pub use zodiac::*; 70 | -------------------------------------------------------------------------------- /tests/zodiac.rs: -------------------------------------------------------------------------------- 1 | use chinese_lunisolar_calendar::{ChineseVariant, EarthlyBranch, Zodiac}; 2 | 3 | #[test] 4 | fn parse_str() { 5 | assert_eq!(Zodiac::Rat, Zodiac::parse_str("鼠").unwrap()); 6 | assert_eq!(Zodiac::Ox, Zodiac::parse_str("牛").unwrap()); 7 | assert_eq!(Zodiac::Dragon, Zodiac::parse_str("龍").unwrap()); 8 | assert_eq!(Zodiac::Dragon, Zodiac::parse_str("龙").unwrap()); 9 | assert_eq!(Zodiac::Pig, Zodiac::parse_str("豬").unwrap()); 10 | assert_eq!(Zodiac::Pig, Zodiac::parse_str("猪").unwrap()); 11 | } 12 | 13 | #[test] 14 | fn to_str() { 15 | assert_eq!("鼠", Zodiac::Rat.to_str(ChineseVariant::Traditional)); 16 | assert_eq!("牛", Zodiac::Ox.to_str(ChineseVariant::Traditional)); 17 | assert_eq!("龍", Zodiac::Dragon.to_str(ChineseVariant::Traditional)); 18 | assert_eq!("龙", Zodiac::Dragon.to_str(ChineseVariant::Simple)); 19 | assert_eq!("豬", Zodiac::Pig.to_str(ChineseVariant::Traditional)); 20 | assert_eq!("猪", Zodiac::Pig.to_str(ChineseVariant::Simple)); 21 | } 22 | 23 | #[test] 24 | fn from_char() { 25 | assert_eq!(Zodiac::Rat, Zodiac::from_char('鼠').unwrap()); 26 | assert_eq!(Zodiac::Ox, Zodiac::from_char('牛').unwrap()); 27 | assert_eq!(Zodiac::Dragon, Zodiac::from_char('龍').unwrap()); 28 | assert_eq!(Zodiac::Dragon, Zodiac::from_char('龙').unwrap()); 29 | assert_eq!(Zodiac::Pig, Zodiac::from_char('豬').unwrap()); 30 | assert_eq!(Zodiac::Pig, Zodiac::from_char('猪').unwrap()); 31 | } 32 | 33 | #[test] 34 | fn to_char() { 35 | assert_eq!('鼠', Zodiac::Rat.to_char(ChineseVariant::Traditional),); 36 | assert_eq!('牛', Zodiac::Ox.to_char(ChineseVariant::Traditional)); 37 | assert_eq!('龍', Zodiac::Dragon.to_char(ChineseVariant::Traditional)); 38 | assert_eq!('龙', Zodiac::Dragon.to_char(ChineseVariant::Simple)); 39 | assert_eq!('豬', Zodiac::Pig.to_char(ChineseVariant::Traditional)); 40 | assert_eq!('猪', Zodiac::Pig.to_char(ChineseVariant::Simple)); 41 | } 42 | 43 | #[test] 44 | fn from_earthly_branch() { 45 | assert_eq!(Zodiac::Rat, Zodiac::from_earthly_branch(EarthlyBranch::First)); 46 | assert_eq!(Zodiac::Ox, Zodiac::from_earthly_branch(EarthlyBranch::Second),); 47 | assert_eq!(Zodiac::Dragon, Zodiac::from_earthly_branch(EarthlyBranch::Fifth)); 48 | assert_eq!(Zodiac::Pig, Zodiac::from_earthly_branch(EarthlyBranch::Twelfth)); 49 | } 50 | 51 | #[test] 52 | fn to_earthly_branch() { 53 | assert_eq!(EarthlyBranch::First, Zodiac::Rat.to_earthly_branch()); 54 | assert_eq!(EarthlyBranch::Second, Zodiac::Ox.to_earthly_branch()); 55 | assert_eq!(EarthlyBranch::Fifth, Zodiac::Dragon.to_earthly_branch()); 56 | assert_eq!(EarthlyBranch::Twelfth, Zodiac::Pig.to_earthly_branch()); 57 | } 58 | -------------------------------------------------------------------------------- /tests/earthly_branch.rs: -------------------------------------------------------------------------------- 1 | use chinese_lunisolar_calendar::{chrono::prelude::*, EarthlyBranch, Zodiac}; 2 | 3 | #[test] 4 | fn parse_str() { 5 | assert_eq!(EarthlyBranch::First, EarthlyBranch::parse_str("子").unwrap()); 6 | assert_eq!(EarthlyBranch::Second, EarthlyBranch::parse_str("丑").unwrap()); 7 | assert_eq!(EarthlyBranch::Fifth, EarthlyBranch::parse_str("辰").unwrap()); 8 | assert_eq!(EarthlyBranch::Twelfth, EarthlyBranch::parse_str("亥").unwrap()); 9 | } 10 | 11 | #[test] 12 | fn from_time() { 13 | assert_eq!( 14 | EarthlyBranch::First, 15 | EarthlyBranch::from_time(NaiveTime::from_hms_opt(23, 30, 0).unwrap()) 16 | ); 17 | assert_eq!( 18 | EarthlyBranch::Second, 19 | EarthlyBranch::from_time(NaiveTime::from_hms_opt(1, 30, 0).unwrap()) 20 | ); 21 | assert_eq!( 22 | EarthlyBranch::Fifth, 23 | EarthlyBranch::from_time(NaiveTime::from_hms_opt(7, 0, 1).unwrap()) 24 | ); 25 | assert_eq!( 26 | EarthlyBranch::Twelfth, 27 | EarthlyBranch::from_time(NaiveTime::from_hms_opt(22, 0, 0).unwrap()) 28 | ); 29 | } 30 | 31 | #[test] 32 | fn to_str() { 33 | assert_eq!("子", EarthlyBranch::First.to_str()); 34 | assert_eq!("丑", EarthlyBranch::Second.to_str()); 35 | assert_eq!("辰", EarthlyBranch::Fifth.to_str()); 36 | assert_eq!("亥", EarthlyBranch::Twelfth.to_str()); 37 | } 38 | 39 | #[test] 40 | fn from_char() { 41 | assert_eq!(EarthlyBranch::First, EarthlyBranch::from_char('子').unwrap()); 42 | assert_eq!(EarthlyBranch::Second, EarthlyBranch::from_char('丑').unwrap()); 43 | assert_eq!(EarthlyBranch::Fifth, EarthlyBranch::from_char('辰').unwrap()); 44 | assert_eq!(EarthlyBranch::Twelfth, EarthlyBranch::from_char('亥').unwrap()); 45 | } 46 | 47 | #[test] 48 | fn to_char() { 49 | assert_eq!('子', EarthlyBranch::First.to_char()); 50 | assert_eq!('丑', EarthlyBranch::Second.to_char()); 51 | assert_eq!('辰', EarthlyBranch::Fifth.to_char()); 52 | assert_eq!('亥', EarthlyBranch::Twelfth.to_char()); 53 | } 54 | 55 | #[test] 56 | fn from_zodiac() { 57 | assert_eq!(EarthlyBranch::First, EarthlyBranch::from_zodiac(Zodiac::Rat),); 58 | assert_eq!(EarthlyBranch::Second, EarthlyBranch::from_zodiac(Zodiac::Ox)); 59 | assert_eq!(EarthlyBranch::Fifth, EarthlyBranch::from_zodiac(Zodiac::Dragon)); 60 | assert_eq!(EarthlyBranch::Twelfth, EarthlyBranch::from_zodiac(Zodiac::Pig)); 61 | } 62 | 63 | #[test] 64 | fn to_zodiac() { 65 | assert_eq!(Zodiac::Rat, EarthlyBranch::First.to_zodiac()); 66 | assert_eq!(Zodiac::Ox, EarthlyBranch::Second.to_zodiac()); 67 | assert_eq!(Zodiac::Dragon, EarthlyBranch::Fifth.to_zodiac()); 68 | assert_eq!(Zodiac::Pig, EarthlyBranch::Twelfth.to_zodiac()); 69 | } 70 | -------------------------------------------------------------------------------- /src/solar/errors.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::{self, Display, Formatter}; 2 | #[cfg(feature = "std")] 3 | use std::error::Error; 4 | 5 | /// 超出西曆支援的日期範圍。 6 | #[derive(Debug, Eq, PartialEq)] 7 | pub struct SolarOutOfRangeError; 8 | 9 | impl Display for SolarOutOfRangeError { 10 | #[inline] 11 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 12 | f.write_str("the solar date is out of range") 13 | } 14 | } 15 | 16 | #[cfg(feature = "std")] 17 | impl Error for SolarOutOfRangeError {} 18 | 19 | /// 錯誤的西曆年。 20 | #[derive(Debug, Eq, PartialEq)] 21 | pub struct SolarYearError; 22 | 23 | impl Display for SolarYearError { 24 | #[inline] 25 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 26 | f.write_str("the solar year is incorrect") 27 | } 28 | } 29 | 30 | #[cfg(feature = "std")] 31 | impl Error for SolarYearError {} 32 | 33 | /// 錯誤的西曆月。 34 | #[derive(Debug, Eq, PartialEq)] 35 | pub struct SolarMonthError; 36 | 37 | impl Display for SolarMonthError { 38 | #[inline] 39 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 40 | f.write_str("the solar date is incorrect") 41 | } 42 | } 43 | 44 | #[cfg(feature = "std")] 45 | impl Error for SolarMonthError {} 46 | 47 | /// 錯誤的西曆日。 48 | #[derive(Debug, Eq, PartialEq)] 49 | pub struct SolarDayError; 50 | 51 | impl Display for SolarDayError { 52 | #[inline] 53 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 54 | f.write_str("the solar day is incorrect") 55 | } 56 | } 57 | 58 | #[cfg(feature = "std")] 59 | impl Error for SolarDayError {} 60 | 61 | /// 錯誤的西曆年月日。 62 | #[derive(Debug, Eq, PartialEq)] 63 | pub enum SolarDateError { 64 | OutOfRange, 65 | YearIncorrect, 66 | MonthIncorrect, 67 | DayIncorrect, 68 | } 69 | 70 | impl Display for SolarDateError { 71 | #[inline] 72 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 73 | match self { 74 | Self::OutOfRange => Display::fmt(&SolarOutOfRangeError, f), 75 | Self::YearIncorrect => Display::fmt(&SolarYearError, f), 76 | Self::MonthIncorrect => Display::fmt(&SolarMonthError, f), 77 | Self::DayIncorrect => Display::fmt(&SolarDayError, f), 78 | } 79 | } 80 | } 81 | 82 | impl From for SolarDateError { 83 | #[inline] 84 | fn from(_: SolarOutOfRangeError) -> Self { 85 | Self::OutOfRange 86 | } 87 | } 88 | 89 | impl From for SolarDateError { 90 | #[inline] 91 | fn from(_: SolarYearError) -> Self { 92 | Self::YearIncorrect 93 | } 94 | } 95 | 96 | impl From for SolarDateError { 97 | #[inline] 98 | fn from(_: SolarMonthError) -> Self { 99 | Self::MonthIncorrect 100 | } 101 | } 102 | 103 | impl From for SolarDateError { 104 | #[inline] 105 | fn from(_: SolarDayError) -> Self { 106 | Self::DayIncorrect 107 | } 108 | } 109 | 110 | #[cfg(feature = "std")] 111 | impl Error for SolarDateError {} 112 | -------------------------------------------------------------------------------- /tests/lunisolar_year.rs: -------------------------------------------------------------------------------- 1 | use chinese_lunisolar_calendar::{ 2 | EarthlyBranch, HeavenlyStems, LunarMonth, LunarYear, LunisolarYear, Zodiac, 3 | }; 4 | 5 | #[test] 6 | fn to_heavenly_stems() { 7 | assert_eq!( 8 | HeavenlyStems::Tenth, 9 | LunisolarYear::from_solar_year(1993.into()).unwrap().to_heavenly_stems() 10 | ); 11 | assert_eq!( 12 | HeavenlyStems::Fifth, 13 | LunisolarYear::from_solar_year(2018.into()).unwrap().to_heavenly_stems() 14 | ); 15 | } 16 | 17 | #[test] 18 | fn to_earthly_branch() { 19 | assert_eq!( 20 | EarthlyBranch::Tenth, 21 | LunisolarYear::from_solar_year(1993.into()).unwrap().to_earthly_branch() 22 | ); 23 | assert_eq!( 24 | EarthlyBranch::Eleventh, 25 | LunisolarYear::from_solar_year(2018.into()).unwrap().to_earthly_branch() 26 | ); 27 | } 28 | 29 | #[test] 30 | fn to_zodiac() { 31 | assert_eq!(Zodiac::Rooster, LunisolarYear::from_solar_year(1993.into()).unwrap().to_zodiac()); 32 | assert_eq!(Zodiac::Dog, LunisolarYear::from_solar_year(2018.into()).unwrap().to_zodiac()); 33 | } 34 | 35 | #[test] 36 | fn get_leap_lunar_month() { 37 | assert_eq!( 38 | Some(LunarMonth::LeapThird), 39 | LunisolarYear::from_solar_year(1993.into()).unwrap().get_leap_lunar_month() 40 | ); 41 | assert_eq!(None, LunisolarYear::from_solar_year(2018.into()).unwrap().get_leap_lunar_month()); 42 | assert_eq!(None, LunisolarYear::from_solar_year(2019.into()).unwrap().get_leap_lunar_month()); 43 | assert_eq!( 44 | Some(LunarMonth::LeapFourth), 45 | LunisolarYear::from_solar_year(2020.into()).unwrap().get_leap_lunar_month() 46 | ); 47 | } 48 | 49 | #[test] 50 | fn get_total_days_in_leap_month() { 51 | assert_eq!( 52 | 29, 53 | LunisolarYear::from_solar_year(1993.into()).unwrap().get_total_days_in_leap_month() 54 | ); 55 | assert_eq!( 56 | 0, 57 | LunisolarYear::from_solar_year(2018.into()).unwrap().get_total_days_in_leap_month() 58 | ); 59 | assert_eq!( 60 | 0, 61 | LunisolarYear::from_solar_year(2019.into()).unwrap().get_total_days_in_leap_month() 62 | ); 63 | assert_eq!( 64 | 29, 65 | LunisolarYear::from_solar_year(2020.into()).unwrap().get_total_days_in_leap_month() 66 | ); 67 | } 68 | 69 | #[test] 70 | fn get_total_days() { 71 | assert_eq!(383, LunisolarYear::from_solar_year(1993.into()).unwrap().get_total_days()); 72 | assert_eq!(354, LunisolarYear::from_solar_year(2018.into()).unwrap().get_total_days()); 73 | assert_eq!(354, LunisolarYear::from_solar_year(2019.into()).unwrap().get_total_days()); 74 | assert_eq!(384, LunisolarYear::from_solar_year(2020.into()).unwrap().get_total_days()); 75 | assert_eq!(355, LunisolarYear::from_solar_year(2022.into()).unwrap().get_total_days()); 76 | } 77 | 78 | #[test] 79 | fn to_lunar_year() { 80 | assert_eq!( 81 | LunarYear::from_era(HeavenlyStems::Tenth, EarthlyBranch::Tenth).unwrap(), 82 | LunisolarYear::from_solar_year(1993.into()).unwrap().to_lunar_year() 83 | ); 84 | assert_eq!( 85 | LunarYear::from_era(HeavenlyStems::Fifth, EarthlyBranch::Eleventh).unwrap(), 86 | LunisolarYear::from_solar_year(2018.into()).unwrap().to_lunar_year() 87 | ); 88 | } 89 | -------------------------------------------------------------------------------- /src/heavenly_stems/mod.rs: -------------------------------------------------------------------------------- 1 | mod built_in_traits; 2 | mod chinese; 3 | mod parse; 4 | 5 | use core::fmt::{self, Display, Formatter}; 6 | 7 | use chinese::{THE_HEAVENLY_STEMS, THE_HEAVENLY_STEMS_CHAR}; 8 | use enum_ordinalize::Ordinalize; 9 | 10 | /// 列舉中國十天干:甲、乙、丙、丁、戊、己、更、辛、壬、葵。 11 | #[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, Ordinalize)] 12 | #[ordinalize(impl_trait = false)] 13 | #[ordinalize(from_ordinal_unsafe( 14 | pub fn from_ordinal_unsafe, 15 | doc = "透過整數來取得 `HeavenlyStems` 列舉實體。", 16 | doc = "# Safety", 17 | doc = "必須先確認傳入的整數是合法的。", 18 | ))] 19 | #[ordinalize(ordinal(pub fn ordinal, doc = "取得 `HeavenlyStems` 列舉實體所代表的整數。"))] 20 | #[repr(u8)] 21 | pub enum HeavenlyStems { 22 | /// 甲 23 | First = 1, 24 | /// 乙 25 | Second, 26 | /// 丙 27 | Third, 28 | /// 丁 29 | Fourth, 30 | /// 戊 31 | Fifth, 32 | /// 己 33 | Sixth, 34 | /// 更 35 | Seventh, 36 | /// 辛 37 | Eighth, 38 | /// 壬 39 | Ninth, 40 | /// 葵 41 | Tenth, 42 | } 43 | 44 | impl Display for HeavenlyStems { 45 | /// Formats the value using the given formatter. 46 | /// 47 | /// # Examples 48 | /// 49 | /// ``` 50 | /// # use chinese_lunisolar_calendar::HeavenlyStems; 51 | /// assert_eq!("戊", format!("{}", HeavenlyStems::Fifth)); 52 | /// ``` 53 | #[inline] 54 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 55 | f.write_str(self.to_str()) 56 | } 57 | } 58 | 59 | /// 用以建立 `HeavenlyStems` 列舉實體的關聯函數。 60 | impl HeavenlyStems { 61 | /// 透過甲、乙、丙、丁、戊、己、更、辛、壬、葵等字元來取得 `HeavenlyStems` 列舉實體。 62 | /// 63 | /// # Examples 64 | /// 65 | /// ``` 66 | /// # use chinese_lunisolar_calendar::HeavenlyStems; 67 | /// assert_eq!(HeavenlyStems::Fifth, HeavenlyStems::from_char('戊').unwrap()); 68 | /// ``` 69 | #[inline] 70 | pub const fn from_char(c: char) -> Option { 71 | let len = THE_HEAVENLY_STEMS_CHAR.len(); 72 | 73 | let mut i = 0; 74 | 75 | loop { 76 | if c == THE_HEAVENLY_STEMS_CHAR[i] { 77 | return Some(unsafe { Self::from_ordinal_unsafe(i as u8 + 1) }); 78 | } 79 | 80 | if i == len { 81 | break; 82 | } 83 | 84 | i += 1; 85 | } 86 | 87 | None 88 | } 89 | } 90 | 91 | /// 將 `HeavenlyStems` 列舉實體轉成其它型別的方法。 92 | impl HeavenlyStems { 93 | /// 取得 `HeavenlyStems` 列舉實體所代表的地支字串。 94 | /// 95 | /// # Examples 96 | /// 97 | /// ``` 98 | /// # use chinese_lunisolar_calendar::HeavenlyStems; 99 | /// assert_eq!("戊", HeavenlyStems::Fifth.to_str()); 100 | /// ``` 101 | #[inline] 102 | pub const fn to_str(self) -> &'static str { 103 | let i = (self.ordinal() - 1) as usize; 104 | 105 | THE_HEAVENLY_STEMS[i] 106 | } 107 | 108 | /// 取得 `HeavenlyStems` 列舉實體所代表的地支字元。 109 | /// 110 | /// # Examples 111 | /// 112 | /// ``` 113 | /// # use chinese_lunisolar_calendar::HeavenlyStems; 114 | /// assert_eq!('戊', HeavenlyStems::Fifth.to_char()); 115 | /// ``` 116 | #[inline] 117 | pub const fn to_char(self) -> char { 118 | let i = (self.ordinal() - 1) as usize; 119 | 120 | THE_HEAVENLY_STEMS_CHAR[i] 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/lunar/day/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "ba-zi-weight")] 2 | mod ba_zi_weight; 3 | mod built_in_traits; 4 | mod chinese; 5 | mod parse; 6 | 7 | use core::fmt::{self, Display, Formatter}; 8 | 9 | use chinese::THE_LUNAR_DAYS; 10 | use enum_ordinalize::Ordinalize; 11 | 12 | use super::LunarDayError; 13 | 14 | /// 列舉農曆三十個天數名稱:初一、初二、...、十一、十二、...、廿一、廿二、...、三十。 15 | #[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, Ordinalize)] 16 | #[ordinalize(impl_trait = false)] 17 | #[ordinalize(from_ordinal_unsafe( 18 | pub fn from_u8_unsafe, 19 | doc = "透過農曆日期數值來取得 `LunarDay` 列舉實體。", 20 | doc = "# Safety", 21 | doc = "必須先確認傳入的整數是合法的。", 22 | ))] 23 | #[ordinalize(ordinal(pub fn to_u8, doc = "取得 `LunarDay` 列舉實體所代表的農曆日期字串。"))] 24 | #[repr(u8)] 25 | pub enum LunarDay { 26 | /// 初一 27 | First = 1, 28 | /// 初二 29 | Second, 30 | /// 初三 31 | Third, 32 | /// 初四 33 | Fourth, 34 | /// 初五 35 | Fifth, 36 | /// 初六 37 | Sixth, 38 | /// 初七 39 | Seventh, 40 | /// 初八 41 | Eighth, 42 | /// 初九 43 | Ninth, 44 | /// 初十 45 | Tenth, 46 | /// 十一 47 | Eleventh, 48 | /// 十二 49 | Twelfth, 50 | /// 十三 51 | Thirteen, 52 | /// 十四 53 | Fourteen, 54 | /// 十五 55 | Fifteen, 56 | /// 十六 57 | Sixteen, 58 | /// 十七 59 | Seventeen, 60 | /// 十八 61 | Eighteen, 62 | /// 十九 63 | Nineteen, 64 | /// 二十 65 | Twenty, 66 | /// 廿一 67 | TwentyFirst, 68 | /// 廿二 69 | TwentySecond, 70 | /// 廿三 71 | TwentyThird, 72 | /// 廿四 73 | TwentyFourth, 74 | /// 廿五 75 | TwentyFifth, 76 | /// 廿六 77 | TwentySixth, 78 | /// 廿七 79 | TwentySeventh, 80 | /// 廿八 81 | TwentyEighth, 82 | /// 廿九 83 | TwentyNinth, 84 | /// 三十 85 | Thirty, 86 | } 87 | 88 | impl Display for LunarDay { 89 | /// Formats the value using the given formatter. 90 | /// 91 | /// # Examples 92 | /// 93 | /// ``` 94 | /// # use chinese_lunisolar_calendar::LunarDay; 95 | /// assert_eq!("初七", format!("{}", LunarDay::Seventh)); 96 | /// ``` 97 | #[inline] 98 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 99 | f.write_str(self.to_str()) 100 | } 101 | } 102 | 103 | /// 用以建立 `LunarDay` 列舉實體的關聯函數。 104 | impl LunarDay { 105 | /// 透過農曆日期數值來取得 `LunarDay` 列舉實體。 106 | /// 107 | /// # Examples 108 | /// 109 | /// ``` 110 | /// # use chinese_lunisolar_calendar::LunarDay; 111 | /// assert_eq!(LunarDay::Seventh, LunarDay::from_u8(7).unwrap()); 112 | /// ``` 113 | #[inline] 114 | pub const fn from_u8(day: u8) -> Result { 115 | if day >= 1 && day <= 30 { 116 | Ok(unsafe { Self::from_u8_unsafe(day) }) 117 | } else { 118 | Err(LunarDayError) 119 | } 120 | } 121 | } 122 | 123 | /// 將 `LunarDay` 列舉實體轉成其它型別的方法。 124 | impl LunarDay { 125 | /// 取得 `LunarDay` 列舉實體所代表的農曆日期字串。 126 | /// 127 | /// # Examples 128 | /// 129 | /// ``` 130 | /// use chinese_lunisolar_calendar::LunarDay; 131 | /// 132 | /// assert_eq!("初七", LunarDay::Seventh.to_str()); 133 | /// ``` 134 | #[inline] 135 | pub const fn to_str(self) -> &'static str { 136 | let i = (self.to_u8() - 1) as usize; 137 | 138 | THE_LUNAR_DAYS[i] 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Intellij+all ### 2 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 3 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 4 | 5 | # User-specific stuff 6 | .idea/**/workspace.xml 7 | .idea/**/tasks.xml 8 | .idea/**/usage.statistics.xml 9 | .idea/**/dictionaries 10 | .idea/**/shelf 11 | 12 | # AWS User-specific 13 | .idea/**/aws.xml 14 | 15 | # Generated files 16 | .idea/**/contentModel.xml 17 | 18 | # Sensitive or high-churn files 19 | .idea/**/dataSources/ 20 | .idea/**/dataSources.ids 21 | .idea/**/dataSources.local.xml 22 | .idea/**/sqlDataSources.xml 23 | .idea/**/dynamic.xml 24 | .idea/**/uiDesigner.xml 25 | .idea/**/dbnavigator.xml 26 | 27 | # Gradle 28 | .idea/**/gradle.xml 29 | .idea/**/libraries 30 | 31 | # Gradle and Maven with auto-import 32 | # When using Gradle or Maven with auto-import, you should exclude module files, 33 | # since they will be recreated, and may cause churn. Uncomment if using 34 | # auto-import. 35 | # .idea/artifacts 36 | # .idea/compiler.xml 37 | # .idea/jarRepositories.xml 38 | # .idea/modules.xml 39 | # .idea/*.iml 40 | # .idea/modules 41 | # *.iml 42 | # *.ipr 43 | 44 | # CMake 45 | cmake-build-*/ 46 | 47 | # Mongo Explorer plugin 48 | .idea/**/mongoSettings.xml 49 | 50 | # File-based project format 51 | *.iws 52 | 53 | # IntelliJ 54 | out/ 55 | 56 | # mpeltonen/sbt-idea plugin 57 | .idea_modules/ 58 | 59 | # JIRA plugin 60 | atlassian-ide-plugin.xml 61 | 62 | # Cursive Clojure plugin 63 | .idea/replstate.xml 64 | 65 | # SonarLint plugin 66 | .idea/sonarlint/ 67 | 68 | # Crashlytics plugin (for Android Studio and IntelliJ) 69 | com_crashlytics_export_strings.xml 70 | crashlytics.properties 71 | crashlytics-build.properties 72 | fabric.properties 73 | 74 | # Editor-based Rest Client 75 | .idea/httpRequests 76 | 77 | # Android studio 3.1+ serialized cache file 78 | .idea/caches/build_file_checksums.ser 79 | 80 | ### Intellij+all Patch ### 81 | # Ignore everything but code style settings and run configurations 82 | # that are supposed to be shared within teams. 83 | 84 | .idea/* 85 | 86 | !.idea/codeStyles 87 | !.idea/runConfigurations 88 | 89 | ### Rust ### 90 | # Generated by Cargo 91 | # will have compiled files and executables 92 | debug/ 93 | target/ 94 | 95 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 96 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 97 | Cargo.lock 98 | 99 | # These are backup files generated by rustfmt 100 | **/*.rs.bk 101 | 102 | # MSVC Windows builds of rustc generate these, which store debugging information 103 | *.pdb 104 | 105 | ### Vim ### 106 | # Swap 107 | [._]*.s[a-v][a-z] 108 | !*.svg # comment out if you don't need vector files 109 | [._]*.sw[a-p] 110 | [._]s[a-rt-v][a-z] 111 | [._]ss[a-gi-z] 112 | [._]sw[a-p] 113 | 114 | # Session 115 | Session.vim 116 | Sessionx.vim 117 | 118 | # Temporary 119 | .netrwhist 120 | *~ 121 | # Auto-generated tag files 122 | tags 123 | # Persistent undo 124 | [._]*.un~ 125 | 126 | ### VisualStudioCode ### 127 | .vscode/* 128 | !.vscode/settings.json 129 | !.vscode/tasks.json 130 | !.vscode/launch.json 131 | !.vscode/extensions.json 132 | !.vscode/*.code-snippets 133 | 134 | # Local History for Visual Studio Code 135 | .history/ 136 | 137 | # Built Visual Studio Code Extensions 138 | *.vsix 139 | 140 | ### VisualStudioCode Patch ### 141 | # Ignore all local history of files 142 | .history 143 | .ionide -------------------------------------------------------------------------------- /tests/solar_date.rs: -------------------------------------------------------------------------------- 1 | use chinese_lunisolar_calendar::{ 2 | chrono::prelude::*, LunisolarDate, SolarDate, SolarDay, SolarMonth, SolarYear, 3 | }; 4 | 5 | #[test] 6 | fn from_date() { 7 | let solar_year = SolarYear::from_u16(2018); 8 | let solar_month = SolarMonth::from_u8(6).unwrap(); 9 | let solar_day = SolarDay::from_u8(19).unwrap(); 10 | 11 | let naive_date = NaiveDate::from_ymd_opt(2018, 6, 19).unwrap(); 12 | 13 | let solar_date = SolarDate::from_date(naive_date).unwrap(); 14 | 15 | assert_eq!(solar_year, solar_date.to_solar_year()); 16 | assert_eq!(solar_month, solar_date.to_solar_month()); 17 | assert_eq!(solar_day, solar_date.to_solar_day()); 18 | } 19 | 20 | #[test] 21 | fn from_solar_year_month_day() { 22 | let solar_year = SolarYear::from_u16(2018); 23 | let solar_month = SolarMonth::from_u8(6).unwrap(); 24 | let solar_day = SolarDay::from_u8(19).unwrap(); 25 | 26 | let solar_date = 27 | SolarDate::from_solar_year_month_day(solar_year, solar_month, solar_day).unwrap(); 28 | 29 | assert_eq!(solar_year, solar_date.to_solar_year()); 30 | assert_eq!(solar_month, solar_date.to_solar_month()); 31 | assert_eq!(solar_day, solar_date.to_solar_day()); 32 | } 33 | 34 | #[test] 35 | fn from_ymd() { 36 | let solar_year = SolarYear::from_u16(2018); 37 | let solar_month = SolarMonth::from_u8(6).unwrap(); 38 | let solar_day = SolarDay::from_u8(19).unwrap(); 39 | 40 | let solar_date = SolarDate::from_ymd(2018, 6, 19).unwrap(); 41 | 42 | assert_eq!(solar_year, solar_date.to_solar_year()); 43 | assert_eq!(solar_month, solar_date.to_solar_month()); 44 | assert_eq!(solar_day, solar_date.to_solar_day()); 45 | } 46 | 47 | #[test] 48 | fn from_lunisolar_date() { 49 | let solar_year = SolarYear::from_u16(2018); 50 | let solar_month = SolarMonth::from_u8(6).unwrap(); 51 | let solar_day = SolarDay::from_u8(19).unwrap(); 52 | 53 | let lunisolar_date = LunisolarDate::from_ymd(2018, 5, false, 6).unwrap(); 54 | 55 | let solar_date = SolarDate::from_lunisolar_date(lunisolar_date); 56 | 57 | assert_eq!(solar_year, solar_date.to_solar_year()); 58 | assert_eq!(solar_month, solar_date.to_solar_month()); 59 | assert_eq!(solar_day, solar_date.to_solar_day()); 60 | } 61 | 62 | #[test] 63 | fn parse_str() { 64 | let solar_date = SolarDate::parse_str("二零一八年六月十九日").unwrap(); 65 | 66 | assert_eq!(SolarYear::from_u16(2018), solar_date.to_solar_year()); 67 | assert_eq!(SolarMonth::from_u8(6).unwrap(), solar_date.to_solar_month()); 68 | assert_eq!(SolarDay::from_u8(19).unwrap(), solar_date.to_solar_day()); 69 | 70 | let solar_date = SolarDate::parse_str("二〇一八年六月十九日").unwrap(); 71 | 72 | assert_eq!(SolarYear::from_u16(2018), solar_date.to_solar_year()); 73 | assert_eq!(SolarMonth::from_u8(6).unwrap(), solar_date.to_solar_month()); 74 | assert_eq!(SolarDay::from_u8(19).unwrap(), solar_date.to_solar_day()); 75 | 76 | let solar_date = SolarDate::parse_str("2018 六月 十九").unwrap(); 77 | 78 | assert_eq!(SolarYear::from_u16(2018), solar_date.to_solar_year()); 79 | assert_eq!(SolarMonth::from_u8(6).unwrap(), solar_date.to_solar_month()); 80 | assert_eq!(SolarDay::from_u8(19).unwrap(), solar_date.to_solar_day()); 81 | } 82 | 83 | #[test] 84 | fn the_n_day_in_this_year() { 85 | assert_eq!(4, SolarDate::from_ymd(2013, 1, 4).unwrap().the_n_day_in_this_year()); 86 | assert_eq!(63, SolarDate::from_ymd(2013, 3, 4).unwrap().the_n_day_in_this_year()); 87 | assert_eq!(94, SolarDate::from_ymd(2013, 4, 4).unwrap().the_n_day_in_this_year()); 88 | assert_eq!(124, SolarDate::from_ymd(2013, 5, 4).unwrap().the_n_day_in_this_year()); 89 | assert_eq!(64, SolarDate::from_ymd(2020, 3, 4).unwrap().the_n_day_in_this_year()); 90 | assert_eq!(95, SolarDate::from_ymd(2020, 4, 4).unwrap().the_n_day_in_this_year()); 91 | assert_eq!(125, SolarDate::from_ymd(2020, 5, 4).unwrap().the_n_day_in_this_year()); 92 | } 93 | -------------------------------------------------------------------------------- /src/solar/day/mod.rs: -------------------------------------------------------------------------------- 1 | mod built_in_traits; 2 | mod chinese; 3 | mod parse; 4 | 5 | use core::fmt::{self, Display, Formatter}; 6 | 7 | use chinese::THE_SOLAR_DAYS; 8 | use enum_ordinalize::Ordinalize; 9 | 10 | use super::SolarDayError; 11 | 12 | /// 列舉西曆三十一個天數名稱:一、二、...、十一、十二、...、二十一、二十二、...、三十、三十一。 13 | #[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, Ordinalize)] 14 | #[ordinalize(impl_trait = false)] 15 | #[ordinalize(from_ordinal_unsafe( 16 | pub fn from_u8_unsafe, 17 | doc = "透過西曆日期數值來取得 `SolarDay` 列舉實體。", 18 | doc = "# Safety", 19 | doc = "必須先確認傳入的整數是合法的。", 20 | ))] 21 | #[ordinalize(ordinal(pub fn to_u8, doc = "取得 `SolarDay` 列舉實體所代表的西曆日期字串。"))] 22 | #[repr(u8)] 23 | pub enum SolarDay { 24 | /// 一 25 | First = 1, 26 | /// 二 27 | Second, 28 | /// 三 29 | Third, 30 | /// 四 31 | Fourth, 32 | /// 五 33 | Fifth, 34 | /// 六 35 | Sixth, 36 | /// 七 37 | Seventh, 38 | /// 八 39 | Eighth, 40 | /// 九 41 | Ninth, 42 | /// 十 43 | Tenth, 44 | /// 十一 45 | Eleventh, 46 | /// 十二 47 | Twelfth, 48 | /// 十三 49 | Thirteen, 50 | /// 十四 51 | Fourteen, 52 | /// 十五 53 | Fifteen, 54 | /// 十六 55 | Sixteen, 56 | /// 十七 57 | Seventeen, 58 | /// 十八 59 | Eighteen, 60 | /// 十九 61 | Nineteen, 62 | /// 二十 63 | Twenty, 64 | /// 二十一 65 | TwentyFirst, 66 | /// 二十二 67 | TwentySecond, 68 | /// 二十三 69 | TwentyThird, 70 | /// 二十四 71 | TwentyFourth, 72 | /// 二十五 73 | TwentyFifth, 74 | /// 二十六 75 | TwentySixth, 76 | /// 二十七 77 | TwentySeventh, 78 | /// 二十八 79 | TwentyEighth, 80 | /// 二十九 81 | TwentyNinth, 82 | /// 三十 83 | Thirty, 84 | /// 三十一 85 | ThirtyFirst, 86 | } 87 | 88 | impl Display for SolarDay { 89 | /// Formats the value using the given formatter. 90 | /// 91 | /// # Examples 92 | /// 93 | /// ``` 94 | /// # use chinese_lunisolar_calendar::SolarDay; 95 | /// assert_eq!("十五", format!("{}", SolarDay::Fifteen)); 96 | /// ``` 97 | #[inline] 98 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 99 | f.write_str(self.to_str()) 100 | } 101 | } 102 | 103 | /// 用以建立 `SolarDay` 列舉實體的關聯函數。 104 | impl SolarDay { 105 | /// 透過西曆日期數值來取得 `SolarDay` 列舉實體。 106 | /// 107 | /// # Examples 108 | /// 109 | /// ``` 110 | /// # use chinese_lunisolar_calendar::SolarDay; 111 | /// let solar_day = SolarDay::from_u8(5).unwrap(); 112 | /// ``` 113 | #[inline] 114 | pub const fn from_u8(day: u8) -> Result { 115 | if day >= 1 && day <= 31 { 116 | Ok(unsafe { Self::from_u8_unsafe(day) }) 117 | } else { 118 | Err(SolarDayError) 119 | } 120 | } 121 | 122 | /// 透過西曆日期數值來取得 `SolarDay` 列舉實體。 123 | /// 124 | /// # Examples 125 | /// 126 | /// ``` 127 | /// # use chinese_lunisolar_calendar::SolarDay; 128 | /// let solar_day = SolarDay::from_u32(5).unwrap(); 129 | /// ``` 130 | #[inline] 131 | pub const fn from_u32(day: u32) -> Result { 132 | if day >= 1 && day <= 31 { 133 | Ok(unsafe { Self::from_u8_unsafe(day as u8) }) 134 | } else { 135 | Err(SolarDayError) 136 | } 137 | } 138 | } 139 | 140 | /// 將 `SolarDay` 列舉實體轉成其它型別的方法。 141 | impl SolarDay { 142 | /// 取得 `SolarDay` 列舉實體所代表的西曆日期字串。 143 | /// 144 | /// # Examples 145 | /// 146 | /// ``` 147 | /// # use chinese_lunisolar_calendar::SolarDay; 148 | /// assert_eq!("十五", SolarDay::Fifteen.to_str()); 149 | /// ``` 150 | #[inline] 151 | pub const fn to_str(self) -> &'static str { 152 | let i = (self.to_u8() - 1) as usize; 153 | 154 | THE_SOLAR_DAYS[i] 155 | } 156 | 157 | /// 取得 `SolarDay` 列舉實體所代表的西曆日期字串。 158 | #[inline] 159 | pub const fn to_u32(self) -> u32 { 160 | self.to_u8() as u32 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /tests/lunar_month.rs: -------------------------------------------------------------------------------- 1 | use chinese_lunisolar_calendar::{ChineseVariant, LunarMonth, LunisolarYear}; 2 | 3 | #[test] 4 | fn parse_str() { 5 | assert_eq!(LunarMonth::First, LunarMonth::parse_str("正月").unwrap()); 6 | assert_eq!(LunarMonth::Second, LunarMonth::parse_str("二月").unwrap()); 7 | assert_eq!(LunarMonth::Fifth, LunarMonth::parse_str("五月").unwrap()); 8 | assert_eq!(LunarMonth::Sixth, LunarMonth::parse_str("六月").unwrap()); 9 | assert_eq!(LunarMonth::Twelfth, LunarMonth::parse_str("臘月").unwrap()); 10 | assert_eq!(LunarMonth::Twelfth, LunarMonth::parse_str("腊月").unwrap()); 11 | assert_eq!(LunarMonth::LeapFirst, LunarMonth::parse_str("閏一月").unwrap()); 12 | assert_eq!(LunarMonth::LeapSecond, LunarMonth::parse_str("閏二月").unwrap()); 13 | assert_eq!(LunarMonth::LeapFifth, LunarMonth::parse_str("閏五月").unwrap()); 14 | assert_eq!(LunarMonth::LeapSixth, LunarMonth::parse_str("閏六月").unwrap()); 15 | assert_eq!(LunarMonth::LeapTwelfth, LunarMonth::parse_str("閏臘月").unwrap()); 16 | assert_eq!(LunarMonth::LeapTwelfth, LunarMonth::parse_str("闰腊月").unwrap()); 17 | } 18 | 19 | #[test] 20 | fn to_str() { 21 | assert_eq!("正月", LunarMonth::First.to_str(ChineseVariant::Traditional)); 22 | assert_eq!("二月", LunarMonth::Second.to_str(ChineseVariant::Traditional)); 23 | assert_eq!("五月", LunarMonth::Fifth.to_str(ChineseVariant::Traditional)); 24 | assert_eq!("六月", LunarMonth::Sixth.to_str(ChineseVariant::Traditional)); 25 | assert_eq!("臘月", LunarMonth::Twelfth.to_str(ChineseVariant::Traditional)); 26 | assert_eq!("腊月", LunarMonth::Twelfth.to_str(ChineseVariant::Simple)); 27 | assert_eq!("閏正月", LunarMonth::LeapFirst.to_str(ChineseVariant::Traditional)); 28 | assert_eq!("閏二月", LunarMonth::LeapSecond.to_str(ChineseVariant::Traditional)); 29 | assert_eq!("閏五月", LunarMonth::LeapFifth.to_str(ChineseVariant::Traditional)); 30 | assert_eq!("閏六月", LunarMonth::LeapSixth.to_str(ChineseVariant::Traditional)); 31 | assert_eq!("閏臘月", LunarMonth::LeapTwelfth.to_str(ChineseVariant::Traditional)); 32 | assert_eq!("闰腊月", LunarMonth::LeapTwelfth.to_str(ChineseVariant::Simple)); 33 | } 34 | 35 | #[test] 36 | fn from_u8_with_leap() { 37 | assert_eq!(LunarMonth::First, LunarMonth::from_u8_with_leap(1, false).unwrap()); 38 | assert_eq!(LunarMonth::Second, LunarMonth::from_u8_with_leap(2, false).unwrap()); 39 | assert_eq!(LunarMonth::Fifth, LunarMonth::from_u8_with_leap(5, false).unwrap()); 40 | assert_eq!(LunarMonth::Sixth, LunarMonth::from_u8_with_leap(6, false).unwrap()); 41 | assert_eq!(LunarMonth::Twelfth, LunarMonth::from_u8_with_leap(12, false).unwrap()); 42 | assert_eq!(LunarMonth::LeapFirst, LunarMonth::from_u8_with_leap(1, true).unwrap()); 43 | assert_eq!(LunarMonth::LeapSecond, LunarMonth::from_u8_with_leap(2, true).unwrap()); 44 | assert_eq!(LunarMonth::LeapFifth, LunarMonth::from_u8_with_leap(5, true).unwrap()); 45 | assert_eq!(LunarMonth::LeapSixth, LunarMonth::from_u8_with_leap(6, true).unwrap()); 46 | assert_eq!(LunarMonth::LeapTwelfth, LunarMonth::from_u8_with_leap(12, true).unwrap()); 47 | } 48 | 49 | #[test] 50 | fn to_u8() { 51 | assert_eq!(1, LunarMonth::First.to_u8()); 52 | assert_eq!(2, LunarMonth::Second.to_u8()); 53 | assert_eq!(5, LunarMonth::Fifth.to_u8()); 54 | assert_eq!(6, LunarMonth::Sixth.to_u8()); 55 | assert_eq!(12, LunarMonth::Twelfth.to_u8()); 56 | assert_eq!(1, LunarMonth::LeapFirst.to_u8()); 57 | assert_eq!(2, LunarMonth::LeapSecond.to_u8()); 58 | assert_eq!(5, LunarMonth::LeapFifth.to_u8()); 59 | assert_eq!(6, LunarMonth::LeapSixth.to_u8()); 60 | assert_eq!(12, LunarMonth::LeapTwelfth.to_u8()); 61 | } 62 | 63 | #[test] 64 | fn get_total_days() { 65 | assert_eq!( 66 | Some(30), 67 | LunarMonth::Fourth.get_total_days(LunisolarYear::from_solar_year(2020.into()).unwrap()) 68 | ); 69 | assert_eq!( 70 | Some(29), 71 | LunarMonth::LeapFourth.get_total_days(LunisolarYear::from_solar_year(2020.into()).unwrap()) 72 | ); 73 | assert_eq!( 74 | Some(30), 75 | LunarMonth::Fifth.get_total_days(LunisolarYear::from_solar_year(2020.into()).unwrap()) 76 | ); 77 | } 78 | -------------------------------------------------------------------------------- /src/lunisolar/constants.rs: -------------------------------------------------------------------------------- 1 | /// u16型別有16位元(15...0),將第15~3個位元用來分別表示1~13月(含閏年)是否為大月(1為大月有30天;0為小月有29天)。此處有西元1901~2100年的資料。 2 | pub(super) const BIG_MONTHS: [u16; 200] = [ 3 | 0x4AE0, 0xA570, 0x5268, 0xD260, 0xD950, 0x6AA8, 0x56A0, 0x9AD0, 0x4AE8, 0x4AE0, // 1910 4 | 0xA4D8, 0xA4D0, 0xD250, 0xD528, 0xB540, 0xD6A0, 0x96D0, 0x95B0, 0x49B8, 0x4970, // 1920 5 | 0xA4B0, 0xB258, 0x6A50, 0x6D40, 0xADA8, 0x2B60, 0x9570, 0x4978, 0x4970, 0x64B0, // 1930 6 | 0xD4A0, 0xEA50, 0x6D48, 0x5AD0, 0x2B60, 0x9370, 0x92E0, 0xC968, 0xC950, 0xD4A0, // 1940 7 | 0xDA50, 0xB550, 0x56A0, 0xAAD8, 0x25D0, 0x92D0, 0xC958, 0xA950, 0xB4A8, 0x6CA0, // 1950 8 | 0xB550, 0x55A8, 0x4DA0, 0xA5B0, 0x52B8, 0x52B0, 0xA950, 0xE950, 0x6AA0, 0xAD50, // 1960 9 | 0xAB50, 0x4B60, 0xA570, 0xA570, 0x5260, 0xE930, 0xD950, 0x5AA8, 0x56A0, 0x96D0, // 1970 10 | 0x4AE8, 0x4AD0, 0xA4D0, 0xD268, 0xD250, 0xD528, 0xB540, 0xB6A0, 0x96D0, 0x95B0, // 1980 11 | 0x49B0, 0xA4B8, 0xA4B0, 0xB258, 0x6A50, 0x6D40, 0xADA0, 0xAB60, 0x9570, 0x4978, // 1990 12 | 0x4970, 0x64B0, 0x6A50, 0xEA50, 0x6B28, 0x5AC0, 0xAB60, 0x9368, 0x92E0, 0xC960, // 2000 13 | 0xD4A8, 0xD4A0, 0xDA50, 0x5AA8, 0x56A0, 0xAAD8, 0x25D0, 0x92D0, 0xC958, 0xA950, // 2010 14 | 0xB4A0, 0xB550, 0xAD50, 0x55A8, 0x4BA0, 0xA5B0, 0x52B8, 0x52B0, 0xA930, 0x74A8, // 2020 15 | 0x6AA0, 0xAD50, 0x4DA8, 0x4B60, 0xA570, 0xA4E0, 0xD260, 0xE930, 0xD530, 0x5AA0, // 2030 16 | 0x6B50, 0x96D0, 0x4AE8, 0x4AD0, 0xA4D0, 0xD258, 0xD250, 0xD520, 0xDAA0, 0xB5A0, // 2040 17 | 0x56D0, 0x4AD8, 0x49B0, 0xA4B8, 0xA4B0, 0xAA50, 0xB528, 0x6D20, 0xADA0, 0x55B0, // 2050 18 | 0x9370, 0x4978, 0x4970, 0x64B0, 0x6A50, 0xEA50, 0x6B20, 0xAB60, 0xAAE0, 0x92E0, // 2060 19 | 0xC970, 0xC960, 0xD4A8, 0xD4A0, 0xDA50, 0x5AA8, 0x56A0, 0xA6D0, 0x52E8, 0x52D0, // 2070 20 | 0xA958, 0xA950, 0xB4A0, 0xB550, 0xAD50, 0x55A0, 0xA5D0, 0xA5B0, 0x52B0, 0xA938, // 2080 21 | 0x6930, 0x7298, 0x6AA0, 0xAD50, 0x4DA8, 0x4B60, 0xA570, 0x5270, 0xD260, 0xE930, // 2090 22 | 0xD520, 0xDAA0, 0x6B50, 0x56D0, 0x4AE0, 0xA4E8, 0xA4D0, 0xD150, 0xD928, 0xD520, // 2100 23 | ]; 24 | 25 | /// u8型別有8位元(7...0),將第7~4個位元用來表示第一年是閏幾月,第3~0個位元用來表示第二年是閏幾月。若該年數值為0,表示該年沒有閏月。此處有西元1901~2100年的資料。 26 | pub(super) const LEAP_MONTHS: [u8; 100] = [ 27 | 0x00, 0x50, 0x04, 0x00, 0x20, // 1910 28 | 0x60, 0x05, 0x00, 0x20, 0x70, // 1920 29 | 0x05, 0x00, 0x40, 0x02, 0x06, // 1930 30 | 0x00, 0x50, 0x03, 0x07, 0x00, // 1940 31 | 0x60, 0x04, 0x00, 0x20, 0x70, // 1950 32 | 0x05, 0x00, 0x30, 0x80, 0x06, // 1960 33 | 0x00, 0x40, 0x03, 0x07, 0x00, // 1970 34 | 0x50, 0x04, 0x08, 0x00, 0x60, // 1980 35 | 0x04, 0x0A, 0x00, 0x60, 0x05, // 1990 36 | 0x00, 0x30, 0x80, 0x05, 0x00, // 2000 37 | 0x40, 0x02, 0x07, 0x00, 0x50, // 2010 38 | 0x04, 0x09, 0x00, 0x60, 0x04, // 2020 39 | 0x00, 0x20, 0x60, 0x05, 0x00, // 2030 40 | 0x30, 0xB0, 0x06, 0x00, 0x50, // 2040 41 | 0x02, 0x07, 0x00, 0x50, 0x03, // 2050 42 | 0x08, 0x00, 0x60, 0x04, 0x00, // 2060 43 | 0x30, 0x70, 0x05, 0x00, 0x40, // 2070 44 | 0x80, 0x06, 0x00, 0x40, 0x03, // 2080 45 | 0x07, 0x00, 0x50, 0x04, 0x08, // 2090 46 | 0x00, 0x60, 0x04, 0x00, 0x20, // 2100 47 | ]; 48 | 49 | /// 農曆年的新年比西曆年晚,這個陣列儲存西曆年和農曆年開始(該年第一天)的偏差量(天數)。此處有西元1901~2100年的資料。 50 | pub(super) const NEW_YEAR_DIFFERENCE: [u8; 200] = [ 51 | 49, 38, 28, 46, 34, 24, 43, 32, 21, 40, // 1910 52 | 29, 48, 36, 25, 44, 33, 22, 41, 31, 50, // 1920 53 | 38, 27, 46, 35, 23, 43, 32, 22, 40, 29, // 1930 54 | 47, 36, 25, 44, 34, 23, 41, 30, 49, 38, // 1940 55 | 26, 45, 35, 24, 43, 32, 21, 40, 28, 47, // 1950 56 | 36, 26, 44, 33, 23, 42, 30, 48, 38, 27, // 1960 57 | 45, 35, 24, 43, 32, 20, 39, 29, 47, 36, // 1970 58 | 26, 45, 33, 22, 41, 30, 48, 37, 27, 46, // 1980 59 | 35, 24, 43, 32, 50, 39, 28, 47, 36, 26, // 1990 60 | 45, 34, 22, 40, 30, 49, 37, 27, 46, 35, // 2000 61 | 23, 42, 31, 21, 39, 28, 48, 37, 25, 44, // 2010 62 | 33, 22, 40, 30, 49, 38, 27, 46, 35, 24, // 2020 63 | 42, 31, 21, 40, 28, 47, 36, 25, 43, 33, // 2030 64 | 22, 41, 30, 49, 38, 27, 45, 34, 23, 42, // 2040 65 | 31, 21, 40, 29, 47, 36, 25, 44, 32, 22, // 2050 66 | 41, 31, 49, 38, 27, 45, 34, 23, 42, 32, // 2060 67 | 20, 39, 28, 47, 35, 25, 44, 33, 22, 41, // 2070 68 | 30, 49, 37, 26, 45, 35, 23, 42, 32, 21, // 2080 69 | 39, 28, 47, 36, 25, 44, 33, 23, 40, 29, // 2090 70 | 48, 37, 26, 45, 35, 24, 42, 31, 20, 39, // 2100 71 | ]; 72 | -------------------------------------------------------------------------------- /src/solar/month/mod.rs: -------------------------------------------------------------------------------- 1 | mod built_in_traits; 2 | mod chinese; 3 | mod parse; 4 | 5 | use core::fmt::{self, Display, Formatter}; 6 | 7 | use chinese::THE_SOLAR_MONTHS; 8 | use enum_ordinalize::Ordinalize; 9 | 10 | use super::{SolarMonthError, SolarYear}; 11 | 12 | /// 列舉西曆十二個月份名稱:一月、二月、三月、四月、五月、六月、七月、八月、九月、十月、十一月、十二月。 13 | #[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, Ordinalize)] 14 | #[ordinalize(impl_trait = false)] 15 | #[ordinalize(from_ordinal_unsafe( 16 | pub fn from_u8_unsafe, 17 | doc = "透過西曆月份數值來取得 `SolarMonth` 列舉實體。", 18 | doc = "# Safety", 19 | doc = "必須先確認傳入的整數是合法的。", 20 | ))] 21 | #[ordinalize(ordinal(pub fn to_u8, doc = "取得 `SolarMonth` 列舉實體所代表的西曆月份數值。"))] 22 | #[repr(u8)] 23 | pub enum SolarMonth { 24 | /// 一月 25 | January = 1, 26 | /// 二月 27 | February, 28 | /// 三月 29 | March, 30 | /// 四月 31 | April, 32 | /// 五月 33 | May, 34 | /// 六月 35 | June, 36 | /// 七月 37 | July, 38 | /// 八月 39 | August, 40 | /// 九月 41 | September, 42 | /// 十月 43 | October, 44 | /// 十一月 45 | November, 46 | /// 十二月 47 | December, 48 | } 49 | 50 | impl Display for SolarMonth { 51 | /// Formats the value using the given formatter. 52 | /// 53 | /// # Examples 54 | /// 55 | /// ``` 56 | /// # use chinese_lunisolar_calendar::SolarMonth; 57 | /// assert_eq!("五月", format!("{}", SolarMonth::May)); 58 | /// ``` 59 | #[inline] 60 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 61 | f.write_str(self.to_str()) 62 | } 63 | } 64 | 65 | /// 用以建立 `SolarMonth` 列舉實體的關聯函數。 66 | impl SolarMonth { 67 | /// 透過西曆月份數值來取得 `SolarMonth` 列舉實體。 68 | /// 69 | /// # Examples 70 | /// 71 | /// ``` 72 | /// # use chinese_lunisolar_calendar::SolarMonth; 73 | /// let solar_month = SolarMonth::from_u8(5).unwrap(); 74 | /// ``` 75 | #[inline] 76 | pub const fn from_u8(month: u8) -> Result { 77 | if month >= 1 && month <= 12 { 78 | Ok(unsafe { Self::from_u8_unsafe(month) }) 79 | } else { 80 | Err(SolarMonthError) 81 | } 82 | } 83 | 84 | /// 透過西曆月份數值來取得 `SolarMonth` 列舉實體。 85 | /// 86 | /// # Examples 87 | /// 88 | /// ``` 89 | /// # use chinese_lunisolar_calendar::SolarMonth; 90 | /// let solar_month = SolarMonth::from_u32(5).unwrap(); 91 | /// ``` 92 | #[inline] 93 | pub const fn from_u32(month: u32) -> Result { 94 | if month >= 1 && month <= 12 { 95 | Ok(unsafe { Self::from_u8_unsafe(month as u8) }) 96 | } else { 97 | Err(SolarMonthError) 98 | } 99 | } 100 | } 101 | 102 | /// 將 `SolarMonth` 列舉實體轉成其它型別的方法。 103 | impl SolarMonth { 104 | /// 取得 `SolarMonth` 列舉實體所代表的西曆月份字串。 105 | /// 106 | /// # Examples 107 | /// 108 | /// ``` 109 | /// # use chinese_lunisolar_calendar::SolarMonth; 110 | /// assert_eq!("五月", SolarMonth::May.to_str()); 111 | /// ``` 112 | #[inline] 113 | pub const fn to_str(self) -> &'static str { 114 | let i = (self.to_u8() - 1) as usize; 115 | 116 | THE_SOLAR_MONTHS[i] 117 | } 118 | 119 | /// 取得 `SolarMonth` 列舉實體所代表的西曆月份數值。 120 | #[inline] 121 | pub const fn to_u32(self) -> u32 { 122 | self.to_u8() as u32 123 | } 124 | } 125 | 126 | /// 西曆月份相關計算方法。 127 | impl SolarMonth { 128 | /// 傳入指定的西曆年,並計算此西曆月在這個指定的西曆年內共有幾天。 129 | /// 130 | /// # Examples 131 | /// 132 | /// ``` 133 | /// # use chinese_lunisolar_calendar::{SolarMonth, SolarYear}; 134 | /// assert_eq!( 135 | /// 31, 136 | /// SolarMonth::January.get_total_days(SolarYear::from_u16(2008)) 137 | /// ); 138 | /// assert_eq!( 139 | /// 29, 140 | /// SolarMonth::February.get_total_days(SolarYear::from_u16(2008)) 141 | /// ); 142 | /// assert_eq!(30, SolarMonth::April.get_total_days(SolarYear::from_u16(2008))); 143 | /// assert_eq!( 144 | /// 28, 145 | /// SolarMonth::February.get_total_days(SolarYear::from_u16(2009)) 146 | /// ); 147 | /// ``` 148 | #[inline] 149 | pub const fn get_total_days(self, solar_year: SolarYear) -> u8 { 150 | solar_year.get_total_days_in_a_month(self) 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/lunar/year/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "ba-zi-weight")] 2 | mod ba_zi_weight; 3 | mod built_in_traits; 4 | mod chinese; 5 | mod parse; 6 | 7 | use core::fmt::{self, Display, Formatter}; 8 | 9 | use chinese::THE_LUNAR_YEARS; 10 | 11 | use super::LunarYearError; 12 | use crate::{EarthlyBranch, HeavenlyStems, Zodiac}; 13 | 14 | /// 農曆年份,由天干加地支組成,六十年一輪。 15 | #[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)] 16 | pub struct LunarYear(u8); 17 | 18 | impl Display for LunarYear { 19 | /// Formats the value using the given formatter. 20 | /// 21 | /// # Examples 22 | /// 23 | /// ``` 24 | /// use chinese_lunisolar_calendar::{EarthlyBranch, HeavenlyStems, LunarYear}; 25 | /// 26 | /// let lunar_year = 27 | /// LunarYear::from_era(HeavenlyStems::Fifth, EarthlyBranch::Eleventh) 28 | /// .unwrap(); 29 | /// 30 | /// assert_eq!("戊戌", format!("{}", lunar_year)); 31 | /// ``` 32 | #[inline] 33 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 34 | f.write_str(self.to_str()) 35 | } 36 | } 37 | 38 | /// 用以建立 `LunarYear` 列舉實體的關聯函數。 39 | impl LunarYear { 40 | /// 透過中國天干地支來取得 `LunarYear` 實體。 41 | /// 42 | /// # Examples 43 | /// 44 | /// ``` 45 | /// use chinese_lunisolar_calendar::{EarthlyBranch, HeavenlyStems, LunarYear}; 46 | /// 47 | /// let lunar_year = 48 | /// LunarYear::from_era(HeavenlyStems::Fifth, EarthlyBranch::Eleventh) 49 | /// .unwrap(); 50 | /// ``` 51 | #[inline] 52 | pub const fn from_era( 53 | heavenly_stems: HeavenlyStems, 54 | earthly_branch: EarthlyBranch, 55 | ) -> Result { 56 | let h = heavenly_stems.ordinal(); 57 | let e = earthly_branch.ordinal(); 58 | 59 | let year_index = if h == e { 60 | h 61 | } else if h < e { 62 | let diff = e - h; 63 | 64 | if diff & 1 == 1 { 65 | return Err(LunarYearError); 66 | } 67 | 68 | h + (12 - diff) * 5 69 | } else { 70 | let diff = h - e; 71 | 72 | if diff & 1 == 1 { 73 | return Err(LunarYearError); 74 | } 75 | 76 | e + diff * 6 77 | } - 1; 78 | 79 | Ok(LunarYear(year_index)) 80 | } 81 | } 82 | 83 | /// 將 `LunarYear` 列舉實體轉成其它型別的方法。 84 | impl LunarYear { 85 | /// 取得 `LunarYear` 實體所代表的農曆年份字串。 86 | /// 87 | /// # Examples 88 | /// 89 | /// ``` 90 | /// use chinese_lunisolar_calendar::{EarthlyBranch, HeavenlyStems, LunarYear}; 91 | /// 92 | /// let lunar_year = 93 | /// LunarYear::from_era(HeavenlyStems::Fifth, EarthlyBranch::Eleventh) 94 | /// .unwrap(); 95 | /// 96 | /// assert_eq!("戊戌", lunar_year.to_str()); 97 | /// ``` 98 | #[inline] 99 | pub const fn to_str(self) -> &'static str { 100 | THE_LUNAR_YEARS[self.0 as usize] 101 | } 102 | 103 | /// 取得天干。 104 | /// 105 | /// # Examples 106 | /// 107 | /// ``` 108 | /// use chinese_lunisolar_calendar::{EarthlyBranch, HeavenlyStems, LunarYear}; 109 | /// 110 | /// let lunar_year = 111 | /// LunarYear::from_era(HeavenlyStems::Fifth, EarthlyBranch::Eleventh) 112 | /// .unwrap(); 113 | /// 114 | /// assert_eq!(HeavenlyStems::Fifth, lunar_year.to_heavenly_stems()); 115 | /// ``` 116 | #[inline] 117 | pub const fn to_heavenly_stems(&self) -> HeavenlyStems { 118 | unsafe { HeavenlyStems::from_ordinal_unsafe(self.0 % 10 + 1) } 119 | } 120 | 121 | /// 取得地支。 122 | /// 123 | /// # Examples 124 | /// 125 | /// ``` 126 | /// use chinese_lunisolar_calendar::{EarthlyBranch, HeavenlyStems, LunarYear}; 127 | /// 128 | /// let lunar_year = 129 | /// LunarYear::from_era(HeavenlyStems::Fifth, EarthlyBranch::Eleventh) 130 | /// .unwrap(); 131 | /// 132 | /// assert_eq!(EarthlyBranch::Eleventh, lunar_year.to_earthly_branch()); 133 | /// ``` 134 | #[inline] 135 | pub const fn to_earthly_branch(&self) -> EarthlyBranch { 136 | unsafe { EarthlyBranch::from_ordinal_unsafe(self.0 % 12 + 1) } 137 | } 138 | 139 | /// 取得生肖。 140 | /// 141 | /// # Examples 142 | /// 143 | /// ``` 144 | /// use chinese_lunisolar_calendar::{ 145 | /// EarthlyBranch, HeavenlyStems, LunarYear, Zodiac, 146 | /// }; 147 | /// 148 | /// let lunar_year = 149 | /// LunarYear::from_era(HeavenlyStems::Fifth, EarthlyBranch::Eleventh) 150 | /// .unwrap(); 151 | /// 152 | /// assert_eq!(Zodiac::Dog, lunar_year.to_zodiac()); 153 | /// ``` 154 | #[inline] 155 | pub const fn to_zodiac(&self) -> Zodiac { 156 | self.to_earthly_branch().to_zodiac() 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/solar/year/mod.rs: -------------------------------------------------------------------------------- 1 | mod built_in_traits; 2 | mod chinese; 3 | mod parse; 4 | 5 | use core::fmt::{self, Display, Formatter, Write}; 6 | 7 | use chinese::{ALTERNATIVE_ZERO, THE_SOLAR_YEAR_NUMBERS_CHAR}; 8 | 9 | use super::{SolarMonth, SolarOutOfRangeError, SolarYearError}; 10 | 11 | /// 西曆年份。 12 | #[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)] 13 | pub struct SolarYear(u16); 14 | 15 | impl Display for SolarYear { 16 | /// Formats the value using the given formatter. 17 | /// 18 | /// # Examples 19 | /// 20 | /// ``` 21 | /// # use chinese_lunisolar_calendar::SolarYear; 22 | /// let solar_year = SolarYear::from_u16(2008); 23 | /// 24 | /// assert_eq!("二〇〇八", format!("{}", solar_year)); 25 | /// ``` 26 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 27 | let mut year = self.0; 28 | 29 | if year == 0 { 30 | f.write_char(THE_SOLAR_YEAR_NUMBERS_CHAR[0])?; 31 | } else { 32 | let mut base = 10u16.pow(year.ilog10()); 33 | 34 | loop { 35 | let digit = year / base; 36 | year -= digit * base; 37 | 38 | f.write_char(THE_SOLAR_YEAR_NUMBERS_CHAR[digit as usize])?; 39 | 40 | base /= 10; 41 | 42 | if base == 0 { 43 | break; 44 | } 45 | } 46 | } 47 | 48 | Ok(()) 49 | } 50 | } 51 | 52 | /// 用以建立 `SolarYear` 列舉實體的關聯函數。 53 | impl SolarYear { 54 | /// 透過西曆年份數值來取得 `SolarYear` 實體。 55 | /// 56 | /// # Examples 57 | /// 58 | /// ``` 59 | /// # use chinese_lunisolar_calendar::SolarYear; 60 | /// let solar_year = SolarYear::from_u16(2008); 61 | /// ``` 62 | #[inline] 63 | pub const fn from_u16(year: u16) -> Self { 64 | Self(year) 65 | } 66 | 67 | /// 透過西曆年份數值來取得 `SolarYear` 實體。 68 | /// 69 | /// # Examples 70 | /// 71 | /// ``` 72 | /// # use chinese_lunisolar_calendar::SolarYear; 73 | /// let solar_year = SolarYear::from_i32(2008).unwrap(); 74 | /// ``` 75 | #[inline] 76 | pub const fn from_i32(year: i32) -> Result { 77 | if year >= 0 && year <= u16::MAX as i32 { 78 | Ok(Self::from_u16(year as u16)) 79 | } else { 80 | Err(SolarOutOfRangeError) 81 | } 82 | } 83 | } 84 | 85 | /// 將 `SolarYear` 列舉實體轉成其它型別的方法。 86 | impl SolarYear { 87 | /// 取得 `SolarYear` 實體所代表的西曆年份數值。 88 | #[inline] 89 | pub const fn to_u16(self) -> u16 { 90 | self.0 91 | } 92 | 93 | /// 取得 `SolarYear` 實體所代表的西曆年份數值。 94 | #[inline] 95 | pub const fn to_i32(self) -> i32 { 96 | self.0 as i32 97 | } 98 | } 99 | 100 | /// 西曆年份相關計算方法。 101 | impl SolarYear { 102 | /// 判斷此西曆年是否為閏年。 103 | /// 104 | /// # Examples 105 | /// 106 | /// ``` 107 | /// # use chinese_lunisolar_calendar::SolarYear; 108 | /// assert_eq!(true, SolarYear::from_u16(2008).is_leap()); 109 | /// assert_eq!(false, SolarYear::from_u16(2009).is_leap()); 110 | /// assert_eq!(false, SolarYear::from_u16(2100).is_leap()); 111 | /// ``` 112 | #[inline] 113 | pub const fn is_leap(self) -> bool { 114 | year_helper::is_leap_year(self.0 as i32) 115 | } 116 | 117 | /// 計算此西曆年共有幾天。 118 | /// 119 | /// # Examples 120 | /// 121 | /// ``` 122 | /// # use chinese_lunisolar_calendar::SolarYear; 123 | /// assert_eq!(366, SolarYear::from_u16(2008).get_total_days()); 124 | /// assert_eq!(365, SolarYear::from_u16(2009).get_total_days()); 125 | /// ``` 126 | #[inline] 127 | pub const fn get_total_days(self) -> u16 { 128 | year_helper::get_days_in_year(self.0 as i32) 129 | } 130 | 131 | /// 計算此西曆年下的某個月共有幾天。。 132 | /// 133 | /// # Examples 134 | /// 135 | /// ``` 136 | /// # use chinese_lunisolar_calendar::{SolarMonth, SolarYear}; 137 | /// assert_eq!( 138 | /// 31, 139 | /// SolarYear::from_u16(2008) 140 | /// .get_total_days_in_a_month(SolarMonth::January) 141 | /// ); 142 | /// assert_eq!( 143 | /// 29, 144 | /// SolarYear::from_u16(2008) 145 | /// .get_total_days_in_a_month(SolarMonth::February) 146 | /// ); 147 | /// assert_eq!( 148 | /// 30, 149 | /// SolarYear::from_u16(2008).get_total_days_in_a_month(SolarMonth::April) 150 | /// ); 151 | /// assert_eq!( 152 | /// 28, 153 | /// SolarYear::from_u16(2009) 154 | /// .get_total_days_in_a_month(SolarMonth::February) 155 | /// ); 156 | /// ``` 157 | #[inline] 158 | pub const fn get_total_days_in_a_month(self, solar_month: SolarMonth) -> u8 { 159 | match year_helper::get_days_in_month(self.0 as i32, solar_month.to_u8()) { 160 | Some(d) => d, 161 | None => unreachable!(), 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/earthly_branch/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "ba-zi-weight")] 2 | mod ba_zi_weight; 3 | mod built_in_traits; 4 | mod chinese; 5 | mod parse; 6 | 7 | use core::{ 8 | fmt::{self, Display, Formatter}, 9 | mem::transmute, 10 | }; 11 | 12 | use chinese::{THE_EARTHLY_BRANCHES, THE_EARTHLY_BRANCHES_CHAR}; 13 | use chrono::prelude::*; 14 | use enum_ordinalize::Ordinalize; 15 | 16 | use crate::Zodiac; 17 | 18 | /// 列舉中國十二地支:子、丑、寅、卯、辰、巳、午、未、申、酉、戌、亥。 19 | #[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, Ordinalize)] 20 | #[ordinalize(impl_trait = false)] 21 | #[ordinalize(from_ordinal_unsafe( 22 | pub fn from_ordinal_unsafe, 23 | doc = "透過整數來取得 `EarthlyBranch` 列舉實體。", 24 | doc = "# Safety", 25 | doc = "必須先確認傳入的整數是合法的。", 26 | ))] 27 | #[ordinalize(ordinal(pub fn ordinal, doc = "取得 `EarthlyBranch` 列舉實體所代表的整數。"))] 28 | #[repr(u8)] 29 | pub enum EarthlyBranch { 30 | /// 子 31 | First = 1, 32 | /// 丑 33 | Second, 34 | /// 寅 35 | Third, 36 | /// 卯 37 | Fourth, 38 | /// 辰 39 | Fifth, 40 | /// 巳 41 | Sixth, 42 | /// 午 43 | Seventh, 44 | /// 未 45 | Eighth, 46 | /// 申 47 | Ninth, 48 | /// 酉 49 | Tenth, 50 | /// 戌 51 | Eleventh, 52 | /// 亥 53 | Twelfth, 54 | } 55 | 56 | impl Display for EarthlyBranch { 57 | /// Formats the value using the given formatter. 58 | /// 59 | /// # Examples 60 | /// 61 | /// ``` 62 | /// # use chinese_lunisolar_calendar::EarthlyBranch; 63 | /// assert_eq!("辰", format!("{}", EarthlyBranch::Fifth)); 64 | /// ``` 65 | #[inline] 66 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 67 | f.write_str(self.to_str()) 68 | } 69 | } 70 | 71 | /// 用以建立 `EarthlyBranch` 列舉實體的關聯函數。 72 | impl EarthlyBranch { 73 | /// 透過子、丑、寅、卯、辰、巳、午、未、申、酉、戌、亥等字元來取得 `EarthlyBranch` 列舉實體。 74 | /// 75 | /// # Examples 76 | /// 77 | /// ``` 78 | /// # use chinese_lunisolar_calendar::EarthlyBranch; 79 | /// assert_eq!(EarthlyBranch::Fifth, EarthlyBranch::from_char('辰').unwrap()); 80 | /// ``` 81 | #[inline] 82 | pub const fn from_char(c: char) -> Option { 83 | let len = THE_EARTHLY_BRANCHES_CHAR.len(); 84 | 85 | let mut i = 0; 86 | 87 | loop { 88 | if c == THE_EARTHLY_BRANCHES_CHAR[i] { 89 | return Some(unsafe { Self::from_ordinal_unsafe(i as u8 + 1) }); 90 | } 91 | 92 | if i == len { 93 | break; 94 | } 95 | 96 | i += 1; 97 | } 98 | 99 | None 100 | } 101 | 102 | /// 透過生肖來取得地支。 103 | /// 104 | /// # Examples 105 | /// 106 | /// ``` 107 | /// use chinese_lunisolar_calendar::{EarthlyBranch, Zodiac}; 108 | /// 109 | /// assert_eq!( 110 | /// EarthlyBranch::Fifth, 111 | /// EarthlyBranch::from_zodiac(Zodiac::Dragon) 112 | /// ); 113 | /// ``` 114 | #[inline] 115 | pub const fn from_zodiac(zodiac: Zodiac) -> Self { 116 | unsafe { transmute(zodiac) } 117 | } 118 | 119 | /// 將時間轉成對應的地支。 120 | /// 121 | /// # Examples 122 | /// 123 | /// ``` 124 | /// use chinese_lunisolar_calendar::{EarthlyBranch, Zodiac}; 125 | /// use chrono::prelude::*; 126 | /// 127 | /// let date_time = Local.with_ymd_and_hms(2024, 1, 1, 12, 30, 45).unwrap(); 128 | /// 129 | /// assert_eq!(EarthlyBranch::Seventh, EarthlyBranch::from_time(date_time)); 130 | /// ``` 131 | #[inline] 132 | pub fn from_time(time: T) -> EarthlyBranch { 133 | let hour = time.hour(); 134 | 135 | let ordinal = ((hour + 1) % 24) / 2; 136 | 137 | unsafe { Self::from_ordinal_unsafe(ordinal as u8 + 1) } 138 | } 139 | } 140 | 141 | /// 將 `EarthlyBranch` 列舉實體轉成其它型別的方法。 142 | impl EarthlyBranch { 143 | /// 取得 `EarthlyBranch` 列舉實體所代表的地支字串。 144 | /// 145 | /// # Examples 146 | /// 147 | /// ``` 148 | /// # use chinese_lunisolar_calendar::EarthlyBranch; 149 | /// assert_eq!("辰", EarthlyBranch::Fifth.to_str()); 150 | /// ``` 151 | #[inline] 152 | pub const fn to_str(self) -> &'static str { 153 | let i = (self.ordinal() - 1) as usize; 154 | 155 | THE_EARTHLY_BRANCHES[i] 156 | } 157 | 158 | /// 取得 `EarthlyBranch` 列舉實體所代表的地支字元。 159 | /// 160 | /// # Examples 161 | /// 162 | /// ``` 163 | /// # use chinese_lunisolar_calendar::EarthlyBranch; 164 | /// assert_eq!('辰', EarthlyBranch::Fifth.to_char()); 165 | /// ``` 166 | #[inline] 167 | pub const fn to_char(self) -> char { 168 | let i = (self.ordinal() - 1) as usize; 169 | 170 | THE_EARTHLY_BRANCHES_CHAR[i] 171 | } 172 | 173 | /// 將地支轉成生肖。 174 | /// 175 | /// # Examples 176 | /// 177 | /// ``` 178 | /// use chinese_lunisolar_calendar::{EarthlyBranch, Zodiac}; 179 | /// 180 | /// assert_eq!(Zodiac::Dragon, EarthlyBranch::Fifth.to_zodiac()); 181 | /// ``` 182 | #[inline] 183 | pub const fn to_zodiac(self) -> Zodiac { 184 | unsafe { transmute(self) } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/zodiac/mod.rs: -------------------------------------------------------------------------------- 1 | mod built_in_traits; 2 | mod chinese; 3 | mod parse; 4 | 5 | use core::{ 6 | fmt::{self, Display, Formatter}, 7 | mem::transmute, 8 | }; 9 | 10 | use chinese::{THE_ZODIAC_SIGNS, THE_ZODIAC_SIGNS_CHAR}; 11 | use enum_ordinalize::Ordinalize; 12 | 13 | use crate::{ChineseVariant, EarthlyBranch}; 14 | 15 | /// 列舉中國十二生肖:鼠、牛、虎、兔、龍、蛇、馬、羊、猴、雞、狗、豬。 16 | #[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, Ordinalize)] 17 | #[ordinalize(impl_trait = false)] 18 | #[ordinalize(from_ordinal_unsafe( 19 | pub fn from_ordinal_unsafe, 20 | doc = "透過整數來取得 `Zodiac` 列舉實體。", 21 | doc = "# Safety", 22 | doc = "必須先確認傳入的整數是合法的。", 23 | ))] 24 | #[ordinalize(ordinal(pub fn ordinal, doc = "取得 `Zodiac` 列舉實體所代表的整數。"))] 25 | #[repr(u8)] 26 | pub enum Zodiac { 27 | /// 鼠 28 | Rat = 1, 29 | /// 牛 30 | Ox, 31 | /// 虎 32 | Tiger, 33 | /// 兔 34 | Rabbit, 35 | /// 龍 36 | Dragon, 37 | /// 蛇 38 | Snake, 39 | /// 馬 40 | Horse, 41 | /// 羊 42 | Goat, 43 | /// 猴 44 | Monkey, 45 | /// 雞 46 | Rooster, 47 | /// 狗 48 | Dog, 49 | /// 豬 50 | Pig, 51 | } 52 | 53 | impl Display for Zodiac { 54 | /// Formats the value using the given formatter. 55 | /// 56 | /// # Examples 57 | /// 58 | /// ``` 59 | /// # use chinese_lunisolar_calendar::Zodiac; 60 | /// assert_eq!("龍", format!("{}", Zodiac::Dragon)); 61 | /// assert_eq!("龙", format!("{:#}", Zodiac::Dragon)); 62 | /// ``` 63 | #[inline] 64 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 65 | if f.alternate() { 66 | f.write_str(self.to_str(ChineseVariant::Simple)) 67 | } else { 68 | f.write_str(self.to_str(ChineseVariant::Traditional)) 69 | } 70 | } 71 | } 72 | 73 | /// 用以建立 `Zodiac` 列舉實體的關聯函數。 74 | impl Zodiac { 75 | /// 透過鼠、牛、虎、兔、龍、蛇、馬、羊、猴、雞、狗、豬等字元來取得 `Zodiac` 列舉實體。 76 | /// 77 | /// # Examples 78 | /// 79 | /// ``` 80 | /// # use chinese_lunisolar_calendar::Zodiac; 81 | /// assert_eq!(Zodiac::Dragon, Zodiac::from_char('龍').unwrap()); 82 | /// assert_eq!(Zodiac::Dragon, Zodiac::from_char('龙').unwrap()); 83 | /// ``` 84 | #[inline] 85 | pub const fn from_char(c: char) -> Option { 86 | let len = THE_ZODIAC_SIGNS_CHAR.len(); 87 | 88 | let mut i = 0; 89 | 90 | loop { 91 | let t = THE_ZODIAC_SIGNS_CHAR[i]; 92 | 93 | if c == t.0 || c == t.1 { 94 | return Some(unsafe { Self::from_ordinal_unsafe(i as u8 + 1) }); 95 | } 96 | 97 | if i == len { 98 | break; 99 | } 100 | 101 | i += 1; 102 | } 103 | 104 | None 105 | } 106 | 107 | /// 透過地支來取得生肖。 108 | /// 109 | /// # Examples 110 | /// 111 | /// ``` 112 | /// use chinese_lunisolar_calendar::{EarthlyBranch, Zodiac}; 113 | /// 114 | /// assert_eq!( 115 | /// Zodiac::Dragon, 116 | /// Zodiac::from_earthly_branch(EarthlyBranch::Fifth) 117 | /// ); 118 | /// ``` 119 | #[inline] 120 | pub const fn from_earthly_branch(earthly_branch: EarthlyBranch) -> Self { 121 | unsafe { transmute(earthly_branch) } 122 | } 123 | } 124 | 125 | /// 將 `Zodiac` 列舉實體轉成其它型別的方法。 126 | impl Zodiac { 127 | /// 取得 `Zodiac` 列舉實體所代表的生肖字串。 128 | /// 129 | /// # Examples 130 | /// 131 | /// ``` 132 | /// use chinese_lunisolar_calendar::{ChineseVariant, Zodiac}; 133 | /// 134 | /// assert_eq!("龍", Zodiac::Dragon.to_str(ChineseVariant::Traditional)); 135 | /// assert_eq!("龙", Zodiac::Dragon.to_str(ChineseVariant::Simple)); 136 | /// ``` 137 | #[inline] 138 | pub const fn to_str(self, chinese_variant: ChineseVariant) -> &'static str { 139 | let i = (self.ordinal() - 1) as usize; 140 | 141 | match chinese_variant { 142 | ChineseVariant::Simple => THE_ZODIAC_SIGNS[i].1, 143 | ChineseVariant::Traditional => THE_ZODIAC_SIGNS[i].0, 144 | } 145 | } 146 | 147 | /// 取得 `Zodiac` 列舉實體所代表的生肖字元。 148 | /// 149 | /// # Examples 150 | /// 151 | /// ``` 152 | /// use chinese_lunisolar_calendar::{Zodiac, ChineseVariant}; 153 | /// 154 | /// assert_eq!('龍', Zodiac::Dragon.to_char(ChineseVariant::Traditional)); 155 | /// assert_eq!('龙', Zodiac::Dragon.to_char(ChineseVariant::Simple)); 156 | #[inline] 157 | pub const fn to_char(self, chinese_variant: ChineseVariant) -> char { 158 | let i = (self.ordinal() - 1) as usize; 159 | 160 | match chinese_variant { 161 | ChineseVariant::Simple => THE_ZODIAC_SIGNS_CHAR[i].1, 162 | ChineseVariant::Traditional => THE_ZODIAC_SIGNS_CHAR[i].0, 163 | } 164 | } 165 | 166 | /// 將生肖轉成地支。 167 | /// 168 | /// # Examples 169 | /// 170 | /// ``` 171 | /// use chinese_lunisolar_calendar::{EarthlyBranch, Zodiac}; 172 | /// 173 | /// assert_eq!(EarthlyBranch::Fifth, Zodiac::Dragon.to_earthly_branch()); 174 | /// ``` 175 | #[inline] 176 | pub const fn to_earthly_branch(self) -> EarthlyBranch { 177 | unsafe { transmute(self) } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/lunar/month/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "ba-zi-weight")] 2 | mod ba_zi_weight; 3 | mod built_in_traits; 4 | mod chinese; 5 | mod parse; 6 | 7 | use core::fmt::{self, Display, Formatter}; 8 | 9 | use chinese::THE_LUNAR_MONTHS; 10 | use chinese_variant::ChineseVariant; 11 | use enum_ordinalize::Ordinalize; 12 | 13 | use super::LunarMonthError; 14 | 15 | /// 列舉農曆十二個月份名稱:正月、二月、三月、四月、五月、六月、七月、八月、九月、十月、冬月、臘月。包含閏月。 16 | #[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, Ordinalize)] 17 | #[ordinalize(impl_trait = false)] 18 | #[ordinalize(from_ordinal_unsafe( 19 | pub fn from_u8_raw_unsafe, 20 | doc = "透過農曆月份數值來取得 `LunarMonth` 列舉實體。閏月必須加上 `100`。", 21 | doc = "# Safety", 22 | doc = "必須先確認傳入的整數是合法的。", 23 | ))] 24 | #[ordinalize(ordinal(pub fn to_u8_raw, doc = "取得 `LunarMonth` 列舉實體所代表的農曆月份數值。閏月會加上 `100`。"))] 25 | #[repr(u8)] 26 | pub enum LunarMonth { 27 | /// 正月 28 | First = 1, 29 | /// 二月 30 | Second, 31 | /// 三月 32 | Third, 33 | /// 四月 34 | Fourth, 35 | /// 五月 36 | Fifth, 37 | /// 六月 38 | Sixth, 39 | /// 七月 40 | Seventh, 41 | /// 八月 42 | Eighth, 43 | /// 九月 44 | Ninth, 45 | /// 十月 46 | Tenth, 47 | /// 冬月 48 | Eleventh, 49 | /// 臘月 50 | Twelfth, 51 | /// 閏正月 52 | LeapFirst = 101, 53 | /// 閏二月 54 | LeapSecond, 55 | /// 閏三月 56 | LeapThird, 57 | /// 閏四月 58 | LeapFourth, 59 | /// 閏五月 60 | LeapFifth, 61 | /// 閏六月 62 | LeapSixth, 63 | /// 閏七月 64 | LeapSeventh, 65 | /// 閏八月 66 | LeapEighth, 67 | /// 閏九月 68 | LeapNinth, 69 | /// 閏十月 70 | LeapTenth, 71 | /// 閏冬月 72 | LeapEleventh, 73 | /// 閏臘月 74 | LeapTwelfth, 75 | } 76 | 77 | impl Display for LunarMonth { 78 | /// Formats the value using the given formatter. 79 | /// 80 | /// # Examples 81 | /// 82 | /// ``` 83 | /// # use chinese_lunisolar_calendar::LunarMonth; 84 | /// assert_eq!("臘月", format!("{}", LunarMonth::Twelfth)); 85 | /// assert_eq!("腊月", format!("{:#}", LunarMonth::Twelfth)); 86 | /// ``` 87 | #[inline] 88 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 89 | if f.alternate() { 90 | f.write_str(self.to_str(ChineseVariant::Simple)) 91 | } else { 92 | f.write_str(self.to_str(ChineseVariant::Traditional)) 93 | } 94 | } 95 | } 96 | 97 | /// 用以建立 `LunarMonth` 列舉實體的關聯函數。 98 | impl LunarMonth { 99 | /// 透過農曆月份數值和是否閏月來取得 `LunarMonth` 列舉實體。 100 | /// 101 | /// # Examples 102 | /// 103 | /// ``` 104 | /// # use chinese_lunisolar_calendar::LunarMonth; 105 | /// assert_eq!(LunarMonth::Fifth, unsafe { 106 | /// LunarMonth::from_u8_with_leap_unsafe(5, false) 107 | /// }); 108 | /// assert_eq!(LunarMonth::LeapFifth, unsafe { 109 | /// LunarMonth::from_u8_with_leap_unsafe(5, true) 110 | /// }); 111 | /// ``` 112 | /// 113 | /// # Safety 114 | /// 必須先確認傳入的整數是合法的。 115 | #[inline] 116 | pub const unsafe fn from_u8_with_leap_unsafe(mut month: u8, leap: bool) -> Self { 117 | if leap { 118 | month += 100; 119 | } 120 | 121 | Self::from_u8_raw_unsafe(month) 122 | } 123 | 124 | /// 透過農曆月份數值和是否閏月來取得 `LunarMonth` 列舉實體。 125 | /// 126 | /// # Examples 127 | /// 128 | /// ``` 129 | /// # use chinese_lunisolar_calendar::LunarMonth; 130 | /// assert_eq!( 131 | /// LunarMonth::Fifth, 132 | /// LunarMonth::from_u8_with_leap(5, false).unwrap() 133 | /// ); 134 | /// assert_eq!( 135 | /// LunarMonth::LeapFifth, 136 | /// LunarMonth::from_u8_with_leap(5, true).unwrap() 137 | /// ); 138 | /// ``` 139 | #[inline] 140 | pub const fn from_u8_with_leap(month: u8, leap: bool) -> Result { 141 | if month >= 1 && month <= 12 { 142 | Ok(unsafe { Self::from_u8_with_leap_unsafe(month, leap) }) 143 | } else { 144 | Err(LunarMonthError) 145 | } 146 | } 147 | } 148 | 149 | /// 將 `LunarMonth` 列舉實體轉成其它型別的方法。 150 | impl LunarMonth { 151 | /// 取得 `Zodiac` 列舉實體所代表的生肖字串。 152 | /// 153 | /// # Examples 154 | /// 155 | /// ``` 156 | /// use chinese_lunisolar_calendar::{ChineseVariant, LunarMonth}; 157 | /// 158 | /// assert_eq!("臘月", LunarMonth::Twelfth.to_str(ChineseVariant::Traditional)); 159 | /// assert_eq!("腊月", LunarMonth::Twelfth.to_str(ChineseVariant::Simple)); 160 | /// ``` 161 | #[inline] 162 | pub const fn to_str(self, chinese_variant: ChineseVariant) -> &'static str { 163 | let mut i = (self.to_u8_raw() - 1) as usize; 164 | 165 | if i >= 100 { 166 | i -= 88; 167 | } 168 | 169 | match chinese_variant { 170 | ChineseVariant::Simple => THE_LUNAR_MONTHS[i].1, 171 | ChineseVariant::Traditional => THE_LUNAR_MONTHS[i].0, 172 | } 173 | } 174 | 175 | /// 取得 `LunarMonth` 列舉實體所代表的農曆月份數值。 176 | #[inline] 177 | pub const fn to_u8(self) -> u8 { 178 | let mut i = self.to_u8_raw(); 179 | 180 | if i >= 101 { 181 | i -= 100; 182 | } 183 | 184 | i 185 | } 186 | } 187 | 188 | /// 農曆月份相關計算方法。 189 | impl LunarMonth { 190 | /// 是否為閏月。 191 | /// 192 | /// # Examples 193 | /// 194 | /// ``` 195 | /// use chinese_lunisolar_calendar::LunarMonth; 196 | /// 197 | /// assert_eq!(false, LunarMonth::Twelfth.is_leap_month()); 198 | /// assert_eq!(true, LunarMonth::LeapTwelfth.is_leap_month()); 199 | /// ``` 200 | #[inline] 201 | pub const fn is_leap_month(self) -> bool { 202 | self.to_u8_raw() >= 101 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /tests/lunisolar_date.rs: -------------------------------------------------------------------------------- 1 | use chinese_lunisolar_calendar::{ 2 | LunarDay, LunarMonth, LunarYear, LunisolarDate, LunisolarYear, SolarDate, SolarYear, 3 | }; 4 | 5 | #[test] 6 | fn from_solar_date() { 7 | let solar_date = SolarDate::from_ymd(1993, 1, 12).unwrap(); 8 | 9 | let lunisolar_date = LunisolarDate::from_solar_date(solar_date).unwrap(); 10 | 11 | assert_eq!(SolarYear::from_u16(1993), lunisolar_date.to_solar_year()); 12 | assert_eq!( 13 | LunisolarYear::from_solar_year(1992.into()).unwrap(), 14 | lunisolar_date.to_lunisolar_year() 15 | ); 16 | assert_eq!(LunarYear::parse_str("壬申").unwrap(), lunisolar_date.to_lunar_year()); 17 | assert_eq!(LunarMonth::from_u8_with_leap(12, false).unwrap(), lunisolar_date.to_lunar_month()); 18 | assert_eq!(LunarDay::from_u8(20).unwrap(), lunisolar_date.to_lunar_day()); 19 | 20 | let solar_date = SolarDate::from_ymd(1993, 1, 23).unwrap(); 21 | 22 | let lunisolar_date = LunisolarDate::from_solar_date(solar_date).unwrap(); 23 | 24 | assert_eq!(SolarYear::from_u16(1993), lunisolar_date.to_solar_year()); 25 | assert_eq!( 26 | LunisolarYear::from_solar_year(1993.into()).unwrap(), 27 | lunisolar_date.to_lunisolar_year() 28 | ); 29 | assert_eq!(LunarYear::parse_str("癸酉").unwrap(), lunisolar_date.to_lunar_year()); 30 | assert_eq!(LunarMonth::from_u8_with_leap(1, false).unwrap(), lunisolar_date.to_lunar_month()); 31 | assert_eq!(LunarDay::from_u8(1).unwrap(), lunisolar_date.to_lunar_day()); 32 | 33 | let solar_date = SolarDate::from_ymd(1993, 4, 22).unwrap(); 34 | 35 | let lunisolar_date = LunisolarDate::from_solar_date(solar_date).unwrap(); 36 | 37 | assert_eq!(SolarYear::from_u16(1993), lunisolar_date.to_solar_year()); 38 | assert_eq!( 39 | LunisolarYear::from_solar_year(1993.into()).unwrap(), 40 | lunisolar_date.to_lunisolar_year() 41 | ); 42 | assert_eq!(LunarYear::parse_str("癸酉").unwrap(), lunisolar_date.to_lunar_year()); 43 | assert_eq!(LunarMonth::from_u8_with_leap(3, true).unwrap(), lunisolar_date.to_lunar_month()); 44 | assert_eq!(LunarDay::from_u8(1).unwrap(), lunisolar_date.to_lunar_day()); 45 | 46 | let solar_date = SolarDate::from_ymd(1993, 8, 10).unwrap(); 47 | 48 | let lunisolar_date = LunisolarDate::from_solar_date(solar_date).unwrap(); 49 | 50 | assert_eq!(SolarYear::from_u16(1993), lunisolar_date.to_solar_year()); 51 | assert_eq!( 52 | LunisolarYear::from_solar_year(1993.into()).unwrap(), 53 | lunisolar_date.to_lunisolar_year() 54 | ); 55 | assert_eq!(LunarYear::parse_str("癸酉").unwrap(), lunisolar_date.to_lunar_year()); 56 | assert_eq!(LunarMonth::from_u8_with_leap(6, false).unwrap(), lunisolar_date.to_lunar_month()); 57 | assert_eq!(LunarDay::from_u8(23).unwrap(), lunisolar_date.to_lunar_day()); 58 | 59 | let solar_date = SolarDate::from_ymd(1993, 12, 12).unwrap(); 60 | 61 | let lunisolar_date = LunisolarDate::from_solar_date(solar_date).unwrap(); 62 | 63 | assert_eq!(SolarYear::from_u16(1993), lunisolar_date.to_solar_year()); 64 | assert_eq!( 65 | LunisolarYear::from_solar_year(1993.into()).unwrap(), 66 | lunisolar_date.to_lunisolar_year() 67 | ); 68 | assert_eq!(LunarYear::parse_str("癸酉").unwrap(), lunisolar_date.to_lunar_year()); 69 | assert_eq!(LunarMonth::from_u8_with_leap(10, false).unwrap(), lunisolar_date.to_lunar_month()); 70 | assert_eq!(LunarDay::from_u8(29).unwrap(), lunisolar_date.to_lunar_day()); 71 | } 72 | 73 | #[test] 74 | fn from_ymd() { 75 | let lunisolar_date = LunisolarDate::from_ymd(1992, 12, false, 20).unwrap(); 76 | 77 | assert_eq!(SolarYear::from_u16(1993), lunisolar_date.to_solar_year()); 78 | assert_eq!( 79 | LunisolarYear::from_solar_year(1992.into()).unwrap(), 80 | lunisolar_date.to_lunisolar_year() 81 | ); 82 | assert_eq!(LunarYear::parse_str("壬申").unwrap(), lunisolar_date.to_lunar_year()); 83 | assert_eq!(LunarMonth::from_u8_with_leap(12, false).unwrap(), lunisolar_date.to_lunar_month()); 84 | assert_eq!(LunarDay::from_u8(20).unwrap(), lunisolar_date.to_lunar_day()); 85 | 86 | let lunisolar_date = LunisolarDate::from_ymd(1993, 1, false, 1).unwrap(); 87 | 88 | assert_eq!(SolarYear::from_u16(1993), lunisolar_date.to_solar_year()); 89 | assert_eq!( 90 | LunisolarYear::from_solar_year(1993.into()).unwrap(), 91 | lunisolar_date.to_lunisolar_year() 92 | ); 93 | assert_eq!(LunarYear::parse_str("癸酉").unwrap(), lunisolar_date.to_lunar_year()); 94 | assert_eq!(LunarMonth::from_u8_with_leap(1, false).unwrap(), lunisolar_date.to_lunar_month()); 95 | assert_eq!(LunarDay::from_u8(1).unwrap(), lunisolar_date.to_lunar_day()); 96 | 97 | let lunisolar_date = LunisolarDate::from_ymd(1993, 3, true, 1).unwrap(); 98 | 99 | assert_eq!(SolarYear::from_u16(1993), lunisolar_date.to_solar_year()); 100 | assert_eq!( 101 | LunisolarYear::from_solar_year(1993.into()).unwrap(), 102 | lunisolar_date.to_lunisolar_year() 103 | ); 104 | assert_eq!(LunarYear::parse_str("癸酉").unwrap(), lunisolar_date.to_lunar_year()); 105 | assert_eq!(LunarMonth::from_u8_with_leap(3, true).unwrap(), lunisolar_date.to_lunar_month()); 106 | assert_eq!(LunarDay::from_u8(1).unwrap(), lunisolar_date.to_lunar_day()); 107 | 108 | let lunisolar_date = LunisolarDate::from_ymd(1993, 6, false, 23).unwrap(); 109 | 110 | assert_eq!(SolarYear::from_u16(1993), lunisolar_date.to_solar_year()); 111 | assert_eq!( 112 | LunisolarYear::from_solar_year(1993.into()).unwrap(), 113 | lunisolar_date.to_lunisolar_year() 114 | ); 115 | assert_eq!(LunarYear::parse_str("癸酉").unwrap(), lunisolar_date.to_lunar_year()); 116 | assert_eq!(LunarMonth::from_u8_with_leap(6, false).unwrap(), lunisolar_date.to_lunar_month()); 117 | assert_eq!(LunarDay::from_u8(23).unwrap(), lunisolar_date.to_lunar_day()); 118 | 119 | let lunisolar_date = LunisolarDate::from_ymd(1993, 10, false, 29).unwrap(); 120 | 121 | assert_eq!(SolarYear::from_u16(1993), lunisolar_date.to_solar_year()); 122 | assert_eq!( 123 | LunisolarYear::from_solar_year(1993.into()).unwrap(), 124 | lunisolar_date.to_lunisolar_year() 125 | ); 126 | assert_eq!(LunarYear::parse_str("癸酉").unwrap(), lunisolar_date.to_lunar_year()); 127 | assert_eq!(LunarMonth::from_u8_with_leap(10, false).unwrap(), lunisolar_date.to_lunar_month()); 128 | assert_eq!(LunarDay::from_u8(29).unwrap(), lunisolar_date.to_lunar_day()); 129 | } 130 | 131 | #[test] 132 | fn parse_str() { 133 | let lunisolar_date = LunisolarDate::parse_str("二零一八 戊戌、狗年 六月 十九").unwrap(); 134 | 135 | assert_eq!(SolarYear::from_u16(2018), lunisolar_date.to_solar_year()); 136 | assert_eq!(LunarMonth::from_u8_with_leap(6, false).unwrap(), lunisolar_date.to_lunar_month()); 137 | assert_eq!(LunarDay::from_u8(19).unwrap(), lunisolar_date.to_lunar_day()); 138 | 139 | let lunisolar_date = LunisolarDate::parse_str("二〇一八 戊戌、狗年 六月 十九").unwrap(); 140 | 141 | assert_eq!(SolarYear::from_u16(2018), lunisolar_date.to_solar_year()); 142 | assert_eq!(LunarMonth::from_u8_with_leap(6, false).unwrap(), lunisolar_date.to_lunar_month()); 143 | assert_eq!(LunarDay::from_u8(19).unwrap(), lunisolar_date.to_lunar_day()); 144 | 145 | let lunisolar_date = LunisolarDate::parse_str("2018 六月 十九日").unwrap(); 146 | 147 | assert_eq!(SolarYear::from_u16(2018), lunisolar_date.to_solar_year()); 148 | assert_eq!(LunarMonth::from_u8_with_leap(6, false).unwrap(), lunisolar_date.to_lunar_month()); 149 | assert_eq!(LunarDay::from_u8(19).unwrap(), lunisolar_date.to_lunar_day()); 150 | } 151 | 152 | #[test] 153 | fn the_n_day_in_this_year() { 154 | assert_eq!(344, LunisolarDate::from_ymd(1992, 12, false, 20).unwrap().the_n_day_in_this_year()); 155 | assert_eq!(1, LunisolarDate::from_ymd(1993, 1, false, 1).unwrap().the_n_day_in_this_year()); 156 | assert_eq!(60, LunisolarDate::from_ymd(1993, 3, false, 1).unwrap().the_n_day_in_this_year()); 157 | assert_eq!(200, LunisolarDate::from_ymd(1993, 6, false, 23).unwrap().the_n_day_in_this_year()); 158 | assert_eq!(324, LunisolarDate::from_ymd(1993, 10, false, 29).unwrap().the_n_day_in_this_year()); 159 | } 160 | -------------------------------------------------------------------------------- /src/solar/date/mod.rs: -------------------------------------------------------------------------------- 1 | mod built_in_traits; 2 | mod parse; 3 | 4 | use core::{ 5 | cmp::Ordering, 6 | fmt::{self, Display, Formatter, Write}, 7 | }; 8 | 9 | use chrono::prelude::*; 10 | 11 | use super::{SolarDateError, SolarDay, SolarDayError, SolarMonth, SolarOutOfRangeError, SolarYear}; 12 | 13 | /// 西曆年月日。 14 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 15 | pub struct SolarDate { 16 | pub(crate) solar_year: SolarYear, 17 | pub(crate) solar_month: SolarMonth, 18 | pub(crate) solar_day: SolarDay, 19 | } 20 | 21 | impl PartialOrd for SolarDate { 22 | #[inline] 23 | fn partial_cmp(&self, other: &SolarDate) -> Option { 24 | Some(Ord::cmp(self, other)) 25 | } 26 | } 27 | 28 | impl Ord for SolarDate { 29 | #[inline] 30 | fn cmp(&self, other: &SolarDate) -> Ordering { 31 | self.cmp(other) 32 | } 33 | } 34 | 35 | impl Display for SolarDate { 36 | /// Formats the value using the given formatter. 37 | /// 38 | /// # Examples 39 | /// 40 | /// ``` 41 | /// # use chinese_lunisolar_calendar::SolarDate; 42 | /// assert_eq!( 43 | /// "二〇二四年一月一日", 44 | /// format!("{}", SolarDate::from_ymd(2024, 1, 1).unwrap()) 45 | /// ); 46 | /// ``` 47 | #[inline] 48 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 49 | Display::fmt(&self.solar_year, f)?; 50 | f.write_char('年')?; 51 | Display::fmt(&self.solar_month, f)?; 52 | Display::fmt(&self.solar_day, f)?; 53 | f.write_char('日') 54 | } 55 | } 56 | 57 | /// 用以建立 `SolarDate` 列舉實體的關聯函數。 58 | impl SolarDate { 59 | /// 將日期轉成 `SolarDate` 實體。 60 | /// 61 | /// # Examples 62 | /// 63 | /// ``` 64 | /// use chinese_lunisolar_calendar::SolarDate; 65 | /// use chrono::prelude::*; 66 | /// 67 | /// let date = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(); 68 | /// 69 | /// let solar_date = SolarDate::from_date(date).unwrap(); 70 | /// ``` 71 | #[inline] 72 | pub fn from_date(date: D) -> Result { 73 | let solar_year = SolarYear::from_i32(date.year())?; 74 | let solar_month = SolarMonth::from_u32(date.month()).unwrap(); 75 | let solar_day = SolarDay::from_u32(date.day()).unwrap(); 76 | 77 | Ok(SolarDate { 78 | solar_year, 79 | solar_month, 80 | solar_day, 81 | }) 82 | } 83 | 84 | /// 利用西曆的年月日來產生 `SolarDate` 實體。 85 | /// 86 | /// # Examples 87 | /// 88 | /// ``` 89 | /// use chinese_lunisolar_calendar::{ 90 | /// SolarDate, SolarDay, SolarMonth, SolarYear, 91 | /// }; 92 | /// 93 | /// let solar_date = unsafe { 94 | /// SolarDate::from_solar_year_month_day_unsafe( 95 | /// SolarYear::from_u16(2004), 96 | /// SolarMonth::January, 97 | /// SolarDay::First, 98 | /// ) 99 | /// }; 100 | /// ``` 101 | /// 102 | /// # Safety 103 | /// 必須先確保傳入的 `solar_day` 在該西元年月下是合法的。 104 | #[inline] 105 | pub const unsafe fn from_solar_year_month_day_unsafe( 106 | solar_year: SolarYear, 107 | solar_month: SolarMonth, 108 | solar_day: SolarDay, 109 | ) -> Self { 110 | SolarDate { 111 | solar_year, 112 | solar_month, 113 | solar_day, 114 | } 115 | } 116 | 117 | /// 利用西曆的年月日來產生 `SolarDate` 實體。 118 | /// 119 | /// # Examples 120 | /// 121 | /// ``` 122 | /// use chinese_lunisolar_calendar::{ 123 | /// SolarDate, SolarDay, SolarMonth, SolarYear, 124 | /// }; 125 | /// 126 | /// let solar_date = SolarDate::from_solar_year_month_day( 127 | /// SolarYear::from_u16(2004), 128 | /// SolarMonth::January, 129 | /// SolarDay::First, 130 | /// ) 131 | /// .unwrap(); 132 | /// ``` 133 | #[inline] 134 | pub const fn from_solar_year_month_day( 135 | solar_year: SolarYear, 136 | solar_month: SolarMonth, 137 | solar_day: SolarDay, 138 | ) -> Result { 139 | let days = solar_month.get_total_days(solar_year); 140 | 141 | let day = solar_day.to_u8(); 142 | 143 | if day <= days { 144 | Ok(SolarDate { 145 | solar_year, 146 | solar_month, 147 | solar_day, 148 | }) 149 | } else { 150 | Err(SolarDayError) 151 | } 152 | } 153 | 154 | /// 利用西曆的年月日來產生 `SolarDate` 實體。 155 | /// 156 | /// # Examples 157 | /// 158 | /// ``` 159 | /// # use chinese_lunisolar_calendar::SolarDate; 160 | /// let solar_date = SolarDate::from_ymd(2024, 1, 1).unwrap(); 161 | /// ``` 162 | #[inline] 163 | pub const fn from_ymd(year: u16, month: u8, day: u8) -> Result { 164 | let solar_year = SolarYear::from_u16(year); 165 | 166 | match SolarMonth::from_u8(month) { 167 | Ok(solar_month) => match SolarDay::from_u8(day) { 168 | Ok(solar_day) => { 169 | match Self::from_solar_year_month_day(solar_year, solar_month, solar_day) { 170 | Ok(solar_date) => Ok(solar_date), 171 | Err(_) => Err(SolarDateError::DayIncorrect), 172 | } 173 | }, 174 | Err(_) => Err(SolarDateError::DayIncorrect), 175 | }, 176 | Err(_) => Err(SolarDateError::MonthIncorrect), 177 | } 178 | } 179 | 180 | /// 以目前的年月日來產生 `SolarDate` 實體。 181 | /// 182 | /// # Examples 183 | /// 184 | /// ``` 185 | /// # use chinese_lunisolar_calendar::SolarDate; 186 | /// let solar_date = SolarDate::now().unwrap(); 187 | /// ``` 188 | #[inline] 189 | pub fn now() -> Result { 190 | Self::from_date(Utc::now().date_naive()) 191 | } 192 | } 193 | 194 | /// 將 `SolarDate` 列舉實體轉成其它型別的方法。 195 | impl SolarDate { 196 | /// 將 `SolarDate` 實體轉成無時區的 `Chrono` 年月日實體。 197 | /// 198 | /// # Examples 199 | /// 200 | /// ``` 201 | /// use chinese_lunisolar_calendar::SolarDate; 202 | /// use chrono::prelude::*; 203 | /// 204 | /// let date = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(); 205 | /// 206 | /// let solar_date = SolarDate::from_ymd(2024, 1, 1).unwrap(); 207 | /// 208 | /// assert_eq!(date, solar_date.to_naive_date()); 209 | /// ``` 210 | #[inline] 211 | pub const fn to_naive_date(self) -> NaiveDate { 212 | match NaiveDate::from_ymd_opt( 213 | self.solar_year.to_i32(), 214 | self.solar_month.to_u32(), 215 | self.solar_day.to_u32(), 216 | ) { 217 | Some(result) => result, 218 | None => unreachable!(), 219 | } 220 | } 221 | 222 | /// 取得西曆年。 223 | /// 224 | /// # Examples 225 | /// 226 | /// ``` 227 | /// use chinese_lunisolar_calendar::{SolarDate, SolarYear}; 228 | /// 229 | /// let solar_date = SolarDate::from_ymd(2024, 1, 1).unwrap(); 230 | /// 231 | /// assert_eq!(SolarYear::from_u16(2024), solar_date.to_solar_year()); 232 | /// ``` 233 | #[inline] 234 | pub const fn to_solar_year(self) -> SolarYear { 235 | self.solar_year 236 | } 237 | 238 | /// 取得西曆月。 239 | /// 240 | /// # Examples 241 | /// 242 | /// ``` 243 | /// use chinese_lunisolar_calendar::{SolarDate, SolarMonth}; 244 | /// 245 | /// let solar_date = SolarDate::from_ymd(2024, 1, 1).unwrap(); 246 | /// 247 | /// assert_eq!(SolarMonth::January, solar_date.to_solar_month()); 248 | /// ``` 249 | #[inline] 250 | pub const fn to_solar_month(self) -> SolarMonth { 251 | self.solar_month 252 | } 253 | 254 | /// 取得西曆日。 255 | /// 256 | /// ``` 257 | /// use chinese_lunisolar_calendar::{SolarDate, SolarDay}; 258 | /// 259 | /// let solar_date = SolarDate::from_ymd(2024, 1, 1).unwrap(); 260 | /// 261 | /// assert_eq!(SolarDay::First, solar_date.to_solar_day()); 262 | /// ``` 263 | #[inline] 264 | pub const fn to_solar_day(self) -> SolarDay { 265 | self.solar_day 266 | } 267 | } 268 | 269 | /// 西曆日期相關計算方法。 270 | impl SolarDate { 271 | /// 計算此西曆年月日是該西曆年的第幾天。舉例:2013-01-04,就是第四天。 272 | /// 273 | /// # Examples 274 | /// 275 | /// ``` 276 | /// # use chinese_lunisolar_calendar::SolarDate; 277 | /// assert_eq!( 278 | /// 4, 279 | /// SolarDate::from_ymd(2013, 1, 4).unwrap().the_n_day_in_this_year() 280 | /// ); 281 | /// assert_eq!( 282 | /// 308, 283 | /// SolarDate::from_ymd(2013, 11, 4).unwrap().the_n_day_in_this_year() 284 | /// ); 285 | /// ``` 286 | #[inline] 287 | pub const fn the_n_day_in_this_year(self) -> u16 { 288 | let mut n = 0u16; 289 | 290 | let leap_year = year_helper::is_leap_year(self.solar_year.to_i32()); 291 | 292 | let month = self.solar_month.to_u8(); 293 | 294 | let mut i = 1; 295 | 296 | while i < month { 297 | match year_helper::get_days_in_month_2(leap_year, i) { 298 | Some(days) => n += days as u16, 299 | None => unreachable!(), 300 | } 301 | 302 | i += 1; 303 | } 304 | 305 | n += self.solar_day.to_u8() as u16; 306 | 307 | n 308 | } 309 | 310 | /// 與其它的 `SolarDate` 結構實體進行大小比較。 311 | /// 312 | /// # Examples 313 | /// 314 | /// ``` 315 | /// use core::cmp::Ordering; 316 | /// 317 | /// use chinese_lunisolar_calendar::SolarDate; 318 | /// 319 | /// let solar_date_1 = SolarDate::from_ymd(2024, 1, 1).unwrap(); 320 | /// let solar_date_2 = SolarDate::from_ymd(2024, 1, 2).unwrap(); 321 | /// 322 | /// assert_eq!(Ordering::Less, solar_date_1.cmp(&solar_date_2)); 323 | /// ``` 324 | pub const fn cmp(&self, other: &SolarDate) -> Ordering { 325 | let year_s = self.solar_year.to_u16(); 326 | let year_o = other.solar_year.to_u16(); 327 | 328 | if year_s > year_o { 329 | return Ordering::Greater; 330 | } else if year_s < year_o { 331 | return Ordering::Less; 332 | } 333 | 334 | let month_s = self.solar_month.to_u8(); 335 | let month_o = other.solar_month.to_u8(); 336 | 337 | if month_s > month_o { 338 | return Ordering::Greater; 339 | } else if month_s < month_o { 340 | return Ordering::Less; 341 | } 342 | 343 | let day_s = self.solar_day.to_u8(); 344 | let day_o = other.solar_day.to_u8(); 345 | 346 | if day_s > day_o { 347 | Ordering::Greater 348 | } else if day_s == day_o { 349 | Ordering::Equal 350 | } else { 351 | Ordering::Less 352 | } 353 | } 354 | } 355 | -------------------------------------------------------------------------------- /src/lunisolar/year/mod.rs: -------------------------------------------------------------------------------- 1 | mod built_in_traits; 2 | use core::fmt::{self, Display, Formatter}; 3 | 4 | use super::{LunisolarOutOfRangeError, BIG_MONTHS, LEAP_MONTHS}; 5 | use crate::{EarthlyBranch, HeavenlyStems, LunarMonth, LunarYear, SolarYear, Zodiac}; 6 | 7 | /// 最小支援的農曆西曆年。 8 | pub const MIN_YEAR_IN_SOLAR_CALENDAR: u16 = 1901; 9 | /// 最大支援的農曆西曆年。 10 | pub const MAX_YEAR_IN_SOLAR_CALENDAR: u16 = 2101; 11 | 12 | /// 農曆西曆年,農曆新年所在的西曆年份。 13 | #[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)] 14 | pub struct LunisolarYear(SolarYear); 15 | 16 | impl Display for LunisolarYear { 17 | /// Formats the value using the given formatter. 18 | /// 19 | /// # Examples 20 | /// 21 | /// ``` 22 | /// use chinese_lunisolar_calendar::{LunisolarYear, SolarYear}; 23 | /// 24 | /// let lunisolar_year = 25 | /// LunisolarYear::from_solar_year(SolarYear::from_u16(2008)).unwrap(); 26 | /// 27 | /// assert_eq!("二〇〇八", format!("{}", lunisolar_year)); 28 | /// ``` 29 | #[inline] 30 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 31 | Display::fmt(&self.0, f) 32 | } 33 | } 34 | 35 | /// 用以建立 `LunisolarYear` 列舉實體的關聯函數。 36 | impl LunisolarYear { 37 | /// 透過西曆年份來取得 `LunisolarYear` 實體。 38 | /// 39 | /// # Examples 40 | /// 41 | /// ``` 42 | /// use chinese_lunisolar_calendar::{LunisolarYear, SolarYear}; 43 | /// 44 | /// let lunisolar_year = unsafe { 45 | /// LunisolarYear::from_solar_year_unsafe(SolarYear::from_u16(2024)) 46 | /// }; 47 | /// ``` 48 | /// 49 | /// # Safety 50 | /// 必須先確認傳入的西曆年份是支援的。 51 | #[inline] 52 | pub const unsafe fn from_solar_year_unsafe(solar_year: SolarYear) -> Self { 53 | Self(solar_year) 54 | } 55 | 56 | /// 透過西曆年份來取得 `LunisolarYear` 實體。 57 | /// 58 | /// # Examples 59 | /// 60 | /// ``` 61 | /// use chinese_lunisolar_calendar::{LunisolarYear, SolarYear}; 62 | /// 63 | /// let lunisolar_year = 64 | /// LunisolarYear::from_solar_year(SolarYear::from_u16(2024)).unwrap(); 65 | /// ``` 66 | #[inline] 67 | pub const fn from_solar_year(solar_year: SolarYear) -> Result { 68 | let year = solar_year.to_u16(); 69 | 70 | if year >= MIN_YEAR_IN_SOLAR_CALENDAR && year <= MAX_YEAR_IN_SOLAR_CALENDAR { 71 | Ok(Self(solar_year)) 72 | } else { 73 | Err(LunisolarOutOfRangeError) 74 | } 75 | } 76 | } 77 | 78 | /// 將 `LunisolarYear` 列舉實體轉成其它型別的方法。 79 | impl LunisolarYear { 80 | /// 取得此西曆年中,農曆新年的中國天干。 81 | /// 82 | /// # Examples 83 | /// 84 | /// ``` 85 | /// use chinese_lunisolar_calendar::{HeavenlyStems, LunisolarYear, SolarYear}; 86 | /// 87 | /// let lunisolar_year = 88 | /// LunisolarYear::from_solar_year(SolarYear::from_u16(2024)).unwrap(); 89 | /// 90 | /// assert_eq!(HeavenlyStems::First, lunisolar_year.to_heavenly_stems()); 91 | /// ``` 92 | #[inline] 93 | pub const fn to_heavenly_stems(self) -> HeavenlyStems { 94 | let index = ((7 + (self.0.to_u16() - MIN_YEAR_IN_SOLAR_CALENDAR)) % 10) as u8; 95 | 96 | unsafe { HeavenlyStems::from_ordinal_unsafe(index + 1) } 97 | } 98 | 99 | /// 取得此西曆年中,農曆新年的中國地支。 100 | /// 101 | /// # Examples 102 | /// 103 | /// ``` 104 | /// use chinese_lunisolar_calendar::{EarthlyBranch, LunisolarYear, SolarYear}; 105 | /// 106 | /// let lunisolar_year = 107 | /// LunisolarYear::from_solar_year(SolarYear::from_u16(2024)).unwrap(); 108 | /// 109 | /// assert_eq!(EarthlyBranch::Fifth, lunisolar_year.to_earthly_branch()); 110 | /// ``` 111 | #[inline] 112 | pub const fn to_earthly_branch(self) -> EarthlyBranch { 113 | let index = ((self.0.to_u16() - MIN_YEAR_IN_SOLAR_CALENDAR + 1) % 12) as u8; 114 | 115 | unsafe { EarthlyBranch::from_ordinal_unsafe(index + 1) } 116 | } 117 | 118 | /// 取得此西曆年中,農曆新年所屬的生肖。 119 | /// 120 | /// # Examples 121 | /// 122 | /// ``` 123 | /// use chinese_lunisolar_calendar::{LunisolarYear, SolarYear, Zodiac}; 124 | /// 125 | /// let lunisolar_year = 126 | /// LunisolarYear::from_solar_year(SolarYear::from_u16(2024)).unwrap(); 127 | /// 128 | /// assert_eq!(Zodiac::Dragon, lunisolar_year.to_zodiac()); 129 | /// ``` 130 | #[inline] 131 | pub const fn to_zodiac(self) -> Zodiac { 132 | let index = ((self.0.to_u16() - MIN_YEAR_IN_SOLAR_CALENDAR + 1) % 12) as u8; 133 | 134 | unsafe { Zodiac::from_ordinal_unsafe(index + 1) } 135 | } 136 | 137 | /// 取得 `LunarYear` 實體。 138 | /// 139 | /// # Examples 140 | /// 141 | /// ``` 142 | /// use chinese_lunisolar_calendar::{LunarYear, LunisolarYear, SolarYear}; 143 | /// 144 | /// let lunisolar_year = 145 | /// LunisolarYear::from_solar_year(SolarYear::from_u16(2024)).unwrap(); 146 | /// 147 | /// assert_eq!( 148 | /// LunarYear::parse_str("甲辰").unwrap(), 149 | /// lunisolar_year.to_lunar_year() 150 | /// ); 151 | /// ``` 152 | #[inline] 153 | pub const fn to_lunar_year(self) -> LunarYear { 154 | let heavenly_stems = self.to_heavenly_stems(); 155 | let earthly_branch = self.to_earthly_branch(); 156 | 157 | match LunarYear::from_era(heavenly_stems, earthly_branch) { 158 | Ok(lunar_year) => lunar_year, 159 | Err(_) => unreachable!(), 160 | } 161 | } 162 | 163 | /// 取得 `SolarYear` 實體。 164 | #[inline] 165 | pub const fn to_solar_year(self) -> SolarYear { 166 | self.0 167 | } 168 | 169 | /// 取得 `LunisolarYear` 實體所代表的西曆年份數值。 170 | #[inline] 171 | pub const fn to_u16(self) -> u16 { 172 | self.0.to_u16() 173 | } 174 | } 175 | 176 | /// 農曆西曆年相關計算方法。 177 | impl LunisolarYear { 178 | /// 取得此年的農曆閏月月份。如果沒有的話就回傳 `None`。 179 | /// 180 | /// # Examples 181 | /// 182 | /// ``` 183 | /// use chinese_lunisolar_calendar::{LunarMonth, LunisolarYear, SolarYear}; 184 | /// 185 | /// let lunisolar_year_2023 = 186 | /// LunisolarYear::from_solar_year(SolarYear::from_u16(2023)).unwrap(); 187 | /// 188 | /// let lunisolar_year_2024 = 189 | /// LunisolarYear::from_solar_year(SolarYear::from_u16(2024)).unwrap(); 190 | /// 191 | /// assert_eq!( 192 | /// Some(LunarMonth::LeapSecond), 193 | /// lunisolar_year_2023.get_leap_lunar_month() 194 | /// ); 195 | /// 196 | /// assert_eq!(None, lunisolar_year_2024.get_leap_lunar_month()); 197 | /// ``` 198 | #[inline] 199 | pub const fn get_leap_lunar_month(self) -> Option { 200 | let year = self.to_u16(); 201 | 202 | let date = LEAP_MONTHS[((year - MIN_YEAR_IN_SOLAR_CALENDAR) / 2) as usize]; 203 | 204 | let index = if year % 2 == 1 { (date & 0xF0) >> 4 } else { date & 0x0F }; 205 | 206 | if index == 0 { 207 | None 208 | } else { 209 | Some(unsafe { LunarMonth::from_u8_with_leap_unsafe(index, true) }) 210 | } 211 | } 212 | 213 | /// 計算此西曆年下的農曆閏月共有幾天。如果沒有閏月,則回傳 `0`。 214 | /// 215 | /// # Examples 216 | /// 217 | /// ``` 218 | /// use chinese_lunisolar_calendar::{LunisolarYear, SolarYear}; 219 | /// 220 | /// let lunisolar_year_2023 = 221 | /// LunisolarYear::from_solar_year(SolarYear::from_u16(2023)).unwrap(); 222 | /// 223 | /// let lunisolar_year_2024 = 224 | /// LunisolarYear::from_solar_year(SolarYear::from_u16(2024)).unwrap(); 225 | /// 226 | /// assert_eq!(29, lunisolar_year_2023.get_total_days_in_leap_month()); 227 | /// 228 | /// assert_eq!(0, lunisolar_year_2024.get_total_days_in_leap_month()); 229 | /// ``` 230 | #[inline] 231 | pub const fn get_total_days_in_leap_month(self) -> u16 { 232 | let leap_lunar_month = self.get_leap_lunar_month(); 233 | 234 | match leap_lunar_month { 235 | Some(leap_lunar_month) => self.get_total_days_in_leap_month_inner(leap_lunar_month), 236 | None => 0, 237 | } 238 | } 239 | 240 | /// 計算指定的農曆閏月共有幾天。 241 | #[inline] 242 | pub(crate) const fn get_total_days_in_leap_month_inner( 243 | self, 244 | leap_lunar_month: LunarMonth, 245 | ) -> u16 { 246 | let year = self.to_u16(); 247 | 248 | let leap_month = leap_lunar_month.to_u8(); 249 | 250 | if BIG_MONTHS[(year - MIN_YEAR_IN_SOLAR_CALENDAR) as usize] & (0x8000 >> leap_month as u16) 251 | == 0 252 | { 253 | 29 254 | } else { 255 | 30 256 | } 257 | } 258 | 259 | /// 計算此西曆年下的農曆年共有幾天。 260 | /// 261 | /// # Examples 262 | /// 263 | /// ``` 264 | /// use chinese_lunisolar_calendar::{LunisolarYear, SolarYear}; 265 | /// 266 | /// let lunisolar_year_2023 = 267 | /// LunisolarYear::from_solar_year(SolarYear::from_u16(2023)).unwrap(); 268 | /// 269 | /// let lunisolar_year_2024 = 270 | /// LunisolarYear::from_solar_year(SolarYear::from_u16(2024)).unwrap(); 271 | /// 272 | /// assert_eq!(384, lunisolar_year_2023.get_total_days()); 273 | /// 274 | /// assert_eq!(354, lunisolar_year_2024.get_total_days()); 275 | /// ``` 276 | pub const fn get_total_days(self) -> u16 { 277 | let (leap_month, mut n) = match self.get_leap_lunar_month() { 278 | Some(leap_lunar_month) => ( 279 | leap_lunar_month.to_u8(), 280 | self.get_total_days_in_leap_month_inner(leap_lunar_month), 281 | ), 282 | None => (0, 0), 283 | }; 284 | 285 | let year = self.to_u16(); 286 | 287 | let bit_months = BIG_MONTHS[(year - MIN_YEAR_IN_SOLAR_CALENDAR) as usize]; 288 | 289 | if leap_month > 0 { 290 | let mut date = 1; 291 | 292 | while date <= leap_month { 293 | if bit_months & (0x8000 >> (date - 1) as u16) == 0 { 294 | n += 29; 295 | } else { 296 | n += 30; 297 | } 298 | 299 | date += 1; 300 | } 301 | 302 | while date <= 12 { 303 | if bit_months & (0x8000 >> (date as u16)) == 0 { 304 | n += 29; 305 | } else { 306 | n += 30; 307 | } 308 | 309 | date += 1; 310 | } 311 | } else { 312 | let mut date = 1; 313 | 314 | while date <= 12 { 315 | if bit_months & (0x8000 >> (date - 1) as u16) == 0 { 316 | n += 29; 317 | } else { 318 | n += 30; 319 | } 320 | 321 | date += 1; 322 | } 323 | } 324 | 325 | n 326 | } 327 | 328 | /// 計算此西曆年下的農曆年的某個月共有幾天。如果傳入的是閏月,但是該年沒有該閏月的話就回傳 `None`。 329 | /// 330 | /// # Examples 331 | /// 332 | /// ``` 333 | /// use chinese_lunisolar_calendar::{LunarMonth, LunisolarYear, SolarYear}; 334 | /// 335 | /// let lunisolar_year_2023 = 336 | /// LunisolarYear::from_solar_year(SolarYear::from_u16(2023)).unwrap(); 337 | /// 338 | /// let lunisolar_year_2024 = 339 | /// LunisolarYear::from_solar_year(SolarYear::from_u16(2024)).unwrap(); 340 | /// 341 | /// assert_eq!( 342 | /// Some(29), 343 | /// lunisolar_year_2023.get_total_days_in_a_month(LunarMonth::First) 344 | /// ); 345 | /// assert_eq!( 346 | /// Some(30), 347 | /// lunisolar_year_2023.get_total_days_in_a_month(LunarMonth::Second) 348 | /// ); 349 | /// assert_eq!( 350 | /// Some(29), 351 | /// lunisolar_year_2023.get_total_days_in_a_month(LunarMonth::LeapSecond) 352 | /// ); 353 | /// assert_eq!( 354 | /// Some(29), 355 | /// lunisolar_year_2023.get_total_days_in_a_month(LunarMonth::Third) 356 | /// ); 357 | /// assert_eq!( 358 | /// None, 359 | /// lunisolar_year_2023.get_total_days_in_a_month(LunarMonth::LeapThird) 360 | /// ); 361 | /// 362 | /// assert_eq!( 363 | /// Some(29), 364 | /// lunisolar_year_2024.get_total_days_in_a_month(LunarMonth::First) 365 | /// ); 366 | /// assert_eq!( 367 | /// Some(30), 368 | /// lunisolar_year_2024.get_total_days_in_a_month(LunarMonth::Second) 369 | /// ); 370 | /// assert_eq!( 371 | /// None, 372 | /// lunisolar_year_2024.get_total_days_in_a_month(LunarMonth::LeapSecond) 373 | /// ); 374 | /// assert_eq!( 375 | /// Some(29), 376 | /// lunisolar_year_2024.get_total_days_in_a_month(LunarMonth::Third) 377 | /// ); 378 | /// assert_eq!( 379 | /// None, 380 | /// lunisolar_year_2024.get_total_days_in_a_month(LunarMonth::LeapThird) 381 | /// ); 382 | /// ``` 383 | pub const fn get_total_days_in_a_month(self, lunar_month: LunarMonth) -> Option { 384 | let year = self.to_u16(); 385 | 386 | let mut month = lunar_month.to_u8(); 387 | 388 | let leap_month = match self.get_leap_lunar_month() { 389 | Some(leap_lunar_month) => leap_lunar_month.to_u8(), 390 | None => 0, 391 | }; 392 | 393 | if lunar_month.is_leap_month() { 394 | if month != leap_month { 395 | // 防呆 396 | None 397 | } else { 398 | // 此為閏月,需計算其後一個月的天數 399 | if (BIG_MONTHS[(year - MIN_YEAR_IN_SOLAR_CALENDAR) as usize] 400 | & (0x8000 >> leap_month as u16)) 401 | == 0 402 | { 403 | Some(29) 404 | } else { 405 | Some(30) 406 | } 407 | } 408 | } else { 409 | if leap_month > 0 && month > leap_month { 410 | // 若今年有閏月,且該西曆月應在閏月之後再加一個月 411 | month += 1; 412 | } 413 | if BIG_MONTHS[(year - MIN_YEAR_IN_SOLAR_CALENDAR) as usize] 414 | & (0x8000 >> (month - 1) as u16) 415 | == 0 416 | { 417 | Some(29) 418 | } else { 419 | Some(30) 420 | } 421 | } 422 | } 423 | } 424 | 425 | /// 額外的實作。 426 | impl LunarMonth { 427 | /// 傳入指定的農曆西曆年,並計算此農曆月在這個指定的農曆西曆年內共有幾天。如果自己本身是閏月,但是該年沒有該閏月的話就回傳 None。 428 | /// 429 | /// # Examples 430 | /// 431 | /// ``` 432 | /// use chinese_lunisolar_calendar::{LunarMonth, LunisolarYear, SolarYear}; 433 | /// 434 | /// let lunisolar_year_2023 = 435 | /// LunisolarYear::from_solar_year(SolarYear::from_u16(2023)).unwrap(); 436 | /// 437 | /// let lunisolar_year_2024 = 438 | /// LunisolarYear::from_solar_year(SolarYear::from_u16(2024)).unwrap(); 439 | /// 440 | /// assert_eq!(Some(29), LunarMonth::First.get_total_days(lunisolar_year_2023)); 441 | /// assert_eq!( 442 | /// Some(30), 443 | /// LunarMonth::Second.get_total_days(lunisolar_year_2023) 444 | /// ); 445 | /// assert_eq!( 446 | /// Some(29), 447 | /// LunarMonth::LeapSecond.get_total_days(lunisolar_year_2023) 448 | /// ); 449 | /// assert_eq!(Some(29), LunarMonth::Third.get_total_days(lunisolar_year_2023)); 450 | /// assert_eq!(None, LunarMonth::LeapThird.get_total_days(lunisolar_year_2023)); 451 | /// 452 | /// assert_eq!(Some(29), LunarMonth::First.get_total_days(lunisolar_year_2024)); 453 | /// assert_eq!( 454 | /// Some(30), 455 | /// LunarMonth::Second.get_total_days(lunisolar_year_2024) 456 | /// ); 457 | /// assert_eq!( 458 | /// None, 459 | /// LunarMonth::LeapSecond.get_total_days(lunisolar_year_2024) 460 | /// ); 461 | /// assert_eq!(Some(29), LunarMonth::Third.get_total_days(lunisolar_year_2024)); 462 | /// assert_eq!(None, LunarMonth::LeapThird.get_total_days(lunisolar_year_2024)); 463 | /// ``` 464 | #[inline] 465 | pub const fn get_total_days(self, lunisolar_year: LunisolarYear) -> Option { 466 | lunisolar_year.get_total_days_in_a_month(self) 467 | } 468 | } 469 | -------------------------------------------------------------------------------- /src/lunisolar/date/mod.rs: -------------------------------------------------------------------------------- 1 | mod built_in_traits; 2 | 3 | #[cfg(feature = "ba-zi-weight")] 4 | mod ba_zi_weight; 5 | mod parse; 6 | 7 | use core::{ 8 | cmp::Ordering, 9 | fmt::{self, Display, Formatter, Write}, 10 | }; 11 | 12 | use chrono::prelude::*; 13 | 14 | use super::{LunisolarDateError, LunisolarOutOfRangeError, LunisolarYear, NEW_YEAR_DIFFERENCE}; 15 | use crate::{ 16 | LunarDay, LunarMonth, LunarYear, SolarDate, SolarDay, SolarMonth, SolarYear, 17 | MAX_YEAR_IN_SOLAR_CALENDAR, MIN_YEAR_IN_SOLAR_CALENDAR, 18 | }; 19 | 20 | /// 最小支援的農曆日期(以西曆日期表示):1901-02-19。 21 | pub const MIN_LUNISOLAR_DATE_IN_SOLAR_DATE: SolarDate = unsafe { 22 | SolarDate::from_solar_year_month_day_unsafe( 23 | SolarYear::from_u16(1901), 24 | SolarMonth::from_u8_unsafe(2), 25 | SolarDay::from_u8_unsafe(19), 26 | ) 27 | }; 28 | 29 | /// 最大支援的農曆日期(以西曆日期表示):2101-01-28。 30 | pub const MAX_LUNISOLAR_DATE_IN_SOLAR_DATE: SolarDate = unsafe { 31 | SolarDate::from_solar_year_month_day_unsafe( 32 | SolarYear::from_u16(2101), 33 | SolarMonth::from_u8_unsafe(1), 34 | SolarDay::from_u8_unsafe(28), 35 | ) 36 | }; 37 | 38 | const MAX_LUNISOLAR_DATE_IN_SOLAR_CALENDAR_NEW_YEAR_DIFFERENCE: u16 = 39 | MAX_LUNISOLAR_DATE_IN_SOLAR_DATE.the_n_day_in_this_year(); 40 | 41 | /// 農曆年月日,必須包含西曆年。 42 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 43 | pub struct LunisolarDate { 44 | solar_year: SolarYear, 45 | lunisolar_year: LunisolarYear, 46 | lunar_month: LunarMonth, 47 | lunar_day: LunarDay, 48 | } 49 | 50 | impl PartialOrd for LunisolarDate { 51 | #[inline] 52 | fn partial_cmp(&self, other: &LunisolarDate) -> Option { 53 | Some(self.cmp(other)) 54 | } 55 | } 56 | 57 | impl Ord for LunisolarDate { 58 | #[inline] 59 | fn cmp(&self, other: &LunisolarDate) -> Ordering { 60 | if self.lunisolar_year > other.lunisolar_year || self.lunar_month > other.lunar_month { 61 | Ordering::Greater 62 | } else { 63 | self.lunar_day.cmp(&other.lunar_day) 64 | } 65 | } 66 | } 67 | 68 | impl Display for LunisolarDate { 69 | /// Formats the value using the given formatter. 70 | /// 71 | /// # Examples 72 | /// 73 | /// ``` 74 | /// use chinese_lunisolar_calendar::{LunisolarDate, SolarDate}; 75 | /// 76 | /// let solar_date = SolarDate::from_ymd(2024, 2, 10).unwrap(); 77 | /// 78 | /// let lunisolar_date = LunisolarDate::from_solar_date(solar_date).unwrap(); 79 | /// 80 | /// assert_eq!("二〇二四 甲辰、龍年 正月 初一", format!("{lunisolar_date}")); 81 | /// assert_eq!("二〇二四 甲辰、龙年 正月 初一", format!("{lunisolar_date:#}")); 82 | /// ``` 83 | #[inline] 84 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 85 | Display::fmt(&self.to_lunisolar_year(), f)?; 86 | f.write_char(' ')?; 87 | Display::fmt(&self.to_lunar_year(), f)?; 88 | f.write_char('、')?; 89 | Display::fmt(&self.to_lunisolar_year().to_zodiac(), f)?; 90 | f.write_char('年')?; 91 | f.write_char(' ')?; 92 | Display::fmt(&self.to_lunar_month(), f)?; 93 | f.write_char(' ')?; 94 | Display::fmt(&self.to_lunar_day(), f) 95 | } 96 | } 97 | 98 | /// 用以建立 `LunisolarDate` 列舉實體的關聯函數。 99 | impl LunisolarDate { 100 | /// 將西曆年月日轉成農曆年月日(包含西曆年)。 101 | /// 102 | /// # Examples 103 | /// 104 | /// ``` 105 | /// use chinese_lunisolar_calendar::{LunisolarDate, SolarDate}; 106 | /// 107 | /// let solar_date = SolarDate::from_ymd(2024, 1, 1).unwrap(); 108 | /// 109 | /// let lunisolar_date = LunisolarDate::from_solar_date(solar_date).unwrap(); 110 | /// ``` 111 | pub const fn from_solar_date( 112 | solar_date: SolarDate, 113 | ) -> Result { 114 | if solar_date.is_safe() { 115 | let solar_year = solar_date.to_solar_year(); 116 | 117 | let year = solar_year.to_u16(); 118 | 119 | let lunisolar_year = unsafe { LunisolarYear::from_solar_year_unsafe(solar_year) }; 120 | 121 | let mut day_diff = solar_date.the_n_day_in_this_year() - 1; 122 | 123 | let (leap_month, leap_days, new_year_diff) = if year == MAX_YEAR_IN_SOLAR_CALENDAR { 124 | (0, 0, MAX_LUNISOLAR_DATE_IN_SOLAR_CALENDAR_NEW_YEAR_DIFFERENCE) 125 | } else { 126 | let new_year_diff = 127 | NEW_YEAR_DIFFERENCE[(year - MIN_YEAR_IN_SOLAR_CALENDAR) as usize] as u16; 128 | 129 | let leap_lunar_month = lunisolar_year.get_leap_lunar_month(); 130 | 131 | match leap_lunar_month { 132 | Some(leap_lunar_month) => ( 133 | leap_lunar_month.to_u8(), 134 | lunisolar_year.get_total_days_in_leap_month_inner(leap_lunar_month), 135 | new_year_diff, 136 | ), 137 | None => (0, 0, new_year_diff), 138 | } 139 | }; 140 | 141 | if day_diff < new_year_diff { 142 | // 若天數差距比「西曆新年與對應農曆年新年」之天數差距小,表示此西曆日期尚未進入下一個農曆年(尚未到達正月,還在農曆年尾)。 143 | let lunisolar_year = 144 | unsafe { LunisolarYear::from_solar_year_unsafe(SolarYear::from_u16(year - 1)) }; 145 | 146 | let leap_lunar_month = lunisolar_year.get_leap_lunar_month(); 147 | 148 | let (leap_month, leap_days) = match leap_lunar_month { 149 | Some(leap_lunar_month) => ( 150 | leap_lunar_month.to_u8(), 151 | lunisolar_year.get_total_days_in_leap_month_inner(leap_lunar_month), 152 | ), 153 | None => (0, 0), 154 | }; 155 | 156 | let mut day_diff = new_year_diff - day_diff; // 此時天數差距為此西曆日期到該西曆年應該對應的農曆年新年之差距天數(若西曆日期為2/3,農曆新年對應的西曆日期為2/10,則兩天數差距為40-(30+3)=7)。 157 | 158 | let mut is_leap = false; 159 | 160 | let mut month = 12; 161 | 162 | loop { 163 | if month == leap_month { 164 | if day_diff > leap_days { 165 | day_diff -= leap_days; 166 | } else { 167 | is_leap = true; 168 | } 169 | } 170 | 171 | let month_days = match lunisolar_year.get_total_days_in_a_month(unsafe { 172 | LunarMonth::from_u8_with_leap_unsafe(month, false) 173 | }) { 174 | Some(days) => days as u16, 175 | None => unreachable!(), 176 | }; 177 | 178 | if day_diff <= month_days { 179 | break; 180 | } 181 | 182 | day_diff -= month_days; 183 | is_leap = false; 184 | month -= 1; 185 | } 186 | 187 | if day_diff == 0 { 188 | Ok(LunisolarDate { 189 | solar_year, 190 | lunisolar_year, 191 | lunar_month: unsafe { 192 | LunarMonth::from_u8_with_leap_unsafe((month + 1) % 12 + 1, is_leap) 193 | }, 194 | lunar_day: unsafe { LunarDay::from_u8_unsafe(1) }, 195 | }) 196 | } else { 197 | let lunar_month = 198 | unsafe { LunarMonth::from_u8_with_leap_unsafe(month, is_leap) }; 199 | 200 | Ok(LunisolarDate { 201 | solar_year, 202 | lunisolar_year, 203 | lunar_month, 204 | lunar_day: unsafe { 205 | LunarDay::from_u8_unsafe( 206 | match lunar_month.get_total_days(lunisolar_year) { 207 | Some(days) => days, 208 | None => unreachable!(), 209 | } - day_diff as u8 210 | + 1, 211 | ) 212 | }, 213 | }) 214 | } 215 | } else { 216 | // 若天數差距沒比「西曆新年與對應農曆年新年」之天數差距小,表示此西曆日期已經進入下一個農曆年(已到達正月,從在農曆年頭開始)。 217 | day_diff -= new_year_diff; // 此時天數差距為西曆日期與對應農曆年第一天之距離(若西曆日期為2/23(此時天數差距為53),而對應農曆年第一天是西曆的2/10(新年偏差為40),則這兩個日期的天數差距為53-40=13)。 218 | 219 | let mut is_leap = false; 220 | 221 | let mut month = 1; 222 | 223 | loop { 224 | let month_days = match lunisolar_year.get_total_days_in_a_month(unsafe { 225 | LunarMonth::from_u8_with_leap_unsafe(month, false) 226 | }) { 227 | Some(days) => days as u16, 228 | None => unreachable!(), 229 | }; 230 | 231 | if day_diff < month_days { 232 | break; 233 | } 234 | 235 | day_diff -= month_days; 236 | 237 | if month == leap_month { 238 | if day_diff < leap_days { 239 | is_leap = true; 240 | break; 241 | } else { 242 | day_diff -= leap_days; 243 | } 244 | } 245 | 246 | month += 1; 247 | } 248 | 249 | Ok(LunisolarDate { 250 | solar_year, 251 | lunisolar_year, 252 | lunar_month: unsafe { LunarMonth::from_u8_with_leap_unsafe(month, is_leap) }, 253 | lunar_day: unsafe { LunarDay::from_u8_unsafe(day_diff as u8 + 1) }, 254 | }) 255 | } 256 | } else { 257 | Err(LunisolarOutOfRangeError) 258 | } 259 | } 260 | 261 | /// 將日期轉成農曆年月日(包含西曆年)。 262 | /// 263 | /// # Examples 264 | /// 265 | /// ``` 266 | /// use chinese_lunisolar_calendar::LunisolarDate; 267 | /// use chrono::prelude::*; 268 | /// 269 | /// let date = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(); 270 | /// 271 | /// let lunisolar_date = LunisolarDate::from_date(date).unwrap(); 272 | /// ``` 273 | #[inline] 274 | pub fn from_date(date: D) -> Result { 275 | let solar_date = SolarDate::from_date(date).map_err(|_| LunisolarOutOfRangeError)?; 276 | 277 | Self::from_solar_date(solar_date) 278 | } 279 | 280 | /// 利用農曆西曆年和農曆月日來產生 `LunisolarDate` 實體。 281 | /// 282 | /// # Examples 283 | /// 284 | /// ``` 285 | /// use chinese_lunisolar_calendar::{ 286 | /// LunarDay, LunarMonth, LunisolarDate, LunisolarYear, SolarYear, 287 | /// }; 288 | /// 289 | /// let lunisolar_date = unsafe { 290 | /// LunisolarDate::from_lunisolar_year_lunar_month_day_unsafe( 291 | /// LunisolarYear::from_solar_year(SolarYear::from_u16(2024)).unwrap(), 292 | /// LunarMonth::First, 293 | /// LunarDay::First, 294 | /// ) 295 | /// }; 296 | /// ``` 297 | /// 298 | /// # Safety 299 | /// 請先確保傳入的農曆西曆年和農曆月日組合是正確的。 300 | #[inline] 301 | pub const unsafe fn from_lunisolar_year_lunar_month_day_unsafe( 302 | lunisolar_year: LunisolarYear, 303 | lunar_month: LunarMonth, 304 | lunar_day: LunarDay, 305 | ) -> LunisolarDate { 306 | let n = Self::the_n_day_in_this_year_inner(lunisolar_year, lunar_month, lunar_day); 307 | 308 | let solar_year = lunisolar_year.to_solar_year(); 309 | 310 | let year = solar_year.to_u16(); 311 | 312 | let solar_year = if n + NEW_YEAR_DIFFERENCE[(year - MIN_YEAR_IN_SOLAR_CALENDAR) as usize] 313 | as u16 314 | >= solar_year.get_total_days() 315 | { 316 | SolarYear::from_u16(year + 1) 317 | } else { 318 | solar_year 319 | }; 320 | 321 | LunisolarDate { 322 | solar_year, 323 | lunisolar_year, 324 | lunar_month, 325 | lunar_day, 326 | } 327 | } 328 | 329 | /// 利用農曆西曆年和農曆月日來產生 `LunisolarDate` 實體。 330 | /// 331 | /// # Examples 332 | /// 333 | /// ``` 334 | /// use chinese_lunisolar_calendar::{ 335 | /// LunarDay, LunarMonth, LunisolarDate, LunisolarYear, SolarYear, 336 | /// }; 337 | /// 338 | /// let lunisolar_date = LunisolarDate::from_lunisolar_year_lunar_month_day( 339 | /// LunisolarYear::from_solar_year(SolarYear::from_u16(2024)).unwrap(), 340 | /// LunarMonth::First, 341 | /// LunarDay::First, 342 | /// ) 343 | /// .unwrap(); 344 | /// ``` 345 | #[inline] 346 | pub const fn from_lunisolar_year_lunar_month_day( 347 | lunisolar_year: LunisolarYear, 348 | lunar_month: LunarMonth, 349 | lunar_day: LunarDay, 350 | ) -> Result { 351 | if lunar_month.is_leap_month() { 352 | let leap_lunar_month = lunisolar_year.get_leap_lunar_month(); 353 | 354 | match leap_lunar_month { 355 | Some(leap_lunar_month) => { 356 | if lunar_month.to_u8() != leap_lunar_month.to_u8() { 357 | return Err(LunisolarDateError::MonthIncorrect); 358 | } 359 | }, 360 | None => return Err(LunisolarDateError::MonthIncorrect), 361 | } 362 | } 363 | 364 | let days = match lunar_month.get_total_days(lunisolar_year) { 365 | Some(days) => days, 366 | None => unreachable!(), 367 | }; 368 | 369 | if lunar_day.to_u8() > days { 370 | Err(LunisolarDateError::DayIncorrect) 371 | } else { 372 | Ok(unsafe { 373 | Self::from_lunisolar_year_lunar_month_day_unsafe( 374 | lunisolar_year, 375 | lunar_month, 376 | lunar_day, 377 | ) 378 | }) 379 | } 380 | } 381 | 382 | /// 利用**農曆西曆年**和**農曆月日**來產生 `LunisolarDate` 實體。 383 | /// 384 | /// # Examples 385 | /// 386 | /// ``` 387 | /// # use chinese_lunisolar_calendar::LunisolarDate; 388 | /// 389 | /// let lunisolar_date = LunisolarDate::from_ymd(2023, 11, false, 20).unwrap(); 390 | /// ``` 391 | #[inline] 392 | pub const fn from_ymd( 393 | year: u16, 394 | month: u8, 395 | leap: bool, 396 | day: u8, 397 | ) -> Result { 398 | let lunisolar_year = match LunisolarYear::from_solar_year(SolarYear::from_u16(year)) { 399 | Ok(lunisolar_year) => lunisolar_year, 400 | Err(_) => return Err(LunisolarDateError::OutOfRange), 401 | }; 402 | 403 | let lunar_month = match LunarMonth::from_u8_with_leap(month, leap) { 404 | Ok(lunar_month) => lunar_month, 405 | Err(_) => return Err(LunisolarDateError::MonthIncorrect), 406 | }; 407 | 408 | let lunar_day = match LunarDay::from_u8(day) { 409 | Ok(lunar_day) => lunar_day, 410 | Err(_) => return Err(LunisolarDateError::DayIncorrect), 411 | }; 412 | 413 | Self::from_lunisolar_year_lunar_month_day(lunisolar_year, lunar_month, lunar_day) 414 | } 415 | 416 | /// 以目前的年月日來產生 `LunisolarDate` 實體。 417 | /// 418 | /// # Examples 419 | /// 420 | /// ``` 421 | /// # use chinese_lunisolar_calendar::LunisolarDate; 422 | /// let lunisolar_date = LunisolarDate::now().unwrap(); 423 | /// ``` 424 | #[inline] 425 | pub fn now() -> Result { 426 | Self::from_date(Utc::now().date_naive()) 427 | } 428 | } 429 | 430 | /// 將 `LunisolarDate` 列舉實體轉成其它型別的方法。 431 | impl LunisolarDate { 432 | /// 轉成西曆年月日。 433 | /// 434 | /// # Examples 435 | /// 436 | /// ``` 437 | /// use chinese_lunisolar_calendar::{LunisolarDate, SolarDate}; 438 | /// 439 | /// let solar_date = SolarDate::from_ymd(2024, 2, 10).unwrap(); 440 | /// 441 | /// let lunisolar_date = LunisolarDate::from_solar_date(solar_date).unwrap(); 442 | /// 443 | /// assert_eq!(solar_date, lunisolar_date.to_solar_date()); 444 | /// ``` 445 | #[inline] 446 | pub const fn to_solar_date(self) -> SolarDate { 447 | let n = self.the_n_day_in_this_year(); 448 | 449 | let ly = self.lunisolar_year.to_u16(); 450 | 451 | // 天數差距為該農曆日期與對應西曆年新年的天數差距。其實就是轉換成西曆日期後,西曆日期與新年的距離。(舉例,農曆2012-01-03,為第3天,和農曆新年差了2天。加上西曆農曆偏差52天。因此天數差距為54) 452 | let mut days_diff = 453 | n - 1 + NEW_YEAR_DIFFERENCE[(ly - MIN_YEAR_IN_SOLAR_CALENDAR) as usize] as u16; 454 | 455 | let mut solar_year = SolarYear::from_u16(ly); 456 | 457 | let mut month = 1; 458 | 459 | let mut solar_month = unsafe { SolarMonth::from_u8_unsafe(month) }; 460 | 461 | let mut month_days = solar_month.get_total_days(solar_year) as u16; 462 | 463 | while days_diff >= month_days { 464 | days_diff -= month_days; 465 | 466 | if month == 12 { 467 | solar_year = SolarYear::from_u16(solar_year.to_u16() + 1); 468 | 469 | month = 1; 470 | } else { 471 | month += 1; 472 | } 473 | 474 | solar_month = unsafe { SolarMonth::from_u8_unsafe(month) }; 475 | 476 | month_days = solar_month.get_total_days(solar_year) as u16; 477 | } 478 | 479 | SolarDate { 480 | solar_year, 481 | solar_month, 482 | solar_day: unsafe { SolarDay::from_u8_unsafe(days_diff as u8 + 1) }, 483 | } 484 | } 485 | 486 | /// 將 `LunisolarDate` 實體轉成無時區的 `Chrono` 年月日實體。 487 | /// 488 | /// # Examples 489 | /// 490 | /// ``` 491 | /// use chinese_lunisolar_calendar::{LunisolarDate, SolarDate}; 492 | /// use chrono::prelude::*; 493 | /// 494 | /// let date = NaiveDate::from_ymd_opt(2024, 2, 10).unwrap(); 495 | /// 496 | /// let solar_date = SolarDate::from_ymd(2024, 2, 10).unwrap(); 497 | /// let lunisolar_date = LunisolarDate::from_solar_date(solar_date).unwrap(); 498 | /// 499 | /// assert_eq!(date, lunisolar_date.to_naive_date()); 500 | /// ``` 501 | #[inline] 502 | pub const fn to_naive_date(self) -> NaiveDate { 503 | self.to_solar_date().to_naive_date() 504 | } 505 | 506 | /// 取得西曆年。 507 | /// 508 | /// # Examples 509 | /// 510 | /// ``` 511 | /// use chinese_lunisolar_calendar::{LunisolarDate, SolarDate}; 512 | /// 513 | /// let solar_date = SolarDate::from_ymd(2024, 2, 10).unwrap(); 514 | /// 515 | /// let lunisolar_date = LunisolarDate::from_solar_date(solar_date).unwrap(); 516 | /// 517 | /// assert_eq!(solar_date.to_solar_year(), lunisolar_date.to_solar_year()); 518 | /// ``` 519 | #[inline] 520 | pub const fn to_solar_year(self) -> SolarYear { 521 | self.solar_year 522 | } 523 | 524 | /// 取得農曆西曆年。 525 | /// 526 | /// # Examples 527 | /// 528 | /// ``` 529 | /// use chinese_lunisolar_calendar::{ 530 | /// LunisolarDate, LunisolarYear, SolarDate, SolarYear, 531 | /// }; 532 | /// 533 | /// let solar_date = SolarDate::from_ymd(2024, 2, 10).unwrap(); 534 | /// 535 | /// let lunisolar_date = LunisolarDate::from_solar_date(solar_date).unwrap(); 536 | /// 537 | /// assert_eq!( 538 | /// LunisolarYear::from_solar_year(SolarYear::from_u16(2024)).unwrap(), 539 | /// lunisolar_date.to_lunisolar_year() 540 | /// ); 541 | /// ``` 542 | #[inline] 543 | pub const fn to_lunisolar_year(self) -> LunisolarYear { 544 | self.lunisolar_year 545 | } 546 | 547 | /// 取得農曆年。 548 | /// 549 | /// # Examples 550 | /// 551 | /// ``` 552 | /// use chinese_lunisolar_calendar::{LunarYear, LunisolarDate, SolarDate}; 553 | /// 554 | /// let solar_date = SolarDate::from_ymd(2024, 2, 10).unwrap(); 555 | /// 556 | /// let lunisolar_date = LunisolarDate::from_solar_date(solar_date).unwrap(); 557 | /// 558 | /// assert_eq!( 559 | /// LunarYear::parse_str("甲辰").unwrap(), 560 | /// lunisolar_date.to_lunar_year() 561 | /// ); 562 | /// ``` 563 | #[inline] 564 | pub const fn to_lunar_year(self) -> LunarYear { 565 | self.lunisolar_year.to_lunar_year() 566 | } 567 | 568 | /// 取得農曆月。 569 | /// 570 | /// # Examples 571 | /// 572 | /// ``` 573 | /// use chinese_lunisolar_calendar::{LunarMonth, LunisolarDate, SolarDate}; 574 | /// 575 | /// let solar_date = SolarDate::from_ymd(2024, 2, 10).unwrap(); 576 | /// 577 | /// let lunisolar_date = LunisolarDate::from_solar_date(solar_date).unwrap(); 578 | /// 579 | /// assert_eq!(LunarMonth::First, lunisolar_date.to_lunar_month()); 580 | /// ``` 581 | #[inline] 582 | pub const fn to_lunar_month(self) -> LunarMonth { 583 | self.lunar_month 584 | } 585 | 586 | /// 取得農曆日。 587 | /// 588 | /// # Examples 589 | /// 590 | /// ``` 591 | /// use chinese_lunisolar_calendar::{LunarDay, LunisolarDate, SolarDate}; 592 | /// 593 | /// let solar_date = SolarDate::from_ymd(2024, 2, 10).unwrap(); 594 | /// 595 | /// let lunisolar_date = LunisolarDate::from_solar_date(solar_date).unwrap(); 596 | /// 597 | /// assert_eq!(LunarDay::First, lunisolar_date.to_lunar_day()); 598 | /// ``` 599 | #[inline] 600 | pub const fn to_lunar_day(self) -> LunarDay { 601 | self.lunar_day 602 | } 603 | } 604 | 605 | /// 農曆年月日相關計算方法。 606 | impl LunisolarDate { 607 | /// 計算此農曆年月日是該農曆年的第幾天。舉例:2013/正月/初五,就是第五天。 608 | #[inline] 609 | pub(crate) const fn the_n_day_in_this_year_inner( 610 | lunisolar_year: LunisolarYear, 611 | lunar_month: LunarMonth, 612 | lunar_day: LunarDay, 613 | ) -> u16 { 614 | let mut n = lunar_day.to_u8() as u16; 615 | 616 | let month = lunar_month.to_u8(); 617 | 618 | if lunar_month.is_leap_month() { 619 | let mut i = 1; 620 | 621 | while i <= month { 622 | let lunar_month = unsafe { LunarMonth::from_u8_with_leap_unsafe(i, false) }; 623 | 624 | n += match lunar_month.get_total_days(lunisolar_year) { 625 | Some(days) => days as u16, 626 | None => unreachable!(), 627 | }; 628 | 629 | i += 1; 630 | } 631 | } else { 632 | let mut i = 1; 633 | 634 | while i < month { 635 | let lunar_month = unsafe { LunarMonth::from_u8_with_leap_unsafe(i, false) }; 636 | 637 | n += match lunar_month.get_total_days(lunisolar_year) { 638 | Some(days) => days as u16, 639 | None => unreachable!(), 640 | }; 641 | 642 | i += 1; 643 | } 644 | 645 | if let Some(leap_lunar_month) = lunisolar_year.get_leap_lunar_month() { 646 | if month > leap_lunar_month.to_u8() { 647 | n += lunisolar_year.get_total_days_in_leap_month_inner(leap_lunar_month); 648 | } 649 | } 650 | } 651 | 652 | n 653 | } 654 | 655 | /// 計算此農曆年月日是該農曆年的第幾天。舉例:2013/正月/初五,就是第五天。 656 | /// 657 | /// # Examples 658 | /// 659 | /// ``` 660 | /// use chinese_lunisolar_calendar::{LunisolarDate, SolarDate}; 661 | /// 662 | /// let lunisolar_date = LunisolarDate::from_solar_date( 663 | /// SolarDate::from_ymd(2024, 2, 10).unwrap(), 664 | /// ) 665 | /// .unwrap(); 666 | /// 667 | /// assert_eq!(1, lunisolar_date.the_n_day_in_this_year()); 668 | /// ``` 669 | #[inline] 670 | pub const fn the_n_day_in_this_year(self) -> u16 { 671 | Self::the_n_day_in_this_year_inner(self.lunisolar_year, self.lunar_month, self.lunar_day) 672 | } 673 | } 674 | 675 | /// 額外的實作。 676 | impl SolarDate { 677 | /// 判斷此 `SolarDate` 結構實體是否可以被安全地轉為 `LunisolarDate` 結構實體。 678 | /// 679 | /// # Examples 680 | /// 681 | /// ``` 682 | /// # use chinese_lunisolar_calendar::SolarDate; 683 | /// let solar_date_1 = SolarDate::from_ymd(2024, 1, 1).unwrap(); 684 | /// let solar_date_2 = SolarDate::from_ymd(3000, 1, 2).unwrap(); 685 | /// 686 | /// assert_eq!(true, solar_date_1.is_safe()); 687 | /// assert_eq!(false, solar_date_2.is_safe()); 688 | /// ``` 689 | #[inline] 690 | pub const fn is_safe(&self) -> bool { 691 | if let Ordering::Less = self.cmp(&MIN_LUNISOLAR_DATE_IN_SOLAR_DATE) { 692 | return false; 693 | } else if let Ordering::Greater = self.cmp(&MAX_LUNISOLAR_DATE_IN_SOLAR_DATE) { 694 | return false; 695 | } 696 | 697 | true 698 | } 699 | 700 | /// 利用農曆年月日來產生 `SolarDate` 實體。 701 | /// 702 | /// # Examples 703 | /// 704 | /// ``` 705 | /// use chinese_lunisolar_calendar::{LunisolarDate, SolarDate}; 706 | /// 707 | /// let solar_date = SolarDate::from_ymd(2024, 2, 10).unwrap(); 708 | /// 709 | /// let lunisolar_date = LunisolarDate::from_solar_date(solar_date).unwrap(); 710 | /// 711 | /// assert_eq!(solar_date, SolarDate::from_lunisolar_date(lunisolar_date)); 712 | /// ``` 713 | pub const fn from_lunisolar_date(lunisolar_date: LunisolarDate) -> SolarDate { 714 | lunisolar_date.to_solar_date() 715 | } 716 | 717 | /// 轉成農曆年月日。 718 | /// 719 | /// # Examples 720 | /// 721 | /// ``` 722 | /// use chinese_lunisolar_calendar::{LunisolarDate, SolarDate}; 723 | /// 724 | /// let solar_date = SolarDate::from_ymd(2024, 1, 1).unwrap(); 725 | /// 726 | /// let lunisolar_date = solar_date.to_lunisolar_date().unwrap(); 727 | /// ``` 728 | #[inline] 729 | pub const fn to_lunisolar_date(self) -> Result { 730 | LunisolarDate::from_solar_date(self) 731 | } 732 | } 733 | --------------------------------------------------------------------------------