/ src / chinese.rs
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  }