Skip to main content

deep_time/dt/
to.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,
5};
6
7impl Dt {
8    /// Returns the whole seconds portion of this [`Dt`] using truncation towards zero
9    /// (i.e., the integer part obtained via truncating division, without rounding).
10    ///
11    /// This is equivalent to `self.attos / ATTOS_PER_SEC_I128`.
12    ///
13    /// Unlike
14    /// [`to_sec_floor`](../struct.Dt.html#method.to_sec_floor)
15    /// (which uses Euclidean division, flooring towards
16    /// negative infinity for negative values to keep the fractional part non-negative),
17    /// this version truncates towards zero.
18    ///
19    /// Consequently, for values in `(-1, 0)` seconds (e.g. -0.3 s or -0.8 s),
20    /// both return `0`.
21    ///
22    /// ## Examples
23    ///
24    /// ```rust
25    /// use deep_time::Dt;
26    ///
27    /// // -0.3 seconds → truncates to 0
28    /// let dt = Dt::span(-300_000_000_000_000_000);
29    /// assert_eq!(dt.to_sec(), 0);
30    ///
31    /// // -0.8 seconds → truncates to 0
32    /// let dt = Dt::span(-800_000_000_000_000_000);
33    /// assert_eq!(dt.to_sec(), 0);
34    ///
35    /// // -1.3 seconds → truncates to -1 (while to_sec_floor gives -2)
36    /// let dt = Dt::span(-1_300_000_000_000_000_000);
37    /// assert_eq!(dt.to_sec(), -1);
38    /// assert_eq!(dt.to_sec_floor(), -2);
39    ///
40    /// // Positive values behave the same as `to_sec_floor`
41    /// let dt = Dt::span(1_300_000_000_000_000_000);
42    /// assert_eq!(dt.to_sec(), 1);
43    /// assert_eq!(dt.to_sec_floor(), 1);
44    /// ```
45    #[inline(always)]
46    pub const fn to_sec(&self) -> i128 {
47        self.attos / ATTOS_PER_SEC_I128
48    }
49
50    /// Returns the whole seconds portion of this [`Dt`] using truncation towards zero,
51    /// then clamped to an [`i64`].
52    ///
53    /// If the truncated seconds value lies outside the `i64` range, the result
54    /// saturates to [`i64::MAX`] or [`i64::MIN`].
55    ///
56    /// See
57    /// [`to_sec`](../struct.Dt.html#method.to_sec)
58    /// for the truncation semantics
59    /// (towards zero, no rounding).
60    ///
61    /// ## Examples
62    ///
63    /// ```rust
64    /// use deep_time::Dt;
65    ///
66    /// let dt = Dt::span(-1_300_000_000_000_000_000);
67    /// assert_eq!(dt.to_sec64(), -1);
68    ///
69    /// let dt = Dt::span(1_300_000_000_000_000_000);
70    /// assert_eq!(dt.to_sec64_floor(), 1);
71    /// ```
72    #[inline(always)]
73    pub const fn to_sec64(&self) -> i64 {
74        Self::to_i64(self.attos / ATTOS_PER_SEC_I128)
75    }
76
77    /// If this time were turned into [`i128`] seconds and [`u64`] (always
78    /// pushing to the positive) fractional attoseconds, this returns the
79    /// whole seconds part.
80    ///
81    /// To just get seconds rounded to the nearest second use
82    /// [`Dt::to_sec_round`](../struct.Dt.html#method.to_sec_round)
83    /// instead.
84    ///
85    /// ## Examples
86    ///
87    /// ```rust
88    /// use deep_time::{Dt, Scale};
89    ///
90    /// // negative 1.3 seconds
91    /// let dt = Dt::span(-1_300_000_000_000_000_000);
92    ///
93    /// // becomes positive 700ms
94    /// let frac = dt.to_sec_ufrac();
95    /// assert_eq!(frac, 700_000_000_000_000_000);
96    ///
97    /// // becomes negative 2 seconds
98    /// let sec = dt.to_sec_floor();
99    /// assert_eq!(sec, -2);
100    ///
101    /// let dt = Dt::span(1_300_000_000_000_000_000);
102    ///
103    /// assert_eq!(dt.to_sec_floor(), 1);
104    /// assert_eq!(dt.to_sec_ufrac(), 300_000_000_000_000_000);
105    ///
106    /// // if you just want rounded seconds
107    /// // use to_sec_round() instead
108    /// let dt = Dt::span(-1_300_000_000_000_000_000);
109    /// let sec = dt.to_sec_round();
110    /// assert_eq!(sec, -1);
111    /// ```
112    #[inline(always)]
113    pub const fn to_sec_floor(&self) -> i128 {
114        self.attos.div_euclid(ATTOS_PER_SEC_I128)
115    }
116
117    /// If this time were turned into [`i64`] seconds and [`u64`] (always
118    /// pushing to the positive) fractional attoseconds, this returns the
119    /// whole seconds part.
120    ///
121    /// To just get seconds rounded to the nearest second use
122    /// [`Dt::to_sec_round`](../struct.Dt.html#method.to_sec_round)
123    /// instead.
124    ///
125    /// ## Examples
126    ///
127    /// ```rust
128    /// use deep_time::{Dt, Scale};
129    ///
130    /// // negative 1.3 seconds
131    /// let dt = Dt::span(-1_300_000_000_000_000_000);
132    ///
133    /// // becomes positive 700ms
134    /// let frac = dt.to_sec_ufrac();
135    /// assert_eq!(frac, 700_000_000_000_000_000);
136    ///
137    /// // becomes negative 2 seconds
138    /// let sec = dt.to_sec64_floor();
139    /// assert_eq!(sec, -2);
140    ///
141    /// let dt = Dt::span(1_300_000_000_000_000_000);
142    ///
143    /// assert_eq!(dt.to_sec64_floor(), 1);
144    /// assert_eq!(dt.to_sec_ufrac(), 300_000_000_000_000_000);
145    ///
146    /// // if you just want rounded seconds
147    /// // use to_sec_round() instead
148    /// let dt = Dt::span(-1_300_000_000_000_000_000);
149    /// let sec = dt.to_sec_round();
150    /// assert_eq!(sec, -1);
151    /// ```
152    #[inline(always)]
153    pub const fn to_sec64_floor(&self) -> i64 {
154        Self::to_i64(self.attos.div_euclid(ATTOS_PER_SEC_I128))
155    }
156
157    /// Returns this [`Dt`] rounded to the nearest whole second, then
158    /// converted to an [`i128`] number of seconds.
159    ///
160    /// - Exactly halfway cases (e.g. 0.5 s, -0.5 s) round as follows:
161    ///   0.5 becomes 1 and -0.5 becomes -1.
162    /// - Matches the behavior of [`Dt::round`].
163    ///
164    /// ## Examples
165    ///
166    /// ```rust
167    /// use deep_time::Dt;
168    ///
169    /// // 1.3 seconds → rounds to 1
170    /// assert_eq!(Dt::span(1_300_000_000_000_000_000).to_sec_round(), 1);
171    ///
172    /// // -1.3 seconds → rounds to -1
173    /// assert_eq!(Dt::span(-1_300_000_000_000_000_000).to_sec_round(), -1);
174    ///
175    /// // 1.6 seconds → rounds to 2
176    /// assert_eq!(Dt::span(1_600_000_000_000_000_000).to_sec_round(), 2);
177    ///
178    /// // Halfway cases
179    /// assert_eq!(Dt::span(500_000_000_000_000_000).to_sec_round(), 1);
180    /// assert_eq!(Dt::span(-500_000_000_000_000_000).to_sec_round(), -1);
181    /// ```
182    #[inline(always)]
183    pub const fn to_sec_round(&self) -> i128 {
184        self.round_to_sec().to_sec()
185    }
186
187    /// Returns this [`Dt`] rounded to the nearest whole second, then
188    /// converted to an [`i64`] number of seconds.
189    ///
190    /// - Exactly halfway cases round as follows: 0.5 becomes 1 and -0.5 becomes -1,
191    ///   same as
192    ///   [`to_sec_round`](../struct.Dt.html#method.to_sec_round).
193    /// - If the rounded value is outside the representable `i64` range,
194    ///   it saturates to [`i64::MAX`] or [`i64::MIN`].
195    ///
196    /// ## Examples
197    ///
198    /// ```rust
199    /// use deep_time::Dt;
200    ///
201    /// let dt = Dt::span(1_300_000_000_000_000_000);
202    /// assert_eq!(dt.to_sec64_round(), 1);
203    ///
204    /// let dt = Dt::span(-1_300_000_000_000_000_000);
205    /// assert_eq!(dt.to_sec64_round(), -1);
206    /// ```
207    #[inline(always)]
208    pub const fn to_sec64_round(&self) -> i64 {
209        Self::to_i64(self.round_to_sec().to_sec())
210    }
211
212    /// Converts this [`Dt`] to an f64 number of seconds since the reference
213    /// epoch of its associated scale.
214    ///
215    /// - The conversion is lossy, as [`f64`] provides approximately 15.95 decimal
216    ///   digits of precision.
217    #[inline(always)]
218    pub const fn to_f64(&self) -> f64 {
219        self.to_sec_f()
220    }
221
222    /// Converts this [`Dt`] to a floating-point number of seconds since the reference
223    /// epoch of its associated scale.
224    ///
225    /// - The conversion is lossy, as [`f64`] provides approximately 15.95 decimal
226    ///   digits of precision.
227    pub const fn to_sec_f(&self) -> Real {
228        let attos = self.attos;
229
230        if attos == 0 {
231            return 0.0;
232        }
233        let sec = attos.div_euclid(ATTOS_PER_SEC_I128);
234        let rem = attos.rem_euclid(ATTOS_PER_SEC_I128); // always in [0, aps)
235
236        if sec < 0 && rem > ATTOS_PER_SEC_I128 / 2 {
237            // original cancellation-avoidance path
238            let small = ATTOS_PER_SEC_I128 - rem;
239            let small_f = f!(small as u64) / ATTOS_PER_SECF;
240            (sec as f64) + 1.0 - small_f
241        } else {
242            (sec as f64) + f!(rem as u64) / ATTOS_PER_SECF
243        }
244    }
245
246    /// If this time were turned into seconds, this returns the signed fractional
247    /// attoseconds part — the amount left over after removing whole seconds, with the
248    /// same sign as the original value when non-zero.
249    ///
250    /// Pairs with [`from_sec_and_frac`](../struct.Dt.html#method.from_sec_and_frac).
251    /// For the floor/Euclidean split, use
252    /// [`to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac) and
253    /// [`from_sec_and_ufrac`](../struct.Dt.html#method.from_sec_and_ufrac).
254    #[inline(always)]
255    pub const fn to_sec_frac(&self) -> i64 {
256        (self.attos % ATTOS_PER_SEC_I128) as i64
257    }
258
259    /// If this time were turned into i64 seconds and u64 (always pushing to the positive)
260    /// fractional attoseconds, this returns the fractional attoseconds part.
261    ///
262    /// - Always returns a value in the range `0 ≤ x < ATTOS_PER_SEC`.
263    /// - For negative [`Dt`]s this is **not** simply the decimal part of the time in seconds.
264    ///
265    /// ## Examples
266    ///
267    /// ```rust
268    /// use deep_time::{Dt, Scale};
269    ///
270    /// // negative 1.3 seconds
271    /// let dt = Dt::span(-1_300_000_000_000_000_000);
272    ///
273    /// // becomes positive 700ms
274    /// let frac = dt.to_sec_ufrac();
275    /// assert_eq!(frac, 700_000_000_000_000_000);
276    ///
277    /// // becomes -2 seconds
278    /// let sec = dt.to_sec64_floor();
279    /// assert_eq!(sec, -2);
280    ///
281    /// let dt = Dt::span(1_300_000_000_000_000_000);
282    ///
283    /// assert_eq!(dt.to_sec64_floor(), 1);
284    /// assert_eq!(dt.to_sec_ufrac(), 300_000_000_000_000_000);
285    /// ```
286    #[inline(always)]
287    pub const fn to_sec_ufrac(&self) -> u64 {
288        self.attos.rem_euclid(ATTOS_PER_SEC_I128) as u64
289    }
290
291    /// Returns a new [`Dt`] rounded to the nearest second.
292    #[inline(always)]
293    pub const fn round_to_sec(&self) -> Dt {
294        self.round(Dt::span(ATTOS_PER_SEC_I128))
295    }
296
297    /// Returns the total time in attoseconds.
298    #[inline(always)]
299    pub const fn to_attos(&self) -> i128 {
300        self.attos
301    }
302
303    /// Converts this [`Dt`] into whole femtoseconds and a fractional part within one femtosecond.
304    ///
305    /// - Returns `(whole, frac_attos)` where `frac_attos` is always non-negative.
306    /// - For negative values this does **not** split at the decimal point — see
307    ///   [`to_sec64_floor`](../struct.Dt.html#method.to_sec64_floor) and
308    ///   [`to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac).
309    /// - For truncation toward zero, use [`to_fs`](../struct.Dt.html#method.to_fs).
310    #[inline(always)]
311    pub const fn to_fs_floor(&self) -> (i128, u128) {
312        (
313            self.attos.div_euclid(ATTOS_PER_FS_I128),
314            self.attos.rem_euclid(ATTOS_PER_FS_I128) as u128,
315        )
316    }
317
318    /// Converts this [`Dt`] into whole picoseconds and a fractional part within one picosecond.
319    ///
320    /// - Returns `(whole, frac_attos)` where `frac_attos` is always non-negative.
321    /// - For negative values this does **not** split at the decimal point — see
322    ///   [`to_sec64_floor`](../struct.Dt.html#method.to_sec64_floor) and
323    ///   [`to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac).
324    /// - For truncation toward zero, use [`to_ps`](../struct.Dt.html#method.to_ps).
325    #[inline(always)]
326    pub const fn to_ps_floor(&self) -> (i128, u128) {
327        (
328            self.attos.div_euclid(ATTOS_PER_PS_I128),
329            self.attos.rem_euclid(ATTOS_PER_PS_I128) as u128,
330        )
331    }
332
333    /// Converts this [`Dt`] into whole nanoseconds and a fractional part within one nanosecond.
334    ///
335    /// - Returns `(whole, frac_attos)` where `frac_attos` is always non-negative.
336    /// - For negative values this does **not** split at the decimal point — see
337    ///   [`to_sec64_floor`](../struct.Dt.html#method.to_sec64_floor) and
338    ///   [`to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac).
339    /// - For truncation toward zero, use [`to_ns`](../struct.Dt.html#method.to_ns).
340    #[inline(always)]
341    pub const fn to_ns_floor(&self) -> (i128, u128) {
342        (
343            self.attos.div_euclid(ATTOS_PER_NS_I128),
344            self.attos.rem_euclid(ATTOS_PER_NS_I128) as u128,
345        )
346    }
347
348    /// Converts this [`Dt`] into whole microseconds and a fractional part within one microsecond.
349    ///
350    /// - Returns `(whole, frac_attos)` where `frac_attos` is always non-negative.
351    /// - For negative values this does **not** split at the decimal point — see
352    ///   [`to_sec64_floor`](../struct.Dt.html#method.to_sec64_floor) and
353    ///   [`to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac).
354    /// - For truncation toward zero, use [`to_us`](../struct.Dt.html#method.to_us).
355    #[inline(always)]
356    pub const fn to_us_floor(&self) -> (i128, u128) {
357        (
358            self.attos.div_euclid(ATTOS_PER_US_I128),
359            self.attos.rem_euclid(ATTOS_PER_US_I128) as u128,
360        )
361    }
362
363    /// Converts this [`Dt`] into whole milliseconds and a fractional part within one millisecond.
364    ///
365    /// - Returns `(whole, frac_attos)` where `frac_attos` is always non-negative.
366    /// - For negative values this does **not** split at the decimal point — see
367    ///   [`to_sec64_floor`](../struct.Dt.html#method.to_sec64_floor) and
368    ///   [`to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac).
369    /// - For truncation toward zero, use [`to_ms`](../struct.Dt.html#method.to_ms).
370    #[inline(always)]
371    pub const fn to_ms_floor(&self) -> (i128, u128) {
372        (
373            self.attos.div_euclid(ATTOS_PER_MS_I128),
374            self.attos.rem_euclid(ATTOS_PER_MS_I128) as u128,
375        )
376    }
377
378    /// Converts this [`Dt`] into whole minutes and a fractional part within one minute.
379    ///
380    /// - Returns `(whole, frac_attos)` where `frac_attos` is always non-negative.
381    /// - For negative values this does **not** split at the decimal point — see
382    ///   [`to_sec64_floor`](../struct.Dt.html#method.to_sec64_floor) and
383    ///   [`to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac).
384    #[inline(always)]
385    pub const fn to_mins_floor(&self) -> (i128, u128) {
386        (
387            self.attos.div_euclid(ATTOS_PER_MIN),
388            self.attos.rem_euclid(ATTOS_PER_MIN) as u128,
389        )
390    }
391
392    /// Converts this [`Dt`] into whole hours and a fractional part within one hour.
393    ///
394    /// - Returns `(whole, frac_attos)` where `frac_attos` is always non-negative.
395    /// - For negative values this does **not** split at the decimal point — see
396    ///   [`to_sec64_floor`](../struct.Dt.html#method.to_sec64_floor) and
397    ///   [`to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac).
398    #[inline(always)]
399    pub const fn to_hours_floor(&self) -> (i128, u128) {
400        (
401            self.attos.div_euclid(ATTOS_PER_HOUR),
402            self.attos.rem_euclid(ATTOS_PER_HOUR) as u128,
403        )
404    }
405
406    /// Converts this [`Dt`] into whole days and a fractional part within one day.
407    ///
408    /// - Returns `(whole, frac_attos)` where `frac_attos` is always non-negative.
409    /// - For negative values this does **not** split at the decimal point — see
410    ///   [`to_sec64_floor`](../struct.Dt.html#method.to_sec64_floor) and
411    ///   [`to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac).
412    ///
413    /// ## Examples
414    ///
415    /// ```rust
416    /// use deep_time::{Dt, Scale};
417    /// use deep_time::consts::ATTOS_PER_HALF_DAY_U128;
418    ///
419    /// // library epoch is 2000-01-01 12:00:00 TAI
420    /// // so result will be negative 2 + half a day
421    /// // effectively -1.5 days
422    /// let dt = Dt::from_ymd(1999, 12, 31, Scale::TAI, 0, 0, 0, 0);
423    /// let (days, attos) = dt.to_days_floor();
424    /// assert_eq!(days, -2);
425    /// assert_eq!(attos, ATTOS_PER_HALF_DAY_U128);
426    /// ```
427    #[inline(always)]
428    pub const fn to_days_floor(&self) -> (i128, u128) {
429        (
430            self.attos.div_euclid(ATTOS_PER_DAY),
431            self.attos.rem_euclid(ATTOS_PER_DAY) as u128,
432        )
433    }
434
435    /// Converts this [`Dt`] into whole milliseconds and a fractional part within one millisecond,
436    /// truncating toward zero.
437    ///
438    /// Returns `(whole, frac_attos)`. `frac_attos` will be negative when `whole` is negative and
439    /// there is a non-zero fraction. See
440    /// [`to_sec`](../struct.Dt.html#method.to_sec)
441    /// for the same rule in seconds.
442    #[inline(always)]
443    pub const fn to_ms(&self) -> (i128, i128) {
444        (
445            self.attos / ATTOS_PER_MS_I128,
446            self.attos % ATTOS_PER_MS_I128,
447        )
448    }
449
450    /// Converts this [`Dt`] into whole microseconds and a fractional part within one microsecond,
451    /// truncating toward zero.
452    ///
453    /// Returns `(whole, frac_attos)`. `frac_attos` will be negative when `whole` is negative and
454    /// there is a non-zero fraction. See
455    /// [`to_sec`](../struct.Dt.html#method.to_sec)
456    /// for the same rule in seconds.
457    #[inline(always)]
458    pub const fn to_us(&self) -> (i128, i128) {
459        (
460            self.attos / ATTOS_PER_US_I128,
461            self.attos % ATTOS_PER_US_I128,
462        )
463    }
464
465    /// Converts this [`Dt`] into whole nanoseconds and a fractional part within one nanosecond,
466    /// truncating toward zero.
467    ///
468    /// Returns `(whole, frac_attos)`. `frac_attos` will be negative when `whole` is negative and
469    /// there is a non-zero fraction. See
470    /// [`to_sec`](../struct.Dt.html#method.to_sec)
471    /// for the same rule in seconds.
472    #[inline(always)]
473    pub const fn to_ns(&self) -> (i128, i128) {
474        (
475            self.attos / ATTOS_PER_NS_I128,
476            self.attos % ATTOS_PER_NS_I128,
477        )
478    }
479
480    /// Converts this [`Dt`] into whole picoseconds and a fractional part within one picosecond,
481    /// truncating toward zero.
482    ///
483    /// Returns `(whole, frac_attos)`. `frac_attos` will be negative when `whole` is negative and
484    /// there is a non-zero fraction. See
485    /// [`to_sec`](../struct.Dt.html#method.to_sec)
486    /// for the same rule in seconds.
487    #[inline(always)]
488    pub const fn to_ps(&self) -> (i128, i128) {
489        (
490            self.attos / ATTOS_PER_PS_I128,
491            self.attos % ATTOS_PER_PS_I128,
492        )
493    }
494
495    /// Converts this [`Dt`] into whole femtoseconds and a fractional part within one femtosecond,
496    /// truncating toward zero.
497    ///
498    /// Returns `(whole, frac_attos)`. `frac_attos` will be negative when `whole` is negative and
499    /// there is a non-zero fraction. See
500    /// [`to_sec`](../struct.Dt.html#method.to_sec)
501    /// for the same rule in seconds.
502    #[inline(always)]
503    pub const fn to_fs(&self) -> (i128, i128) {
504        (
505            self.attos / ATTOS_PER_FS_I128,
506            self.attos % ATTOS_PER_FS_I128,
507        )
508    }
509}