Skip to main content

lunar_lite/
solar_term_datetime.rs

1//! Public exact solar-term datetime primitive.
2//!
3//! Exposes [`SolarTermDateTime`] and [`li_chun_datetime`] as stable public
4//! primitives so downstream crates can consume datetime-level 立春 (LiChun)
5//! without depending on internal astronomical helpers.
6
7use crate::SolarDate;
8use crate::astronomical::solar_term;
9use crate::error::LunarError;
10use crate::solar_terms::{MAX_YEAR, MIN_YEAR};
11
12/// The exact date and wall-clock time at which a solar term occurs.
13///
14/// This API returns the backend's tyme-compatible calendar datetime and does
15/// not apply longitude or time-zone correction. The wall-clock value is
16/// China Standard Time (UTC+8), matching tyme4rs; no local-timezone or
17/// true-solar-time adjustment is applied.
18///
19/// # Examples
20///
21/// ```
22/// use lunar_lite::li_chun_datetime;
23///
24/// let dt = li_chun_datetime(2000).unwrap();
25/// assert_eq!(dt.date.year, 2000);
26/// assert_eq!(dt.date.month, 2);
27/// assert_eq!(dt.date.day, 4);
28/// assert_eq!(dt.hour, 20);
29/// assert_eq!(dt.minute, 40);
30/// assert_eq!(dt.second, 24);
31/// ```
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
34pub struct SolarTermDateTime {
35    /// Gregorian date on which the solar term falls.
36    pub date: SolarDate,
37    /// Hour of the solar term (0–23).
38    pub hour: u8,
39    /// Minute of the solar term (0–59).
40    pub minute: u8,
41    /// Second of the solar term (0–59).
42    pub second: u8,
43}
44
45/// Returns the exact datetime of 立春 (LiChun, Start of Spring) for `year`.
46///
47/// The calculation reuses the internal astronomical backend. The supported
48/// range is `1..=9999`; years outside that range return
49/// [`LunarError::SolarYearOutOfRange`].
50///
51/// The returned datetime is a China Standard Time (UTC+8) wall-clock value,
52/// matching tyme4rs; no longitude, local-timezone, or true-solar-time
53/// correction is applied.
54///
55/// This function exposes a public primitive for downstream crates that need
56/// datetime-level LiChun precision. It does **not** change
57/// [`YearDivide::Exact`](crate::YearDivide) semantics in the four-pillar
58/// API, which remains date-level for compatibility.
59///
60/// # Errors
61///
62/// Returns [`LunarError::SolarYearOutOfRange`] when `year` is outside
63/// `1..=9999`.
64///
65/// # Examples
66///
67/// ```
68/// use lunar_lite::{li_chun_datetime, LunarError};
69///
70/// let dt = li_chun_datetime(2000).unwrap();
71/// assert_eq!(dt.date.year, 2000);
72/// assert_eq!(dt.date.month, 2);
73/// assert_eq!(dt.date.day, 4);
74/// assert_eq!(dt.hour, 20);
75/// assert_eq!(dt.minute, 40);
76/// assert_eq!(dt.second, 24);
77///
78/// assert_eq!(
79///     li_chun_datetime(0).unwrap_err(),
80///     LunarError::SolarYearOutOfRange { year: 0 },
81/// );
82/// ```
83pub fn li_chun_datetime(year: i32) -> Result<SolarTermDateTime, LunarError> {
84    if !(MIN_YEAR..=MAX_YEAR).contains(&year) {
85        return Err(LunarError::SolarYearOutOfRange { year });
86    }
87    // Term index 3 = 立春 in Tyme's 24-term order (0=winter solstice, 3=start of spring).
88    let term = solar_term::term_datetime(year, 3);
89    Ok(SolarTermDateTime {
90        date: term.date,
91        hour: term.hour,
92        minute: term.minute,
93        second: term.second,
94    })
95}