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}