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 fractional part is always in `[0, ATTOS_PER_DAY)`.
10    ///
11    /// This is the inverse of [`Dt::from_jd`](../struct.Dt.html#method.from_jd).
12    ///
13    /// ## Important:
14    ///
15    /// - This [`Dt`] first converts itself to the time scale of its `target` field
16    ///   before producing a result.
17    /// - **You may need to change the [`Dt`]'s `target` field** before calling this
18    ///   function if you need the JD on a particular time scale (e.g. `Scale::TT` or
19    ///   `Scale::TDB`).
20    /// - This function assumes this [`Dt`] is currently from the 2000-01-01 noon
21    ///   epoch. If it is not, the output will be incorrect.
22    ///
23    /// ## Returns
24    ///
25    /// A `(days, attos)` pair where:
26    ///
27    /// - `days` (`i64`): integer part of the Julian Date on this [`Dt`]'s `target` scale.
28    /// - `attos` (`u128`): fractional part in attoseconds since the start of that JD.
29    ///   Always in the range `[0, ATTOS_PER_DAY)`.
30    ///
31    /// The returned JD is expressed in the time scale of this [`Dt`]'s `target` field.
32    ///
33    /// ## See also
34    ///
35    /// - [`Dt::to_jd_f`](../struct.Dt.html#method.to_jd_f)
36    /// - [`Dt::from_jd`](../struct.Dt.html#method.from_jd)
37    /// - [`Dt::to_mjd`](../struct.Dt.html#method.to_mjd)
38    #[inline(always)]
39    pub const fn to_jd(&self) -> (i64, u128) {
40        self.to(self.target).to_jd_raw()
41    }
42
43    /// Returns the exact Julian Date of this instant as `(integer_days, fractional_attoseconds)`
44    /// **without** converting to this [`Dt`]'s `target` scale.
45    ///
46    /// The fractional part is always in `[0, ATTOS_PER_DAY)`.
47    ///
48    /// This is the low-level counterpart to [`Dt::to_jd`](../struct.Dt.html#method.to_jd).
49    ///
50    /// ## Important:
51    ///
52    /// - The JD is computed directly from this [`Dt`]'s current `attos` and `scale` fields.
53    /// - This function assumes this [`Dt`] is currently from the 2000-01-01 noon
54    ///   epoch. If it is not, the output will be incorrect.
55    ///
56    /// ## Returns
57    ///
58    /// A `(days, attos)` pair where:
59    ///
60    /// - `days` (`i64`): integer part of the Julian Date on this [`Dt`]'s **current** `scale`.
61    /// - `attos` (`u128`): fractional part in attoseconds since the start of that JD.
62    ///   Always in the range `[0, ATTOS_PER_DAY)`.
63    ///
64    /// ## See also
65    ///
66    /// - [`Dt::to_jd_f_raw`](../struct.Dt.html#method.to_jd_f_raw)
67    /// - [`Dt::to_jd`](../struct.Dt.html#method.to_jd)
68    #[inline(always)]
69    pub const fn to_jd_raw(&self) -> (i64, u128) {
70        let days_since_j2000 = self.to_attos().div_euclid(ATTOS_PER_DAY);
71        let remaining_attos = self.to_attos().rem_euclid(ATTOS_PER_DAY);
72
73        let jd_int = JD_2000_2_451_545.saturating_add(days_since_j2000 as i64);
74
75        (jd_int, remaining_attos as u128)
76    }
77
78    /// Returns the Julian Date of this instant as a floating-point `Real`.
79    ///
80    /// This is the lossy counterpart to [`Dt::to_jd`](../struct.Dt.html#method.to_jd).
81    ///
82    /// ## Important:
83    ///
84    /// - This [`Dt`] first converts itself to the time scale of its `target` field
85    ///   before producing a result.
86    /// - **You may need to change the [`Dt`]'s `target` field** before calling this
87    ///   function if you need the JD on a particular time scale (e.g. `Scale::TT` or
88    ///   `Scale::TDB`).
89    /// - This function assumes this [`Dt`] is currently from the 2000-01-01 noon
90    ///   epoch. If it is not, the output will be incorrect.
91    ///
92    /// ## Returns
93    ///
94    /// The Julian Date as a `Real`, expressed in the time scale of this [`Dt`]'s `target` field.
95    ///
96    /// ## See also
97    ///
98    /// - [`Dt::to_jd`](../struct.Dt.html#method.to_jd)
99    /// - [`Dt::to_jd_f_raw`](../struct.Dt.html#method.to_jd_f_raw)
100    #[inline]
101    pub const fn to_jd_f(&self) -> Real {
102        let (days, attos) = self.to_jd();
103        f!(days) + f!(attos) / f!(ATTOS_PER_DAY)
104    }
105
106    /// Returns the Julian Date of this instant as a floating-point `Real`
107    /// **without** converting to this [`Dt`]'s `target` scale.
108    ///
109    /// This is the low-level counterpart to [`Dt::to_jd_f`](../struct.Dt.html#method.to_jd_f).
110    ///
111    /// ## Important:
112    ///
113    /// - The JD is computed directly from this [`Dt`]'s current `attos` and `scale` fields.
114    /// - This function assumes this [`Dt`] is currently from the 2000-01-01 noon
115    ///   epoch. If it is not, the output will be incorrect.
116    ///
117    /// ## Returns
118    ///
119    /// The Julian Date as a `Real`, expressed in this [`Dt`]'s **current** `scale`.
120    ///
121    /// ## See also
122    ///
123    /// - [`Dt::to_jd_f`](../struct.Dt.html#method.to_jd_f)
124    /// - [`Dt::to_jd_raw`](../struct.Dt.html#method.to_jd_raw)
125    #[inline]
126    pub const fn to_jd_f_raw(&self) -> Real {
127        let (days, attos) = self.to_jd_raw();
128        f!(days) + f!(attos) / f!(ATTOS_PER_DAY)
129    }
130
131    /// Returns the exact Modified Julian Date of this instant as `(integer_days, fractional_attoseconds)`.
132    ///
133    /// The fractional part is always in `[0, ATTOS_PER_DAY)`.
134    ///
135    /// This is the inverse of [`Dt::from_mjd`](../struct.Dt.html#method.from_mjd).
136    ///
137    /// ## Important:
138    ///
139    /// - This [`Dt`] first converts itself to the time scale of its `target` field
140    ///   before producing a result.
141    /// - **You may need to change the [`Dt`]'s `target` field** before calling this
142    ///   function if you need the MJD on a particular time scale.
143    /// - This function assumes this [`Dt`] is currently from the 2000-01-01 noon
144    ///   epoch. If it is not, the output will be incorrect.
145    ///
146    /// ## Returns
147    ///
148    /// A `(days, attos)` pair where:
149    ///
150    /// - `days` (`i64`): integer part of the Modified Julian Date on this [`Dt`]'s `target` scale.
151    /// - `attos` (`u128`): fractional part in attoseconds since the start of that MJD.
152    ///
153    /// The returned MJD is expressed in the time scale of this [`Dt`]'s `target` field.
154    ///
155    /// ## See also
156    ///
157    /// - [`Dt::to_mjd_f`](../struct.Dt.html#method.to_mjd_f)
158    /// - [`Dt::from_mjd`](../struct.Dt.html#method.from_mjd)
159    /// - [`Dt::to_jd`](../struct.Dt.html#method.to_jd)
160    #[inline(always)]
161    pub const fn to_mjd(&self) -> (i64, u128) {
162        self.to(self.target).to_mjd_raw()
163    }
164
165    /// Returns the exact Modified Julian Date of this instant as `(integer_days, fractional_attoseconds)`
166    /// **without** converting to this [`Dt`]'s `target` scale.
167    ///
168    /// The fractional part is always in `[0, ATTOS_PER_DAY)`.
169    ///
170    /// This is the low-level counterpart to [`Dt::to_mjd`](../struct.Dt.html#method.to_mjd).
171    ///
172    /// ## Important:
173    ///
174    /// - The MJD is computed directly from this [`Dt`]'s current `attos` and `scale` fields.
175    /// - This function assumes this [`Dt`] is currently from the 2000-01-01 noon
176    ///   epoch. If it is not, the output will be incorrect.
177    ///
178    /// ## Returns
179    ///
180    /// A `(days, attos)` pair where:
181    ///
182    /// - `days` (`i64`): integer part of the Modified Julian Date on this [`Dt`]'s **current** `scale`.
183    /// - `attos` (`u128`): fractional part in attoseconds since the start of that MJD.
184    ///
185    /// ## See also
186    ///
187    /// - [`Dt::to_mjd_f_raw`](../struct.Dt.html#method.to_mjd_f_raw)
188    /// - [`Dt::to_mjd`](../struct.Dt.html#method.to_mjd)
189    #[inline(always)]
190    pub const fn to_mjd_raw(&self) -> (i64, u128) {
191        let (jd_days, frac_attos) = self.to_jd_raw();
192
193        let mjd_days = jd_days.saturating_sub(2_400_001);
194        let mjd_attos = frac_attos.saturating_add(ATTOS_PER_HALF_DAY as u128);
195
196        if mjd_attos >= ATTOS_PER_DAY as u128 {
197            (
198                mjd_days.saturating_add(1),
199                mjd_attos.saturating_sub(ATTOS_PER_DAY as u128),
200            )
201        } else {
202            (mjd_days, mjd_attos)
203        }
204    }
205
206    /// Returns the Modified Julian Date of this instant as a floating-point `Real`.
207    ///
208    /// This is the lossy counterpart to [`Dt::to_mjd`](../struct.Dt.html#method.to_mjd).
209    ///
210    /// ## Important:
211    ///
212    /// - This [`Dt`] first converts itself to the time scale of its `target` field
213    ///   before producing a result.
214    /// - **You may need to change the [`Dt`]'s `target` field** before calling this
215    ///   function if you need the MJD on a particular time scale.
216    /// - This function assumes this [`Dt`] is currently from the 2000-01-01 noon
217    ///   epoch. If it is not, the output will be incorrect.
218    ///
219    /// ## Returns
220    ///
221    /// The Modified Julian Date as a `Real`, expressed in the time scale of this [`Dt`]'s `target` field.
222    ///
223    /// ## See also
224    ///
225    /// - [`Dt::to_mjd`](../struct.Dt.html#method.to_mjd)
226    /// - [`Dt::to_mjd_f_raw`](../struct.Dt.html#method.to_mjd_f_raw)
227    #[inline]
228    pub const fn to_mjd_f(&self) -> Real {
229        let (days, attos) = self.to_mjd();
230        f!(days) + f!(attos) / f!(ATTOS_PER_DAY)
231    }
232
233    /// Returns the Modified Julian Date of this instant as a floating-point `Real`
234    /// **without** converting to this [`Dt`]'s `target` scale.
235    ///
236    /// This is the low-level counterpart to [`Dt::to_mjd_f`](../struct.Dt.html#method.to_mjd_f).
237    ///
238    /// ## Important:
239    ///
240    /// - The MJD is computed directly from this [`Dt`]'s current `attos` and `scale` fields.
241    /// - This function assumes this [`Dt`] is currently from the 2000-01-01 noon
242    ///   epoch. If it is not, the output will be incorrect.
243    ///
244    /// ## Returns
245    ///
246    /// The Modified Julian Date as a `Real`, expressed in this [`Dt`]'s **current** `scale`.
247    ///
248    /// ## See also
249    ///
250    /// - [`Dt::to_mjd_f`](../struct.Dt.html#method.to_mjd_f)
251    /// - [`Dt::to_mjd_raw`](../struct.Dt.html#method.to_mjd_raw)
252    #[inline]
253    pub const fn to_mjd_f_raw(&self) -> Real {
254        let (days, attos) = self.to_mjd_raw();
255        f!(days) + f!(attos) / f!(ATTOS_PER_DAY)
256    }
257
258    /// Creates a **TAI** [`Dt`] from an exact Julian Date `(integer_days, fractional_attoseconds)`.
259    ///
260    /// This is the inverse of [`Dt::to_jd`](../struct.Dt.html#method.to_jd).
261    ///
262    /// ## Important:
263    ///
264    /// - The `on` parameter becomes the `target` of the returned [`Dt`].
265    /// - The returned [`Dt`] always has `scale = TAI`.
266    /// - Internally the input JD is interpreted on the `on` scale and then converted to TAI.
267    /// - For correct round-tripping you must pass the same [`Scale`] that was used when
268    ///   the original JD was produced (or the scale you want the resulting [`Dt`]'s `target` to be).
269    ///
270    /// ## Returns
271    ///
272    /// A **TAI** [`Dt`] (its `scale` field is `TAI`). Its `target` field is set to `on`.
273    /// The internal `attos` are relative to the library epoch (2000-01-01 noon TAI).
274    ///
275    /// ## See also
276    ///
277    /// - [`Dt::from_jd_f`](../struct.Dt.html#method.from_jd_f)
278    /// - [`Dt::to_jd`](../struct.Dt.html#method.to_jd)
279    /// - [`Dt::from_mjd`](../struct.Dt.html#method.from_mjd)
280    pub const fn from_jd(jd_days: i64, frac_attos: u128, on: Scale) -> Dt {
281        let days_since_j2000 = jd_days.saturating_sub(JD_2000_2_451_545);
282        let frac_attos_i128 = if frac_attos > i128::MAX as u128 {
283            i128::MAX
284        } else {
285            frac_attos as i128
286        };
287        let attos_from_days = (days_since_j2000 as i128).saturating_mul(ATTOS_PER_DAY);
288        let total_attos = attos_from_days.saturating_add(frac_attos_i128);
289
290        Self::from_attos(total_attos, on)
291    }
292
293    /// Creates a **TAI** [`Dt`] from an exact Modified Julian Date `(integer_days, fractional_attoseconds)`.
294    ///
295    /// This is the inverse of [`Dt::to_mjd`](../struct.Dt.html#method.to_mjd).
296    ///
297    /// ## Important:
298    ///
299    /// - The `on` parameter becomes the `target` of the returned [`Dt`].
300    /// - The returned [`Dt`] always has `scale = TAI`.
301    /// - Internally the input MJD is interpreted on the `on` scale and then converted to TAI.
302    /// - For correct round-tripping you must pass the same [`Scale`] that was used when
303    ///   the original MJD was produced.
304    ///
305    /// ## See also
306    ///
307    /// - [`Dt::from_mjd_f`](../struct.Dt.html#method.from_mjd_f)
308    /// - [`Dt::to_mjd`](../struct.Dt.html#method.to_mjd)
309    /// - [`Dt::from_jd`](../struct.Dt.html#method.from_jd)
310    pub const fn from_mjd(mjd_days: i64, frac_attos: u128, on: Scale) -> Dt {
311        let jd_days = mjd_days.saturating_add(2_400_000);
312        let jd_attos = frac_attos.saturating_add(ATTOS_PER_HALF_DAY as u128);
313
314        if jd_attos >= ATTOS_PER_DAY as u128 {
315            Self::from_jd(
316                jd_days.saturating_add(1),
317                jd_attos.saturating_sub(ATTOS_PER_DAY as u128),
318                on,
319            )
320        } else {
321            Self::from_jd(jd_days, jd_attos, on)
322        }
323    }
324
325    /// Creates a **TAI** [`Dt`] from a floating-point Julian Date.
326    ///
327    /// This is the inverse of [`Dt::to_jd_f`](../struct.Dt.html#method.to_jd_f).
328    ///
329    /// ## Important:
330    ///
331    /// - The `on` parameter becomes the `target` of the returned [`Dt`].
332    /// - The returned [`Dt`] always has `scale = TAI`.
333    /// - Internally the input JD is interpreted on the `on` scale and then converted to TAI.
334    /// - For correct round-tripping you must pass the same [`Scale`] that matches the
335    ///   scale of the original JD.
336    /// - Fractional days are handled with high precision (attosecond level).
337    ///
338    /// ## See also
339    ///
340    /// - [`Dt::from_jd`](../struct.Dt.html#method.from_jd)
341    /// - [`Dt::to_jd_f`](../struct.Dt.html#method.to_jd_f)
342    /// - [`Dt::from_mjd_f`](../struct.Dt.html#method.from_mjd_f)
343    pub const fn from_jd_f(jd: Real, on: Scale) -> Dt {
344        let jd_days_f = floor_f(jd);
345        let jd_days = jd_days_f as i64;
346
347        let mut frac_day = jd - jd_days_f;
348        if frac_day < 0.0 {
349            frac_day = 0.0;
350        } else if frac_day >= 1.0 {
351            frac_day = 1.0 - f64::EPSILON;
352        }
353
354        let total_sec_f = frac_day * 86_400.0;
355        let whole_sec = floor_f(total_sec_f) as i64;
356        let frac_sec = total_sec_f - (whole_sec as Real);
357
358        let attos_whole: i128 = (whole_sec as i128).saturating_mul(ATTOS_PER_SEC_I128);
359
360        let attos_frac_f = frac_sec * 1_000_000_000_000_000_000.0;
361        let attos_frac: i128 = floor_f(attos_frac_f + 0.5) as i128;
362
363        let mut total_attos: i128 = attos_whole.saturating_add(attos_frac);
364
365        let mut extra_days: i64 = 0;
366        if total_attos >= ATTOS_PER_DAY {
367            extra_days = 1;
368            total_attos = total_attos.saturating_sub(ATTOS_PER_DAY);
369        } else if total_attos < 0 {
370            extra_days = -1;
371            total_attos = total_attos.saturating_add(ATTOS_PER_DAY);
372        }
373
374        let final_jd_days = jd_days.saturating_add(extra_days);
375        let frac_attos = total_attos as u128;
376
377        Self::from_jd(final_jd_days, frac_attos, on)
378    }
379
380    /// Creates a **TAI** [`Dt`] from a floating-point Modified Julian Date.
381    ///
382    /// This is the inverse of [`Dt::to_mjd_f`](../struct.Dt.html#method.to_mjd_f).
383    ///
384    /// ## Important:
385    ///
386    /// - The `on` parameter becomes the `target` of the returned [`Dt`].
387    /// - The returned [`Dt`] always has `scale = TAI`.
388    /// - Internally the input MJD is interpreted on the `on` scale and then converted to TAI.
389    #[inline]
390    pub const fn from_mjd_f(mjd: Real, on: Scale) -> Dt {
391        let jd = mjd + f!(2_400_000.5);
392        Self::from_jd_f(jd, on)
393    }
394}