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