Skip to main content

deep_time/dt/
to_unit.rs

1use crate::{
2    ATTOS_PER_DAY, ATTOS_PER_FS_I128, ATTOS_PER_HOUR, ATTOS_PER_MIN, ATTOS_PER_MS_I128,
3    ATTOS_PER_NS_I128, ATTOS_PER_PS_I128, ATTOS_PER_SEC_I128, ATTOS_PER_SECF, ATTOS_PER_US_I128,
4    Dt, Real, dt,
5};
6
7impl Dt {
8    /// Returns this duration as a whole number of seconds, dropping any
9    /// fraction (always toward zero: 1.7 → 1, −1.7 → −1).
10    ///
11    /// Same as `self.attos / ATTOS_PER_SEC_I128`. Does not round.
12    ///
13    /// On positive values this matches
14    /// [`to_sec_floor`](../struct.Dt.html#method.to_sec_floor).
15    /// On negatives they diverge: here −1.3 → −1, while `to_sec_floor` gives
16    /// −2 (always rounds down). Prefer `to_sec_floor` when you also need a
17    /// non-negative fractional part via
18    /// [`to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac).
19    ///
20    /// ## Examples
21    ///
22    /// ```rust
23    /// use deep_time::Dt;
24    /// use deep_time::macros::{from_sec, ms};
25    ///
26    /// // Fraction only — less than one second either way → 0
27    /// assert_eq!(from_sec!(0, -ms!(300)).to_sec(), 0);
28    /// assert_eq!(from_sec!(0, ms!(300)).to_sec(), 0);
29    ///
30    /// // -1.3 s → -1 here, but -2 with `to_sec_floor`
31    /// let dt = from_sec!(-1, -ms!(300));
32    /// assert_eq!(dt.to_sec(), -1);
33    /// assert_eq!(dt.to_sec_floor(), -2);
34    ///
35    /// // Positive values match `to_sec_floor`
36    /// let dt = from_sec!(1, ms!(300));
37    /// assert_eq!(dt.to_sec(), 1);
38    /// assert_eq!(dt.to_sec_floor(), 1);
39    /// ```
40    #[inline(always)]
41    pub const fn to_sec(&self) -> i128 {
42        self.attos / ATTOS_PER_SEC_I128
43    }
44
45    /// Same as [`to_sec`](../struct.Dt.html#method.to_sec), but returns an
46    /// [`i64`].
47    ///
48    /// Values outside the `i64` range clamp to [`i64::MAX`] or [`i64::MIN`].
49    ///
50    /// ## Examples
51    ///
52    /// ```rust
53    /// use deep_time::Dt;
54    /// use deep_time::macros::{from_sec, ms};
55    ///
56    /// // Same toward-zero rule as `to_sec`
57    /// assert_eq!(from_sec!(1, ms!(300)).to_sec64(), 1);
58    /// assert_eq!(from_sec!(-1, -ms!(300)).to_sec64(), -1);
59    ///
60    /// // Fits in i64 here; huge values would clamp instead of panicking
61    /// assert_eq!(from_sec!(i64::MAX as i128).to_sec64(), i64::MAX);
62    /// ```
63    #[inline(always)]
64    pub const fn to_sec64(&self) -> i64 {
65        Self::to_i64(self.attos / ATTOS_PER_SEC_I128)
66    }
67
68    /// Returns whole seconds, always rounding down (toward −∞).
69    ///
70    /// So 1.3 → 1 and −1.3 → −2. The leftover attoseconds are then always
71    /// ≥ 0; get them with
72    /// [`to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac).
73    ///
74    /// For truncation toward zero (1.3 → 1, −1.3 → −1), use
75    /// [`to_sec`](../struct.Dt.html#method.to_sec).
76    /// For nearest-second rounding, use
77    /// [`to_sec_round`](../struct.Dt.html#method.to_sec_round).
78    ///
79    /// ## Examples
80    ///
81    /// ```rust
82    /// use deep_time::Dt;
83    /// use deep_time::macros::{from_sec, ms};
84    ///
85    /// // -1.3 s → whole -2, leftover +0.7 s
86    /// let dt = from_sec!(-1, -ms!(300));
87    /// assert_eq!(dt.to_sec_floor(), -2);
88    /// assert_eq!(dt.to_sec_ufrac(), ms!(700) as u64);
89    ///
90    /// // +1.3 s → whole 1, leftover +0.3 s
91    /// let dt = from_sec!(1, ms!(300));
92    /// assert_eq!(dt.to_sec_floor(), 1);
93    /// assert_eq!(dt.to_sec_ufrac(), ms!(300) as u64);
94    /// ```
95    #[inline(always)]
96    pub const fn to_sec_floor(&self) -> i128 {
97        self.attos.div_euclid(ATTOS_PER_SEC_I128)
98    }
99
100    /// Same as [`to_sec_floor`](../struct.Dt.html#method.to_sec_floor),
101    /// but returns an [`i64`].
102    ///
103    /// Values outside the `i64` range clamp to [`i64::MAX`] or [`i64::MIN`].
104    /// Pair with [`to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac)
105    /// for the leftover.
106    ///
107    /// ## Examples
108    ///
109    /// ```rust
110    /// use deep_time::Dt;
111    /// use deep_time::macros::{from_sec, ms};
112    ///
113    /// let dt = from_sec!(-1, -ms!(300));
114    /// assert_eq!(dt.to_sec64_floor(), -2);
115    /// assert_eq!(dt.to_sec_ufrac(), ms!(700) as u64);
116    ///
117    /// let dt = from_sec!(1, ms!(300));
118    /// assert_eq!(dt.to_sec64_floor(), 1);
119    /// assert_eq!(dt.to_sec_ufrac(), ms!(300) as u64);
120    /// ```
121    #[inline(always)]
122    pub const fn to_sec64_floor(&self) -> i64 {
123        Self::to_i64(self.attos.div_euclid(ATTOS_PER_SEC_I128))
124    }
125
126    /// Rounds to the nearest whole second and returns that count as [`i128`].
127    ///
128    /// Halfway cases go away from zero: 0.5 → 1 and −0.5 → −1
129    /// (same rule as [`Dt::round`]).
130    ///
131    /// ## Examples
132    ///
133    /// ```rust
134    /// use deep_time::Dt;
135    /// use deep_time::macros::{from_sec, ms};
136    ///
137    /// assert_eq!(from_sec!(1, ms!(300)).to_sec_round(), 1);
138    /// assert_eq!(from_sec!(1, ms!(600)).to_sec_round(), 2);
139    /// assert_eq!(from_sec!(-1, -ms!(300)).to_sec_round(), -1);
140    ///
141    /// // Halfway: away from zero
142    /// assert_eq!(from_sec!(0, ms!(500)).to_sec_round(), 1);
143    /// assert_eq!(from_sec!(0, -ms!(500)).to_sec_round(), -1);
144    /// ```
145    #[inline(always)]
146    pub const fn to_sec_round(&self) -> i128 {
147        self.round_to_sec().to_sec()
148    }
149
150    /// Same as [`to_sec_round`](../struct.Dt.html#method.to_sec_round),
151    /// but returns an [`i64`].
152    ///
153    /// Values outside the `i64` range clamp to [`i64::MAX`] or [`i64::MIN`].
154    ///
155    /// ## Examples
156    ///
157    /// ```rust
158    /// use deep_time::Dt;
159    /// use deep_time::macros::{from_sec, ms};
160    ///
161    /// assert_eq!(from_sec!(1, ms!(300)).to_sec64_round(), 1);
162    /// assert_eq!(from_sec!(1, ms!(600)).to_sec64_round(), 2);
163    /// assert_eq!(from_sec!(-1, -ms!(300)).to_sec64_round(), -1);
164    /// assert_eq!(from_sec!(0, ms!(500)).to_sec64_round(), 1);
165    /// ```
166    #[inline(always)]
167    pub const fn to_sec64_round(&self) -> i64 {
168        Self::to_i64(self.round_to_sec().to_sec())
169    }
170
171    /// Converts this duration to seconds as an [`f64`].
172    ///
173    /// Alias of [`to_sec_f`](../struct.Dt.html#method.to_sec_f).
174    #[inline(always)]
175    pub const fn to_f64(&self) -> f64 {
176        self.to_sec_f()
177    }
178
179    /// Converts this duration to seconds as a floating-point number.
180    ///
181    /// ## Examples
182    ///
183    /// ```rust
184    /// use deep_time::Dt;
185    /// use deep_time::macros::{from_sec, ms};
186    ///
187    /// assert_eq!(Dt::ZERO.to_sec_f(), 0.0);
188    /// assert!((from_sec!(1, ms!(500)).to_sec_f() - 1.5).abs() < 1e-12);
189    /// assert!((from_sec!(-1, -ms!(500)).to_sec_f() + 1.5).abs() < 1e-12);
190    /// ```
191    pub const fn to_sec_f(&self) -> Real {
192        if self.attos == 0 {
193            return 0.0;
194        }
195        let sec = self.attos.div_euclid(ATTOS_PER_SEC_I128);
196        let rem = self.attos.rem_euclid(ATTOS_PER_SEC_I128); // always in [0, aps)
197
198        if sec < 0 && rem > ATTOS_PER_SEC_I128 / 2 {
199            // original cancellation-avoidance path
200            let small = ATTOS_PER_SEC_I128 - rem;
201            let small_f = f!(small as u64) / ATTOS_PER_SECF;
202            f!(sec) + 1.0 - small_f
203        } else {
204            f!(sec) + f!(rem as u64) / ATTOS_PER_SECF
205        }
206    }
207
208    /// Returns the signed leftover attoseconds after removing whole seconds
209    /// toward zero.
210    ///
211    /// Same sign as the original value when non-zero: 1.3 s → `+0.3 s` in
212    /// attoseconds, −1.3 s → `−0.3 s` in attoseconds. Pairs with
213    /// [`from_sec_and_frac`](../struct.Dt.html#method.from_sec_and_frac)
214    /// and [`to_sec`](../struct.Dt.html#method.to_sec).
215    ///
216    /// For a leftover that is always ≥ 0 (paired with floor), use
217    /// [`to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac).
218    ///
219    /// ## Examples
220    ///
221    /// ```rust
222    /// use deep_time::Dt;
223    /// use deep_time::macros::{from_sec, ms};
224    ///
225    /// let dt = from_sec!(1, ms!(300));
226    /// assert_eq!(dt.to_sec(), 1);
227    /// assert_eq!(dt.to_sec_frac(), ms!(300) as i64);
228    ///
229    /// let dt = from_sec!(-1, -ms!(300));
230    /// assert_eq!(dt.to_sec(), -1);
231    /// assert_eq!(dt.to_sec_frac(), -ms!(300) as i64);
232    /// ```
233    #[inline(always)]
234    pub const fn to_sec_frac(&self) -> i64 {
235        (self.attos % ATTOS_PER_SEC_I128) as i64
236    }
237
238    /// Returns the leftover attoseconds after
239    /// [`to_sec_floor`](../struct.Dt.html#method.to_sec_floor) /
240    /// [`to_sec64_floor`](../struct.Dt.html#method.to_sec64_floor).
241    ///
242    /// Always in `0 .. ATTOS_PER_SEC`. On negatives this is **not** “the
243    /// decimal part with a sign”: −1.3 s floors to −2 whole seconds with a
244    /// **+0.7 s** leftover.
245    ///
246    /// ## Examples
247    ///
248    /// ```rust
249    /// use deep_time::Dt;
250    /// use deep_time::macros::{from_sec, ms};
251    ///
252    /// // -1.3 s → floor -2 s + 0.7 s leftover
253    /// let dt = from_sec!(-1, -ms!(300));
254    /// assert_eq!(dt.to_sec64_floor(), -2);
255    /// assert_eq!(dt.to_sec_ufrac(), ms!(700) as u64);
256    ///
257    /// // +1.3 s → floor 1 s + 0.3 s leftover
258    /// let dt = from_sec!(1, ms!(300));
259    /// assert_eq!(dt.to_sec64_floor(), 1);
260    /// assert_eq!(dt.to_sec_ufrac(), ms!(300) as u64);
261    /// ```
262    #[inline(always)]
263    pub const fn to_sec_ufrac(&self) -> u64 {
264        self.attos.rem_euclid(ATTOS_PER_SEC_I128) as u64
265    }
266
267    /// Returns a new [`Dt`] rounded to the nearest whole second.
268    ///
269    /// Halfway cases go away from zero (same rule as [`Dt::round`]). For the
270    /// rounded value as an integer count, see
271    /// [`to_sec_round`](../struct.Dt.html#method.to_sec_round).
272    ///
273    /// ## Examples
274    ///
275    /// ```rust
276    /// use deep_time::Dt;
277    /// use deep_time::macros::{from_sec, ms, sec};
278    ///
279    /// assert_eq!(from_sec!(1, ms!(300)).round_to_sec(), from_sec!(1));
280    /// assert_eq!(from_sec!(1, ms!(600)).round_to_sec(), from_sec!(2));
281    /// assert_eq!(from_sec!(0, ms!(500)).round_to_sec(), from_sec!(1));
282    /// assert_eq!(from_sec!(0, -ms!(500)).round_to_sec(), from_sec!(-1));
283    /// ```
284    #[inline(always)]
285    pub const fn round_to_sec(&self) -> Dt {
286        self.round(dt!(ATTOS_PER_SEC_I128))
287    }
288
289    /// Returns the total time in attoseconds.
290    #[inline(always)]
291    pub const fn to_attos(&self) -> i128 {
292        self.attos
293    }
294
295    /// Splits into whole femtoseconds and leftover attoseconds, rounding the
296    /// whole part down so the leftover is always ≥ 0.
297    ///
298    /// Returns `(whole, frac_attos)`. Same floor rule as
299    /// [`to_sec_floor`](../struct.Dt.html#method.to_sec_floor).
300    /// For truncation toward zero, use [`to_fs`](../struct.Dt.html#method.to_fs).
301    #[inline(always)]
302    pub const fn to_fs_floor(&self) -> (i128, i128) {
303        (
304            self.attos.div_euclid(ATTOS_PER_FS_I128),
305            self.attos.rem_euclid(ATTOS_PER_FS_I128),
306        )
307    }
308
309    /// Splits into whole picoseconds and leftover attoseconds, rounding the
310    /// whole part down so the leftover is always ≥ 0.
311    ///
312    /// Returns `(whole, frac_attos)`. Same floor rule as
313    /// [`to_sec_floor`](../struct.Dt.html#method.to_sec_floor).
314    /// For truncation toward zero, use [`to_ps`](../struct.Dt.html#method.to_ps).
315    #[inline(always)]
316    pub const fn to_ps_floor(&self) -> (i128, i128) {
317        (
318            self.attos.div_euclid(ATTOS_PER_PS_I128),
319            self.attos.rem_euclid(ATTOS_PER_PS_I128),
320        )
321    }
322
323    /// Splits into whole nanoseconds and leftover attoseconds, rounding the
324    /// whole part down so the leftover is always ≥ 0.
325    ///
326    /// Returns `(whole, frac_attos)`. Same floor rule as
327    /// [`to_sec_floor`](../struct.Dt.html#method.to_sec_floor).
328    /// For truncation toward zero, use [`to_ns`](../struct.Dt.html#method.to_ns).
329    #[inline(always)]
330    pub const fn to_ns_floor(&self) -> (i128, i128) {
331        (
332            self.attos.div_euclid(ATTOS_PER_NS_I128),
333            self.attos.rem_euclid(ATTOS_PER_NS_I128),
334        )
335    }
336
337    /// Splits into whole microseconds and leftover attoseconds, rounding the
338    /// whole part down so the leftover is always ≥ 0.
339    ///
340    /// Returns `(whole, frac_attos)`. Same floor rule as
341    /// [`to_sec_floor`](../struct.Dt.html#method.to_sec_floor).
342    /// For truncation toward zero, use [`to_us`](../struct.Dt.html#method.to_us).
343    #[inline(always)]
344    pub const fn to_us_floor(&self) -> (i128, i128) {
345        (
346            self.attos.div_euclid(ATTOS_PER_US_I128),
347            self.attos.rem_euclid(ATTOS_PER_US_I128),
348        )
349    }
350
351    /// Splits into whole milliseconds and leftover attoseconds, rounding the
352    /// whole part down so the leftover is always ≥ 0.
353    ///
354    /// Returns `(whole, frac_attos)`. Same floor rule as
355    /// [`to_sec_floor`](../struct.Dt.html#method.to_sec_floor)
356    /// (e.g. −1.3 ms → `(-2, 0.7 ms)`). For truncation toward zero, use
357    /// [`to_ms`](../struct.Dt.html#method.to_ms).
358    ///
359    /// ## Examples
360    ///
361    /// ```rust
362    /// use deep_time::Dt;
363    /// use deep_time::macros::{from_ms, us};
364    ///
365    /// // +1.3 ms → 1 ms + 0.3 ms leftover
366    /// assert_eq!(from_ms!(1, us!(300)).to_ms_floor(), (1, us!(300)));
367    ///
368    /// // -1.3 ms → -2 ms + 0.7 ms leftover
369    /// assert_eq!(from_ms!(-1, -us!(300)).to_ms_floor(), (-2, us!(700)));
370    /// ```
371    #[inline(always)]
372    pub const fn to_ms_floor(&self) -> (i128, i128) {
373        (
374            self.attos.div_euclid(ATTOS_PER_MS_I128),
375            self.attos.rem_euclid(ATTOS_PER_MS_I128),
376        )
377    }
378
379    /// Splits into whole minutes and leftover attoseconds, rounding the whole
380    /// part down so the leftover is always ≥ 0.
381    ///
382    /// Returns `(whole, frac_attos)`. For truncation toward zero (signed
383    /// leftover), use [`to_mins`](../struct.Dt.html#method.to_mins).
384    ///
385    /// ## Examples
386    ///
387    /// ```rust
388    /// use deep_time::Dt;
389    /// use deep_time::macros::{from_sec, sec};
390    ///
391    /// assert_eq!(from_sec!(90).to_mins_floor(), (1, sec!(30)));
392    /// // -90 s → -2 min + 30 s leftover
393    /// assert_eq!(from_sec!(-90).to_mins_floor(), (-2, sec!(30)));
394    /// ```
395    #[inline(always)]
396    pub const fn to_mins_floor(&self) -> (i128, i128) {
397        (
398            self.attos.div_euclid(ATTOS_PER_MIN),
399            self.attos.rem_euclid(ATTOS_PER_MIN),
400        )
401    }
402
403    /// Splits into whole minutes and leftover attoseconds, dropping any
404    /// fraction toward zero (1.5 → 1, −1.5 → −1).
405    ///
406    /// Returns `(whole, frac_attos)`. When negative with a fraction,
407    /// `frac_attos` is negative too. For a leftover that is always ≥ 0, use
408    /// [`to_mins_floor`](../struct.Dt.html#method.to_mins_floor).
409    ///
410    /// ## Examples
411    ///
412    /// ```rust
413    /// use deep_time::Dt;
414    /// use deep_time::macros::{from_sec, sec};
415    ///
416    /// assert_eq!(from_sec!(90).to_mins(), (1, sec!(30)));
417    /// assert_eq!(from_sec!(-90).to_mins(), (-1, sec!(-30)));
418    /// ```
419    #[inline(always)]
420    pub const fn to_mins(&self) -> (i128, i128) {
421        (self.attos / ATTOS_PER_MIN, self.attos % ATTOS_PER_MIN)
422    }
423
424    /// Converts this duration to minutes as a floating-point number.
425    ///
426    /// ## Examples
427    ///
428    /// ```rust
429    /// use deep_time::Dt;
430    /// use deep_time::macros::from_sec;
431    ///
432    /// assert_eq!(Dt::ZERO.to_mins_f(), 0.0);
433    /// assert_eq!(from_sec!(90).to_mins_f(), 1.5);
434    /// assert_eq!(from_sec!(-90).to_mins_f(), -1.5);
435    /// ```
436    pub const fn to_mins_f(&self) -> Real {
437        if self.attos == 0 {
438            return 0.0;
439        }
440        let mins = self.attos.div_euclid(ATTOS_PER_MIN);
441        let rem = self.attos.rem_euclid(ATTOS_PER_MIN);
442
443        if mins < 0 && rem > ATTOS_PER_MIN / 2 {
444            let small = ATTOS_PER_MIN - rem;
445            let small_f = f!(small as u128) / f!(ATTOS_PER_MIN);
446            f!(mins) + 1.0 - small_f
447        } else {
448            f!(mins) + f!(rem as u128) / f!(ATTOS_PER_MIN)
449        }
450    }
451
452    /// Splits into whole hours and leftover attoseconds, rounding the whole
453    /// part down so the leftover is always ≥ 0.
454    ///
455    /// Returns `(whole, frac_attos)`. For truncation toward zero (signed
456    /// leftover), use [`to_hours`](../struct.Dt.html#method.to_hours).
457    ///
458    /// ## Examples
459    ///
460    /// ```rust
461    /// use deep_time::Dt;
462    /// use deep_time::macros::{from_sec, sec};
463    ///
464    /// // 90 minutes → 1 hour + 30 minutes
465    /// assert_eq!(from_sec!(90 * 60).to_hours_floor(), (1, sec!(30 * 60)));
466    /// // -90 minutes → -2 hours + 30 minutes leftover
467    /// assert_eq!(from_sec!(-90 * 60).to_hours_floor(), (-2, sec!(30 * 60)));
468    /// ```
469    #[inline(always)]
470    pub const fn to_hours_floor(&self) -> (i128, i128) {
471        (
472            self.attos.div_euclid(ATTOS_PER_HOUR),
473            self.attos.rem_euclid(ATTOS_PER_HOUR),
474        )
475    }
476
477    /// Splits into whole hours and leftover attoseconds, dropping any
478    /// fraction toward zero (1.5 → 1, −1.5 → −1).
479    ///
480    /// Returns `(whole, frac_attos)`. When negative with a fraction,
481    /// `frac_attos` is negative too. For a leftover that is always ≥ 0, use
482    /// [`to_hours_floor`](../struct.Dt.html#method.to_hours_floor).
483    ///
484    /// ## Examples
485    ///
486    /// ```rust
487    /// use deep_time::Dt;
488    /// use deep_time::macros::{from_sec, sec};
489    ///
490    /// assert_eq!(from_sec!(90 * 60).to_hours(), (1, sec!(30 * 60)));
491    /// assert_eq!(from_sec!(-90 * 60).to_hours(), (-1, sec!(-30 * 60)));
492    /// ```
493    #[inline(always)]
494    pub const fn to_hours(&self) -> (i128, i128) {
495        (self.attos / ATTOS_PER_HOUR, self.attos % ATTOS_PER_HOUR)
496    }
497
498    /// Converts this duration to hours as a floating-point number.
499    ///
500    /// ## Examples
501    ///
502    /// ```rust
503    /// use deep_time::Dt;
504    /// use deep_time::macros::from_sec;
505    ///
506    /// assert_eq!(Dt::ZERO.to_hours_f(), 0.0);
507    /// assert_eq!(from_sec!(90 * 60).to_hours_f(), 1.5);
508    /// assert_eq!(from_sec!(-90 * 60).to_hours_f(), -1.5);
509    /// ```
510    pub const fn to_hours_f(&self) -> Real {
511        if self.attos == 0 {
512            return 0.0;
513        }
514        let hours = self.attos.div_euclid(ATTOS_PER_HOUR);
515        let rem = self.attos.rem_euclid(ATTOS_PER_HOUR);
516
517        if hours < 0 && rem > ATTOS_PER_HOUR / 2 {
518            let small = ATTOS_PER_HOUR - rem;
519            let small_f = f!(small as u128) / f!(ATTOS_PER_HOUR);
520            f!(hours) + 1.0 - small_f
521        } else {
522            f!(hours) + f!(rem as u128) / f!(ATTOS_PER_HOUR)
523        }
524    }
525
526    /// Splits into whole days and leftover attoseconds, dropping any fraction
527    /// toward zero (1.25 → 1, −1.25 → −1).
528    ///
529    /// Returns `(whole, frac_attos)`. When negative with a fraction,
530    /// `frac_attos` is negative too — e.g. −1.25 days is
531    /// `(-1, -ATTOS_PER_DAY / 4)`.
532    ///
533    /// For a leftover that is always ≥ 0, use
534    /// [`to_days_floor`](../struct.Dt.html#method.to_days_floor).
535    ///
536    /// ## Examples
537    ///
538    /// ```rust
539    /// use deep_time::{Dt, Scale, consts::ATTOS_PER_DAY};
540    ///
541    /// let dt = Dt::from_days_f(1.25, Scale::TAI, Scale::TAI);
542    /// assert_eq!(dt.to_days(), (1, ATTOS_PER_DAY / 4));
543    ///
544    /// let dt = Dt::from_days_f(-1.25, Scale::TAI, Scale::TAI);
545    /// assert_eq!(dt.to_days(), (-1, -ATTOS_PER_DAY / 4));
546    /// ```
547    ///
548    /// ## See also
549    ///
550    /// - [`Dt::to_days_floor`](../struct.Dt.html#method.to_days_floor)
551    /// - [`Dt::from_days`](../struct.Dt.html#method.from_days)
552    #[inline(always)]
553    pub const fn to_days(&self) -> (i128, i128) {
554        (self.attos / ATTOS_PER_DAY, self.attos % ATTOS_PER_DAY)
555    }
556
557    /// Splits into whole days and leftover attoseconds, rounding the whole
558    /// part down so the leftover is always ≥ 0.
559    ///
560    /// Returns `(whole, frac_attos)`. Same floor rule as
561    /// [`to_sec_floor`](../struct.Dt.html#method.to_sec_floor) (e.g. −1.5 days →
562    /// `(-2, half a day)`). For truncation toward zero, use
563    /// [`to_days`](../struct.Dt.html#method.to_days).
564    ///
565    /// ## Examples
566    ///
567    /// ```rust
568    /// use deep_time::{Dt, Scale, consts::ATTOS_PER_DAY};
569    ///
570    /// let dt = Dt::from_days_f(1.25, Scale::TAI, Scale::TAI);
571    /// assert_eq!(dt.to_days_floor(), (1, ATTOS_PER_DAY / 4));
572    ///
573    /// // -1.25 days → -2 whole days + 0.75 day leftover
574    /// let dt = Dt::from_days_f(-1.25, Scale::TAI, Scale::TAI);
575    /// assert_eq!(dt.to_days_floor(), (-2, (ATTOS_PER_DAY * 3) / 4));
576    /// ```
577    ///
578    /// ## See also
579    ///
580    /// - [`Dt::to_days`](../struct.Dt.html#method.to_days)
581    #[inline(always)]
582    pub const fn to_days_floor(&self) -> (i128, i128) {
583        (
584            self.attos.div_euclid(ATTOS_PER_DAY),
585            self.attos.rem_euclid(ATTOS_PER_DAY),
586        )
587    }
588
589    /// Converts this duration to days as a floating-point number.
590    ///
591    /// ## Examples
592    ///
593    /// ```rust
594    /// use deep_time::{Dt, Scale};
595    ///
596    /// assert_eq!(Dt::ZERO.to_days_f(), 0.0);
597    /// assert_eq!(Dt::from_days_f(1.5, Scale::TAI, Scale::TAI).to_days_f(), 1.5);
598    /// assert_eq!(Dt::from_days_f(-1.5, Scale::TAI, Scale::TAI).to_days_f(), -1.5);
599    /// ```
600    ///
601    /// ## See also
602    ///
603    /// - [`Dt::from_days_f`](../struct.Dt.html#method.from_days_f)
604    pub const fn to_days_f(&self) -> Real {
605        if self.attos == 0 {
606            return 0.0;
607        }
608        let days = self.attos.div_euclid(ATTOS_PER_DAY);
609        let rem = self.attos.rem_euclid(ATTOS_PER_DAY);
610
611        if days < 0 && rem > ATTOS_PER_DAY / 2 {
612            let small = ATTOS_PER_DAY - rem;
613            let small_f = f!(small as u128) / f!(ATTOS_PER_DAY);
614            f!(days) + 1.0 - small_f
615        } else {
616            f!(days) + f!(rem as u128) / f!(ATTOS_PER_DAY)
617        }
618    }
619
620    /// Splits into whole milliseconds and leftover attoseconds, dropping any
621    /// fraction toward zero (1.7 → 1, −1.7 → −1).
622    ///
623    /// Returns `(whole, frac_attos)`. When negative with a fraction,
624    /// `frac_attos` is negative too. For a leftover that is always ≥ 0, use
625    /// [`to_ms_floor`](../struct.Dt.html#method.to_ms_floor).
626    ///
627    /// ## Examples
628    ///
629    /// ```rust
630    /// use deep_time::Dt;
631    /// use deep_time::macros::{from_ms, us};
632    ///
633    /// assert_eq!(from_ms!(1, us!(300)).to_ms(), (1, us!(300)));
634    /// assert_eq!(from_ms!(-1, -us!(300)).to_ms(), (-1, -us!(300)));
635    /// ```
636    #[inline(always)]
637    pub const fn to_ms(&self) -> (i128, i128) {
638        (
639            self.attos / ATTOS_PER_MS_I128,
640            self.attos % ATTOS_PER_MS_I128,
641        )
642    }
643
644    /// Splits into whole microseconds and leftover attoseconds, dropping any
645    /// fraction toward zero (1.7 → 1, −1.7 → −1).
646    ///
647    /// Returns `(whole, frac_attos)`. When negative with a fraction,
648    /// `frac_attos` is negative too. For a leftover that is always ≥ 0, use
649    /// [`to_us_floor`](../struct.Dt.html#method.to_us_floor).
650    #[inline(always)]
651    pub const fn to_us(&self) -> (i128, i128) {
652        (
653            self.attos / ATTOS_PER_US_I128,
654            self.attos % ATTOS_PER_US_I128,
655        )
656    }
657
658    /// Splits into whole nanoseconds and leftover attoseconds, dropping any
659    /// fraction toward zero (1.7 → 1, −1.7 → −1).
660    ///
661    /// Returns `(whole, frac_attos)`. When negative with a fraction,
662    /// `frac_attos` is negative too. For a leftover that is always ≥ 0, use
663    /// [`to_ns_floor`](../struct.Dt.html#method.to_ns_floor).
664    #[inline(always)]
665    pub const fn to_ns(&self) -> (i128, i128) {
666        (
667            self.attos / ATTOS_PER_NS_I128,
668            self.attos % ATTOS_PER_NS_I128,
669        )
670    }
671
672    /// Splits into whole picoseconds and leftover attoseconds, dropping any
673    /// fraction toward zero (1.7 → 1, −1.7 → −1).
674    ///
675    /// Returns `(whole, frac_attos)`. When negative with a fraction,
676    /// `frac_attos` is negative too. For a leftover that is always ≥ 0, use
677    /// [`to_ps_floor`](../struct.Dt.html#method.to_ps_floor).
678    #[inline(always)]
679    pub const fn to_ps(&self) -> (i128, i128) {
680        (
681            self.attos / ATTOS_PER_PS_I128,
682            self.attos % ATTOS_PER_PS_I128,
683        )
684    }
685
686    /// Splits into whole femtoseconds and leftover attoseconds, dropping any
687    /// fraction toward zero (1.7 → 1, −1.7 → −1).
688    ///
689    /// Returns `(whole, frac_attos)`. When negative with a fraction,
690    /// `frac_attos` is negative too. For a leftover that is always ≥ 0, use
691    /// [`to_fs_floor`](../struct.Dt.html#method.to_fs_floor).
692    #[inline(always)]
693    pub const fn to_fs(&self) -> (i128, i128) {
694        (
695            self.attos / ATTOS_PER_FS_I128,
696            self.attos % ATTOS_PER_FS_I128,
697        )
698    }
699}