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