Skip to main content

deep_time/dt/
julian_date.rs

1use crate::{
2    ATTOS_PER_DAY, ATTOS_PER_HALF_DAY, ATTOS_PER_SEC_I128, Dt, JD_2000_2_451_545, Real,
3    SEC_PER_DAYI64, Scale, floor_f,
4};
5
6impl Dt {
7    /// Returns the exact Julian Date of this instant as `(integer_days, fractional_attoseconds)`.
8    ///
9    /// - The returned JD is expressed in the time scale of this [`Dt`].
10    /// - The fractional part is always in `[0, ATTOS_PER_DAY)`.
11    ///
12    /// For a float value use [`Self::to_jd_f`].
13    pub const fn to_jd(&self) -> (i64, u128) {
14        let days_since_j2000 = self.sec.div_euclid(SEC_PER_DAYI64);
15        let remaining_sec = self.sec.rem_euclid(SEC_PER_DAYI64);
16
17        let frac_attos =
18            (remaining_sec as u128) * ATTOS_PER_SEC_I128 as u128 + (self.attos as u128);
19
20        let jd_int = JD_2000_2_451_545.saturating_add(days_since_j2000);
21        (jd_int, frac_attos)
22    }
23
24    /// Returns the Julian Date of this instant as a floating-point `Real`.
25    ///
26    /// This is the lossy counterpart to [`Self::to_jd`].
27    #[inline]
28    pub const fn to_jd_f(&self) -> Real {
29        let (days, attos) = self.to_jd();
30        f!(days) + f!(attos) / f!(ATTOS_PER_DAY)
31    }
32
33    /// Returns the exact Modified Julian Date of this instant as `(integer_days, fractional_attoseconds)`.
34    ///
35    /// - The returned MJD is expressed in the time scale of this [`Dt`].
36    /// - The fractional part is always in `[0, ATTOS_PER_DAY)`.
37    ///
38    /// For a float value use [`Self::to_mjd_f`].
39    pub const fn to_mjd(&self) -> (i64, u128) {
40        let (jd_days, frac_attos) = self.to_jd();
41
42        let mjd_days = jd_days.saturating_sub(2_400_001);
43        let mjd_attos = frac_attos.saturating_add(ATTOS_PER_HALF_DAY as u128);
44
45        if mjd_attos >= ATTOS_PER_DAY as u128 {
46            (
47                mjd_days.saturating_add(1),
48                mjd_attos.saturating_sub(ATTOS_PER_DAY as u128),
49            )
50        } else {
51            (mjd_days, mjd_attos)
52        }
53    }
54
55    /// Returns the Modified Julian Date of this instant as a floating-point `Real`.
56    ///
57    /// This is the lossy counterpart to [`Self::to_mjd`].
58    #[inline]
59    pub const fn to_mjd_f(&self) -> Real {
60        let (days, attos) = self.to_mjd();
61        f!(days) + f!(attos) / f!(ATTOS_PER_DAY)
62    }
63
64    /// Creates a `Dt` from an exact Julian Date.
65    ///
66    /// This is the inverse of [`Self::to_jd`]. For correct round-tripping you must
67    /// pass the same `on: Scale` that matches the scale of the original [`Dt`].
68    pub const fn from_jd(jd_days: i64, frac_attos: u128, on: Scale) -> Self {
69        let days_since_j2000 = jd_days.saturating_sub(JD_2000_2_451_545);
70        let seconds_from_days = days_since_j2000.saturating_mul(SEC_PER_DAYI64);
71
72        let extra_seconds = {
73            let quot = frac_attos / (ATTOS_PER_SEC_I128 as u128);
74            if quot > i64::MAX as u128 {
75                i64::MAX
76            } else {
77                quot as i64
78            }
79        };
80
81        let total_sec = seconds_from_days.saturating_add(extra_seconds);
82        let attos = (frac_attos % (ATTOS_PER_SEC_I128 as u128)) as u64;
83
84        Dt::from(total_sec, attos, on)
85    }
86
87    /// Creates a `Dt` from an exact Modified Julian Date.
88    ///
89    /// This is the inverse of [`Self::to_mjd`]. For correct round-tripping you must
90    /// pass the same `on: Scale` that matches the scale of the original [`Dt`].
91    pub const fn from_mjd(mjd_days: i64, frac_attos: u128, on: Scale) -> Self {
92        let jd_days = mjd_days.saturating_add(2_400_000);
93        let jd_attos = frac_attos.saturating_add(ATTOS_PER_HALF_DAY as u128);
94
95        if jd_attos >= ATTOS_PER_DAY as u128 {
96            Self::from_jd(
97                jd_days.saturating_add(1),
98                jd_attos.saturating_sub(ATTOS_PER_DAY as u128),
99                on,
100            )
101        } else {
102            Self::from_jd(jd_days, jd_attos, on)
103        }
104    }
105
106    /// Creates a `Dt` from a float Julian Date.
107    ///
108    /// This is the inverse of [`Self::to_jd_f`]. For correct round-tripping you must
109    /// pass the same `on: Scale` that matches the scale of the original [`Dt`].
110    pub const fn from_jd_f(jd: Real, on: Scale) -> Self {
111        let jd_days_f = floor_f(jd);
112        let jd_days = jd_days_f as i64;
113
114        let mut frac_day = jd - jd_days_f;
115        if frac_day < 0.0 {
116            frac_day = 0.0;
117        } else if frac_day >= 1.0 {
118            frac_day = 1.0 - f64::EPSILON;
119        }
120
121        let total_sec_f = frac_day * 86_400.0;
122        let whole_sec = floor_f(total_sec_f) as i64;
123        let frac_sec = total_sec_f - (whole_sec as Real);
124
125        let attos_whole: i128 = (whole_sec as i128).saturating_mul(ATTOS_PER_SEC_I128);
126
127        let attos_frac_f = frac_sec * 1_000_000_000_000_000_000.0;
128        let attos_frac: i128 = floor_f(attos_frac_f + 0.5) as i128;
129
130        let mut total_attos: i128 = attos_whole + attos_frac;
131
132        let mut extra_days: i64 = 0;
133        if total_attos >= ATTOS_PER_DAY {
134            extra_days = 1;
135            total_attos -= ATTOS_PER_DAY;
136        } else if total_attos < 0 {
137            extra_days = -1;
138            total_attos += ATTOS_PER_DAY;
139        }
140
141        let final_jd_days = jd_days.saturating_add(extra_days);
142        let frac_attos = total_attos as u128;
143
144        Self::from_jd(final_jd_days, frac_attos, on)
145    }
146
147    /// Creates a `Dt` from a float Modified Julian Date.
148    ///
149    /// This is the inverse of [`Self::to_mjd_f`]. For correct round-tripping you must
150    /// pass the same `on: Scale` that matches the scale of the original [`Dt`].
151    #[inline]
152    pub const fn from_mjd_f(mjd: Real, on: Scale) -> Self {
153        let jd = mjd + f!(2_400_000.5);
154        Self::from_jd_f(jd, on)
155    }
156}