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}