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        Dt::new(total_attos, on, on).to_tai()
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 attos_from_days = days_since_j2000.saturating_mul(ATTOS_PER_DAY);
410        let total_attos = attos_from_days.saturating_add(Self::to_i128(frac_attos));
411
412        Dt::new(total_attos, on, on).to_tai()
413    }
414
415    /// Builds a **TAI** [`Dt`] from a Modified Julian Date given as whole days plus attoseconds.
416    ///
417    /// ## Important
418    ///
419    /// This converts from the `on` time scale to `TAI` so for example, an
420    /// `on` scale of `Scale::UTC` will add leap seconds to the end result.
421    ///
422    /// To avoid a time scale conversion use `Scale::TAI` for the `on` argument.
423    ///
424    /// ## Returns
425    ///
426    /// A [`Dt`] counting attoseconds since the library epoch
427    /// [`Dt::ZERO`](../constant.ZERO.html) with its `scale` field set to
428    /// `TAI` and its `target` field set to the `on` arg.
429    ///
430    /// ## Examples
431    ///
432    /// ```rust
433    /// use deep_time::{Dt, Scale, consts::ATTOS_PER_DAY};
434    ///
435    /// // 60_961.25 as whole days plus 0.25 days in attoseconds
436    /// let dt = Dt::from_mjd(60_961, ATTOS_PER_DAY / 4, Scale::TAI);
437    /// assert_eq!(dt.to_mjd(), (60_961, ATTOS_PER_DAY / 4));
438    ///
439    /// // -1_000.25 as -1_001 plus 0.75 days in attoseconds
440    /// let dt = Dt::from_mjd(-1_001, 3 * ATTOS_PER_DAY / 4, Scale::TAI);
441    /// assert_eq!(dt.to_mjd(), (-1_001, 3 * ATTOS_PER_DAY / 4));
442    /// ```
443    ///
444    /// ## See also
445    ///
446    /// - [`Dt::from_mjd_floor`](../struct.Dt.html#method.from_mjd_floor)
447    /// - [`Dt::from_mjd_f`](../struct.Dt.html#method.from_mjd_f)
448    /// - [`Dt::to_mjd`](../struct.Dt.html#method.to_mjd)
449    /// - [`Dt::from_jd`](../struct.Dt.html#method.from_jd)
450    pub const fn from_mjd(mjd_days: i128, frac_attos: i128, on: Scale) -> Dt {
451        // Inverse of `to_mjd_raw`: that subtracts 2_400_001 from the JD integer part and
452        // adds half a day to the fraction. Here we undo both steps.
453        let jd_days = mjd_days.saturating_add(2_400_001);
454        let jd_attos = frac_attos.saturating_sub(ATTOS_PER_HALF_DAY);
455
456        if jd_attos < 0 {
457            Self::from_jd(
458                jd_days.saturating_sub(1),
459                jd_attos.saturating_add(ATTOS_PER_DAY),
460                on,
461            )
462        } else if jd_attos >= ATTOS_PER_DAY {
463            Self::from_jd(
464                jd_days.saturating_add(1),
465                jd_attos.saturating_sub(ATTOS_PER_DAY),
466                on,
467            )
468        } else {
469            Self::from_jd(jd_days, jd_attos, on)
470        }
471    }
472
473    /// Builds a **TAI** [`Dt`] from a Modified Julian Date given as whole days plus attoseconds.
474    ///
475    /// ## Important
476    ///
477    /// This converts from the `on` time scale to `TAI` so for example, an
478    /// `on` scale of `Scale::UTC` will add leap seconds to the end result.
479    ///
480    /// To avoid a time scale conversion use `Scale::TAI` for the `on` argument.
481    ///
482    /// MJD and JD relate by `JD = MJD + 2_400_000.5`.
483    ///
484    /// `frac_attos` must be zero or positive and less than one day. e.g. whole `-1001`
485    /// and remainder `0.75 * ATTOS_PER_DAY` builds mjd `-1000.25`.
486    ///
487    /// ## Returns
488    ///
489    /// A [`Dt`] counting attoseconds since the library epoch
490    /// [`Dt::ZERO`](../constant.ZERO.html) with its `scale` field set to
491    /// `TAI` and its `target` field set to the `on` arg.
492    ///
493    /// ## Examples
494    ///
495    /// ```rust
496    /// use deep_time::{Dt, Scale, consts::ATTOS_PER_HALF_DAY_U128};
497    ///
498    /// // 60_961.25 as whole days plus 0.25 days in attoseconds
499    /// let dt = Dt::from_mjd_floor(60_961, ATTOS_PER_HALF_DAY_U128 / 2, Scale::TAI);
500    /// assert_eq!(dt.to_mjd_floor(), (60_961, ATTOS_PER_HALF_DAY_U128 / 2));
501    ///
502    /// // -1_000.25 as -1_001 plus 0.75 days in attoseconds
503    /// let dt = Dt::from_mjd_floor(-1_001, 3 * ATTOS_PER_HALF_DAY_U128 / 2, Scale::TAI);
504    /// assert_eq!(dt.to_mjd_floor(), (-1_001, 3 * ATTOS_PER_HALF_DAY_U128 / 2));
505    /// ```
506    ///
507    /// ## See also
508    ///
509    /// - [`Dt::from_mjd`](../struct.Dt.html#method.from_mjd)
510    /// - [`Dt::from_mjd_f`](../struct.Dt.html#method.from_mjd_f)
511    /// - [`Dt::to_mjd_floor`](../struct.Dt.html#method.to_mjd_floor)
512    pub const fn from_mjd_floor(mjd_days: i128, frac_attos: u128, on: Scale) -> Dt {
513        let jd_days = mjd_days.saturating_add(2_400_000);
514        let jd_attos = frac_attos.saturating_add(ATTOS_PER_HALF_DAY as u128);
515
516        if jd_attos >= ATTOS_PER_DAY as u128 {
517            Self::from_jd_floor(
518                jd_days.saturating_add(1),
519                jd_attos.saturating_sub(ATTOS_PER_DAY as u128),
520                on,
521            )
522        } else {
523            Self::from_jd_floor(jd_days, jd_attos, on)
524        }
525    }
526
527    /// Builds a **TAI** [`Dt`] from a floating-point Julian Date.
528    ///
529    /// ## Important
530    ///
531    /// - The `on` scale becomes this [`Dt`]'s `target`; its `scale` is always `TAI`.
532    ///
533    /// ## Examples
534    ///
535    /// ```rust
536    /// use deep_time::{Dt, Scale, consts::ATTOS_PER_DAY};
537    ///
538    /// let dt = Dt::from_jd_f(2_460_782.25, Scale::TAI);
539    /// // 2_460_782 and 0.25 days in attoseconds
540    /// assert_eq!(dt.to_jd(), (2_460_782, ATTOS_PER_DAY / 4));
541    ///
542    /// let dt = Dt::from_jd_f(-1_000.25, Scale::TAI);
543    /// // -1_000 and -0.25 days in attoseconds
544    /// assert_eq!(dt.to_jd(), (-1_000, -ATTOS_PER_DAY / 4));
545    /// ```
546    ///
547    /// ## See also
548    ///
549    /// - [`Dt::from_jd`](../struct.Dt.html#method.from_jd)
550    /// - [`Dt::from_jd_floor`](../struct.Dt.html#method.from_jd_floor)
551    /// - [`Dt::to_jd_f`](../struct.Dt.html#method.to_jd_f)
552    /// - [`Dt::from_mjd_f`](../struct.Dt.html#method.from_mjd_f)
553    pub const fn from_jd_f(jd: Real, on: Scale) -> Dt {
554        let jd_days_f = floor_f(jd);
555        let jd_days = jd_days_f as i128;
556
557        let mut frac_day = jd - jd_days_f;
558        if frac_day < 0.0 {
559            frac_day = 0.0;
560        } else if frac_day >= 1.0 {
561            frac_day = 1.0 - f64::EPSILON;
562        }
563
564        let total_sec_f = frac_day * 86_400.0;
565        let whole_sec = floor_f(total_sec_f) as i64;
566        let frac_sec = total_sec_f - (whole_sec as Real);
567
568        let attos_whole: i128 = (whole_sec as i128).saturating_mul(ATTOS_PER_SEC_I128);
569
570        let attos_frac_f = frac_sec * 1_000_000_000_000_000_000.0;
571        let attos_frac: i128 = floor_f(attos_frac_f + 0.5) as i128;
572
573        let mut total_attos: i128 = attos_whole.saturating_add(attos_frac);
574
575        let mut extra_days: i128 = 0;
576        if total_attos >= ATTOS_PER_DAY {
577            extra_days = 1;
578            total_attos = total_attos.saturating_sub(ATTOS_PER_DAY);
579        } else if total_attos < 0 {
580            extra_days = -1;
581            total_attos = total_attos.saturating_add(ATTOS_PER_DAY);
582        }
583
584        let final_jd_days = jd_days.saturating_add(extra_days);
585        let frac_attos = total_attos as u128;
586
587        Self::from_jd_floor(final_jd_days, frac_attos, on)
588    }
589
590    /// Builds a **TAI** [`Dt`] from a floating-point Modified Julian Date.
591    ///
592    /// ## Important
593    ///
594    /// This converts from the `on` time scale to `TAI` so for example, an
595    /// `on` scale of `Scale::UTC` will add leap seconds to the end result.
596    ///
597    /// To avoid a time scale conversion use `Scale::TAI` for the `on` argument.
598    ///
599    /// ## Returns
600    ///
601    /// A [`Dt`] counting attoseconds since the library epoch
602    /// [`Dt::ZERO`](../constant.ZERO.html) with its `scale` field set to
603    /// `TAI` and its `target` field set to the `on` arg.
604    ///
605    /// ## Examples
606    ///
607    /// ```rust
608    /// use deep_time::{Dt, Scale, consts::ATTOS_PER_DAY};
609    ///
610    /// let dt = Dt::from_mjd_f(60_961.25, Scale::TAI);
611    /// // 60_961 and 0.25 days in attoseconds
612    /// assert_eq!(dt.to_mjd(), (60_961, ATTOS_PER_DAY / 4));
613    ///
614    /// let dt = Dt::from_mjd_f(-1_000.25, Scale::TAI);
615    /// // -1_001 and effectively 0.75 days in attoseconds
616    /// assert_eq!(dt.to_mjd(), (-1_001, 3 * ATTOS_PER_DAY / 4));
617    /// ```
618    ///
619    /// ## See also
620    ///
621    /// - [`Dt::from_mjd`](../struct.Dt.html#method.from_mjd)
622    /// - [`Dt::from_mjd_floor`](../struct.Dt.html#method.from_mjd_floor)
623    /// - [`Dt::from_jd_f`](../struct.Dt.html#method.from_jd_f)
624    /// - [`Dt::to_mjd_f`](../struct.Dt.html#method.to_mjd_f)
625    #[inline]
626    pub const fn from_mjd_f(mjd: Real, on: Scale) -> Dt {
627        let jd = mjd + f!(2_400_000.5);
628        Self::from_jd_f(jd, on)
629    }
630}