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