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