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    ATTOS_PER_WEEK, Dt, Real, dt,
5};
6
7impl Dt {
8    /// Converts total attoseconds to a floating-point count of
9    /// `unit_attos`-sized units.
10    ///
11    /// Euclidean split keeps the remainder in `[0, unit_attos)`. For large
12    /// negative whole parts with a large remainder, rewrites as
13    /// `whole + 1 − small_frac` to avoid cancellation in the float add.
14    #[inline(always)]
15    const fn attos_to_unit_f(attos: i128, unit_attos: i128) -> Real {
16        if attos == 0 {
17            return 0.0;
18        }
19        let whole = attos.div_euclid(unit_attos);
20        let rem = attos.rem_euclid(unit_attos);
21
22        if whole < 0 && rem > unit_attos / 2 {
23            let small = unit_attos - rem;
24            let small_f = f!(small as u128) / f!(unit_attos);
25            f!(whole) + 1.0 - small_f
26        } else {
27            f!(whole) + f!(rem as u128) / f!(unit_attos)
28        }
29    }
30
31    /// Returns this duration as a whole number of seconds, dropping any
32    /// fraction (always toward zero: 1.7 → 1, −1.7 → −1).
33    ///
34    /// Same as `self.attos / ATTOS_PER_SEC_I128`. Does not round.
35    ///
36    /// On positive values this matches
37    /// [`to_sec_floor`](../struct.Dt.html#method.to_sec_floor).
38    /// On negatives they diverge: here −1.3 → −1, while `to_sec_floor` gives
39    /// −2 (always rounds down). Prefer `to_sec_floor` when you also need a
40    /// non-negative fractional part via
41    /// [`to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac).
42    ///
43    /// ## Examples
44    ///
45    /// ```rust
46    /// use deep_time::Dt;
47    /// use deep_time::macros::{from_sec, ms};
48    ///
49    /// // Fraction only — less than one second either way → 0
50    /// assert_eq!(from_sec!(0, -ms!(300)).to_sec(), 0);
51    /// assert_eq!(from_sec!(0, ms!(300)).to_sec(), 0);
52    ///
53    /// // -1.3 s → -1 here, but -2 with `to_sec_floor`
54    /// let dt = from_sec!(-1, -ms!(300));
55    /// assert_eq!(dt.to_sec(), -1);
56    /// assert_eq!(dt.to_sec_floor(), -2);
57    ///
58    /// // Positive values match `to_sec_floor`
59    /// let dt = from_sec!(1, ms!(300));
60    /// assert_eq!(dt.to_sec(), 1);
61    /// assert_eq!(dt.to_sec_floor(), 1);
62    /// ```
63    #[inline(always)]
64    pub const fn to_sec(&self) -> i128 {
65        self.attos / ATTOS_PER_SEC_I128
66    }
67
68    /// Same as [`to_sec`](../struct.Dt.html#method.to_sec), but returns an
69    /// [`i64`].
70    ///
71    /// Values outside the `i64` range clamp to [`i64::MAX`] or [`i64::MIN`].
72    ///
73    /// ## Examples
74    ///
75    /// ```rust
76    /// use deep_time::Dt;
77    /// use deep_time::macros::{from_sec, ms};
78    ///
79    /// // Same toward-zero rule as `to_sec`
80    /// assert_eq!(from_sec!(1, ms!(300)).to_sec64(), 1);
81    /// assert_eq!(from_sec!(-1, -ms!(300)).to_sec64(), -1);
82    ///
83    /// // Fits in i64 here; huge values would clamp instead of panicking
84    /// assert_eq!(from_sec!(i64::MAX as i128).to_sec64(), i64::MAX);
85    /// ```
86    #[inline(always)]
87    pub const fn to_sec64(&self) -> i64 {
88        Self::to_i64(self.attos / ATTOS_PER_SEC_I128)
89    }
90
91    /// Returns whole seconds, always rounding down (toward −∞).
92    ///
93    /// So 1.3 → 1 and −1.3 → −2. The leftover attoseconds are then always
94    /// ≥ 0; get them with
95    /// [`to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac).
96    ///
97    /// For truncation toward zero (1.3 → 1, −1.3 → −1), use
98    /// [`to_sec`](../struct.Dt.html#method.to_sec).
99    /// For nearest-second rounding, use
100    /// [`to_sec_round`](../struct.Dt.html#method.to_sec_round).
101    ///
102    /// ## Examples
103    ///
104    /// ```rust
105    /// use deep_time::Dt;
106    /// use deep_time::macros::{from_sec, ms};
107    ///
108    /// // -1.3 s → whole -2, leftover +0.7 s
109    /// let dt = from_sec!(-1, -ms!(300));
110    /// assert_eq!(dt.to_sec_floor(), -2);
111    /// assert_eq!(dt.to_sec_ufrac(), ms!(700) as u64);
112    ///
113    /// // +1.3 s → whole 1, leftover +0.3 s
114    /// let dt = from_sec!(1, ms!(300));
115    /// assert_eq!(dt.to_sec_floor(), 1);
116    /// assert_eq!(dt.to_sec_ufrac(), ms!(300) as u64);
117    /// ```
118    #[inline(always)]
119    pub const fn to_sec_floor(&self) -> i128 {
120        self.attos.div_euclid(ATTOS_PER_SEC_I128)
121    }
122
123    /// Same as [`to_sec_floor`](../struct.Dt.html#method.to_sec_floor),
124    /// but returns an [`i64`].
125    ///
126    /// Values outside the `i64` range clamp to [`i64::MAX`] or [`i64::MIN`].
127    /// Pair with [`to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac)
128    /// for the leftover.
129    ///
130    /// ## Examples
131    ///
132    /// ```rust
133    /// use deep_time::Dt;
134    /// use deep_time::macros::{from_sec, ms};
135    ///
136    /// let dt = from_sec!(-1, -ms!(300));
137    /// assert_eq!(dt.to_sec64_floor(), -2);
138    /// assert_eq!(dt.to_sec_ufrac(), ms!(700) as u64);
139    ///
140    /// let dt = from_sec!(1, ms!(300));
141    /// assert_eq!(dt.to_sec64_floor(), 1);
142    /// assert_eq!(dt.to_sec_ufrac(), ms!(300) as u64);
143    /// ```
144    #[inline(always)]
145    pub const fn to_sec64_floor(&self) -> i64 {
146        Self::to_i64(self.attos.div_euclid(ATTOS_PER_SEC_I128))
147    }
148
149    /// Rounds to the nearest whole second and returns that count as [`i128`].
150    ///
151    /// Halfway cases go away from zero: 0.5 → 1 and −0.5 → −1
152    /// (same rule as [`Dt::round`]).
153    ///
154    /// ## Examples
155    ///
156    /// ```rust
157    /// use deep_time::Dt;
158    /// use deep_time::macros::{from_sec, ms};
159    ///
160    /// assert_eq!(from_sec!(1, ms!(300)).to_sec_round(), 1);
161    /// assert_eq!(from_sec!(1, ms!(600)).to_sec_round(), 2);
162    /// assert_eq!(from_sec!(-1, -ms!(300)).to_sec_round(), -1);
163    ///
164    /// // Halfway: away from zero
165    /// assert_eq!(from_sec!(0, ms!(500)).to_sec_round(), 1);
166    /// assert_eq!(from_sec!(0, -ms!(500)).to_sec_round(), -1);
167    /// ```
168    #[inline(always)]
169    pub const fn to_sec_round(&self) -> i128 {
170        self.round_to_sec().to_sec()
171    }
172
173    /// Same as [`to_sec_round`](../struct.Dt.html#method.to_sec_round),
174    /// but returns an [`i64`].
175    ///
176    /// Values outside the `i64` range clamp to [`i64::MAX`] or [`i64::MIN`].
177    ///
178    /// ## Examples
179    ///
180    /// ```rust
181    /// use deep_time::Dt;
182    /// use deep_time::macros::{from_sec, ms};
183    ///
184    /// assert_eq!(from_sec!(1, ms!(300)).to_sec64_round(), 1);
185    /// assert_eq!(from_sec!(1, ms!(600)).to_sec64_round(), 2);
186    /// assert_eq!(from_sec!(-1, -ms!(300)).to_sec64_round(), -1);
187    /// assert_eq!(from_sec!(0, ms!(500)).to_sec64_round(), 1);
188    /// ```
189    #[inline(always)]
190    pub const fn to_sec64_round(&self) -> i64 {
191        Self::to_i64(self.round_to_sec().to_sec())
192    }
193
194    /// Converts this duration to seconds as an [`f64`].
195    ///
196    /// Alias of [`to_sec_f`](../struct.Dt.html#method.to_sec_f).
197    ///
198    /// ## Examples
199    ///
200    /// ```rust
201    /// use deep_time::Dt;
202    /// use deep_time::macros::{from_sec, ms};
203    ///
204    /// assert_eq!(Dt::ZERO.to_f64(), 0.0);
205    /// assert!((from_sec!(1, ms!(500)).to_f64() - 1.5).abs() < 1e-12);
206    /// ```
207    #[inline(always)]
208    pub const fn to_f64(&self) -> f64 {
209        self.to_sec_f()
210    }
211
212    /// Converts this duration to seconds as a floating-point number.
213    ///
214    /// ## Examples
215    ///
216    /// ```rust
217    /// use deep_time::Dt;
218    /// use deep_time::macros::{from_sec, ms};
219    ///
220    /// assert_eq!(Dt::ZERO.to_sec_f(), 0.0);
221    /// assert!((from_sec!(1, ms!(500)).to_sec_f() - 1.5).abs() < 1e-12);
222    /// assert!((from_sec!(-1, -ms!(500)).to_sec_f() + 1.5).abs() < 1e-12);
223    /// ```
224    pub const fn to_sec_f(&self) -> Real {
225        if self.attos == 0 {
226            return 0.0;
227        }
228        let sec = self.attos.div_euclid(ATTOS_PER_SEC_I128);
229        let rem = self.attos.rem_euclid(ATTOS_PER_SEC_I128); // always in [0, aps)
230
231        if sec < 0 && rem > ATTOS_PER_SEC_I128 / 2 {
232            // original cancellation-avoidance path
233            let small = ATTOS_PER_SEC_I128 - rem;
234            let small_f = f!(small as u64) / ATTOS_PER_SECF;
235            f!(sec) + 1.0 - small_f
236        } else {
237            f!(sec) + f!(rem as u64) / ATTOS_PER_SECF
238        }
239    }
240
241    /// Returns the signed leftover attoseconds after removing whole seconds
242    /// toward zero.
243    ///
244    /// Same sign as the original value when non-zero: 1.3 s → `+0.3 s` in
245    /// attoseconds, −1.3 s → `−0.3 s` in attoseconds. Pairs with
246    /// [`from_sec_and_frac`](../struct.Dt.html#method.from_sec_and_frac)
247    /// and [`to_sec`](../struct.Dt.html#method.to_sec).
248    ///
249    /// For a leftover that is always ≥ 0 (paired with floor), use
250    /// [`to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac).
251    ///
252    /// ## Examples
253    ///
254    /// ```rust
255    /// use deep_time::Dt;
256    /// use deep_time::macros::{from_sec, ms};
257    ///
258    /// let dt = from_sec!(1, ms!(300));
259    /// assert_eq!(dt.to_sec(), 1);
260    /// assert_eq!(dt.to_sec_frac(), ms!(300) as i64);
261    ///
262    /// let dt = from_sec!(-1, -ms!(300));
263    /// assert_eq!(dt.to_sec(), -1);
264    /// assert_eq!(dt.to_sec_frac(), -ms!(300) as i64);
265    /// ```
266    #[inline(always)]
267    pub const fn to_sec_frac(&self) -> i64 {
268        (self.attos % ATTOS_PER_SEC_I128) as i64
269    }
270
271    /// Returns the leftover attoseconds after
272    /// [`to_sec_floor`](../struct.Dt.html#method.to_sec_floor) /
273    /// [`to_sec64_floor`](../struct.Dt.html#method.to_sec64_floor).
274    ///
275    /// Always in `0 .. ATTOS_PER_SEC`. On negatives this is **not** “the
276    /// decimal part with a sign”: −1.3 s floors to −2 whole seconds with a
277    /// **+0.7 s** leftover.
278    ///
279    /// ## Examples
280    ///
281    /// ```rust
282    /// use deep_time::Dt;
283    /// use deep_time::macros::{from_sec, ms};
284    ///
285    /// // -1.3 s → floor -2 s + 0.7 s leftover
286    /// let dt = from_sec!(-1, -ms!(300));
287    /// assert_eq!(dt.to_sec64_floor(), -2);
288    /// assert_eq!(dt.to_sec_ufrac(), ms!(700) as u64);
289    ///
290    /// // +1.3 s → floor 1 s + 0.3 s leftover
291    /// let dt = from_sec!(1, ms!(300));
292    /// assert_eq!(dt.to_sec64_floor(), 1);
293    /// assert_eq!(dt.to_sec_ufrac(), ms!(300) as u64);
294    /// ```
295    #[inline(always)]
296    pub const fn to_sec_ufrac(&self) -> u64 {
297        self.attos.rem_euclid(ATTOS_PER_SEC_I128) as u64
298    }
299
300    /// Returns a new [`Dt`] rounded to the nearest whole second.
301    ///
302    /// Halfway cases go away from zero (same rule as [`Dt::round`]). For the
303    /// rounded value as an integer count, see
304    /// [`to_sec_round`](../struct.Dt.html#method.to_sec_round).
305    ///
306    /// ## Examples
307    ///
308    /// ```rust
309    /// use deep_time::Dt;
310    /// use deep_time::macros::{from_sec, ms};
311    ///
312    /// assert_eq!(from_sec!(1, ms!(300)).round_to_sec(), from_sec!(1));
313    /// assert_eq!(from_sec!(1, ms!(600)).round_to_sec(), from_sec!(2));
314    /// assert_eq!(from_sec!(0, ms!(500)).round_to_sec(), from_sec!(1));
315    /// assert_eq!(from_sec!(0, -ms!(500)).round_to_sec(), from_sec!(-1));
316    /// ```
317    #[inline(always)]
318    pub const fn round_to_sec(&self) -> Dt {
319        self.round(dt!(ATTOS_PER_SEC_I128))
320    }
321
322    /// Returns the total time in attoseconds.
323    ///
324    /// ## Examples
325    ///
326    /// ```rust
327    /// use deep_time::Dt;
328    /// use deep_time::macros::{from_sec, ms, sec};
329    ///
330    /// assert_eq!(Dt::ZERO.to_attos(), 0);
331    /// assert_eq!(from_sec!(1, ms!(300)).to_attos(), sec!(1) + ms!(300));
332    /// ```
333    #[inline(always)]
334    pub const fn to_attos(&self) -> i128 {
335        self.attos
336    }
337
338    /// Splits into whole femtoseconds and leftover attoseconds, rounding the
339    /// whole part down so the leftover is always ≥ 0.
340    ///
341    /// Returns `(whole, frac_attos)`. Same floor rule as
342    /// [`to_sec_floor`](../struct.Dt.html#method.to_sec_floor).
343    /// For truncation toward zero, use [`to_fs`](../struct.Dt.html#method.to_fs).
344    ///
345    /// ## Examples
346    ///
347    /// ```rust
348    /// use deep_time::Dt;
349    /// use deep_time::macros::from_fs;
350    ///
351    /// // +1.5 fs → 1 fs + 500 attoseconds leftover
352    /// assert_eq!(from_fs!(1, 500).to_fs_floor(), (1, 500));
353    /// // -1.5 fs → -2 fs + 500 attoseconds leftover
354    /// assert_eq!(from_fs!(-1, -500).to_fs_floor(), (-2, 500));
355    /// ```
356    #[inline(always)]
357    pub const fn to_fs_floor(&self) -> (i128, i128) {
358        (
359            self.attos.div_euclid(ATTOS_PER_FS_I128),
360            self.attos.rem_euclid(ATTOS_PER_FS_I128),
361        )
362    }
363
364    /// Splits into whole picoseconds and leftover attoseconds, rounding the
365    /// whole part down so the leftover is always ≥ 0.
366    ///
367    /// Returns `(whole, frac_attos)`. Same floor rule as
368    /// [`to_sec_floor`](../struct.Dt.html#method.to_sec_floor).
369    /// For truncation toward zero, use [`to_ps`](../struct.Dt.html#method.to_ps).
370    ///
371    /// ## Examples
372    ///
373    /// ```rust
374    /// use deep_time::Dt;
375    /// use deep_time::macros::{from_ps, fs};
376    ///
377    /// // +1.3 ps → 1 ps + 0.3 ps leftover
378    /// assert_eq!(from_ps!(1, fs!(300)).to_ps_floor(), (1, fs!(300)));
379    /// // -1.3 ps → -2 ps + 0.7 ps leftover
380    /// assert_eq!(from_ps!(-1, -fs!(300)).to_ps_floor(), (-2, fs!(700)));
381    /// ```
382    #[inline(always)]
383    pub const fn to_ps_floor(&self) -> (i128, i128) {
384        (
385            self.attos.div_euclid(ATTOS_PER_PS_I128),
386            self.attos.rem_euclid(ATTOS_PER_PS_I128),
387        )
388    }
389
390    /// Splits into whole nanoseconds and leftover attoseconds, rounding the
391    /// whole part down so the leftover is always ≥ 0.
392    ///
393    /// Returns `(whole, frac_attos)`. Same floor rule as
394    /// [`to_sec_floor`](../struct.Dt.html#method.to_sec_floor).
395    /// For truncation toward zero, use [`to_ns`](../struct.Dt.html#method.to_ns).
396    ///
397    /// ## Examples
398    ///
399    /// ```rust
400    /// use deep_time::Dt;
401    /// use deep_time::macros::{from_ns, ps};
402    ///
403    /// // +1.3 ns → 1 ns + 0.3 ns leftover
404    /// assert_eq!(from_ns!(1, ps!(300)).to_ns_floor(), (1, ps!(300)));
405    /// // -1.3 ns → -2 ns + 0.7 ns leftover
406    /// assert_eq!(from_ns!(-1, -ps!(300)).to_ns_floor(), (-2, ps!(700)));
407    /// ```
408    #[inline(always)]
409    pub const fn to_ns_floor(&self) -> (i128, i128) {
410        (
411            self.attos.div_euclid(ATTOS_PER_NS_I128),
412            self.attos.rem_euclid(ATTOS_PER_NS_I128),
413        )
414    }
415
416    /// Splits into whole microseconds and leftover attoseconds, rounding the
417    /// whole part down so the leftover is always ≥ 0.
418    ///
419    /// Returns `(whole, frac_attos)`. Same floor rule as
420    /// [`to_sec_floor`](../struct.Dt.html#method.to_sec_floor).
421    /// For truncation toward zero, use [`to_us`](../struct.Dt.html#method.to_us).
422    ///
423    /// ## Examples
424    ///
425    /// ```rust
426    /// use deep_time::Dt;
427    /// use deep_time::macros::{from_us, ns};
428    ///
429    /// // +1.3 µs → 1 µs + 0.3 µs leftover
430    /// assert_eq!(from_us!(1, ns!(300)).to_us_floor(), (1, ns!(300)));
431    /// // -1.3 µs → -2 µs + 0.7 µs leftover
432    /// assert_eq!(from_us!(-1, -ns!(300)).to_us_floor(), (-2, ns!(700)));
433    /// ```
434    #[inline(always)]
435    pub const fn to_us_floor(&self) -> (i128, i128) {
436        (
437            self.attos.div_euclid(ATTOS_PER_US_I128),
438            self.attos.rem_euclid(ATTOS_PER_US_I128),
439        )
440    }
441
442    /// Splits into whole milliseconds and leftover attoseconds, rounding the
443    /// whole part down so the leftover is always ≥ 0.
444    ///
445    /// Returns `(whole, frac_attos)`. Same floor rule as
446    /// [`to_sec_floor`](../struct.Dt.html#method.to_sec_floor)
447    /// (e.g. −1.3 ms → `(-2, 700 µs in attoseconds)`). For truncation toward
448    /// zero, use [`to_ms`](../struct.Dt.html#method.to_ms).
449    ///
450    /// ## Examples
451    ///
452    /// ```rust
453    /// use deep_time::Dt;
454    /// use deep_time::macros::{from_ms, us};
455    ///
456    /// // +1.3 ms → 1 ms + 0.3 ms leftover
457    /// assert_eq!(from_ms!(1, us!(300)).to_ms_floor(), (1, us!(300)));
458    ///
459    /// // -1.3 ms → -2 ms + 0.7 ms leftover
460    /// assert_eq!(from_ms!(-1, -us!(300)).to_ms_floor(), (-2, us!(700)));
461    /// ```
462    #[inline(always)]
463    pub const fn to_ms_floor(&self) -> (i128, i128) {
464        (
465            self.attos.div_euclid(ATTOS_PER_MS_I128),
466            self.attos.rem_euclid(ATTOS_PER_MS_I128),
467        )
468    }
469
470    /// Splits into whole minutes and leftover attoseconds, rounding the whole
471    /// part down so the leftover is always ≥ 0.
472    ///
473    /// Returns `(whole, frac_attos)`. For truncation toward zero (signed
474    /// leftover), use [`to_mins`](../struct.Dt.html#method.to_mins).
475    ///
476    /// ## Examples
477    ///
478    /// ```rust
479    /// use deep_time::Dt;
480    /// use deep_time::macros::{from_sec, sec};
481    ///
482    /// // 90 s → 1 min + 30 s leftover
483    /// assert_eq!(from_sec!(90).to_mins_floor(), (1, sec!(30)));
484    /// // -90 s → -2 min + 30 s leftover
485    /// assert_eq!(from_sec!(-90).to_mins_floor(), (-2, sec!(30)));
486    /// ```
487    #[inline(always)]
488    pub const fn to_mins_floor(&self) -> (i128, i128) {
489        (
490            self.attos.div_euclid(ATTOS_PER_MIN),
491            self.attos.rem_euclid(ATTOS_PER_MIN),
492        )
493    }
494
495    /// Splits into whole minutes and leftover attoseconds, dropping any
496    /// fraction toward zero (1.5 → 1, −1.5 → −1).
497    ///
498    /// Returns `(whole, frac_attos)`. When negative with a fraction,
499    /// `frac_attos` is negative too. For a leftover that is always ≥ 0, use
500    /// [`to_mins_floor`](../struct.Dt.html#method.to_mins_floor).
501    ///
502    /// ## Examples
503    ///
504    /// ```rust
505    /// use deep_time::Dt;
506    /// use deep_time::macros::{from_sec, sec};
507    ///
508    /// // 90 s → 1 min + 30 s leftover (signed on negatives)
509    /// assert_eq!(from_sec!(90).to_mins(), (1, sec!(30)));
510    /// assert_eq!(from_sec!(-90).to_mins(), (-1, sec!(-30)));
511    /// ```
512    #[inline(always)]
513    pub const fn to_mins(&self) -> (i128, i128) {
514        (self.attos / ATTOS_PER_MIN, self.attos % ATTOS_PER_MIN)
515    }
516
517    /// Converts this duration to minutes as a floating-point number.
518    ///
519    /// ## Examples
520    ///
521    /// ```rust
522    /// use deep_time::Dt;
523    /// use deep_time::macros::from_sec;
524    ///
525    /// assert_eq!(Dt::ZERO.to_mins_f(), 0.0);
526    /// assert_eq!(from_sec!(90).to_mins_f(), 1.5);
527    /// assert_eq!(from_sec!(-90).to_mins_f(), -1.5);
528    /// ```
529    #[inline(always)]
530    pub const fn to_mins_f(&self) -> Real {
531        Self::attos_to_unit_f(self.attos, ATTOS_PER_MIN)
532    }
533
534    /// Splits into whole hours and leftover attoseconds, rounding the whole
535    /// part down so the leftover is always ≥ 0.
536    ///
537    /// Returns `(whole, frac_attos)`. For truncation toward zero (signed
538    /// leftover), use [`to_hours`](../struct.Dt.html#method.to_hours).
539    ///
540    /// ## Examples
541    ///
542    /// ```rust
543    /// use deep_time::Dt;
544    /// use deep_time::macros::{dt, mins};
545    ///
546    /// // 90 minutes → 1 hour + 30 minutes leftover
547    /// assert_eq!(dt!(mins!(90)).to_hours_floor(), (1, mins!(30)));
548    /// // -90 minutes → -2 hours + 30 minutes leftover
549    /// assert_eq!(dt!(mins!(-90)).to_hours_floor(), (-2, mins!(30)));
550    /// ```
551    #[inline(always)]
552    pub const fn to_hours_floor(&self) -> (i128, i128) {
553        (
554            self.attos.div_euclid(ATTOS_PER_HOUR),
555            self.attos.rem_euclid(ATTOS_PER_HOUR),
556        )
557    }
558
559    /// Splits into whole hours and leftover attoseconds, dropping any
560    /// fraction toward zero (1.5 → 1, −1.5 → −1).
561    ///
562    /// Returns `(whole, frac_attos)`. When negative with a fraction,
563    /// `frac_attos` is negative too. For a leftover that is always ≥ 0, use
564    /// [`to_hours_floor`](../struct.Dt.html#method.to_hours_floor).
565    ///
566    /// ## Examples
567    ///
568    /// ```rust
569    /// use deep_time::Dt;
570    /// use deep_time::macros::{dt, mins};
571    ///
572    /// assert_eq!(dt!(mins!(90)).to_hours(), (1, mins!(30)));
573    /// assert_eq!(dt!(mins!(-90)).to_hours(), (-1, -mins!(30)));
574    /// ```
575    #[inline(always)]
576    pub const fn to_hours(&self) -> (i128, i128) {
577        (self.attos / ATTOS_PER_HOUR, self.attos % ATTOS_PER_HOUR)
578    }
579
580    /// Converts this duration to hours as a floating-point number.
581    ///
582    /// ## Examples
583    ///
584    /// ```rust
585    /// use deep_time::Dt;
586    /// use deep_time::macros::{dt, mins};
587    ///
588    /// assert_eq!(Dt::ZERO.to_hours_f(), 0.0);
589    /// assert_eq!(dt!(mins!(90)).to_hours_f(), 1.5);
590    /// assert_eq!(dt!(mins!(-90)).to_hours_f(), -1.5);
591    /// ```
592    #[inline(always)]
593    pub const fn to_hours_f(&self) -> Real {
594        Self::attos_to_unit_f(self.attos, ATTOS_PER_HOUR)
595    }
596
597    /// Splits into whole days and leftover attoseconds, dropping any fraction
598    /// toward zero (1.25 → 1, −1.25 → −1).
599    ///
600    /// Returns `(whole, frac_attos)`. When negative with a fraction,
601    /// `frac_attos` is negative too — e.g. −1.25 days is
602    /// `(-1, −6 hours in attoseconds)`.
603    ///
604    /// For a leftover that is always ≥ 0, use
605    /// [`to_days_floor`](../struct.Dt.html#method.to_days_floor).
606    ///
607    /// ## Examples
608    ///
609    /// ```rust
610    /// use deep_time::Dt;
611    /// use deep_time::macros::{from_days_f, hours};
612    ///
613    /// // 1.25 d → 1 day + 6 hours leftover
614    /// assert_eq!(from_days_f!(1.25).to_days(), (1, hours!(6)));
615    ///
616    /// // -1.25 d → -1 day with signed -6 hours leftover
617    /// assert_eq!(from_days_f!(-1.25).to_days(), (-1, -hours!(6)));
618    /// ```
619    ///
620    /// ## See also
621    ///
622    /// - [`Dt::to_days_floor`](../struct.Dt.html#method.to_days_floor)
623    /// - [`Dt::from_days`](../struct.Dt.html#method.from_days)
624    #[inline(always)]
625    pub const fn to_days(&self) -> (i128, i128) {
626        (self.attos / ATTOS_PER_DAY, self.attos % ATTOS_PER_DAY)
627    }
628
629    /// Splits into whole days and leftover attoseconds, rounding the whole
630    /// part down so the leftover is always ≥ 0.
631    ///
632    /// Returns `(whole, frac_attos)`. Same floor rule as
633    /// [`to_sec_floor`](../struct.Dt.html#method.to_sec_floor) (e.g. −1.5 days →
634    /// `(-2, half a day in attoseconds)`). For truncation toward zero, use
635    /// [`to_days`](../struct.Dt.html#method.to_days).
636    ///
637    /// ## Examples
638    ///
639    /// ```rust
640    /// use deep_time::Dt;
641    /// use deep_time::macros::{from_days_f, hours};
642    ///
643    /// // 1.25 d → 1 day + 6 hours leftover
644    /// assert_eq!(from_days_f!(1.25).to_days_floor(), (1, hours!(6)));
645    ///
646    /// // -1.25 d → -2 whole days + 18 hours leftover
647    /// assert_eq!(from_days_f!(-1.25).to_days_floor(), (-2, hours!(18)));
648    /// ```
649    ///
650    /// ## See also
651    ///
652    /// - [`Dt::to_days`](../struct.Dt.html#method.to_days)
653    #[inline(always)]
654    pub const fn to_days_floor(&self) -> (i128, i128) {
655        (
656            self.attos.div_euclid(ATTOS_PER_DAY),
657            self.attos.rem_euclid(ATTOS_PER_DAY),
658        )
659    }
660
661    /// Converts this duration to days as a floating-point number.
662    ///
663    /// ## Examples
664    ///
665    /// ```rust
666    /// use deep_time::Dt;
667    /// use deep_time::macros::from_days_f;
668    ///
669    /// assert_eq!(Dt::ZERO.to_days_f(), 0.0);
670    /// assert_eq!(from_days_f!(1.5).to_days_f(), 1.5);
671    /// assert_eq!(from_days_f!(-1.5).to_days_f(), -1.5);
672    /// ```
673    ///
674    /// ## See also
675    ///
676    /// - [`Dt::from_days_f`](../struct.Dt.html#method.from_days_f)
677    #[inline(always)]
678    pub const fn to_days_f(&self) -> Real {
679        Self::attos_to_unit_f(self.attos, ATTOS_PER_DAY)
680    }
681
682    /// Splits into whole weeks and leftover attoseconds, dropping any fraction
683    /// toward zero (1.25 → 1, −1.25 → −1).
684    ///
685    /// A week is 7 civil days (`604_800` seconds). Returns `(whole, frac_attos)`.
686    /// When negative with a fraction, `frac_attos` is negative too — e.g.
687    /// −1.25 weeks is `(-1, −1 day − 18 hours in attoseconds)`.
688    ///
689    /// For a leftover that is always ≥ 0, use
690    /// [`to_weeks_floor`](../struct.Dt.html#method.to_weeks_floor).
691    ///
692    /// ## Examples
693    ///
694    /// ```rust
695    /// use deep_time::Dt;
696    /// use deep_time::macros::{days, dt, hours, weeks};
697    ///
698    /// // Quarter week = 1 day + 18 hours
699    /// let q = days!(1) + hours!(18);
700    /// assert_eq!(dt!(weeks!(1) + q).to_weeks(), (1, q));
701    /// assert_eq!(dt!(-weeks!(1) - q).to_weeks(), (-1, -q));
702    /// ```
703    ///
704    /// ## See also
705    ///
706    /// - [`Dt::to_weeks_floor`](../struct.Dt.html#method.to_weeks_floor)
707    /// - [`Dt::from_weeks`](../struct.Dt.html#method.from_weeks)
708    #[inline(always)]
709    pub const fn to_weeks(&self) -> (i128, i128) {
710        (self.attos / ATTOS_PER_WEEK, self.attos % ATTOS_PER_WEEK)
711    }
712
713    /// Splits into whole weeks and leftover attoseconds, rounding the whole
714    /// part down so the leftover is always ≥ 0.
715    ///
716    /// A week is 7 civil days (`604_800` seconds). Returns `(whole, frac_attos)`.
717    /// Same floor rule as
718    /// [`to_sec_floor`](../struct.Dt.html#method.to_sec_floor) (e.g. −1.5 weeks →
719    /// `(-2, half a week in attoseconds)`). For truncation toward zero, use
720    /// [`to_weeks`](../struct.Dt.html#method.to_weeks).
721    ///
722    /// ## Examples
723    ///
724    /// ```rust
725    /// use deep_time::Dt;
726    /// use deep_time::macros::{days, dt, hours, weeks};
727    ///
728    /// // Quarter week = 1 day + 18 hours; three-quarter = 5 days + 6 hours
729    /// let q = days!(1) + hours!(18);
730    /// let three_q = days!(5) + hours!(6);
731    /// assert_eq!(dt!(weeks!(1) + q).to_weeks_floor(), (1, q));
732    ///
733    /// // -1.25 weeks → -2 whole weeks + 0.75 week leftover
734    /// assert_eq!(dt!(-weeks!(1) - q).to_weeks_floor(), (-2, three_q));
735    /// ```
736    ///
737    /// ## See also
738    ///
739    /// - [`Dt::to_weeks`](../struct.Dt.html#method.to_weeks)
740    #[inline(always)]
741    pub const fn to_weeks_floor(&self) -> (i128, i128) {
742        (
743            self.attos.div_euclid(ATTOS_PER_WEEK),
744            self.attos.rem_euclid(ATTOS_PER_WEEK),
745        )
746    }
747
748    /// Converts this duration to weeks as a floating-point number.
749    ///
750    /// A week is 7 civil days (`604_800` seconds).
751    ///
752    /// ## Examples
753    ///
754    /// ```rust
755    /// use deep_time::Dt;
756    /// use deep_time::macros::{days, dt, hours, weeks};
757    ///
758    /// assert_eq!(Dt::ZERO.to_weeks_f(), 0.0);
759    ///
760    /// // Half week = 3 days + 12 hours
761    /// let half = days!(3) + hours!(12);
762    /// assert_eq!(dt!(weeks!(1) + half).to_weeks_f(), 1.5);
763    /// assert_eq!(dt!(-weeks!(1) - half).to_weeks_f(), -1.5);
764    /// ```
765    ///
766    /// ## See also
767    ///
768    /// - [`Dt::from_weeks`](../struct.Dt.html#method.from_weeks)
769    /// - [`Dt::to_weeks`](../struct.Dt.html#method.to_weeks)
770    #[inline(always)]
771    pub const fn to_weeks_f(&self) -> Real {
772        Self::attos_to_unit_f(self.attos, ATTOS_PER_WEEK)
773    }
774
775    /// Splits into whole milliseconds and leftover attoseconds, dropping any
776    /// fraction toward zero (1.7 → 1, −1.7 → −1).
777    ///
778    /// Returns `(whole, frac_attos)`. When negative with a fraction,
779    /// `frac_attos` is negative too. For a leftover that is always ≥ 0, use
780    /// [`to_ms_floor`](../struct.Dt.html#method.to_ms_floor).
781    ///
782    /// ## Examples
783    ///
784    /// ```rust
785    /// use deep_time::Dt;
786    /// use deep_time::macros::{from_ms, us};
787    ///
788    /// assert_eq!(from_ms!(1, us!(300)).to_ms(), (1, us!(300)));
789    /// assert_eq!(from_ms!(-1, -us!(300)).to_ms(), (-1, -us!(300)));
790    /// ```
791    #[inline(always)]
792    pub const fn to_ms(&self) -> (i128, i128) {
793        (
794            self.attos / ATTOS_PER_MS_I128,
795            self.attos % ATTOS_PER_MS_I128,
796        )
797    }
798
799    /// Splits into whole microseconds and leftover attoseconds, dropping any
800    /// fraction toward zero (1.7 → 1, −1.7 → −1).
801    ///
802    /// Returns `(whole, frac_attos)`. When negative with a fraction,
803    /// `frac_attos` is negative too. For a leftover that is always ≥ 0, use
804    /// [`to_us_floor`](../struct.Dt.html#method.to_us_floor).
805    ///
806    /// ## Examples
807    ///
808    /// ```rust
809    /// use deep_time::Dt;
810    /// use deep_time::macros::{from_us, ns};
811    ///
812    /// assert_eq!(from_us!(1, ns!(300)).to_us(), (1, ns!(300)));
813    /// assert_eq!(from_us!(-1, -ns!(300)).to_us(), (-1, -ns!(300)));
814    /// ```
815    #[inline(always)]
816    pub const fn to_us(&self) -> (i128, i128) {
817        (
818            self.attos / ATTOS_PER_US_I128,
819            self.attos % ATTOS_PER_US_I128,
820        )
821    }
822
823    /// Splits into whole nanoseconds and leftover attoseconds, dropping any
824    /// fraction toward zero (1.7 → 1, −1.7 → −1).
825    ///
826    /// Returns `(whole, frac_attos)`. When negative with a fraction,
827    /// `frac_attos` is negative too. For a leftover that is always ≥ 0, use
828    /// [`to_ns_floor`](../struct.Dt.html#method.to_ns_floor).
829    ///
830    /// ## Examples
831    ///
832    /// ```rust
833    /// use deep_time::Dt;
834    /// use deep_time::macros::{from_ns, ps};
835    ///
836    /// assert_eq!(from_ns!(1, ps!(300)).to_ns(), (1, ps!(300)));
837    /// assert_eq!(from_ns!(-1, -ps!(300)).to_ns(), (-1, -ps!(300)));
838    /// ```
839    #[inline(always)]
840    pub const fn to_ns(&self) -> (i128, i128) {
841        (
842            self.attos / ATTOS_PER_NS_I128,
843            self.attos % ATTOS_PER_NS_I128,
844        )
845    }
846
847    /// Splits into whole picoseconds and leftover attoseconds, dropping any
848    /// fraction toward zero (1.7 → 1, −1.7 → −1).
849    ///
850    /// Returns `(whole, frac_attos)`. When negative with a fraction,
851    /// `frac_attos` is negative too. For a leftover that is always ≥ 0, use
852    /// [`to_ps_floor`](../struct.Dt.html#method.to_ps_floor).
853    ///
854    /// ## Examples
855    ///
856    /// ```rust
857    /// use deep_time::Dt;
858    /// use deep_time::macros::{from_ps, fs};
859    ///
860    /// assert_eq!(from_ps!(1, fs!(300)).to_ps(), (1, fs!(300)));
861    /// assert_eq!(from_ps!(-1, -fs!(300)).to_ps(), (-1, -fs!(300)));
862    /// ```
863    #[inline(always)]
864    pub const fn to_ps(&self) -> (i128, i128) {
865        (
866            self.attos / ATTOS_PER_PS_I128,
867            self.attos % ATTOS_PER_PS_I128,
868        )
869    }
870
871    /// Splits into whole femtoseconds and leftover attoseconds, dropping any
872    /// fraction toward zero (1.7 → 1, −1.7 → −1).
873    ///
874    /// Returns `(whole, frac_attos)`. When negative with a fraction,
875    /// `frac_attos` is negative too. For a leftover that is always ≥ 0, use
876    /// [`to_fs_floor`](../struct.Dt.html#method.to_fs_floor).
877    ///
878    /// ## Examples
879    ///
880    /// ```rust
881    /// use deep_time::Dt;
882    /// use deep_time::macros::from_fs;
883    ///
884    /// assert_eq!(from_fs!(1, 500).to_fs(), (1, 500));
885    /// assert_eq!(from_fs!(-1, -500).to_fs(), (-1, -500));
886    /// ```
887    #[inline(always)]
888    pub const fn to_fs(&self) -> (i128, i128) {
889        (
890            self.attos / ATTOS_PER_FS_I128,
891            self.attos % ATTOS_PER_FS_I128,
892        )
893    }
894}