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 pub const fn to_mjd_raw(&self) -> (i64, u128) {
190 let (jd_days, frac_attos) = self.to_jd_raw();
191
192 let mjd_days = jd_days.saturating_sub(2_400_001);
193 let mjd_attos = frac_attos.saturating_add(ATTOS_PER_HALF_DAY as u128);
194
195 if mjd_attos >= ATTOS_PER_DAY as u128 {
196 (
197 mjd_days.saturating_add(1),
198 mjd_attos.saturating_sub(ATTOS_PER_DAY as u128),
199 )
200 } else {
201 (mjd_days, mjd_attos)
202 }
203 }
204
205 /// Returns the Modified Julian Date of this instant as a floating-point `Real`.
206 ///
207 /// This is the lossy counterpart to [`Dt::to_mjd`](../struct.Dt.html#method.to_mjd).
208 ///
209 /// ## Important:
210 ///
211 /// - This [`Dt`] first converts itself to the time scale of its `target` field
212 /// before producing a result.
213 /// - **You may need to change the [`Dt`]'s `target` field** before calling this
214 /// function if you need the MJD on a particular time scale.
215 /// - This function assumes this [`Dt`] is currently from the 2000-01-01 noon
216 /// epoch. If it is not, the output will be incorrect.
217 ///
218 /// ## Returns
219 ///
220 /// The Modified Julian Date as a `Real`, expressed in the time scale of this [`Dt`]'s `target` field.
221 ///
222 /// ## See also
223 ///
224 /// - [`Dt::to_mjd`](../struct.Dt.html#method.to_mjd)
225 /// - [`Dt::to_mjd_f_raw`](../struct.Dt.html#method.to_mjd_f_raw)
226 #[inline]
227 pub const fn to_mjd_f(&self) -> Real {
228 let (days, attos) = self.to_mjd();
229 f!(days) + f!(attos) / f!(ATTOS_PER_DAY)
230 }
231
232 /// Returns the Modified Julian Date of this instant as a floating-point `Real`
233 /// **without** converting to this [`Dt`]'s `target` scale.
234 ///
235 /// This is the low-level counterpart to [`Dt::to_mjd_f`](../struct.Dt.html#method.to_mjd_f).
236 ///
237 /// ## Important:
238 ///
239 /// - The MJD is computed directly from this [`Dt`]'s current `attos` and `scale` fields.
240 /// - This function assumes this [`Dt`] is currently from the 2000-01-01 noon
241 /// epoch. If it is not, the output will be incorrect.
242 ///
243 /// ## Returns
244 ///
245 /// The Modified Julian Date as a `Real`, expressed in this [`Dt`]'s **current** `scale`.
246 ///
247 /// ## See also
248 ///
249 /// - [`Dt::to_mjd_f`](../struct.Dt.html#method.to_mjd_f)
250 /// - [`Dt::to_mjd_raw`](../struct.Dt.html#method.to_mjd_raw)
251 #[inline]
252 pub const fn to_mjd_f_raw(&self) -> Real {
253 let (days, attos) = self.to_mjd_raw();
254 f!(days) + f!(attos) / f!(ATTOS_PER_DAY)
255 }
256
257 /// Creates a **TAI** [`Dt`] from an exact Julian Date `(integer_days, fractional_attoseconds)`.
258 ///
259 /// This is the inverse of [`Dt::to_jd`](../struct.Dt.html#method.to_jd).
260 ///
261 /// ## Important:
262 ///
263 /// - The `on` parameter becomes the `target` of the returned [`Dt`].
264 /// - The returned [`Dt`] always has `scale = TAI`.
265 /// - Internally the input JD is interpreted on the `on` scale and then converted to TAI.
266 /// - For correct round-tripping you must pass the same [`Scale`] that was used when
267 /// the original JD was produced (or the scale you want the resulting [`Dt`]'s `target` to be).
268 ///
269 /// ## Returns
270 ///
271 /// A **TAI** [`Dt`] (its `scale` field is `TAI`). Its `target` field is set to `on`.
272 /// The internal `attos` are relative to the library epoch (2000-01-01 noon TAI).
273 ///
274 /// ## See also
275 ///
276 /// - [`Dt::from_jd_f`](../struct.Dt.html#method.from_jd_f)
277 /// - [`Dt::to_jd`](../struct.Dt.html#method.to_jd)
278 /// - [`Dt::from_mjd`](../struct.Dt.html#method.from_mjd)
279 pub const fn from_jd(jd_days: i64, frac_attos: u128, on: Scale) -> Dt {
280 let days_since_j2000 = jd_days.saturating_sub(JD_2000_2_451_545);
281 let frac_attos_i128 = if frac_attos > i128::MAX as u128 {
282 i128::MAX
283 } else {
284 frac_attos as i128
285 };
286 let attos_from_days = (days_since_j2000 as i128).saturating_mul(ATTOS_PER_DAY);
287 let total_attos = attos_from_days.saturating_add(frac_attos_i128);
288
289 Self::from_attos(total_attos, on)
290 }
291
292 /// Creates a **TAI** [`Dt`] from an exact Modified Julian Date `(integer_days, fractional_attoseconds)`.
293 ///
294 /// This is the inverse of [`Dt::to_mjd`](../struct.Dt.html#method.to_mjd).
295 ///
296 /// ## Important:
297 ///
298 /// - The `on` parameter becomes the `target` of the returned [`Dt`].
299 /// - The returned [`Dt`] always has `scale = TAI`.
300 /// - Internally the input MJD is interpreted on the `on` scale and then converted to TAI.
301 /// - For correct round-tripping you must pass the same [`Scale`] that was used when
302 /// the original MJD was produced.
303 ///
304 /// ## See also
305 ///
306 /// - [`Dt::from_mjd_f`](../struct.Dt.html#method.from_mjd_f)
307 /// - [`Dt::to_mjd`](../struct.Dt.html#method.to_mjd)
308 /// - [`Dt::from_jd`](../struct.Dt.html#method.from_jd)
309 pub const fn from_mjd(mjd_days: i64, frac_attos: u128, on: Scale) -> Dt {
310 let jd_days = mjd_days.saturating_add(2_400_000);
311 let jd_attos = frac_attos.saturating_add(ATTOS_PER_HALF_DAY as u128);
312
313 if jd_attos >= ATTOS_PER_DAY as u128 {
314 Self::from_jd(
315 jd_days.saturating_add(1),
316 jd_attos.saturating_sub(ATTOS_PER_DAY as u128),
317 on,
318 )
319 } else {
320 Self::from_jd(jd_days, jd_attos, on)
321 }
322 }
323
324 /// Creates a **TAI** [`Dt`] from a floating-point Julian Date.
325 ///
326 /// This is the inverse of [`Dt::to_jd_f`](../struct.Dt.html#method.to_jd_f).
327 ///
328 /// ## Important:
329 ///
330 /// - The `on` parameter becomes the `target` of the returned [`Dt`].
331 /// - The returned [`Dt`] always has `scale = TAI`.
332 /// - Internally the input JD is interpreted on the `on` scale and then converted to TAI.
333 /// - For correct round-tripping you must pass the same [`Scale`] that matches the
334 /// scale of the original JD.
335 /// - Fractional days are handled with high precision (attosecond level).
336 ///
337 /// ## See also
338 ///
339 /// - [`Dt::from_jd`](../struct.Dt.html#method.from_jd)
340 /// - [`Dt::to_jd_f`](../struct.Dt.html#method.to_jd_f)
341 /// - [`Dt::from_mjd_f`](../struct.Dt.html#method.from_mjd_f)
342 pub const fn from_jd_f(jd: Real, on: Scale) -> Dt {
343 let jd_days_f = floor_f(jd);
344 let jd_days = jd_days_f as i64;
345
346 let mut frac_day = jd - jd_days_f;
347 if frac_day < 0.0 {
348 frac_day = 0.0;
349 } else if frac_day >= 1.0 {
350 frac_day = 1.0 - f64::EPSILON;
351 }
352
353 let total_sec_f = frac_day * 86_400.0;
354 let whole_sec = floor_f(total_sec_f) as i64;
355 let frac_sec = total_sec_f - (whole_sec as Real);
356
357 let attos_whole: i128 = (whole_sec as i128).saturating_mul(ATTOS_PER_SEC_I128);
358
359 let attos_frac_f = frac_sec * 1_000_000_000_000_000_000.0;
360 let attos_frac: i128 = floor_f(attos_frac_f + 0.5) as i128;
361
362 let mut total_attos: i128 = attos_whole.saturating_add(attos_frac);
363
364 let mut extra_days: i64 = 0;
365 if total_attos >= ATTOS_PER_DAY {
366 extra_days = 1;
367 total_attos = total_attos.saturating_sub(ATTOS_PER_DAY);
368 } else if total_attos < 0 {
369 extra_days = -1;
370 total_attos = total_attos.saturating_add(ATTOS_PER_DAY);
371 }
372
373 let final_jd_days = jd_days.saturating_add(extra_days);
374 let frac_attos = total_attos as u128;
375
376 Self::from_jd(final_jd_days, frac_attos, on)
377 }
378
379 /// Creates a **TAI** [`Dt`] from a floating-point Modified Julian Date.
380 ///
381 /// This is the inverse of [`Dt::to_mjd_f`](../struct.Dt.html#method.to_mjd_f).
382 ///
383 /// ## Important:
384 ///
385 /// - The `on` parameter becomes the `target` of the returned [`Dt`].
386 /// - The returned [`Dt`] always has `scale = TAI`.
387 /// - Internally the input MJD is interpreted on the `on` scale and then converted to TAI.
388 #[inline]
389 pub const fn from_mjd_f(mjd: Real, on: Scale) -> Dt {
390 let jd = mjd + f!(2_400_000.5);
391 Self::from_jd_f(jd, on)
392 }
393}