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