use crate::{
error::StemBranchError,
stem_branch::{EarthlyBranch, HeavenlyStem},
};
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct StemBranch {
stem: HeavenlyStem,
branch: EarthlyBranch,
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for StemBranch {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(serde::Deserialize)]
struct RawStemBranch {
stem: HeavenlyStem,
branch: EarthlyBranch,
}
let raw = RawStemBranch::deserialize(deserializer)?;
StemBranch::try_new(raw.stem, raw.branch).map_err(serde::de::Error::custom)
}
}
impl StemBranch {
pub fn try_new(stem: HeavenlyStem, branch: EarthlyBranch) -> Result<Self, StemBranchError> {
if stem.index() % 2 == branch.index() % 2 {
Ok(Self { stem, branch })
} else {
Err(StemBranchError::InvalidStemBranchPair { stem, branch })
}
}
pub fn from_cycle_index(index: usize) -> Self {
let index = index % 60;
Self {
stem: HeavenlyStem::from_index(index),
branch: EarthlyBranch::from_index(index),
}
}
pub fn from_lunar_year(year: i32) -> Self {
let index = (year - 1984).rem_euclid(60) as usize;
Self::from_cycle_index(index)
}
pub const fn stem(&self) -> HeavenlyStem {
self.stem
}
pub const fn branch(&self) -> EarthlyBranch {
self.branch
}
pub fn cycle_index(&self) -> usize {
(0..60)
.find(|&index| index % 10 == self.stem.index() && index % 12 == self.branch.index())
.expect("StemBranch invariant guarantees a valid cycle index")
}
}
pub fn lunar_year_stem_branch(lunar_year: i32) -> StemBranch {
StemBranch::from_lunar_year(lunar_year)
}
pub fn lunar_year_stem(lunar_year: i32) -> HeavenlyStem {
StemBranch::from_lunar_year(lunar_year).stem()
}
pub fn lunar_year_branch(lunar_year: i32) -> EarthlyBranch {
StemBranch::from_lunar_year(lunar_year).branch()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lunar_year_helpers_delegate_to_from_lunar_year() {
let expected = StemBranch::from_lunar_year(2024);
assert_eq!(lunar_year_stem_branch(2024), expected);
assert_eq!(lunar_year_stem(2024), HeavenlyStem::Jia);
assert_eq!(lunar_year_branch(2024), EarthlyBranch::Chen);
}
#[test]
fn lunar_year_helpers_agree_with_pillar_accessors() {
for year in [1850, 1984, 2000, 2023, 2150] {
let pillar = StemBranch::from_lunar_year(year);
assert_eq!(lunar_year_stem_branch(year), pillar);
assert_eq!(lunar_year_stem(year), pillar.stem());
assert_eq!(lunar_year_branch(year), pillar.branch());
}
}
}