Skip to main content

deep_time/dt/
decimal_year.rs

1use crate::{Dt, JD_2000_2_451_545F, Real, Scale};
2
3impl Dt {
4    /// Returns the **Julian epoch year**.
5    #[inline]
6    pub const fn to_jyear(&self) -> Real {
7        let jd_tt = self.to_jd_f();
8        f!(2000.0) + (jd_tt - JD_2000_2_451_545F) / f!(365.25)
9    }
10
11    /// Inverse of [`Self::to_jyear`].
12    #[inline]
13    pub const fn from_jyear(jyear: Real, scale: Scale) -> Self {
14        if jyear.is_nan() {
15            return Self::ZERO;
16        }
17        if jyear.is_infinite() {
18            return if jyear.is_sign_positive() {
19                Self::MAX
20            } else {
21                Self::MIN
22            };
23        }
24
25        let jd = JD_2000_2_451_545F + (jyear - f!(2000.0)) * f!(365.25);
26        Self::from_jd_f(jd, scale)
27    }
28
29    /// Returns the **Besselian epoch year**.
30    #[inline]
31    pub const fn to_byear(&self) -> Real {
32        let jd_tt = self.to_jd_f();
33        f!(1900.0) + (jd_tt - f!(2415020.31352)) / f!(365.242198781)
34    }
35
36    /// Inverse of [`Self::to_byear`].
37    #[inline]
38    pub const fn from_byear(byear: Real, scale: Scale) -> Self {
39        if byear.is_nan() {
40            return Self::ZERO;
41        }
42        if byear.is_infinite() {
43            return if byear.is_sign_positive() {
44                Self::MAX
45            } else {
46                Self::MIN
47            };
48        }
49
50        let jd = f!(2415020.31352) + (byear - f!(1900.0)) * f!(365.242198781);
51        Self::from_jd_f(jd, scale)
52    }
53
54    /// Returns the **decimal year** (Gregorian calendar year + fraction of the year).
55    ///
56    /// This is the direct equivalent of Astropy’s `Time.decimalyear`:
57    /// - Uses the *actual* length of the specific Gregorian year (365 or 366 days,
58    ///   plus any leap seconds on UTC/UTCSpice/etc.).
59    /// - Fully scale-aware (TAI, TT, UTC, TDB, custom clocks, …).
60    /// - Exact integer arithmetic for the year boundaries, then a high-precision
61    ///   `to_sec_f` division (lossy only at the final `Real` step, same as Astropy).
62    #[inline]
63    pub const fn to_decimalyear(&self, current: Scale) -> Real {
64        let ymd = self.to_ymdhms(current);
65        let year = ymd.yr;
66
67        let start = Self::from_ymd_on(year, 1, 1, current);
68        let next_start = Self::from_ymd_on(year + 1, 1, 1, current);
69
70        let elapsed = self.to_diff_raw(start).to_sec_f();
71        let year_length = next_start.to_diff_raw(start).to_sec_f();
72
73        // year_length is never zero for representable years
74        f!(year) + elapsed / year_length
75    }
76}