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