deep_time/dt/conveniences.rs
1use crate::{
2 ATTOS_PER_SEC_I128, ATTOS_PER_WEEK, Dt, JD_2000_2_451_545F, Real, SEC_PER_DAYI64, Scale,
3};
4
5impl Dt {
6 /// Returns this [`Dt`] but as time since the
7 /// [`Dt::UNIX_EPOCH`](../struct.Dt.html#associatedconstant.UNIX_EPOCH) on its
8 /// `target` time scale.
9 ///
10 /// ## Important:
11 ///
12 /// - The [`Dt`] first converts itself and the epoch to the time scale of its
13 /// `target` field before doing a raw difference with the epoch.
14 /// - **You may need to change the [`Dt`]'s `target` field** before calling the function
15 /// if you need the timestamp to be on a particular time scale, e.g. `UTC`.
16 /// - This function assumes this [`Dt`] is currently from the 2000-01-01 noon epoch,
17 /// if it's not then the output will be incorrect.
18 ///
19 /// ## Returns
20 ///
21 /// - A [`Dt`] whose `attos` is how many attoseconds have elapsed since
22 /// [`UNIX_EPOCH`](../struct.Dt.html#associatedconstant.UNIX_EPOCH).
23 /// - The count is on whatever scale sits in this [`Dt`]'s `target` field — for example
24 /// `Scale::UTC` if you built it with `from_ymd(..., Scale::UTC, ...)`. The result's
25 /// `scale` and `target` are both set to that same value.
26 ///
27 /// ## Examples
28 ///
29 /// ```rust
30 /// use deep_time::{Dt, Scale};
31 ///
32 /// // because from_ymd() with Scale::UTC sets the returned
33 /// // Dt's target field to Scale::UTC, we do not need to use
34 /// // .target() prior to calling to_unix() in order to get
35 /// // a utc unix timestamp
36 /// let dt = Dt::from_ymd(2000, 1, 1, Scale::UTC, 12, 0, 0, 0);
37 /// let unix = dt.to_unix();
38 ///
39 /// assert_eq!(
40 /// unix.to_sec(),
41 /// 946728000,
42 /// "unix sec for 2000-01-01 12:00:00 UTC is wrong, got: {}, expected: 946728000",
43 /// unix.to_sec()
44 /// );
45 ///
46 /// let dt2 = Dt::from_unix(unix);
47 ///
48 /// assert_eq!(
49 /// dt.to_attos(), dt2.to_attos(),
50 /// "round trip to Dt got wrong attos, old: {}, new: {}",
51 /// dt.to_attos(), dt2.to_attos()
52 /// );
53 ///
54 /// let ymd = dt2.to_ymd();
55 /// assert_eq!(ymd.yr(), 2000_i64);
56 /// assert_eq!(ymd.mo(), 1);
57 /// assert_eq!(ymd.day(), 1);
58 /// assert_eq!(ymd.hr(), 12);
59 /// assert_eq!(ymd.min(), 0);
60 /// assert_eq!(ymd.sec(), 0);
61 /// assert_eq!(ymd.attos(), 0);
62 /// ```
63 ///
64 /// ## See also
65 ///
66 /// - [`Dt::from_unix`](../struct.Dt.html#method.from_unix)
67 #[inline(always)]
68 pub const fn to_unix(&self) -> Dt {
69 self.to_scale_and_diff(Self::UNIX_EPOCH, true)
70 }
71
72 /// Creates a **TAI** [`Dt`] from a [`Dt`] that is attoseconds since
73 /// [`Dt::UNIX_EPOCH`](../struct.Dt.html#associatedconstant.UNIX_EPOCH).
74 ///
75 /// This is the inverse of [`Dt::to_unix`](../struct.Dt.html#method.to_unix).
76 ///
77 /// ## Important:
78 ///
79 /// - `unix` must be a [`Dt`] whose `attos` is how many attoseconds have elapsed since
80 /// [`UNIX_EPOCH`](../struct.Dt.html#associatedconstant.UNIX_EPOCH) — typically the
81 /// return value of [`Dt::to_unix`](../struct.Dt.html#method.to_unix).
82 /// The input's `scale` field says which time scale that count is on — if it
83 /// is `Scale::UTC`, the count is treated as UTC and converted to TAI (leap seconds
84 /// included).
85 /// - [`Dt::UNIX_EPOCH`](../struct.Dt.html#associatedconstant.UNIX_EPOCH) is converted
86 /// to that same scale before the sum.
87 ///
88 /// ## Returns
89 ///
90 /// A **TAI** [`Dt`] for the reconstructed instant. Its `attos` is no longer a count since
91 /// [`UNIX_EPOCH`](../struct.Dt.html#associatedconstant.UNIX_EPOCH) — it is attoseconds since
92 /// the library epoch (2000-01-01 noon TAI). Its `target` field is taken from `unix`.
93 ///
94 /// ## Examples
95 ///
96 /// ```rust
97 /// use deep_time::{Dt, Scale};
98 ///
99 /// let dt = Dt::from_ymd(2000, 1, 1, Scale::UTC, 12, 0, 0, 0);
100 /// let unix = dt.to_unix();
101 /// let roundtrip = Dt::from_unix(unix);
102 ///
103 /// assert_eq!(roundtrip, dt);
104 /// ```
105 ///
106 /// ### From an external POSIX unix seconds count
107 ///
108 /// ```rust
109 /// use deep_time::{Dt, Scale};
110 ///
111 /// // 2012-08-08 15:30:00 → 1344439800.000000 s
112 /// let unix = 1344439800_i128;
113 ///
114 /// // use Dt::new to avoid time scale conversions on the
115 /// // seconds count, other functions can do the same thing
116 /// // but this way lets us easily set the time scale fields
117 /// // in one go
118 /// let unix_dt = Dt::new_sec(unix, Scale::UTC, Scale::UTC);
119 ///
120 /// let dt = Dt::from_unix(unix_dt);
121 ///
122 /// let ymd = dt.to_ymd();
123 /// assert_eq!(ymd.yr(), 2012);
124 /// assert_eq!(ymd.mo(), 8);
125 /// assert_eq!(ymd.day(), 8);
126 /// assert_eq!(ymd.hr(), 15);
127 /// assert_eq!(ymd.min(), 30);
128 /// assert_eq!(ymd.sec(), 0);
129 /// assert_eq!(ymd.attos(), 0);
130 /// ```
131 ///
132 /// ## See also
133 ///
134 /// - [`Dt::to_unix`](../struct.Dt.html#method.to_unix)
135 #[inline(always)]
136 pub const fn from_unix(unix: Dt) -> Dt {
137 Self::from_diff_and_scale(unix, Dt::UNIX_EPOCH, true)
138 }
139
140 /// Returns this [`Dt`] but as time since the
141 /// [`Dt::NTP_EPOCH`](../struct.Dt.html#associatedconstant.NTP_EPOCH) on its
142 /// `target` time scale.
143 ///
144 /// ## Important:
145 ///
146 /// - The [`Dt`] first converts itself and the epoch to the time scale of its
147 /// `target` field before doing a raw difference with the epoch.
148 /// - **You may need to change the [`Dt`]'s `target` field** before calling the function
149 /// if you need the timestamp to be on a particular time scale, e.g. `UTC`.
150 /// - This function assumes this [`Dt`] is currently from the 2000-01-01 noon epoch,
151 /// if it's not then the output will be incorrect.
152 ///
153 /// ## Returns
154 ///
155 /// - A [`Dt`] whose `attos` is how many attoseconds have elapsed since
156 /// [`NTP_EPOCH`](../struct.Dt.html#associatedconstant.NTP_EPOCH).
157 /// - The count is on whatever scale sits in this [`Dt`]'s `target` field — for example
158 /// `Scale::UTC` if you built it with `from_ymd(..., Scale::UTC, ...)`. The result's
159 /// `scale` and `target` are both set to that same value.
160 ///
161 /// ## Examples
162 ///
163 /// ```rust
164 /// use deep_time::{Dt, Scale};
165 ///
166 /// // 2698012800
167 /// let dt = Dt::from_ymd(1985, 7, 1, Scale::TAI, 0, 0, 0, 0);
168 /// let ntp = dt.to_ntp();
169 ///
170 /// assert_eq!(
171 /// ntp.to_attos(), Dt::sec_to_attos(2698012800_i128),
172 /// "ntp sec for 1985 is wrong, got: {}, expected: {}",
173 /// ntp.to_attos(), Dt::sec_to_attos(2698012800_i128)
174 /// );
175 ///
176 /// let dt2 = Dt::from_ntp(ntp);
177 ///
178 /// assert_eq!(
179 /// dt.to_attos(), dt2.to_attos(),
180 /// "round trip to Dt got wrong sec, old: {}, new: {}",
181 /// dt.to_attos(), dt2.to_attos()
182 /// );
183 ///
184 /// let ymd = dt2.to_ymd();
185 /// assert_eq!(ymd.yr(), 1985_i64);
186 /// assert_eq!(ymd.mo(), 7);
187 /// assert_eq!(ymd.day(), 1);
188 /// assert_eq!(ymd.hr(), 0);
189 /// assert_eq!(ymd.min(), 0);
190 /// assert_eq!(ymd.sec(), 0);
191 /// assert_eq!(ymd.attos(), 0);
192 /// ```
193 ///
194 /// ## See also
195 ///
196 /// - [`Dt::from_ntp`](../struct.Dt.html#method.from_ntp)
197 #[inline(always)]
198 pub const fn to_ntp(&self) -> Dt {
199 self.to_scale_and_diff(Self::NTP_EPOCH, true)
200 }
201
202 /// Creates a **TAI** [`Dt`] from a [`Dt`] that is attoseconds since
203 /// [`Dt::NTP_EPOCH`](../struct.Dt.html#associatedconstant.NTP_EPOCH).
204 ///
205 /// This is the inverse of [`Dt::to_ntp`](../struct.Dt.html#method.to_ntp).
206 ///
207 /// ## Important:
208 ///
209 /// - `ntp` must be a [`Dt`] whose `attos` is how many attoseconds have elapsed since
210 /// [`NTP_EPOCH`](../struct.Dt.html#associatedconstant.NTP_EPOCH) — typically the
211 /// return value of [`Dt::to_ntp`](../struct.Dt.html#method.to_ntp)
212 /// The input's `scale` field says which time scale that count is on — if it
213 /// is `Scale::UTC`, the count is treated as UTC and converted to TAI (leap seconds
214 /// included).
215 /// - [`Dt::NTP_EPOCH`](../struct.Dt.html#associatedconstant.NTP_EPOCH) is converted
216 /// to that same scale before the sum.
217 ///
218 /// ## Returns
219 ///
220 /// A **TAI** [`Dt`] for the reconstructed instant. Its `attos` is no longer a count since
221 /// [`NTP_EPOCH`](../struct.Dt.html#associatedconstant.NTP_EPOCH) — it is attoseconds since
222 /// the library epoch (2000-01-01 noon TAI). Its `target` field is taken from `ntp`.
223 ///
224 /// ## Examples
225 ///
226 /// ```rust
227 /// use deep_time::{Dt, Scale};
228 ///
229 /// let dt = Dt::from_ymd(1985, 7, 1, Scale::TAI, 0, 0, 0, 0);
230 /// let ntp = dt.to_ntp();
231 /// let roundtrip = Dt::from_ntp(ntp);
232 ///
233 /// assert_eq!(roundtrip, dt);
234 /// ```
235 ///
236 /// ## See also
237 ///
238 /// - [`Dt::to_ntp`](../struct.Dt.html#method.to_ntp)
239 #[inline(always)]
240 pub const fn from_ntp(ntp: Dt) -> Dt {
241 Self::from_diff_and_scale(ntp, Self::NTP_EPOCH, true)
242 }
243
244 /// Returns this [`Dt`] but as time since the
245 /// [`Dt::GPS_EPOCH`](../struct.Dt.html#associatedconstant.GPS_EPOCH) on its
246 /// `target` time scale.
247 ///
248 /// ## Important:
249 ///
250 /// - The [`Dt`] first converts itself and the epoch to the time scale of its
251 /// `target` field before doing a raw difference with the epoch.
252 /// - **You may need to change the [`Dt`]'s `target` field** before calling the function
253 /// if you need the timestamp to be on a particular time scale, e.g.
254 /// `.target(Scale::GPS)`.
255 /// - This function assumes this [`Dt`] is currently from the 2000-01-01 noon epoch,
256 /// if it's not then the output will be incorrect.
257 ///
258 /// ## Returns
259 ///
260 /// - A [`Dt`] whose `attos` is how many attoseconds have elapsed since
261 /// [`GPS_EPOCH`](../struct.Dt.html#associatedconstant.GPS_EPOCH).
262 /// - The count is on whatever scale sits in this [`Dt`]'s `target` field — for example
263 /// `Scale::GPS` after `.target(Scale::GPS)`. The result's `scale` and `target` are both
264 /// set to that same value.
265 ///
266 /// ## See also
267 ///
268 /// - [`Dt::from_gps`](../struct.Dt.html#method.from_gps)
269 /// - [`Dt::from_ymd`](../struct.Dt.html#method.from_ymd)
270 /// - [`Dt::to_ymd`](../struct.Dt.html#method.to_ymd)
271 ///
272 /// ## Implementation
273 ///
274 /// `convert_epoch` is `true`. If we did not convert the epoch, we would not get seconds
275 /// since the GPS epoch; we would get seconds since something else.
276 ///
277 /// [`Dt::from_ymd`](../struct.Dt.html#method.from_ymd) / [`Dt::to_ymd`](../struct.Dt.html#method.to_ymd)
278 /// do the opposite: if they converted the epoch too, the difference would cancel out. See
279 /// [`to_ymd`](../struct.Dt.html#method.to_ymd).
280 #[inline(always)]
281 pub const fn to_gps(&self) -> Dt {
282 self.to_scale_and_diff(Self::GPS_EPOCH, true)
283 }
284
285 /// Creates a **TAI** [`Dt`] from a [`Dt`] that is attoseconds since
286 /// [`Dt::GPS_EPOCH`](../struct.Dt.html#associatedconstant.GPS_EPOCH).
287 ///
288 /// This is the inverse of [`Dt::to_gps`](../struct.Dt.html#method.to_gps).
289 ///
290 /// ## Important:
291 ///
292 /// - `elapsed` must be a [`Dt`] whose `attos` is how many attoseconds have elapsed since
293 /// [`GPS_EPOCH`](../struct.Dt.html#associatedconstant.GPS_EPOCH) — typically the
294 /// return value of [`Dt::to_gps`](../struct.Dt.html#method.to_gps)
295 /// The input's `scale` field says which time scale that count is on — if it
296 /// is `Scale::UTC`, the count is treated as UTC and converted to TAI (leap seconds
297 /// included).
298 /// - [`Dt::GPS_EPOCH`](../struct.Dt.html#associatedconstant.GPS_EPOCH) is converted
299 /// to that same scale before the sum.
300 ///
301 /// ## Returns
302 ///
303 /// A **TAI** [`Dt`] for the reconstructed instant. Its `attos` is no longer a count since
304 /// [`GPS_EPOCH`](../struct.Dt.html#associatedconstant.GPS_EPOCH) — it is attoseconds since
305 /// the library epoch (2000-01-01 noon TAI). Its `target` field is taken from `elapsed`.
306 ///
307 /// ## Examples
308 ///
309 /// ```rust
310 /// use deep_time::{Dt, Scale};
311 ///
312 /// let x = Dt::from_ymd(2000, 1, 1, Scale::TAI, 12, 0, 0, 0);
313 /// let gps = x.target(Scale::GPS).to_gps();
314 /// let roundtrip = Dt::from_gps(gps);
315 ///
316 /// assert_eq!(roundtrip, x);
317 /// ```
318 ///
319 /// ## See also
320 ///
321 /// - [`Dt::to_gps`](../struct.Dt.html#method.to_gps)
322 /// - [`Dt::from_gps_wk_and_tow`](../struct.Dt.html#method.from_gps_wk_and_tow)
323 #[inline(always)]
324 pub const fn from_gps(elapsed: Dt) -> Dt {
325 Self::from_diff_and_scale(elapsed, Self::GPS_EPOCH, true)
326 }
327
328 /// Returns the GPS week number and Time of Week (TOW) for this instant.
329 ///
330 /// Elapsed time since [`Dt::GPS_EPOCH`](../struct.Dt.html#associatedconstant.GPS_EPOCH)
331 /// is computed by [`Dt::to_gps`](../struct.Dt.html#method.to_gps) — on this [`Dt`]'s
332 /// `target` time scale — and then split into whole weeks plus a remainder.
333 ///
334 /// This is the inverse of
335 /// [`Dt::from_gps_wk_and_tow`](../struct.Dt.html#method.from_gps_wk_and_tow).
336 ///
337 /// ## Important:
338 ///
339 /// - Uses [`Dt::to_gps`](../struct.Dt.html#method.to_gps) internally: this [`Dt`] and
340 /// [`Dt::GPS_EPOCH`](../struct.Dt.html#associatedconstant.GPS_EPOCH) are both converted
341 /// to the `target` time scale before differencing.
342 /// - **You may need to change the [`Dt`]'s `target` field** before calling if you need
343 /// week/TOW on a particular time scale, e.g. `Scale::GPS`.
344 /// - This function assumes this [`Dt`] is currently from the 2000-01-01 noon epoch,
345 /// if it's not then the output will be incorrect.
346 ///
347 /// ## Returns
348 ///
349 /// A `(week, tow)` pair:
350 ///
351 /// - `week` (`i64`): whole weeks in the elapsed time from
352 /// [`Dt::to_gps`](../struct.Dt.html#method.to_gps). Week 0 starts at the GPS epoch
353 /// (1980-01-06). Before that date the elapsed time is negative and `div_euclid` yields a
354 /// negative week — this is not a broadcast GPS week number, just how the split is defined.
355 /// A plain integer is enough here; it is only a week count, not a duration in attoseconds.
356 /// - `tow` ([`Dt`]): seconds-within-the-week as attoseconds in `0 .. 604800`. Its `scale` and
357 /// `target` are set to this [`Dt`]'s `target` so
358 /// [`Dt::from_gps_wk_and_tow`](../struct.Dt.html#method.from_gps_wk_and_tow) knows which
359 /// time scale the pair belongs to. `tow` is a [`Dt`] rather than a bare integer so
360 /// sub-second precision and scale are preserved together; the week number alone cannot
361 /// carry either. `div_euclid` / `rem_euclid` are used (not truncating `/`) so TOW stays
362 /// non-negative even when the elapsed time is negative.
363 ///
364 /// ## Examples
365 ///
366 /// ```rust
367 /// use deep_time::{Dt, Scale};
368 ///
369 /// let x = Dt::from_ymd(2000, 1, 1, Scale::TAI, 12, 0, 0, 0);
370 /// let g = x.to_gps_wk_and_tow();
371 /// let z = Dt::from_gps_wk_and_tow(g.0, g.1);
372 /// assert_eq!(x, z);
373 ///
374 /// // for conventional GPS-time week/TOW, set target first:
375 /// let g = x.target(Scale::GPS).to_gps_wk_and_tow();
376 /// ```
377 ///
378 /// ## See also
379 ///
380 /// - [`Dt::from_gps_wk_and_tow`](../struct.Dt.html#method.from_gps_wk_and_tow)
381 /// - [`Dt::to_gps`](../struct.Dt.html#method.to_gps)
382 pub const fn to_gps_wk_and_tow(&self) -> (i64, Dt) {
383 let total_attos = self.to_gps().to_attos();
384 let wk = total_attos.div_euclid(ATTOS_PER_WEEK) as i64;
385 let tow_attos = total_attos.rem_euclid(ATTOS_PER_WEEK);
386 // was converted to target scale, scale is now target
387 (wk, Dt::new(tow_attos, self.target, self.target))
388 }
389
390 /// Creates a [`Dt`] from a GPS week number and Time of Week (TOW).
391 ///
392 /// Recombines `week` and `tow` into elapsed time since
393 /// [`Dt::GPS_EPOCH`](../struct.Dt.html#associatedconstant.GPS_EPOCH), then passes that to
394 /// [`Dt::from_gps`](../struct.Dt.html#method.from_gps).
395 ///
396 /// This is the inverse of
397 /// [`Dt::to_gps_wk_and_tow`](../struct.Dt.html#method.to_gps_wk_and_tow).
398 ///
399 /// ## Important:
400 ///
401 /// - Uses [`Dt::from_gps`](../struct.Dt.html#method.from_gps) internally: the elapsed time
402 /// is interpreted on the `tow` [`Dt`]'s `scale` / `target` fields, and
403 /// [`Dt::GPS_EPOCH`](../struct.Dt.html#associatedconstant.GPS_EPOCH) is converted to that
404 /// same scale before the sum.
405 /// - Pass back the `tow` from [`Dt::to_gps_wk_and_tow`](../struct.Dt.html#method.to_gps_wk_and_tow)
406 /// unchanged if you want a round trip.
407 ///
408 /// ## Returns
409 ///
410 /// A **TAI** [`Dt`] for the reconstructed instant. Its `target` field is taken from `tow`.
411 ///
412 /// `tow` must be a [`Dt`] (not a bare second count) because
413 /// [`Dt::from_gps`](../struct.Dt.html#method.from_gps) needs both the within-week attoseconds
414 /// and the `scale` / `target` that say which time scale `week` and `tow` were expressed on.
415 /// The week number is multiplied back into attoseconds (`week * 604800` seconds); only `tow`
416 /// carries the scale and sub-week precision needed for the round trip.
417 ///
418 /// `tow` should be in `0 .. 604800` seconds, as returned by
419 /// [`Dt::to_gps_wk_and_tow`](../struct.Dt.html#method.to_gps_wk_and_tow). Negative `week`
420 /// values only arise from dates before 1980-01-06 (see that function).
421 ///
422 /// ## Examples
423 ///
424 /// ```rust
425 /// use deep_time::{Dt, Scale};
426 ///
427 /// let x = Dt::from_ymd(2000, 1, 1, Scale::TAI, 12, 0, 0, 0);
428 /// let g = x.to_gps_wk_and_tow();
429 /// let z = Dt::from_gps_wk_and_tow(g.0, g.1);
430 /// assert_eq!(x, z);
431 /// ```
432 ///
433 /// ## See also
434 ///
435 /// - [`Dt::to_gps_wk_and_tow`](../struct.Dt.html#method.to_gps_wk_and_tow)
436 /// - [`Dt::from_gps`](../struct.Dt.html#method.from_gps)
437 pub const fn from_gps_wk_and_tow(wk: i64, tow: Dt) -> Dt {
438 let total_attos = (wk as i128)
439 .saturating_mul(ATTOS_PER_WEEK)
440 .saturating_add(tow.to_attos());
441
442 Self::from_gps(Dt::new(total_attos, tow.scale, tow.target))
443 }
444
445 /// Returns the day of the GPS week (0 = Sunday, 1 = Monday, …, 6 = Saturday).
446 ///
447 /// This value is computed directly from the GPS Time of Week and is
448 /// independent of the Gregorian calendar or civil time.
449 pub const fn to_gps_day_of_wk(&self) -> u8 {
450 let (_, tow) = self.to_gps_wk_and_tow();
451 let secs = tow.to_attos() / ATTOS_PER_SEC_I128;
452
453 (secs / SEC_PER_DAYI64 as i128) as u8
454 }
455
456 /// Returns this [`Dt`] but as time since the
457 /// [`Dt::CXC_EPOCH`](../struct.Dt.html#associatedconstant.CXC_EPOCH) on its
458 /// `target` time scale.
459 ///
460 /// ## Important:
461 ///
462 /// - The [`Dt`] first converts itself and the epoch to the time scale of its
463 /// `target` field before doing a raw difference with the epoch.
464 /// - **You may need to change the [`Dt`]'s `target` field** before calling the function
465 /// if you need the timestamp to be on a particular time scale, e.g. `UTC`.
466 /// - This function assumes this [`Dt`] is currently from the 2000-01-01 noon epoch,
467 /// if it's not then the output will be incorrect.
468 ///
469 /// ## Returns
470 ///
471 /// - A [`Dt`] whose `attos` is how many attoseconds have elapsed since
472 /// [`CXC_EPOCH`](../struct.Dt.html#associatedconstant.CXC_EPOCH).
473 /// - The count is on whatever scale sits in this [`Dt`]'s `target` field — for example
474 /// `Scale::TT` after `.target(Scale::TT)`. The result's `scale` and `target` are both
475 /// set to that same value.
476 ///
477 /// ## Examples
478 ///
479 /// ```rust
480 /// use deep_time::{Dt, Scale};
481 ///
482 /// let cxc = Dt::from_ymd(2020, 1, 1, Scale::TAI, 0, 0, 0, 0)
483 /// .target(Scale::TT)
484 /// .to_cxcsec()
485 /// .to_sec_f();
486 ///
487 /// // cxcsec 694224032.184 (matches Astropy)
488 /// assert_eq!(cxc, 694224032.184);
489 /// ```
490 ///
491 /// ## See also
492 ///
493 /// - [`Dt::from_cxcsec`](../struct.Dt.html#method.from_cxcsec)
494 #[inline(always)]
495 pub const fn to_cxcsec(&self) -> Dt {
496 self.to_scale_and_diff(Self::CXC_EPOCH, true)
497 }
498
499 /// Creates a **TAI** [`Dt`] from a [`Dt`] that is attoseconds since
500 /// [`Dt::CXC_EPOCH`](../struct.Dt.html#associatedconstant.CXC_EPOCH).
501 ///
502 /// This is the inverse of [`Dt::to_cxcsec`](../struct.Dt.html#method.to_cxcsec).
503 ///
504 /// ## Important:
505 ///
506 /// - `elapsed` must be a [`Dt`] whose `attos` is how many attoseconds have elapsed since
507 /// [`CXC_EPOCH`](../struct.Dt.html#associatedconstant.CXC_EPOCH) — typically the
508 /// return value of [`Dt::to_cxcsec`](../struct.Dt.html#method.to_cxcsec)
509 /// The input's `scale` field says which time scale that count is on — if it
510 /// is `Scale::UTC`, the count is treated as UTC and converted to TAI (leap seconds
511 /// included).
512 /// - [`Dt::CXC_EPOCH`](../struct.Dt.html#associatedconstant.CXC_EPOCH) is converted
513 /// to that same scale before the sum.
514 ///
515 /// ## Returns
516 ///
517 /// A **TAI** [`Dt`] for the reconstructed instant. Its `attos` is no longer a count since
518 /// [`CXC_EPOCH`](../struct.Dt.html#associatedconstant.CXC_EPOCH) — it is attoseconds since
519 /// the library epoch (2000-01-01 noon TAI). Its `target` field is taken from `elapsed`.
520 ///
521 /// ## Examples
522 ///
523 /// ```rust
524 /// use deep_time::{Dt, Scale};
525 ///
526 /// let x = Dt::from_ymd(2020, 1, 1, Scale::TAI, 0, 0, 0, 0);
527 /// let cxc = x.target(Scale::TT).to_cxcsec();
528 /// let roundtrip = Dt::from_cxcsec(cxc);
529 ///
530 /// assert_eq!(roundtrip, x);
531 /// ```
532 ///
533 /// ## See also
534 ///
535 /// - [`Dt::to_cxcsec`](../struct.Dt.html#method.to_cxcsec)
536 /// - [`Dt::from_cxcsec_f`](../struct.Dt.html#method.from_cxcsec_f)
537 #[inline(always)]
538 pub const fn from_cxcsec(elapsed: Dt) -> Dt {
539 Self::from_diff_and_scale(elapsed, Self::CXC_EPOCH, true)
540 }
541
542 /// Convenience wrapper around [`Self::from_cxcsec`] for a bare floating-point
543 /// second count.
544 ///
545 /// Wraps `sec` in a [`Dt`] via [`Dt::sec_f_to_attos`] and
546 /// [`Dt::new`], then passes it to [`Self::from_cxcsec`]. Unlike [`Dt::from_sec_f`],
547 /// this does not convert the count to TAI up front — [`Self::from_cxcsec`] performs
548 /// that conversion once, from `on`.
549 ///
550 /// ## Parameters
551 ///
552 /// - `sec` — seconds elapsed since
553 /// [`CXC_EPOCH`](../struct.Dt.html#associatedconstant.CXC_EPOCH).
554 /// - `on` — which [`Scale`] the count is measured in (for example `Scale::TT` or
555 /// `Scale::UTC`). This becomes the wrapped [`Dt`]'s `scale`; [`Self::from_cxcsec`]
556 /// then uses it when turning the elapsed count into an absolute TAI instant
557 /// (including leap-second handling where applicable). Same role as the `scale`
558 /// field on the [`Dt`] you would hand to [`Self::from_cxcsec`] directly.
559 ///
560 /// ## Examples
561 ///
562 /// ```rust
563 /// use deep_time::{Dt, Scale};
564 ///
565 /// let x = Dt::from_ymd(2020, 1, 1, Scale::TAI, 0, 0, 0, 0);
566 /// let cxc = x.target(Scale::TT).to_cxcsec().to_sec_f();
567 /// let roundtrip = Dt::from_cxcsec_f(cxc, Scale::TT);
568 ///
569 /// assert_eq!(roundtrip.to_cxcsec().to_sec_f(), cxc);
570 /// ```
571 ///
572 /// ## See also
573 ///
574 /// - [`Dt::from_cxcsec`](../struct.Dt.html#method.from_cxcsec)
575 /// - [`Dt::to_cxcsec`](../struct.Dt.html#method.to_cxcsec)
576 #[inline(always)]
577 pub const fn from_cxcsec_f(sec: Real, on: Scale) -> Dt {
578 Self::from_cxcsec(Dt::new(Dt::sec_f_to_attos(sec), on, on))
579 }
580
581 /// Returns the elapsed time since the GALEX epoch as a [`Dt`] expressed
582 /// in this object's current `target` scale.
583 ///
584 /// This method can match Astropy’s `Time.galexsec` format. To match
585 /// Astropy output, set `.target(Scale::UTC)` (or the appropriate scale)
586 /// before calling.
587 ///
588 /// The GALEX epoch is [`Self::GPS_EPOCH`] (same epoch used by GPS time).
589 ///
590 /// ## Important:
591 ///
592 /// - The [`Dt`] first converts itself and the [`Dt::GPS_EPOCH`] to the time
593 /// scale of its `target` field before doing a raw difference with the epoch.
594 /// - This function assumes this [`Dt`] is currently from the 2000-01-01 noon
595 /// epoch, if it's not then the output will be incorrect.
596 ///
597 /// ## Returns
598 ///
599 /// - A [`Dt`] whose `attos` is how many attoseconds have elapsed since
600 /// [`GPS_EPOCH`](../struct.Dt.html#associatedconstant.GPS_EPOCH).
601 /// - The count is on whatever scale sits in this [`Dt`]'s `target` field — for example
602 /// `Scale::UTC` after `.target(Scale::UTC)`. The result's `scale` and `target` are both
603 /// set to that same value.
604 ///
605 /// ## Examples
606 ///
607 /// ```rust
608 /// use deep_time::{Dt, Scale};
609 ///
610 /// let galexsec = Dt::from_ymd(2020, 1, 1, Scale::TAI, 0, 0, 0, 0)
611 /// .target(Scale::UTC)
612 /// .to_galexsec()
613 /// .to_sec_f();
614 ///
615 /// assert_eq!(galexsec, 1261871963.0);
616 /// ```
617 ///
618 /// ## See also
619 ///
620 /// - [`Dt::from_galexsec`](../struct.Dt.html#method.from_galexsec)
621 #[inline(always)]
622 pub const fn to_galexsec(&self) -> Dt {
623 self.to_scale_and_diff(Self::GPS_EPOCH, true)
624 }
625
626 /// Creates a **TAI** [`Dt`] from a [`Dt`] that is attoseconds since
627 /// [`Dt::GPS_EPOCH`](../struct.Dt.html#associatedconstant.GPS_EPOCH).
628 ///
629 /// This is the inverse of [`Dt::to_galexsec`](../struct.Dt.html#method.to_galexsec).
630 /// GALEX seconds use the same epoch as GPS time.
631 ///
632 /// ## Important:
633 ///
634 /// - `elapsed` must be a [`Dt`] whose `attos` is how many attoseconds have elapsed since
635 /// [`GPS_EPOCH`](../struct.Dt.html#associatedconstant.GPS_EPOCH) — typically the
636 /// return value of [`Dt::to_galexsec`](../struct.Dt.html#method.to_galexsec)
637 /// The input's `scale` field says which time scale that count is on — if it
638 /// is `Scale::UTC`, the count is treated as UTC and converted to TAI (leap seconds
639 /// included).
640 /// - [`Dt::GPS_EPOCH`](../struct.Dt.html#associatedconstant.GPS_EPOCH) is converted
641 /// to that same scale before the sum.
642 ///
643 /// ## Returns
644 ///
645 /// A **TAI** [`Dt`] for the reconstructed instant. Its `attos` is no longer a count since
646 /// [`GPS_EPOCH`](../struct.Dt.html#associatedconstant.GPS_EPOCH) — it is attoseconds since
647 /// the library epoch (2000-01-01 noon TAI). Its `target` field is taken from `elapsed`.
648 ///
649 /// ## Examples
650 ///
651 /// ```rust
652 /// use deep_time::{Dt, Scale};
653 ///
654 /// let x = Dt::from_ymd(2020, 1, 1, Scale::TAI, 0, 0, 0, 0);
655 /// let galex = x.target(Scale::UTC).to_galexsec();
656 /// let roundtrip = Dt::from_galexsec(galex);
657 ///
658 /// assert_eq!(roundtrip, x);
659 /// ```
660 ///
661 /// ## See also
662 ///
663 /// - [`Dt::to_galexsec`](../struct.Dt.html#method.to_galexsec)
664 /// - [`Dt::from_galexsec_f`](../struct.Dt.html#method.from_galexsec_f)
665 #[inline(always)]
666 pub const fn from_galexsec(elapsed: Dt) -> Dt {
667 Self::from_diff_and_scale(elapsed, Self::GPS_EPOCH, true)
668 }
669
670 /// Convenience wrapper around [`Self::from_galexsec`] for a bare floating-point
671 /// second count.
672 ///
673 /// Wraps `sec` in a [`Dt`] via [`Dt::sec_f_to_attos`] and
674 /// [`Dt::new`], then passes it to [`Self::from_galexsec`]. Unlike [`Dt::from_sec_f`],
675 /// this does not convert the count to TAI up front — [`Self::from_galexsec`] performs
676 /// that conversion once, from `on`.
677 ///
678 /// ## Parameters
679 ///
680 /// - `sec` — seconds elapsed since
681 /// [`GPS_EPOCH`](../struct.Dt.html#associatedconstant.GPS_EPOCH).
682 /// - `on` — which [`Scale`] the count is measured in (for example `Scale::UTC` or
683 /// `Scale::TT`). This becomes the wrapped [`Dt`]'s `scale`; [`Self::from_galexsec`]
684 /// then uses it when turning the elapsed count into an absolute TAI instant
685 /// (including leap-second handling where applicable). Same role as the `scale`
686 /// field on the [`Dt`] you would hand to [`Self::from_galexsec`] directly.
687 ///
688 /// ## Examples
689 ///
690 /// ```rust
691 /// use deep_time::{Dt, Scale};
692 ///
693 /// let x = Dt::from_ymd(2020, 1, 1, Scale::TAI, 0, 0, 0, 0);
694 /// let galex = x.target(Scale::UTC).to_galexsec().to_sec_f();
695 /// let roundtrip = Dt::from_galexsec_f(galex, Scale::UTC);
696 ///
697 /// assert_eq!(roundtrip, x);
698 /// ```
699 ///
700 /// ## See also
701 ///
702 /// - [`Dt::from_galexsec`](../struct.Dt.html#method.from_galexsec)
703 /// - [`Dt::to_galexsec`](../struct.Dt.html#method.to_galexsec)
704 #[inline(always)]
705 pub const fn from_galexsec_f(sec: Real, on: Scale) -> Dt {
706 Self::from_galexsec(Dt::new(Dt::sec_f_to_attos(sec), on, on))
707 }
708
709 /// Returns the **Julian epoch year** (JYEAR) for this instant.
710 ///
711 /// Julian years are defined as exactly 365.25 days of 86400 SI seconds.
712 /// This is the system used for J2000.0 and many astronomical calculations.
713 ///
714 /// This is **not** the same as [`Self::to_decimalyear`], which uses the
715 /// actual length of the specific Gregorian year.
716 ///
717 /// This is the inverse of [`Self::from_jyear`].
718 ///
719 /// ## Examples
720 ///
721 /// ```rust
722 /// use deep_time::{Dt, Scale};
723 ///
724 /// let x = Dt::from_ymd(2020, 1, 1, Scale::TAI, 0, 0, 0, 0);
725 /// assert_eq!(x.to_jyear(), 2019.9986310746065);
726 /// ```
727 #[inline(always)]
728 pub const fn to_jyear(&self) -> Real {
729 let jd_tt = self.to_jd_f();
730 f!(2000.0) + (jd_tt - JD_2000_2_451_545F) / f!(365.25)
731 }
732
733 /// Inverse of [`Self::to_jyear`].
734 pub const fn from_jyear(jyear: Real, scale: Scale) -> Dt {
735 if jyear.is_nan() {
736 return Self::ZERO;
737 }
738 if jyear.is_infinite() {
739 return if jyear.is_sign_positive() {
740 Self::MAX
741 } else {
742 Self::MIN
743 };
744 }
745
746 let jd = JD_2000_2_451_545F + (jyear - f!(2000.0)) * f!(365.25);
747 Self::from_jd_f(jd, scale)
748 }
749
750 /// Returns the **Besselian epoch year** (BYEAR) for this instant.
751 ///
752 /// Besselian years are an older astronomical convention based on a
753 /// tropical year length of approximately 365.242198781 days.
754 ///
755 /// This is the inverse of [`Self::from_byear`].
756 ///
757 /// ## Examples
758 ///
759 /// ```rust
760 /// use deep_time::{Dt, Scale};
761 ///
762 /// let x = Dt::from_ymd(2020, 1, 1, Scale::TAI, 0, 0, 0, 0);
763 /// assert!((x.to_byear() - 2020.000335739628).abs() < 1e-12);
764 /// ```
765 #[inline]
766 pub const fn to_byear(&self) -> Real {
767 let jd_tt = self.to_jd_f();
768 f!(1900.0) + (jd_tt - f!(2415020.31352)) / f!(365.242198781)
769 }
770
771 /// Inverse of [`Self::to_byear`].
772 pub const fn from_byear(byear: Real, scale: Scale) -> Dt {
773 if byear.is_nan() {
774 return Self::ZERO;
775 }
776 if byear.is_infinite() {
777 return if byear.is_sign_positive() {
778 Self::MAX
779 } else {
780 Self::MIN
781 };
782 }
783
784 let jd = f!(2415020.31352) + (byear - f!(1900.0)) * f!(365.242198781);
785 Self::from_jd_f(jd, scale)
786 }
787
788 /// Returns the **decimal year** (Gregorian calendar year + fraction of the year).
789 ///
790 /// This is the direct equivalent of Astropy’s `Time.decimalyear`:
791 /// - Uses the *actual* length of the specific Gregorian year (365 or 366 days,
792 /// plus any leap seconds on UTC/UtcSpice/etc.).
793 /// - Scale-aware (TAI, TT, UTC, TDB, etc.), converts to this [`Dt`]'s target time
794 /// scale before producing an output.
795 /// - Exact integer arithmetic for the year boundaries, then a high-precision
796 /// `to_sec_f` division (lossy only at the final `Real` step, same as Astropy).
797 ///
798 /// ## Examples
799 ///
800 /// ```rust
801 /// use deep_time::{Dt, Scale};
802 ///
803 /// let x = Dt::from_ymd(2020, 1, 1, Scale::TAI, 0, 0, 0, 0);
804 /// assert_eq!(x.to_decimalyear(), 2020.0);
805 ///
806 /// // Also works for negative years
807 /// let y = Dt::from_ymd(-2000, 1, 1, Scale::TAI, 0, 0, 0, 0);
808 /// assert_eq!(y.to_decimalyear(), -2000.0);
809 /// ```
810 pub fn to_decimalyear(&self) -> Real {
811 let ymd = self.to_ymd();
812 let year = ymd.yr;
813
814 let start = Self::from_ymd(year, 1, 1, self.target, 0, 0, 0, 0);
815 let next_start = Self::from_ymd(year + 1, 1, 1, self.target, 0, 0, 0, 0);
816
817 let elapsed = self.to_diff_raw(start).to_sec_f();
818 let year_length = next_start.to_diff_raw(start).to_sec_f();
819
820 // year_length is never zero for representable years
821 f!(year) + elapsed / year_length
822 }
823}