Skip to main content

deep_time/dt/
conversions_mars.rs

1use crate::{
2    ATTOS_PER_SEC_I128, Dt, J2000_JD_TT, MARS_MSD_REF_JD_INT, MARS_MSD_REF_TOD_SEC,
3    MARS_MSD_REF_TOD_SUBSEC, MARS_REF_TT, MARS_SOL_ATTOS, MARS_SOL_LENGTH_SEC, Real,
4    SEC_PER_DAYI64, SEC_PER_DAYI128, Scale, TSpan, floor_f, to_sec_f,
5};
6
7impl Dt {
8    /// Exact helper: elapsed attoseconds since the Mars MSD reference epoch (JD 2405522.0028779 TT).
9    pub(crate) const fn elapsed_to_attos_since_mars_ref(numerical_tt: TSpan) -> i128 {
10        let days_since_j2000 = numerical_tt.sec.div_euclid(SEC_PER_DAYI64);
11        let tod_sec = numerical_tt.sec.rem_euclid(SEC_PER_DAYI64);
12
13        let jd_days = J2000_JD_TT + days_since_j2000;
14        let days_diff = jd_days - MARS_MSD_REF_JD_INT;
15
16        let mut sec_diff = (days_diff as i128) * SEC_PER_DAYI128
17            + (tod_sec as i128 - MARS_MSD_REF_TOD_SEC as i128);
18        let mut attos_diff = (numerical_tt.attos as i128) - (MARS_MSD_REF_TOD_SUBSEC as i128);
19
20        if attos_diff < 0 {
21            attos_diff += ATTOS_PER_SEC_I128;
22            sec_diff -= 1;
23        }
24
25        sec_diff * ATTOS_PER_SEC_I128 + attos_diff
26    }
27
28    /// Returns the exact Mars Sol Date (MSD) as a tuple of integer sols and the fractional part of a sol.
29    ///
30    /// The computation follows the canonical NASA GISS / AM2000 formulation and works for any input
31    /// [`Scale`]. Leap seconds are automatically accounted for when converting from UTC.
32    pub const fn to_msd_exact(self) -> (i64, u128) {
33        let tt = self.to(Scale::TT);
34        let elapsed = Self::elapsed_to_attos_since_mars_ref(tt);
35        let attos_per_sol = MARS_SOL_ATTOS;
36
37        let whole_sols = elapsed.div_euclid(attos_per_sol) as i64;
38        let frac_attos = elapsed.rem_euclid(attos_per_sol) as u128;
39
40        (whole_sols, frac_attos)
41    }
42
43    /// Returns Mars Coordinated Time (MTC) as a [`TSpan`] representing
44    /// seconds into the current sol (range `[0, one Martian sol)`).
45    #[inline]
46    pub const fn to_mtc(self) -> TSpan {
47        let (_, frac_attos) = self.to_msd_exact();
48        TSpan::from_attos(frac_attos as i128)
49    }
50
51    /// Creates a `Dt` (in TT) from an exact Mars Sol Date using full library precision.
52    pub const fn from_msd_exact(whole_sols: i64, frac_attos: u128) -> Self {
53        let elapsed_attos = (whole_sols as i128) * MARS_SOL_ATTOS + frac_attos as i128;
54
55        let tt = MARS_REF_TT.add(TSpan::from_attos(elapsed_attos));
56        Self::from(tt.sec, tt.attos, Scale::TT)
57    }
58
59    /// Creates a `Dt` (in TT) from a floating-point Mars Sol Date.
60    /// Non-exact Real.
61    pub const fn from_msd(msd: Real) -> Self {
62        let whole = floor_f(msd) as i64;
63        let frac = msd - f!(whole);
64        let frac_span = TSpan::from_sec_f(frac * MARS_SOL_LENGTH_SEC);
65        Self::from_msd_exact(whole, frac_span.to_attos() as u128)
66    }
67
68    /// Returns the Mars Sol Date (MSD) as a floating-point value (matches NASA Mars24 output).
69    /// Non-exact Real.
70    #[inline]
71    pub const fn to_msd(self) -> Real {
72        let (whole, frac) = self.to_msd_exact();
73        f!(whole) + to_sec_f(frac) / MARS_SOL_LENGTH_SEC
74    }
75}