chinese.rs
1 use std::collections::HashMap; 2 use std::sync::{LazyLock, Mutex}; 3 4 use derivative::Derivative; 5 use strum::{Display, EnumCount, EnumProperty, EnumString, FromRepr, VariantArray}; 6 7 use crate::calendar::{Calendar as _, Day as _, Month as _, Year as _}; 8 use crate::*; 9 10 const LEAP_NAMES: [&str; 2] = ["", "闰"]; 11 const MONTH_NAMES: [&str; 12] = [ 12 "正月", 13 "二月", 14 "三月", 15 "四月", 16 "五月", 17 "六月", 18 "七月", 19 "八月", 20 "九月", 21 "十月", 22 "十一月", 23 "十二月", 24 ]; 25 const DAY_NAMES: [&str; 30] = [ 26 "初一", "初二", "初三", "初四", "初五", "初六", "初七", "初八", "初九", "初十", "十一", "十二", 27 "十三", "十四", "十五", "十六", "十七", "十八", "十九", "二十", "廿一", "廿二", "廿三", "廿四", 28 "廿五", "廿六", "廿七", "廿八", "廿九", "三十", 29 ]; 30 31 #[derive(Debug, Clone)] 32 struct YearData { 33 first_day: Date, 34 num_days_of_months: [u8; 13], 35 leap_month: u8, 36 } 37 38 static CHINESE_YEAR_CACHE: LazyLock<Mutex<HashMap<i32, YearData>>> = 39 LazyLock::new(|| Mutex::new(HashMap::new())); 40 41 fn get_winter_solstice(year: i32, tz: f64) -> Date { 42 let mut d: Date = GregorianCalendar::from_ymd(year, 12, 21).unwrap().into(); 43 while d.solar_term(tz) != Some(WinterSolstice) { 44 d = d.succ(); 45 } 46 d 47 } 48 49 fn get_prev_new_moon(date: Date, tz: f64) -> Date { 50 let mut d = date; 51 while d.lunar_phase(tz) != NewMoon { 52 d = d.pred(); 53 } 54 d 55 } 56 57 fn calc_chinese_year_period_data(year: i32) -> (Date, Vec<u8>, Option<usize>) { 58 let mut data = Vec::new(); 59 let last_ws = get_winter_solstice(year - 1, crate::BEIJING_TIMEZONE); 60 let next_ws_p1 = get_winter_solstice(year, crate::BEIJING_TIMEZONE).succ(); 61 let nm_before_last_ws = get_prev_new_moon(last_ws, crate::BEIJING_TIMEZONE); 62 let mut d = nm_before_last_ws; 63 let mut last_nm = None; 64 let mut has_mt = false; 65 while d != next_ws_p1 { 66 let lp = d.lunar_phase(crate::BEIJING_TIMEZONE); 67 let st = d.solar_term(crate::BEIJING_TIMEZONE); 68 if lp == NewMoon || st.is_some() { 69 if lp == NewMoon { 70 if let Some(last_nm) = last_nm { 71 let num_days = d - last_nm; 72 data.push((num_days as u8, has_mt)); 73 } 74 last_nm = Some(d); 75 has_mt = false; 76 } 77 if let Some(st) = st 78 && st.is_mid_term() 79 { 80 has_mt = true; 81 } 82 } 83 d = d.succ(); 84 } 85 let is_leap_year = data.len() > 12; 86 let leap_month = if is_leap_year { 87 let mut i = 0; 88 loop { 89 if !data[i].1 { 90 break Some(i); 91 } 92 i += 1; 93 } 94 } else { 95 None 96 }; 97 let data = data.into_iter().map(|(x, _)| x).collect(); 98 (nm_before_last_ws, data, leap_month) 99 } 100 101 fn calc_chinese_year_data(year: i32) -> (Date, [u8; 13], u8) { 102 let (fd1, data1, lm1) = calc_chinese_year_period_data(year); 103 let (_, data2, lm2) = calc_chinese_year_period_data(year + 1); 104 let (off1, nlm1) = match lm1 { 105 Some(lm1) => { 106 if lm1 <= 2 { 107 (3, None) 108 } else { 109 (2, Some(lm1 - 2)) 110 } 111 } 112 None => (2, None), 113 }; 114 let (off2, nlm2) = match lm2 { 115 Some(lm2) => { 116 if lm2 <= 2 { 117 (3, Some(lm2 + 10)) 118 } else { 119 (2, None) 120 } 121 } 122 None => (2, None), 123 }; 124 let mut data = [&data1[off1..], &data2[..off2]].concat(); 125 if data.len() == 12 { 126 data.push(0); 127 } 128 let num_days_of_months: [u8; 13] = data.try_into().unwrap(); 129 let nlm = nlm1.or(nlm2); 130 let fd = fd1 + data1[..off1].iter().sum::<u8>() as i32; 131 let leap_month = nlm.unwrap_or(13) as u8; 132 (fd, num_days_of_months, leap_month) 133 } 134 135 fn get_or_calc_chinese_year_data(year: i32) -> YearData { 136 { 137 let cache = CHINESE_YEAR_CACHE.lock().unwrap(); 138 if let Some(data) = cache.get(&year) { 139 return data.clone(); 140 } 141 } 142 143 let (first_day, num_days_of_months, leap_month) = calc_chinese_year_data(year); 144 let data = YearData { 145 first_day, 146 num_days_of_months, 147 leap_month, 148 }; 149 150 { 151 let mut cache = CHINESE_YEAR_CACHE.lock().unwrap(); 152 cache.insert(year, data.clone()); 153 } 154 155 data 156 } 157 158 #[test] 159 fn test_calc_chinese_year_data() { 160 let result = calc_chinese_year_data(2014); 161 assert_eq!( 162 result, 163 ( 164 Date::from_jdn(2456689), 165 [29, 30, 29, 30, 29, 30, 29, 30, 30, 29, 30, 29, 30], 166 9 167 ) 168 ); 169 let result = calc_chinese_year_data(2023); 170 assert_eq!( 171 result, 172 ( 173 Date::from_jdn(2459967), 174 [29, 30, 29, 29, 30, 30, 29, 30, 30, 29, 30, 29, 30], 175 2 176 ) 177 ); 178 } 179 180 #[derive( 181 Debug, 182 Clone, 183 Copy, 184 PartialEq, 185 Eq, 186 EnumCount, 187 VariantArray, 188 Display, 189 EnumString, 190 FromRepr, 191 EnumProperty, 192 )] 193 pub enum Stem { 194 #[strum(props(zh = "甲"))] 195 Jia, 196 #[strum(props(zh = "乙"))] 197 Yi, 198 #[strum(props(zh = "丙"))] 199 Bing, 200 #[strum(props(zh = "丁"))] 201 Ding, 202 #[strum(props(zh = "戊"))] 203 Wu, 204 #[strum(props(zh = "己"))] 205 Ji, 206 #[strum(props(zh = "庚"))] 207 Geng, 208 #[strum(props(zh = "辛"))] 209 Xin, 210 #[strum(props(zh = "壬"))] 211 Ren, 212 #[strum(props(zh = "癸"))] 213 Gui, 214 } 215 216 impl Stem { 217 pub fn ord(&self) -> i8 { 218 *self as i8 + 1 219 } 220 221 pub fn from_ord(ord: i8) -> Option<Self> { 222 Self::from_repr((ord - 1) as usize) 223 } 224 225 pub fn from_year(year: i32) -> Self { 226 Self::from_repr((year - 4).rem_euclid(Self::COUNT as i32) as usize).unwrap() 227 } 228 229 pub fn chinese(&self) -> &str { 230 self.get_str("zh").unwrap() 231 } 232 } 233 234 #[derive( 235 Debug, 236 Clone, 237 Copy, 238 PartialEq, 239 Eq, 240 EnumCount, 241 VariantArray, 242 Display, 243 EnumString, 244 FromRepr, 245 EnumProperty, 246 )] 247 pub enum Branch { 248 #[strum(props(zh = "子"))] 249 Zi, 250 #[strum(props(zh = "丑"))] 251 Chou, 252 #[strum(props(zh = "寅"))] 253 Yin, 254 #[strum(props(zh = "卯"))] 255 Mao, 256 #[strum(props(zh = "辰"))] 257 Chen, 258 #[strum(props(zh = "巳"))] 259 Si, 260 #[strum(props(zh = "午"))] 261 Wu, 262 #[strum(props(zh = "未"))] 263 Wei, 264 #[strum(props(zh = "申"))] 265 Shen, 266 #[strum(props(zh = "酉"))] 267 You, 268 #[strum(props(zh = "戌"))] 269 Xu, 270 #[strum(props(zh = "亥"))] 271 Hai, 272 } 273 274 impl Branch { 275 pub fn ord(&self) -> i8 { 276 *self as i8 + 1 277 } 278 279 pub fn from_ord(ord: i8) -> Option<Self> { 280 Self::from_repr((ord - 1) as usize) 281 } 282 283 pub fn from_year(year: i32) -> Self { 284 Self::from_repr((year - 4).rem_euclid(Self::COUNT as i32) as usize).unwrap() 285 } 286 287 pub fn chinese(&self) -> &str { 288 self.get_str("zh").unwrap() 289 } 290 } 291 292 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 293 pub struct StemBranch { 294 stem: Stem, 295 branch: Branch, 296 } 297 298 impl StemBranch { 299 pub fn new(stem: Stem, branch: Branch) -> Self { 300 Self { stem, branch } 301 } 302 303 pub fn new_with_repr(repr: usize) -> Self { 304 let s = repr % 10; 305 let b = repr % 12; 306 Self::new(Stem::from_repr(s).unwrap(), Branch::from_repr(b).unwrap()) 307 } 308 309 pub fn ord(&self) -> usize { 310 let m = self.stem as isize; 311 let n = self.branch as isize; 312 (m * 6 - n * 5).rem_euclid(60) as usize + 1 313 } 314 315 pub fn from_ord(ord: usize) -> Option<Self> { 316 if !(1..=60).contains(&ord) { 317 return None; 318 } 319 Some(Self::new_with_repr(ord - 1)) 320 } 321 322 pub fn from_stem_branch(stem: Stem, branch: Branch) -> Option<Self> { 323 if stem.ord() % 2 != branch.ord() % 2 { 324 return None; 325 } 326 Some(Self::new(stem, branch)) 327 } 328 329 pub fn from_year(year: i32) -> Self { 330 Self::new(Stem::from_year(year), Branch::from_year(year)) 331 } 332 } 333 334 #[test] 335 fn test_stem_branch() { 336 assert_eq!( 337 StemBranch::from_ord(1), 338 StemBranch::from_stem_branch(Stem::Jia, Branch::Zi) 339 ); 340 assert_eq!( 341 StemBranch::from_stem_branch(Stem::Jia, Branch::Zi) 342 .unwrap() 343 .ord(), 344 1 345 ); 346 assert_eq!( 347 StemBranch::from_ord(2), 348 StemBranch::from_stem_branch(Stem::Yi, Branch::Chou) 349 ); 350 assert_eq!( 351 StemBranch::from_stem_branch(Stem::Yi, Branch::Chou) 352 .unwrap() 353 .ord(), 354 2 355 ); 356 assert_eq!( 357 StemBranch::from_ord(13), 358 StemBranch::from_stem_branch(Stem::Bing, Branch::Zi) 359 ); 360 assert_eq!( 361 StemBranch::from_stem_branch(Stem::Bing, Branch::Zi) 362 .unwrap() 363 .ord(), 364 13 365 ); 366 assert_eq!( 367 StemBranch::from_ord(41), 368 StemBranch::from_stem_branch(Stem::Jia, Branch::Chen) 369 ); 370 assert_eq!( 371 StemBranch::from_stem_branch(Stem::Jia, Branch::Chen) 372 .unwrap() 373 .ord(), 374 41 375 ); 376 assert_eq!( 377 StemBranch::from_ord(60), 378 StemBranch::from_stem_branch(Stem::Gui, Branch::Hai) 379 ); 380 assert_eq!( 381 StemBranch::from_stem_branch(Stem::Gui, Branch::Hai) 382 .unwrap() 383 .ord(), 384 60 385 ); 386 } 387 388 pub struct Calendar; 389 390 impl calendar::Calendar for Calendar { 391 type Year = Year; 392 type Month = Month; 393 type Day = Day; 394 395 fn from_y(year: i32) -> Option<Year> { 396 if (crate::MIN_VALID_YEAR..=crate::MAX_VALID_YEAR).contains(&year) { 397 Some(Year::new(year)) 398 } else { 399 None 400 } 401 } 402 } 403 404 impl Calendar { 405 pub fn from_ylm(year: i32, leap: bool, month: u8) -> Option<Month> { 406 let year = Self::from_y(year)?; 407 if leap && month != year.leap_month { 408 None 409 } else if month < year.leap_month || !leap && month == year.leap_month { 410 year.month(month) 411 } else { 412 year.month(month + 1) 413 } 414 } 415 416 pub fn from_ylmd(year: i32, leap: bool, month: u8, day: u8) -> Option<Day> { 417 Self::from_ylm(year, leap, month)?.day(day) 418 } 419 } 420 421 #[derive(Debug, Clone, Copy, Derivative)] 422 #[derivative(PartialEq, Eq)] 423 pub struct Year { 424 year: i32, 425 #[derivative(PartialEq = "ignore")] 426 first_day: Date, 427 #[derivative(PartialEq = "ignore")] 428 num_days_of_months: [u8; 13], 429 #[derivative(PartialEq = "ignore")] 430 leap_month: u8, 431 } 432 433 impl Year { 434 fn new(year: i32) -> Self { 435 let data = get_or_calc_chinese_year_data(year); 436 Self { 437 year, 438 first_day: data.first_day, 439 num_days_of_months: data.num_days_of_months, 440 leap_month: data.leap_month, 441 } 442 } 443 444 pub fn stem(&self) -> Stem { 445 Stem::from_year(self.year) 446 } 447 448 pub fn branch(&self) -> Branch { 449 Branch::from_year(self.year) 450 } 451 452 pub fn stem_branch(&self) -> StemBranch { 453 StemBranch::from_year(self.year) 454 } 455 } 456 457 impl calendar::Year<Calendar> for Year { 458 fn ord(&self) -> i32 { 459 self.year 460 } 461 462 fn succ(&self) -> Self { 463 Self::new(self.year + 1) 464 } 465 466 fn pred(&self) -> Self { 467 Self::new(self.year - 1) 468 } 469 470 fn num_months(&self) -> usize { 471 if self.leap_month < 13 { 13 } else { 12 } 472 } 473 474 fn month(&self, ord: u8) -> Option<Month> { 475 if ord >= 1 && ord <= self.num_months() as u8 { 476 Some(Month::new(*self, ord - 1)) 477 } else { 478 None 479 } 480 } 481 482 fn is_leap(&self) -> bool { 483 self.leap_month < 13 484 } 485 } 486 487 #[test] 488 fn test_year() { 489 let year = Calendar::from_y(2021).unwrap(); 490 assert_eq!(year.ord(), 2021); 491 assert_eq!(year.stem(), Stem::Xin); 492 assert_eq!(year.branch(), Branch::Chou); 493 assert_eq!( 494 year.stem_branch(), 495 StemBranch::from_stem_branch(Stem::Xin, Branch::Chou).unwrap() 496 ); 497 assert!(!year.is_leap()); 498 assert!(Calendar::from_y(2023).unwrap().is_leap()); 499 500 assert_eq!(year.day(1), Calendar::from_ymd(2021, 1, 1)); 501 } 502 503 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 504 pub struct Month { 505 year: Year, 506 month: u8, 507 } 508 509 impl Month { 510 fn new(year: Year, month: u8) -> Self { 511 Self { year, month } 512 } 513 514 pub fn ord_no_leap(&self) -> u8 { 515 if self.month < self.year.leap_month { 516 self.month + 1 517 } else { 518 self.month 519 } 520 } 521 } 522 523 impl calendar::Month<Calendar> for Month { 524 fn ord(&self) -> u8 { 525 self.month + 1 526 } 527 528 fn succ(&self) -> Self { 529 if self.month < self.year.num_months() as u8 - 1 { 530 Self::new(self.year, self.month + 1) 531 } else { 532 self.year.succ().first_month() 533 } 534 } 535 536 fn pred(&self) -> Self { 537 if self.month > 0 { 538 Self::new(self.year, self.month - 1) 539 } else { 540 self.year.pred().last_month() 541 } 542 } 543 544 fn the_year(&self) -> Year { 545 self.year 546 } 547 548 fn num_days(&self) -> usize { 549 self.year.num_days_of_months[self.month as usize] as usize 550 } 551 552 fn day(&self, ord: u8) -> Option<Day> { 553 if ord > 0 && ord <= self.num_days() as u8 { 554 Some(Day::new(*self, ord - 1)) 555 } else { 556 None 557 } 558 } 559 560 fn is_leap(&self) -> bool { 561 self.year.leap_month == self.month 562 } 563 } 564 565 #[test] 566 fn test_month() { 567 let year = Calendar::from_y(2023).unwrap(); 568 assert_eq!(Calendar::from_ym(2023, 1).unwrap(), Month::new(year, 0)); 569 assert_eq!(Calendar::from_ym(2023, 2).unwrap(), Month::new(year, 1)); 570 assert_eq!(Calendar::from_ym(2023, 3).unwrap(), Month::new(year, 2)); 571 assert_eq!(Calendar::from_ym(2023, 1).unwrap().ord_no_leap(), 1); 572 assert_eq!(Calendar::from_ym(2023, 2).unwrap().ord_no_leap(), 2); 573 assert_eq!(Calendar::from_ym(2023, 3).unwrap().ord_no_leap(), 2); 574 assert_eq!(Calendar::from_ym(2023, 4).unwrap().ord_no_leap(), 3); 575 assert_eq!( 576 Calendar::from_ylm(2023, false, 1).unwrap(), 577 Month::new(year, 0) 578 ); 579 assert_eq!( 580 Calendar::from_ylm(2023, false, 2).unwrap(), 581 Month::new(year, 1) 582 ); 583 assert_eq!( 584 Calendar::from_ylm(2023, false, 3).unwrap(), 585 Month::new(year, 3) 586 ); 587 assert_eq!(Calendar::from_ylm(2023, true, 1), None); 588 assert_eq!( 589 Calendar::from_ylm(2023, true, 2).unwrap(), 590 Month::new(year, 2) 591 ); 592 assert_eq!(Calendar::from_ylm(2023, true, 3), None); 593 } 594 595 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 596 pub struct Day { 597 month: Month, 598 day: u8, 599 } 600 601 impl Day { 602 fn new(month: Month, day: u8) -> Self { 603 Self { month, day } 604 } 605 606 pub fn from_date_with_tz(date: Date, tz: f64) -> Self { 607 let gd = GregorianDay::from_date_with_tz(date, tz); 608 let cy = Calendar::from_y(gd.the_year().ord()).unwrap(); 609 let cd = cy.first_day(); 610 let cd_date = Date::from(cd); 611 if date >= cd_date { 612 cy.day((date - cd_date) as u16 + 1).unwrap() 613 } else { 614 let cy = cy.pred(); 615 let cd = cy.first_day(); 616 let cd_date = Date::from(cd); 617 cy.day((date - cd_date) as u16 + 1).unwrap() 618 } 619 } 620 621 pub fn stem_branch(&self) -> StemBranch { 622 let date = Date::from(*self); 623 let repr = (date.jdn() + 18).rem_euclid(60) as usize; 624 StemBranch::new_with_repr(repr) 625 } 626 } 627 628 impl calendar::Day<Calendar> for Day { 629 fn ord(&self) -> u8 { 630 self.day + 1 631 } 632 633 fn succ(&self) -> Self { 634 if self.day < self.month.num_days() as u8 - 1 { 635 Self::new(self.month, self.day + 1) 636 } else { 637 self.month.succ().first_day() 638 } 639 } 640 641 fn pred(&self) -> Self { 642 if self.day > 0 { 643 Self::new(self.month, self.day - 1) 644 } else { 645 self.month.pred().last_day() 646 } 647 } 648 649 fn the_year(&self) -> Year { 650 self.month.year 651 } 652 653 fn the_month(&self) -> Month { 654 self.month 655 } 656 } 657 658 impl From<Day> for Date { 659 fn from(day: Day) -> Self { 660 day.the_year().first_day 661 + (0..day.month.month) 662 .map(|m| day.the_year().num_days_of_months[m as usize] as i32) 663 .sum::<i32>() 664 + day.day as i32 665 } 666 } 667 668 impl From<Date> for Day { 669 fn from(date: Date) -> Self { 670 Self::from_date_with_tz(date, crate::BEIJING_TIMEZONE) 671 } 672 } 673 674 #[test] 675 fn test_day() { 676 let day = Calendar::from_ymd(1949, 8, 10).unwrap(); 677 assert_eq!( 678 day.stem_branch(), 679 StemBranch::from_stem_branch(Stem::Jia, Branch::Zi).unwrap() 680 ); 681 } 682 683 impl std::fmt::Display for Year { 684 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 685 if f.alternate() { 686 write!( 687 f, 688 "公元{}年农历{}{}年", 689 self.year, 690 self.stem().chinese(), 691 self.branch().chinese() 692 ) 693 } else { 694 write!(f, "{}{}年", self.stem().chinese(), self.branch().chinese()) 695 } 696 } 697 } 698 699 impl std::fmt::Display for Month { 700 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 701 let l = self.year.leap_month < 13 && self.month == self.year.leap_month; 702 let m = if self.month < self.year.leap_month { 703 self.month 704 } else { 705 self.month - 1 706 }; 707 if f.alternate() { 708 write!( 709 f, 710 "{:#}{}{}", 711 self.year, LEAP_NAMES[l as usize], MONTH_NAMES[m as usize] 712 ) 713 } else { 714 write!( 715 f, 716 "{}{}{}", 717 self.year, LEAP_NAMES[l as usize], MONTH_NAMES[m as usize] 718 ) 719 } 720 } 721 } 722 723 impl std::fmt::Display for Day { 724 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 725 if f.alternate() { 726 write!(f, "{:#}{}", self.month, DAY_NAMES[self.day as usize]) 727 } else { 728 write!(f, "{}{}", self.month, DAY_NAMES[self.day as usize]) 729 } 730 } 731 }