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, dt};
26 ///
27 /// // -0.3 seconds → truncates to 0
28 /// let dt = dt!(-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!(-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!(-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!(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, dt};
65 ///
66 /// let dt = dt!(-1_300_000_000_000_000_000);
67 /// assert_eq!(dt.to_sec64(), -1);
68 ///
69 /// let dt = dt!(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, dt};
89 ///
90 /// // negative 1.3 seconds
91 /// let dt = dt!(-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!(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!(-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, dt};
129 ///
130 /// // negative 1.3 seconds
131 /// let dt = dt!(-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!(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!(-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, dt};
168 ///
169 /// // 1.3 seconds → rounds to 1
170 /// assert_eq!(dt!(1_300_000_000_000_000_000).to_sec_round(), 1);
171 ///
172 /// // -1.3 seconds → rounds to -1
173 /// assert_eq!(dt!(-1_300_000_000_000_000_000).to_sec_round(), -1);
174 ///
175 /// // 1.6 seconds → rounds to 2
176 /// assert_eq!(dt!(1_600_000_000_000_000_000).to_sec_round(), 2);
177 ///
178 /// // Halfway cases
179 /// assert_eq!(dt!(500_000_000_000_000_000).to_sec_round(), 1);
180 /// assert_eq!(dt!(-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, dt};
200 ///
201 /// let dt = dt!(1_300_000_000_000_000_000);
202 /// assert_eq!(dt.to_sec64_round(), 1);
203 ///
204 /// let dt = dt!(-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 #[inline(always)]
252 pub const fn to_sec_frac(&self) -> i64 {
253 (self.attos % ATTOS_PER_SEC_I128) as i64
254 }
255
256 /// If this time were turned into i64 seconds and u64 (always pushing to the positive)
257 /// fractional attoseconds, this returns the fractional attoseconds part.
258 ///
259 /// - Always returns a value in the range `0 ≤ x < ATTOS_PER_SEC`.
260 /// - For negative [`Dt`]s this is **not** simply the decimal part of the time in seconds.
261 ///
262 /// ## Examples
263 ///
264 /// ```rust
265 /// use deep_time::{Dt, Scale, dt};
266 ///
267 /// // negative 1.3 seconds
268 /// let dt = dt!(-1_300_000_000_000_000_000);
269 ///
270 /// // becomes positive 700ms
271 /// let frac = dt.to_sec_ufrac();
272 /// assert_eq!(frac, 700_000_000_000_000_000);
273 ///
274 /// // becomes -2 seconds
275 /// let sec = dt.to_sec64_floor();
276 /// assert_eq!(sec, -2);
277 ///
278 /// let dt = dt!(1_300_000_000_000_000_000);
279 ///
280 /// assert_eq!(dt.to_sec64_floor(), 1);
281 /// assert_eq!(dt.to_sec_ufrac(), 300_000_000_000_000_000);
282 /// ```
283 #[inline(always)]
284 pub const fn to_sec_ufrac(&self) -> u64 {
285 self.attos.rem_euclid(ATTOS_PER_SEC_I128) as u64
286 }
287
288 /// Returns a new [`Dt`] rounded to the nearest second.
289 #[inline(always)]
290 pub const fn round_to_sec(&self) -> Dt {
291 self.round(crate::dt!(ATTOS_PER_SEC_I128))
292 }
293
294 /// Returns the total time in attoseconds.
295 #[inline(always)]
296 pub const fn to_attos(&self) -> i128 {
297 self.attos
298 }
299
300 /// Converts this [`Dt`] into whole femtoseconds and a fractional part within one femtosecond.
301 ///
302 /// - Returns `(whole, frac_attos)` where `frac_attos` is always non-negative.
303 /// - For negative values this does **not** split at the decimal point — see
304 /// [`to_sec64_floor`](../struct.Dt.html#method.to_sec64_floor) and
305 /// [`to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac).
306 /// - For truncation toward zero, use [`to_fs`](../struct.Dt.html#method.to_fs).
307 #[inline(always)]
308 pub const fn to_fs_floor(&self) -> (i128, u128) {
309 (
310 self.attos.div_euclid(ATTOS_PER_FS_I128),
311 self.attos.rem_euclid(ATTOS_PER_FS_I128) as u128,
312 )
313 }
314
315 /// Converts this [`Dt`] into whole picoseconds and a fractional part within one picosecond.
316 ///
317 /// - Returns `(whole, frac_attos)` where `frac_attos` is always non-negative.
318 /// - For negative values this does **not** split at the decimal point — see
319 /// [`to_sec64_floor`](../struct.Dt.html#method.to_sec64_floor) and
320 /// [`to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac).
321 /// - For truncation toward zero, use [`to_ps`](../struct.Dt.html#method.to_ps).
322 #[inline(always)]
323 pub const fn to_ps_floor(&self) -> (i128, u128) {
324 (
325 self.attos.div_euclid(ATTOS_PER_PS_I128),
326 self.attos.rem_euclid(ATTOS_PER_PS_I128) as u128,
327 )
328 }
329
330 /// Converts this [`Dt`] into whole nanoseconds and a fractional part within one nanosecond.
331 ///
332 /// - Returns `(whole, frac_attos)` where `frac_attos` is always non-negative.
333 /// - For negative values this does **not** split at the decimal point — see
334 /// [`to_sec64_floor`](../struct.Dt.html#method.to_sec64_floor) and
335 /// [`to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac).
336 /// - For truncation toward zero, use [`to_ns`](../struct.Dt.html#method.to_ns).
337 #[inline(always)]
338 pub const fn to_ns_floor(&self) -> (i128, u128) {
339 (
340 self.attos.div_euclid(ATTOS_PER_NS_I128),
341 self.attos.rem_euclid(ATTOS_PER_NS_I128) as u128,
342 )
343 }
344
345 /// Converts this [`Dt`] into whole microseconds and a fractional part within one microsecond.
346 ///
347 /// - Returns `(whole, frac_attos)` where `frac_attos` is always non-negative.
348 /// - For negative values this does **not** split at the decimal point — see
349 /// [`to_sec64_floor`](../struct.Dt.html#method.to_sec64_floor) and
350 /// [`to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac).
351 /// - For truncation toward zero, use [`to_us`](../struct.Dt.html#method.to_us).
352 #[inline(always)]
353 pub const fn to_us_floor(&self) -> (i128, u128) {
354 (
355 self.attos.div_euclid(ATTOS_PER_US_I128),
356 self.attos.rem_euclid(ATTOS_PER_US_I128) as u128,
357 )
358 }
359
360 /// Converts this [`Dt`] into whole milliseconds and a fractional part within one millisecond.
361 ///
362 /// - Returns `(whole, frac_attos)` where `frac_attos` is always non-negative.
363 /// - For negative values this does **not** split at the decimal point — see
364 /// [`to_sec64_floor`](../struct.Dt.html#method.to_sec64_floor) and
365 /// [`to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac).
366 /// - For truncation toward zero, use [`to_ms`](../struct.Dt.html#method.to_ms).
367 #[inline(always)]
368 pub const fn to_ms_floor(&self) -> (i128, u128) {
369 (
370 self.attos.div_euclid(ATTOS_PER_MS_I128),
371 self.attos.rem_euclid(ATTOS_PER_MS_I128) as u128,
372 )
373 }
374
375 /// Converts this [`Dt`] into whole minutes and a fractional part within one minute.
376 ///
377 /// - Returns `(whole, frac_attos)` where `frac_attos` is always non-negative.
378 /// - For negative values this does **not** split at the decimal point — see
379 /// [`to_sec64_floor`](../struct.Dt.html#method.to_sec64_floor) and
380 /// [`to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac).
381 #[inline(always)]
382 pub const fn to_mins_floor(&self) -> (i128, u128) {
383 (
384 self.attos.div_euclid(ATTOS_PER_MIN),
385 self.attos.rem_euclid(ATTOS_PER_MIN) as u128,
386 )
387 }
388
389 /// Converts this [`Dt`] into whole hours and a fractional part within one hour.
390 ///
391 /// - Returns `(whole, frac_attos)` where `frac_attos` is always non-negative.
392 /// - For negative values this does **not** split at the decimal point — see
393 /// [`to_sec64_floor`](../struct.Dt.html#method.to_sec64_floor) and
394 /// [`to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac).
395 #[inline(always)]
396 pub const fn to_hours_floor(&self) -> (i128, u128) {
397 (
398 self.attos.div_euclid(ATTOS_PER_HOUR),
399 self.attos.rem_euclid(ATTOS_PER_HOUR) as u128,
400 )
401 }
402
403 /// Converts this [`Dt`] into whole days and a fractional part within one day.
404 ///
405 /// - Returns `(whole, frac_attos)` where `frac_attos` is always non-negative.
406 /// - For negative values this does **not** split at the decimal point — see
407 /// [`to_sec64_floor`](../struct.Dt.html#method.to_sec64_floor) and
408 /// [`to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac).
409 ///
410 /// ## Examples
411 ///
412 /// ```rust
413 /// use deep_time::{Dt, Scale, dt};
414 /// use deep_time::consts::ATTOS_PER_HALF_DAY_U128;
415 ///
416 /// // library epoch is 2000-01-01 12:00:00 TAI
417 /// // so result will be negative 2 + half a day
418 /// // effectively -1.5 days
419 /// let dt = Dt::from_ymd(1999, 12, 31, Scale::TAI, 0, 0, 0, 0);
420 /// let (days, attos) = dt.to_days_floor();
421 /// assert_eq!(days, -2);
422 /// assert_eq!(attos, ATTOS_PER_HALF_DAY_U128);
423 /// ```
424 #[inline(always)]
425 pub const fn to_days_floor(&self) -> (i128, u128) {
426 (
427 self.attos.div_euclid(ATTOS_PER_DAY),
428 self.attos.rem_euclid(ATTOS_PER_DAY) as u128,
429 )
430 }
431
432 /// Converts this [`Dt`] into whole milliseconds and a fractional part within one millisecond,
433 /// truncating toward zero.
434 ///
435 /// Returns `(whole, frac_attos)`. `frac_attos` will be negative when `whole` is negative and
436 /// there is a non-zero fraction. See
437 /// [`to_sec`](../struct.Dt.html#method.to_sec)
438 /// for the same rule in seconds.
439 #[inline(always)]
440 pub const fn to_ms(&self) -> (i128, i128) {
441 (
442 self.attos / ATTOS_PER_MS_I128,
443 self.attos % ATTOS_PER_MS_I128,
444 )
445 }
446
447 /// Converts this [`Dt`] into whole microseconds and a fractional part within one microsecond,
448 /// truncating toward zero.
449 ///
450 /// Returns `(whole, frac_attos)`. `frac_attos` will be negative when `whole` is negative and
451 /// there is a non-zero fraction. See
452 /// [`to_sec`](../struct.Dt.html#method.to_sec)
453 /// for the same rule in seconds.
454 #[inline(always)]
455 pub const fn to_us(&self) -> (i128, i128) {
456 (
457 self.attos / ATTOS_PER_US_I128,
458 self.attos % ATTOS_PER_US_I128,
459 )
460 }
461
462 /// Converts this [`Dt`] into whole nanoseconds and a fractional part within one nanosecond,
463 /// truncating toward zero.
464 ///
465 /// Returns `(whole, frac_attos)`. `frac_attos` will be negative when `whole` is negative and
466 /// there is a non-zero fraction. See
467 /// [`to_sec`](../struct.Dt.html#method.to_sec)
468 /// for the same rule in seconds.
469 #[inline(always)]
470 pub const fn to_ns(&self) -> (i128, i128) {
471 (
472 self.attos / ATTOS_PER_NS_I128,
473 self.attos % ATTOS_PER_NS_I128,
474 )
475 }
476
477 /// Converts this [`Dt`] into whole picoseconds and a fractional part within one picosecond,
478 /// truncating toward zero.
479 ///
480 /// Returns `(whole, frac_attos)`. `frac_attos` will be negative when `whole` is negative and
481 /// there is a non-zero fraction. See
482 /// [`to_sec`](../struct.Dt.html#method.to_sec)
483 /// for the same rule in seconds.
484 #[inline(always)]
485 pub const fn to_ps(&self) -> (i128, i128) {
486 (
487 self.attos / ATTOS_PER_PS_I128,
488 self.attos % ATTOS_PER_PS_I128,
489 )
490 }
491
492 /// Converts this [`Dt`] into whole femtoseconds and a fractional part within one femtosecond,
493 /// truncating toward zero.
494 ///
495 /// Returns `(whole, frac_attos)`. `frac_attos` will be negative when `whole` is negative and
496 /// there is a non-zero fraction. See
497 /// [`to_sec`](../struct.Dt.html#method.to_sec)
498 /// for the same rule in seconds.
499 #[inline(always)]
500 pub const fn to_fs(&self) -> (i128, i128) {
501 (
502 self.attos / ATTOS_PER_FS_I128,
503 self.attos % ATTOS_PER_FS_I128,
504 )
505 }
506}