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}