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}