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