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, Real, Scale,
3 floor_f,
4};
5
6impl Dt {
7 /// Returns the exact Julian Date of this instant as `(integer_days, fractional_attoseconds)`.
8 ///
9 /// The fractional part is always in `[0, ATTOS_PER_DAY)`.
10 ///
11 /// This is the inverse of [`Dt::from_jd`](../struct.Dt.html#method.from_jd).
12 ///
13 /// ## Important:
14 ///
15 /// - This [`Dt`] first converts itself to the time scale of its `target` field
16 /// before producing a result.
17 /// - **You may need to change the [`Dt`]'s `target` field** before calling this
18 /// function if you need the JD on a particular time scale (e.g. `Scale::TT` or
19 /// `Scale::TDB`).
20 /// - This function assumes this [`Dt`] is currently from the 2000-01-01 noon
21 /// epoch. If it is not, the output will be incorrect.
22 ///
23 /// ## Returns
24 ///
25 /// A `(days, attos)` pair where:
26 ///
27 /// - `days` (`i64`): integer part of the Julian Date on this [`Dt`]'s `target` scale.
28 /// - `attos` (`u128`): fractional part in attoseconds since the start of that JD.
29 /// Always in the range `[0, ATTOS_PER_DAY)`.
30 ///
31 /// The returned JD is expressed in the time scale of this [`Dt`]'s `target` field.
32 ///
33 /// ## See also
34 ///
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) -> (i64, u128) {
40 self.to(self.target).to_jd_raw()
41 }
42
43 /// Returns the exact Julian Date of this instant as `(integer_days, fractional_attoseconds)`
44 /// **without** converting to this [`Dt`]'s `target` scale.
45 ///
46 /// The fractional part is always in `[0, ATTOS_PER_DAY)`.
47 ///
48 /// This is the low-level counterpart to [`Dt::to_jd`](../struct.Dt.html#method.to_jd).
49 ///
50 /// ## Important:
51 ///
52 /// - The JD is computed directly from this [`Dt`]'s current `attos` and `scale` fields.
53 /// - This function assumes this [`Dt`] is currently from the 2000-01-01 noon
54 /// epoch. If it is not, the output will be incorrect.
55 ///
56 /// ## Returns
57 ///
58 /// A `(days, attos)` pair where:
59 ///
60 /// - `days` (`i64`): integer part of the Julian Date on this [`Dt`]'s **current** `scale`.
61 /// - `attos` (`u128`): fractional part in attoseconds since the start of that JD.
62 /// Always in the range `[0, ATTOS_PER_DAY)`.
63 ///
64 /// ## See also
65 ///
66 /// - [`Dt::to_jd_f_raw`](../struct.Dt.html#method.to_jd_f_raw)
67 /// - [`Dt::to_jd`](../struct.Dt.html#method.to_jd)
68 #[inline(always)]
69 pub const fn to_jd_raw(&self) -> (i64, u128) {
70 let days_since_j2000 = self.to_attos().div_euclid(ATTOS_PER_DAY);
71 let remaining_attos = self.to_attos().rem_euclid(ATTOS_PER_DAY);
72
73 let jd_int = JD_2000_2_451_545.saturating_add(days_since_j2000 as i64);
74
75 (jd_int, remaining_attos as u128)
76 }
77
78 /// Returns the Julian Date of this instant as a floating-point `Real`.
79 ///
80 /// This is the lossy counterpart to [`Dt::to_jd`](../struct.Dt.html#method.to_jd).
81 ///
82 /// ## Important:
83 ///
84 /// - This [`Dt`] first converts itself to the time scale of its `target` field
85 /// before producing a result.
86 /// - **You may need to change the [`Dt`]'s `target` field** before calling this
87 /// function if you need the JD on a particular time scale (e.g. `Scale::TT` or
88 /// `Scale::TDB`).
89 /// - This function assumes this [`Dt`] is currently from the 2000-01-01 noon
90 /// epoch. If it is not, the output will be incorrect.
91 ///
92 /// ## Returns
93 ///
94 /// The Julian Date as a `Real`, expressed in the time scale of this [`Dt`]'s `target` field.
95 ///
96 /// ## See also
97 ///
98 /// - [`Dt::to_jd`](../struct.Dt.html#method.to_jd)
99 /// - [`Dt::to_jd_f_raw`](../struct.Dt.html#method.to_jd_f_raw)
100 #[inline]
101 pub const fn to_jd_f(&self) -> Real {
102 let (days, attos) = self.to_jd();
103 f!(days) + f!(attos) / f!(ATTOS_PER_DAY)
104 }
105
106 /// Returns the Julian Date of this instant as a floating-point `Real`
107 /// **without** converting to this [`Dt`]'s `target` scale.
108 ///
109 /// This is the low-level counterpart to [`Dt::to_jd_f`](../struct.Dt.html#method.to_jd_f).
110 ///
111 /// ## Important:
112 ///
113 /// - The JD is computed directly from this [`Dt`]'s current `attos` and `scale` fields.
114 /// - This function assumes this [`Dt`] is currently from the 2000-01-01 noon
115 /// epoch. If it is not, the output will be incorrect.
116 ///
117 /// ## Returns
118 ///
119 /// The Julian Date as a `Real`, expressed in this [`Dt`]'s **current** `scale`.
120 ///
121 /// ## See also
122 ///
123 /// - [`Dt::to_jd_f`](../struct.Dt.html#method.to_jd_f)
124 /// - [`Dt::to_jd_raw`](../struct.Dt.html#method.to_jd_raw)
125 #[inline]
126 pub const fn to_jd_f_raw(&self) -> Real {
127 let (days, attos) = self.to_jd_raw();
128 f!(days) + f!(attos) / f!(ATTOS_PER_DAY)
129 }
130
131 /// Returns the exact Modified Julian Date of this instant as `(integer_days, fractional_attoseconds)`.
132 ///
133 /// The fractional part is always in `[0, ATTOS_PER_DAY)`.
134 ///
135 /// This is the inverse of [`Dt::from_mjd`](../struct.Dt.html#method.from_mjd).
136 ///
137 /// ## Important:
138 ///
139 /// - This [`Dt`] first converts itself to the time scale of its `target` field
140 /// before producing a result.
141 /// - **You may need to change the [`Dt`]'s `target` field** before calling this
142 /// function if you need the MJD on a particular time scale.
143 /// - This function assumes this [`Dt`] is currently from the 2000-01-01 noon
144 /// epoch. If it is not, the output will be incorrect.
145 ///
146 /// ## Returns
147 ///
148 /// A `(days, attos)` pair where:
149 ///
150 /// - `days` (`i64`): integer part of the Modified Julian Date on this [`Dt`]'s `target` scale.
151 /// - `attos` (`u128`): fractional part in attoseconds since the start of that MJD.
152 ///
153 /// The returned MJD is expressed in the time scale of this [`Dt`]'s `target` field.
154 ///
155 /// ## See also
156 ///
157 /// - [`Dt::to_mjd_f`](../struct.Dt.html#method.to_mjd_f)
158 /// - [`Dt::from_mjd`](../struct.Dt.html#method.from_mjd)
159 /// - [`Dt::to_jd`](../struct.Dt.html#method.to_jd)
160 #[inline(always)]
161 pub const fn to_mjd(&self) -> (i64, u128) {
162 self.to(self.target).to_mjd_raw()
163 }
164
165 /// Returns the exact Modified Julian Date of this instant as `(integer_days, fractional_attoseconds)`
166 /// **without** converting to this [`Dt`]'s `target` scale.
167 ///
168 /// The fractional part is always in `[0, ATTOS_PER_DAY)`.
169 ///
170 /// This is the low-level counterpart to [`Dt::to_mjd`](../struct.Dt.html#method.to_mjd).
171 ///
172 /// ## Important:
173 ///
174 /// - The MJD is computed directly from this [`Dt`]'s current `attos` and `scale` fields.
175 /// - This function assumes this [`Dt`] is currently from the 2000-01-01 noon
176 /// epoch. If it is not, the output will be incorrect.
177 ///
178 /// ## Returns
179 ///
180 /// A `(days, attos)` pair where:
181 ///
182 /// - `days` (`i64`): integer part of the Modified Julian Date on this [`Dt`]'s **current** `scale`.
183 /// - `attos` (`u128`): fractional part in attoseconds since the start of that MJD.
184 ///
185 /// ## See also
186 ///
187 /// - [`Dt::to_mjd_f_raw`](../struct.Dt.html#method.to_mjd_f_raw)
188 /// - [`Dt::to_mjd`](../struct.Dt.html#method.to_mjd)
189 #[inline(always)]
190 pub const fn to_mjd_raw(&self) -> (i64, u128) {
191 let (jd_days, frac_attos) = self.to_jd_raw();
192
193 let mjd_days = jd_days.saturating_sub(2_400_001);
194 let mjd_attos = frac_attos.saturating_add(ATTOS_PER_HALF_DAY as u128);
195
196 if mjd_attos >= ATTOS_PER_DAY as u128 {
197 (
198 mjd_days.saturating_add(1),
199 mjd_attos.saturating_sub(ATTOS_PER_DAY as u128),
200 )
201 } else {
202 (mjd_days, mjd_attos)
203 }
204 }
205
206 /// Returns the Modified Julian Date of this instant as a floating-point `Real`.
207 ///
208 /// This is the lossy counterpart to [`Dt::to_mjd`](../struct.Dt.html#method.to_mjd).
209 ///
210 /// ## Important:
211 ///
212 /// - This [`Dt`] first converts itself to the time scale of its `target` field
213 /// before producing a result.
214 /// - **You may need to change the [`Dt`]'s `target` field** before calling this
215 /// function if you need the MJD on a particular time scale.
216 /// - This function assumes this [`Dt`] is currently from the 2000-01-01 noon
217 /// epoch. If it is not, the output will be incorrect.
218 ///
219 /// ## Returns
220 ///
221 /// The Modified Julian Date as a `Real`, expressed in the time scale of this [`Dt`]'s `target` field.
222 ///
223 /// ## See also
224 ///
225 /// - [`Dt::to_mjd`](../struct.Dt.html#method.to_mjd)
226 /// - [`Dt::to_mjd_f_raw`](../struct.Dt.html#method.to_mjd_f_raw)
227 #[inline]
228 pub const fn to_mjd_f(&self) -> Real {
229 let (days, attos) = self.to_mjd();
230 f!(days) + f!(attos) / f!(ATTOS_PER_DAY)
231 }
232
233 /// Returns the Modified Julian Date of this instant as a floating-point `Real`
234 /// **without** converting to this [`Dt`]'s `target` scale.
235 ///
236 /// This is the low-level counterpart to [`Dt::to_mjd_f`](../struct.Dt.html#method.to_mjd_f).
237 ///
238 /// ## Important:
239 ///
240 /// - The MJD is computed directly from this [`Dt`]'s current `attos` and `scale` fields.
241 /// - This function assumes this [`Dt`] is currently from the 2000-01-01 noon
242 /// epoch. If it is not, the output will be incorrect.
243 ///
244 /// ## Returns
245 ///
246 /// The Modified Julian Date as a `Real`, expressed in this [`Dt`]'s **current** `scale`.
247 ///
248 /// ## See also
249 ///
250 /// - [`Dt::to_mjd_f`](../struct.Dt.html#method.to_mjd_f)
251 /// - [`Dt::to_mjd_raw`](../struct.Dt.html#method.to_mjd_raw)
252 #[inline]
253 pub const fn to_mjd_f_raw(&self) -> Real {
254 let (days, attos) = self.to_mjd_raw();
255 f!(days) + f!(attos) / f!(ATTOS_PER_DAY)
256 }
257
258 /// Creates a **TAI** [`Dt`] from an exact Julian Date `(integer_days, fractional_attoseconds)`.
259 ///
260 /// This is the inverse of [`Dt::to_jd`](../struct.Dt.html#method.to_jd).
261 ///
262 /// ## Important:
263 ///
264 /// - The `on` parameter becomes the `target` of the returned [`Dt`].
265 /// - The returned [`Dt`] always has `scale = TAI`.
266 /// - Internally the input JD is interpreted on the `on` scale and then converted to TAI.
267 /// - For correct round-tripping you must pass the same [`Scale`] that was used when
268 /// the original JD was produced (or the scale you want the resulting [`Dt`]'s `target` to be).
269 ///
270 /// ## Returns
271 ///
272 /// A **TAI** [`Dt`] (its `scale` field is `TAI`). Its `target` field is set to `on`.
273 /// The internal `attos` are relative to the library epoch (2000-01-01 noon TAI).
274 ///
275 /// ## See also
276 ///
277 /// - [`Dt::from_jd_f`](../struct.Dt.html#method.from_jd_f)
278 /// - [`Dt::to_jd`](../struct.Dt.html#method.to_jd)
279 /// - [`Dt::from_mjd`](../struct.Dt.html#method.from_mjd)
280 pub const fn from_jd(jd_days: i64, frac_attos: u128, on: Scale) -> Dt {
281 let days_since_j2000 = jd_days.saturating_sub(JD_2000_2_451_545);
282 let frac_attos_i128 = if frac_attos > i128::MAX as u128 {
283 i128::MAX
284 } else {
285 frac_attos as i128
286 };
287 let attos_from_days = (days_since_j2000 as i128).saturating_mul(ATTOS_PER_DAY);
288 let total_attos = attos_from_days.saturating_add(frac_attos_i128);
289
290 Self::from_attos(total_attos, on)
291 }
292
293 /// Creates a **TAI** [`Dt`] from an exact Modified Julian Date `(integer_days, fractional_attoseconds)`.
294 ///
295 /// This is the inverse of [`Dt::to_mjd`](../struct.Dt.html#method.to_mjd).
296 ///
297 /// ## Important:
298 ///
299 /// - The `on` parameter becomes the `target` of the returned [`Dt`].
300 /// - The returned [`Dt`] always has `scale = TAI`.
301 /// - Internally the input MJD is interpreted on the `on` scale and then converted to TAI.
302 /// - For correct round-tripping you must pass the same [`Scale`] that was used when
303 /// the original MJD was produced.
304 ///
305 /// ## See also
306 ///
307 /// - [`Dt::from_mjd_f`](../struct.Dt.html#method.from_mjd_f)
308 /// - [`Dt::to_mjd`](../struct.Dt.html#method.to_mjd)
309 /// - [`Dt::from_jd`](../struct.Dt.html#method.from_jd)
310 pub const fn from_mjd(mjd_days: i64, frac_attos: u128, on: Scale) -> Dt {
311 let jd_days = mjd_days.saturating_add(2_400_000);
312 let jd_attos = frac_attos.saturating_add(ATTOS_PER_HALF_DAY as u128);
313
314 if jd_attos >= ATTOS_PER_DAY as u128 {
315 Self::from_jd(
316 jd_days.saturating_add(1),
317 jd_attos.saturating_sub(ATTOS_PER_DAY as u128),
318 on,
319 )
320 } else {
321 Self::from_jd(jd_days, jd_attos, on)
322 }
323 }
324
325 /// Creates a **TAI** [`Dt`] from a floating-point Julian Date.
326 ///
327 /// This is the inverse of [`Dt::to_jd_f`](../struct.Dt.html#method.to_jd_f).
328 ///
329 /// ## Important:
330 ///
331 /// - The `on` parameter becomes the `target` of the returned [`Dt`].
332 /// - The returned [`Dt`] always has `scale = TAI`.
333 /// - Internally the input JD is interpreted on the `on` scale and then converted to TAI.
334 /// - For correct round-tripping you must pass the same [`Scale`] that matches the
335 /// scale of the original JD.
336 /// - Fractional days are handled with high precision (attosecond level).
337 ///
338 /// ## See also
339 ///
340 /// - [`Dt::from_jd`](../struct.Dt.html#method.from_jd)
341 /// - [`Dt::to_jd_f`](../struct.Dt.html#method.to_jd_f)
342 /// - [`Dt::from_mjd_f`](../struct.Dt.html#method.from_mjd_f)
343 pub const fn from_jd_f(jd: Real, on: Scale) -> Dt {
344 let jd_days_f = floor_f(jd);
345 let jd_days = jd_days_f as i64;
346
347 let mut frac_day = jd - jd_days_f;
348 if frac_day < 0.0 {
349 frac_day = 0.0;
350 } else if frac_day >= 1.0 {
351 frac_day = 1.0 - f64::EPSILON;
352 }
353
354 let total_sec_f = frac_day * 86_400.0;
355 let whole_sec = floor_f(total_sec_f) as i64;
356 let frac_sec = total_sec_f - (whole_sec as Real);
357
358 let attos_whole: i128 = (whole_sec as i128).saturating_mul(ATTOS_PER_SEC_I128);
359
360 let attos_frac_f = frac_sec * 1_000_000_000_000_000_000.0;
361 let attos_frac: i128 = floor_f(attos_frac_f + 0.5) as i128;
362
363 let mut total_attos: i128 = attos_whole.saturating_add(attos_frac);
364
365 let mut extra_days: i64 = 0;
366 if total_attos >= ATTOS_PER_DAY {
367 extra_days = 1;
368 total_attos = total_attos.saturating_sub(ATTOS_PER_DAY);
369 } else if total_attos < 0 {
370 extra_days = -1;
371 total_attos = total_attos.saturating_add(ATTOS_PER_DAY);
372 }
373
374 let final_jd_days = jd_days.saturating_add(extra_days);
375 let frac_attos = total_attos as u128;
376
377 Self::from_jd(final_jd_days, frac_attos, on)
378 }
379
380 /// Creates a **TAI** [`Dt`] from a floating-point Modified Julian Date.
381 ///
382 /// This is the inverse of [`Dt::to_mjd_f`](../struct.Dt.html#method.to_mjd_f).
383 ///
384 /// ## Important:
385 ///
386 /// - The `on` parameter becomes the `target` of the returned [`Dt`].
387 /// - The returned [`Dt`] always has `scale = TAI`.
388 /// - Internally the input MJD is interpreted on the `on` scale and then converted to TAI.
389 #[inline]
390 pub const fn from_mjd_f(mjd: Real, on: Scale) -> Dt {
391 let jd = mjd + f!(2_400_000.5);
392 Self::from_jd_f(jd, on)
393 }
394}