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_I128, Real, Scale,
3    floor_f,
4};
5
6impl Dt {
7    /// Splits this instant's Julian Date into whole days and a remainder in attoseconds.
8    ///
9    /// ## Important
10    ///
11    /// - Converts to this [`Dt`]'s `target` scale before splitting. Set `target` first
12    ///   if you need JD on a particular scale (e.g. `Scale::TT` or `Scale::TDB`).
13    /// - Assumes this [`Dt`] is on the 2000-01-01 noon epoch.
14    /// - When the whole part is negative and there is a non-zero remainder, `frac_attos`
15    ///   is negative too. e.g. a jd of `-1000.25` will return the whole part as `-1000`
16    ///   and the remainder as `-0.25 * ATTOS_PER_DAY` as an `i128`.
17    ///
18    /// ## Examples
19    ///
20    /// ```rust
21    /// use deep_time::{Dt, Scale, consts::ATTOS_PER_DAY};
22    ///
23    /// let dt = Dt::from_jd_f(2_460_782.25, Scale::TAI);
24    /// // 2_460_782 and 0.25 days in attoseconds
25    /// assert_eq!(dt.to_jd(), (2_460_782, ATTOS_PER_DAY / 4));
26    ///
27    /// let dt = Dt::from_jd_f(-1_000.25, Scale::TAI);
28    /// // -1_000 and -0.25 days in attoseconds
29    /// assert_eq!(dt.to_jd(), (-1_000, -ATTOS_PER_DAY / 4));
30    /// ```
31    ///
32    /// ## See also
33    ///
34    /// - [`Dt::to_jd_floor`](../struct.Dt.html#method.to_jd_floor)
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) -> (i128, i128) {
40        self.to(self.target).to_jd_raw()
41    }
42
43    /// Like [`Dt::to_jd`](../struct.Dt.html#method.to_jd), but uses this [`Dt`]'s current
44    /// `scale` instead of converting to `target` first.
45    ///
46    /// ## See also
47    ///
48    /// - [`Dt::to_jd`](../struct.Dt.html#method.to_jd)
49    /// - [`Dt::to_jd_floor_raw`](../struct.Dt.html#method.to_jd_floor_raw)
50    /// - [`Dt::to_jd_f_raw`](../struct.Dt.html#method.to_jd_f_raw)
51    #[inline(always)]
52    pub const fn to_jd_raw(&self) -> (i128, i128) {
53        let attos = self.to_attos();
54        let days_since_j2000 = attos / ATTOS_PER_DAY;
55        let remaining_attos = attos % ATTOS_PER_DAY;
56
57        let jd_int = JD_2000_2_451_545_I128.saturating_add(days_since_j2000);
58
59        (jd_int, remaining_attos)
60    }
61
62    /// Splits this instant's Julian Date into whole days and a remainder in attoseconds.
63    ///
64    /// ## Important
65    ///
66    /// - Converts to this [`Dt`]'s `target` scale before splitting. Set `target` first
67    ///   if you need JD on a particular scale (e.g. `Scale::TT` or `Scale::TDB`).
68    /// - Assumes this [`Dt`] is on the 2000-01-01 noon epoch.
69    /// - The remainder is always zero or positive and less than one day. e.g. a jd of
70    ///   `-1000.25` will return the whole part as `-1001` and the remainder as
71    ///   `0.75 * ATTOS_PER_DAY` as a `u128`.
72    ///
73    /// ## Examples
74    ///
75    /// ```rust
76    /// use deep_time::{Dt, Scale, consts::ATTOS_PER_HALF_DAY_U128};
77    ///
78    /// let dt = Dt::from_jd_f(2_460_782.25, Scale::TAI);
79    /// // 2_460_782 and 0.25 days in attoseconds
80    /// assert_eq!(dt.to_jd_floor(), (2_460_782, ATTOS_PER_HALF_DAY_U128 / 2));
81    ///
82    /// let dt = Dt::from_jd_f(-1_000.25, Scale::TAI);
83    /// // -1_001 and effectively 0.75 days in attoseconds
84    /// assert_eq!(dt.to_jd_floor(), (-1_001, 3 * ATTOS_PER_HALF_DAY_U128 / 2));
85    /// ```
86    ///
87    /// ## See also
88    ///
89    /// - [`Dt::to_jd`](../struct.Dt.html#method.to_jd)
90    /// - [`Dt::to_jd_f`](../struct.Dt.html#method.to_jd_f)
91    /// - [`Dt::from_jd_floor`](../struct.Dt.html#method.from_jd_floor)
92    #[inline(always)]
93    pub const fn to_jd_floor(&self) -> (i128, u128) {
94        self.to(self.target).to_jd_floor_raw()
95    }
96
97    /// Like [`Dt::to_jd_floor`](../struct.Dt.html#method.to_jd_floor), but uses this [`Dt`]'s
98    /// current `scale` instead of converting to `target` first.
99    ///
100    /// ## See also
101    ///
102    /// - [`Dt::to_jd_floor`](../struct.Dt.html#method.to_jd_floor)
103    /// - [`Dt::to_jd_raw`](../struct.Dt.html#method.to_jd_raw)
104    #[inline(always)]
105    pub const fn to_jd_floor_raw(&self) -> (i128, u128) {
106        let attos = self.to_attos();
107        let days_since_j2000 = attos.div_euclid(ATTOS_PER_DAY);
108        let remaining_attos = attos.rem_euclid(ATTOS_PER_DAY);
109
110        let jd_int = JD_2000_2_451_545_I128.saturating_add(days_since_j2000);
111
112        (jd_int, remaining_attos as u128)
113    }
114
115    /// Returns this instant's Julian Date as an `f64`.
116    ///
117    /// ## Important
118    ///
119    /// - Converts to this [`Dt`]'s `target` scale first. Set `target` first if you need
120    ///   JD on a particular scale (e.g. `Scale::TT` or `Scale::TDB`).
121    /// - Assumes this [`Dt`] is on the 2000-01-01 noon epoch.
122    /// - Same value as [`Dt::to_jd`](../struct.Dt.html#method.to_jd), expressed as a single
123    ///   `f64` instead of a `(days, frac_attos)` pair.
124    ///
125    /// ## Examples
126    ///
127    /// ```rust
128    /// use deep_time::{Dt, Scale};
129    ///
130    /// let dt = Dt::from_jd_f(2_460_782.25, Scale::TAI);
131    /// assert_eq!(dt.to_jd_f(), 2_460_782.25);
132    ///
133    /// let dt = Dt::from_jd_f(-1_000.25, Scale::TAI);
134    /// assert_eq!(dt.to_jd_f(), -1_000.25);
135    /// ```
136    ///
137    /// ## See also
138    ///
139    /// - [`Dt::to_jd`](../struct.Dt.html#method.to_jd)
140    /// - [`Dt::to_jd_f_raw`](../struct.Dt.html#method.to_jd_f_raw)
141    /// - [`Dt::from_jd_f`](../struct.Dt.html#method.from_jd_f)
142    #[inline]
143    pub const fn to_jd_f(&self) -> Real {
144        let (days, attos) = self.to_jd();
145        f!(days) + f!(attos) / f!(ATTOS_PER_DAY)
146    }
147
148    /// Like [`Dt::to_jd_f`](../struct.Dt.html#method.to_jd_f), but uses this [`Dt`]'s current
149    /// `scale` instead of converting to `target` first.
150    ///
151    /// ## See also
152    ///
153    /// - [`Dt::to_jd_f`](../struct.Dt.html#method.to_jd_f)
154    /// - [`Dt::to_jd_raw`](../struct.Dt.html#method.to_jd_raw)
155    #[inline]
156    pub const fn to_jd_f_raw(&self) -> Real {
157        let (days, attos) = self.to_jd_raw();
158        f!(days) + f!(attos) / f!(ATTOS_PER_DAY)
159    }
160
161    /// Splits this instant's Modified Julian Date into whole days and a remainder in attoseconds.
162    ///
163    /// ## Important
164    ///
165    /// - Converts to this [`Dt`]'s `target` scale before splitting. Set `target` first
166    ///   if you need MJD on a particular scale.
167    /// - Assumes this [`Dt`] is on the 2000-01-01 noon epoch.
168    /// - MJD and JD relate by `JD = MJD + 2_400_000.5`.
169    /// - e.g. an mjd of `-1000.25` will return the whole part as `-1001` and the
170    ///   remainder as `0.75 * ATTOS_PER_DAY` as an `i128`.
171    ///
172    /// ## Examples
173    ///
174    /// ```rust
175    /// use deep_time::{Dt, Scale, consts::ATTOS_PER_DAY};
176    ///
177    /// let dt = Dt::from_mjd_f(60_961.25, Scale::TAI);
178    /// // 60_961 and 0.25 days in attoseconds
179    /// assert_eq!(dt.to_mjd(), (60_961, ATTOS_PER_DAY / 4));
180    ///
181    /// let dt = Dt::from_mjd_f(-1_000.25, Scale::TAI);
182    /// // -1_001 and effectively 0.75 days in attoseconds
183    /// assert_eq!(dt.to_mjd(), (-1_001, 3 * ATTOS_PER_DAY / 4));
184    /// ```
185    ///
186    /// ## See also
187    ///
188    /// - [`Dt::to_mjd_floor`](../struct.Dt.html#method.to_mjd_floor)
189    /// - [`Dt::to_mjd_f`](../struct.Dt.html#method.to_mjd_f)
190    /// - [`Dt::from_mjd`](../struct.Dt.html#method.from_mjd)
191    /// - [`Dt::to_jd`](../struct.Dt.html#method.to_jd)
192    #[inline(always)]
193    pub const fn to_mjd(&self) -> (i128, i128) {
194        self.to(self.target).to_mjd_raw()
195    }
196
197    /// Like [`Dt::to_mjd`](../struct.Dt.html#method.to_mjd), but uses this [`Dt`]'s current
198    /// `scale` instead of converting to `target` first.
199    ///
200    /// ## See also
201    ///
202    /// - [`Dt::to_mjd`](../struct.Dt.html#method.to_mjd)
203    /// - [`Dt::to_mjd_floor_raw`](../struct.Dt.html#method.to_mjd_floor_raw)
204    /// - [`Dt::to_mjd_f_raw`](../struct.Dt.html#method.to_mjd_f_raw)
205    pub const fn to_mjd_raw(&self) -> (i128, i128) {
206        let (jd_days, frac_attos) = self.to_jd_raw();
207
208        let mut mjd_days = jd_days.saturating_sub(2_400_001);
209        let mut mjd_attos = frac_attos.saturating_add(ATTOS_PER_HALF_DAY);
210
211        if mjd_attos >= ATTOS_PER_DAY {
212            mjd_days = mjd_days.saturating_add(1);
213            mjd_attos = mjd_attos.saturating_sub(ATTOS_PER_DAY);
214        } else if mjd_attos < 0 {
215            mjd_days = mjd_days.saturating_sub(1);
216            mjd_attos = mjd_attos.saturating_add(ATTOS_PER_DAY);
217        }
218
219        (mjd_days, mjd_attos)
220    }
221
222    /// Splits this instant's Modified Julian Date into whole days and a remainder in attoseconds.
223    ///
224    /// ## Important
225    ///
226    /// - Converts to this [`Dt`]'s `target` scale before splitting. Set `target` first
227    ///   if you need MJD on a particular scale.
228    /// - Assumes this [`Dt`] is on the 2000-01-01 noon epoch.
229    /// - MJD and JD relate by `JD = MJD + 2_400_000.5`.
230    /// - The remainder is always zero or positive and less than one day. e.g. an mjd of
231    ///   `-1000.25` will return the whole part as `-1001` and the remainder as
232    ///   `0.75 * ATTOS_PER_DAY` as a `u128`.
233    ///
234    /// ## Examples
235    ///
236    /// ```rust
237    /// use deep_time::{Dt, Scale, consts::ATTOS_PER_HALF_DAY_U128};
238    ///
239    /// let dt = Dt::from_mjd_f(60_961.25, Scale::TAI);
240    /// // 60_961 and 0.25 days in attoseconds
241    /// assert_eq!(dt.to_mjd_floor(), (60_961, ATTOS_PER_HALF_DAY_U128 / 2));
242    ///
243    /// let dt = Dt::from_mjd_f(-1_000.25, Scale::TAI);
244    /// // -1_001 and effectively 0.75 days in attoseconds
245    /// assert_eq!(dt.to_mjd_floor(), (-1_001, 3 * ATTOS_PER_HALF_DAY_U128 / 2));
246    /// ```
247    ///
248    /// ## See also
249    ///
250    /// - [`Dt::to_mjd`](../struct.Dt.html#method.to_mjd)
251    /// - [`Dt::to_mjd_f`](../struct.Dt.html#method.to_mjd_f)
252    /// - [`Dt::from_mjd_floor`](../struct.Dt.html#method.from_mjd_floor)
253    #[inline(always)]
254    pub const fn to_mjd_floor(&self) -> (i128, u128) {
255        self.to(self.target).to_mjd_floor_raw()
256    }
257
258    /// Like [`Dt::to_mjd_floor`](../struct.Dt.html#method.to_mjd_floor), but uses this [`Dt`]'s
259    /// current `scale` instead of converting to `target` first.
260    ///
261    /// ## See also
262    ///
263    /// - [`Dt::to_mjd_floor`](../struct.Dt.html#method.to_mjd_floor)
264    /// - [`Dt::to_mjd_raw`](../struct.Dt.html#method.to_mjd_raw)
265    pub const fn to_mjd_floor_raw(&self) -> (i128, u128) {
266        let (jd_days, frac_attos) = self.to_jd_floor_raw();
267
268        let mjd_days = jd_days.saturating_sub(2_400_001);
269        let mjd_attos = frac_attos.saturating_add(ATTOS_PER_HALF_DAY as u128);
270
271        if mjd_attos >= ATTOS_PER_DAY as u128 {
272            (
273                mjd_days.saturating_add(1),
274                mjd_attos.saturating_sub(ATTOS_PER_DAY as u128),
275            )
276        } else {
277            (mjd_days, mjd_attos)
278        }
279    }
280
281    /// Returns this instant's Modified Julian Date as an `f64`.
282    ///
283    /// ## Important
284    ///
285    /// - Converts to this [`Dt`]'s `target` scale first. Set `target` first if you need
286    ///   MJD on a particular scale.
287    /// - Assumes this [`Dt`] is on the 2000-01-01 noon epoch.
288    /// - Same value as [`Dt::to_mjd`](../struct.Dt.html#method.to_mjd), expressed as a single
289    ///   `f64` instead of a `(days, frac_attos)` pair.
290    ///
291    /// ## Examples
292    ///
293    /// ```rust
294    /// use deep_time::{Dt, Scale};
295    ///
296    /// let dt = Dt::from_mjd_f(60_961.25, Scale::TAI);
297    /// assert_eq!(dt.to_mjd_f(), 60_961.25);
298    ///
299    /// let dt = Dt::from_mjd_f(-1_000.25, Scale::TAI);
300    /// assert_eq!(dt.to_mjd_f(), -1_000.25);
301    /// ```
302    ///
303    /// ## See also
304    ///
305    /// - [`Dt::to_mjd`](../struct.Dt.html#method.to_mjd)
306    /// - [`Dt::to_mjd_f_raw`](../struct.Dt.html#method.to_mjd_f_raw)
307    /// - [`Dt::from_mjd_f`](../struct.Dt.html#method.from_mjd_f)
308    #[inline]
309    pub const fn to_mjd_f(&self) -> Real {
310        let (days, attos) = self.to_mjd();
311        f!(days) + f!(attos) / f!(ATTOS_PER_DAY)
312    }
313
314    /// Like [`Dt::to_mjd_f`](../struct.Dt.html#method.to_mjd_f), but uses this [`Dt`]'s current
315    /// `scale` instead of converting to `target` first.
316    ///
317    /// ## See also
318    ///
319    /// - [`Dt::to_mjd_f`](../struct.Dt.html#method.to_mjd_f)
320    /// - [`Dt::to_mjd_raw`](../struct.Dt.html#method.to_mjd_raw)
321    #[inline]
322    pub const fn to_mjd_f_raw(&self) -> Real {
323        let (days, attos) = self.to_mjd_raw();
324        f!(days) + f!(attos) / f!(ATTOS_PER_DAY)
325    }
326
327    /// Builds a **TAI** [`Dt`] from a Julian Date given as whole days plus attoseconds.
328    ///
329    /// ## Important
330    ///
331    /// This converts from the `on` time scale to `TAI` so for example, an
332    /// `on` scale of `Scale::UTC` will add leap seconds to the end result.
333    ///
334    /// To avoid a time scale conversion use `Scale::TAI` for the `on` argument.
335    ///
336    /// ## Returns
337    ///
338    /// A [`Dt`] counting attoseconds since the library epoch
339    /// [`Dt::ZERO`](../constant.ZERO.html) with its `scale` field set to
340    /// `TAI` and its `target` field set to the `on` arg.
341    ///
342    /// ## Examples
343    ///
344    /// ```rust
345    /// use deep_time::{Dt, Scale, consts::ATTOS_PER_DAY};
346    ///
347    /// // 2_460_782.25 as whole days plus 0.25 days in attoseconds
348    /// let dt = Dt::from_jd(2_460_782, ATTOS_PER_DAY / 4, Scale::TAI);
349    /// assert_eq!(dt.to_jd(), (2_460_782, ATTOS_PER_DAY / 4));
350    ///
351    /// // -1_000.25 as whole days plus -0.25 days in attoseconds
352    /// let dt = Dt::from_jd(-1_000, -ATTOS_PER_DAY / 4, Scale::TAI);
353    /// assert_eq!(dt.to_jd(), (-1_000, -ATTOS_PER_DAY / 4));
354    /// ```
355    ///
356    /// ## See also
357    ///
358    /// - [`Dt::from_jd_floor`](../struct.Dt.html#method.from_jd_floor)
359    /// - [`Dt::from_jd_f`](../struct.Dt.html#method.from_jd_f)
360    /// - [`Dt::to_jd`](../struct.Dt.html#method.to_jd)
361    /// - [`Dt::from_mjd`](../struct.Dt.html#method.from_mjd)
362    pub const fn from_jd(jd_days: i128, frac_attos: i128, on: Scale) -> Dt {
363        let days_since_j2000 = jd_days.saturating_sub(JD_2000_2_451_545_I128);
364        let attos_from_days = days_since_j2000.saturating_mul(ATTOS_PER_DAY);
365        let total_attos = attos_from_days.saturating_add(frac_attos);
366
367        Self::from_attos(total_attos, on)
368    }
369
370    /// Builds a **TAI** [`Dt`] from a Julian Date given as whole days plus attoseconds.
371    ///
372    /// ## Important
373    ///
374    /// This converts from the `on` time scale to `TAI` so for example, an
375    /// `on` scale of `Scale::UTC` will add leap seconds to the end result.
376    ///
377    /// To avoid a time scale conversion use `Scale::TAI` for the `on` argument.
378    ///
379    /// `frac_attos` must be zero or positive and less than one day. e.g. whole `-1001`
380    /// and remainder `0.75 * ATTOS_PER_DAY` builds jd `-1000.25`.
381    ///
382    /// ## Returns
383    ///
384    /// A [`Dt`] counting attoseconds since the library epoch
385    /// [`Dt::ZERO`](../constant.ZERO.html) with its `scale` field set to
386    /// `TAI` and its `target` field set to the `on` arg.
387    ///
388    /// ## Examples
389    ///
390    /// ```rust
391    /// use deep_time::{Dt, Scale, consts::ATTOS_PER_HALF_DAY_U128};
392    ///
393    /// // 2_460_782.25 as whole days plus 0.25 days in attoseconds
394    /// let dt = Dt::from_jd_floor(2_460_782, ATTOS_PER_HALF_DAY_U128 / 2, Scale::TAI);
395    /// assert_eq!(dt.to_jd_floor(), (2_460_782, ATTOS_PER_HALF_DAY_U128 / 2));
396    ///
397    /// // -1_000.25 as -1_001 plus 0.75 days in attoseconds
398    /// let dt = Dt::from_jd_floor(-1_001, 3 * ATTOS_PER_HALF_DAY_U128 / 2, Scale::TAI);
399    /// assert_eq!(dt.to_jd_floor(), (-1_001, 3 * ATTOS_PER_HALF_DAY_U128 / 2));
400    /// ```
401    ///
402    /// ## See also
403    ///
404    /// - [`Dt::from_jd`](../struct.Dt.html#method.from_jd)
405    /// - [`Dt::from_jd_f`](../struct.Dt.html#method.from_jd_f)
406    /// - [`Dt::to_jd_floor`](../struct.Dt.html#method.to_jd_floor)
407    pub const fn from_jd_floor(jd_days: i128, frac_attos: u128, on: Scale) -> Dt {
408        let days_since_j2000 = jd_days.saturating_sub(JD_2000_2_451_545_I128);
409        let frac_attos_i128 = if frac_attos > i128::MAX as u128 {
410            i128::MAX
411        } else {
412            frac_attos as i128
413        };
414        let attos_from_days = days_since_j2000.saturating_mul(ATTOS_PER_DAY);
415        let total_attos = attos_from_days.saturating_add(frac_attos_i128);
416
417        Self::from_attos(total_attos, on)
418    }
419
420    /// Builds a **TAI** [`Dt`] from a Modified Julian Date given as whole days plus attoseconds.
421    ///
422    /// ## Important
423    ///
424    /// This converts from the `on` time scale to `TAI` so for example, an
425    /// `on` scale of `Scale::UTC` will add leap seconds to the end result.
426    ///
427    /// To avoid a time scale conversion use `Scale::TAI` for the `on` argument.
428    ///
429    /// ## Returns
430    ///
431    /// A [`Dt`] counting attoseconds since the library epoch
432    /// [`Dt::ZERO`](../constant.ZERO.html) with its `scale` field set to
433    /// `TAI` and its `target` field set to the `on` arg.
434    ///
435    /// ## Examples
436    ///
437    /// ```rust
438    /// use deep_time::{Dt, Scale, consts::ATTOS_PER_DAY};
439    ///
440    /// // 60_961.25 as whole days plus 0.25 days in attoseconds
441    /// let dt = Dt::from_mjd(60_961, ATTOS_PER_DAY / 4, Scale::TAI);
442    /// assert_eq!(dt.to_mjd(), (60_961, ATTOS_PER_DAY / 4));
443    ///
444    /// // -1_000.25 as -1_001 plus 0.75 days in attoseconds
445    /// let dt = Dt::from_mjd(-1_001, 3 * ATTOS_PER_DAY / 4, Scale::TAI);
446    /// assert_eq!(dt.to_mjd(), (-1_001, 3 * ATTOS_PER_DAY / 4));
447    /// ```
448    ///
449    /// ## See also
450    ///
451    /// - [`Dt::from_mjd_floor`](../struct.Dt.html#method.from_mjd_floor)
452    /// - [`Dt::from_mjd_f`](../struct.Dt.html#method.from_mjd_f)
453    /// - [`Dt::to_mjd`](../struct.Dt.html#method.to_mjd)
454    /// - [`Dt::from_jd`](../struct.Dt.html#method.from_jd)
455    pub const fn from_mjd(mjd_days: i128, frac_attos: i128, on: Scale) -> Dt {
456        // Inverse of `to_mjd_raw`: that subtracts 2_400_001 from the JD integer part and
457        // adds half a day to the fraction. Here we undo both steps.
458        let jd_days = mjd_days.saturating_add(2_400_001);
459        let jd_attos = frac_attos.saturating_sub(ATTOS_PER_HALF_DAY);
460
461        if jd_attos < 0 {
462            Self::from_jd(
463                jd_days.saturating_sub(1),
464                jd_attos.saturating_add(ATTOS_PER_DAY),
465                on,
466            )
467        } else if jd_attos >= ATTOS_PER_DAY {
468            Self::from_jd(
469                jd_days.saturating_add(1),
470                jd_attos.saturating_sub(ATTOS_PER_DAY),
471                on,
472            )
473        } else {
474            Self::from_jd(jd_days, jd_attos, on)
475        }
476    }
477
478    /// Builds a **TAI** [`Dt`] from a Modified Julian Date given as whole days plus attoseconds.
479    ///
480    /// ## Important
481    ///
482    /// This converts from the `on` time scale to `TAI` so for example, an
483    /// `on` scale of `Scale::UTC` will add leap seconds to the end result.
484    ///
485    /// To avoid a time scale conversion use `Scale::TAI` for the `on` argument.
486    ///
487    /// MJD and JD relate by `JD = MJD + 2_400_000.5`.
488    ///
489    /// `frac_attos` must be zero or positive and less than one day. e.g. whole `-1001`
490    /// and remainder `0.75 * ATTOS_PER_DAY` builds mjd `-1000.25`.
491    ///
492    /// ## Returns
493    ///
494    /// A [`Dt`] counting attoseconds since the library epoch
495    /// [`Dt::ZERO`](../constant.ZERO.html) with its `scale` field set to
496    /// `TAI` and its `target` field set to the `on` arg.
497    ///
498    /// ## Examples
499    ///
500    /// ```rust
501    /// use deep_time::{Dt, Scale, consts::ATTOS_PER_HALF_DAY_U128};
502    ///
503    /// // 60_961.25 as whole days plus 0.25 days in attoseconds
504    /// let dt = Dt::from_mjd_floor(60_961, ATTOS_PER_HALF_DAY_U128 / 2, Scale::TAI);
505    /// assert_eq!(dt.to_mjd_floor(), (60_961, ATTOS_PER_HALF_DAY_U128 / 2));
506    ///
507    /// // -1_000.25 as -1_001 plus 0.75 days in attoseconds
508    /// let dt = Dt::from_mjd_floor(-1_001, 3 * ATTOS_PER_HALF_DAY_U128 / 2, Scale::TAI);
509    /// assert_eq!(dt.to_mjd_floor(), (-1_001, 3 * ATTOS_PER_HALF_DAY_U128 / 2));
510    /// ```
511    ///
512    /// ## See also
513    ///
514    /// - [`Dt::from_mjd`](../struct.Dt.html#method.from_mjd)
515    /// - [`Dt::from_mjd_f`](../struct.Dt.html#method.from_mjd_f)
516    /// - [`Dt::to_mjd_floor`](../struct.Dt.html#method.to_mjd_floor)
517    pub const fn from_mjd_floor(mjd_days: i128, frac_attos: u128, on: Scale) -> Dt {
518        let jd_days = mjd_days.saturating_add(2_400_000);
519        let jd_attos = frac_attos.saturating_add(ATTOS_PER_HALF_DAY as u128);
520
521        if jd_attos >= ATTOS_PER_DAY as u128 {
522            Self::from_jd_floor(
523                jd_days.saturating_add(1),
524                jd_attos.saturating_sub(ATTOS_PER_DAY as u128),
525                on,
526            )
527        } else {
528            Self::from_jd_floor(jd_days, jd_attos, on)
529        }
530    }
531
532    /// Builds a **TAI** [`Dt`] from a floating-point Julian Date.
533    ///
534    /// ## Important
535    ///
536    /// - The `on` scale becomes this [`Dt`]'s `target`; its `scale` is always `TAI`.
537    ///
538    /// ## Examples
539    ///
540    /// ```rust
541    /// use deep_time::{Dt, Scale, consts::ATTOS_PER_DAY};
542    ///
543    /// let dt = Dt::from_jd_f(2_460_782.25, Scale::TAI);
544    /// // 2_460_782 and 0.25 days in attoseconds
545    /// assert_eq!(dt.to_jd(), (2_460_782, ATTOS_PER_DAY / 4));
546    ///
547    /// let dt = Dt::from_jd_f(-1_000.25, Scale::TAI);
548    /// // -1_000 and -0.25 days in attoseconds
549    /// assert_eq!(dt.to_jd(), (-1_000, -ATTOS_PER_DAY / 4));
550    /// ```
551    ///
552    /// ## See also
553    ///
554    /// - [`Dt::from_jd`](../struct.Dt.html#method.from_jd)
555    /// - [`Dt::from_jd_floor`](../struct.Dt.html#method.from_jd_floor)
556    /// - [`Dt::to_jd_f`](../struct.Dt.html#method.to_jd_f)
557    /// - [`Dt::from_mjd_f`](../struct.Dt.html#method.from_mjd_f)
558    pub const fn from_jd_f(jd: Real, on: Scale) -> Dt {
559        let jd_days_f = floor_f(jd);
560        let jd_days = jd_days_f as i128;
561
562        let mut frac_day = jd - jd_days_f;
563        if frac_day < 0.0 {
564            frac_day = 0.0;
565        } else if frac_day >= 1.0 {
566            frac_day = 1.0 - f64::EPSILON;
567        }
568
569        let total_sec_f = frac_day * 86_400.0;
570        let whole_sec = floor_f(total_sec_f) as i64;
571        let frac_sec = total_sec_f - (whole_sec as Real);
572
573        let attos_whole: i128 = (whole_sec as i128).saturating_mul(ATTOS_PER_SEC_I128);
574
575        let attos_frac_f = frac_sec * 1_000_000_000_000_000_000.0;
576        let attos_frac: i128 = floor_f(attos_frac_f + 0.5) as i128;
577
578        let mut total_attos: i128 = attos_whole.saturating_add(attos_frac);
579
580        let mut extra_days: i128 = 0;
581        if total_attos >= ATTOS_PER_DAY {
582            extra_days = 1;
583            total_attos = total_attos.saturating_sub(ATTOS_PER_DAY);
584        } else if total_attos < 0 {
585            extra_days = -1;
586            total_attos = total_attos.saturating_add(ATTOS_PER_DAY);
587        }
588
589        let final_jd_days = jd_days.saturating_add(extra_days);
590        let frac_attos = total_attos as u128;
591
592        Self::from_jd_floor(final_jd_days, frac_attos, on)
593    }
594
595    /// Builds a **TAI** [`Dt`] from a floating-point Modified Julian Date.
596    ///
597    /// ## Important
598    ///
599    /// This converts from the `on` time scale to `TAI` so for example, an
600    /// `on` scale of `Scale::UTC` will add leap seconds to the end result.
601    ///
602    /// To avoid a time scale conversion use `Scale::TAI` for the `on` argument.
603    ///
604    /// ## Returns
605    ///
606    /// A [`Dt`] counting attoseconds since the library epoch
607    /// [`Dt::ZERO`](../constant.ZERO.html) with its `scale` field set to
608    /// `TAI` and its `target` field set to the `on` arg.
609    ///
610    /// ## Examples
611    ///
612    /// ```rust
613    /// use deep_time::{Dt, Scale, consts::ATTOS_PER_DAY};
614    ///
615    /// let dt = Dt::from_mjd_f(60_961.25, Scale::TAI);
616    /// // 60_961 and 0.25 days in attoseconds
617    /// assert_eq!(dt.to_mjd(), (60_961, ATTOS_PER_DAY / 4));
618    ///
619    /// let dt = Dt::from_mjd_f(-1_000.25, Scale::TAI);
620    /// // -1_001 and effectively 0.75 days in attoseconds
621    /// assert_eq!(dt.to_mjd(), (-1_001, 3 * ATTOS_PER_DAY / 4));
622    /// ```
623    ///
624    /// ## See also
625    ///
626    /// - [`Dt::from_mjd`](../struct.Dt.html#method.from_mjd)
627    /// - [`Dt::from_mjd_floor`](../struct.Dt.html#method.from_mjd_floor)
628    /// - [`Dt::from_jd_f`](../struct.Dt.html#method.from_jd_f)
629    /// - [`Dt::to_mjd_f`](../struct.Dt.html#method.to_mjd_f)
630    #[inline]
631    pub const fn from_mjd_f(mjd: Real, on: Scale) -> Dt {
632        let jd = mjd + f!(2_400_000.5);
633        Self::from_jd_f(jd, on)
634    }
635}