Coverage Report

Created: 2026-09-04 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/hifitime-4.3.1/src/duration/mod.rs
Line
Count
Source
1
/*
2
* Hifitime
3
* Copyright (C) 2017-onward Christopher Rabotin <christopher.rabotin@gmail.com> et al. (cf. https://github.com/nyx-space/hifitime/graphs/contributors)
4
* This Source Code Form is subject to the terms of the Mozilla Public
5
* License, v. 2.0. If a copy of the MPL was not distributed with this
6
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
7
*
8
* Documentation: https://nyxspace.com/
9
*/
10
11
use crate::errors::{DurationError, HifitimeError};
12
use crate::{SECONDS_PER_CENTURY, SECONDS_PER_DAY, SECONDS_PER_HOUR, SECONDS_PER_MINUTE};
13
14
pub use crate::{Freq, Frequencies, TimeUnits, Unit};
15
16
use core::cmp::Ordering;
17
use core::fmt;
18
use core::hash::{Hash, Hasher};
19
20
#[cfg(feature = "serde")]
21
use serde::{Deserialize, Deserializer, Serialize, Serializer};
22
23
#[cfg(feature = "serde")]
24
use core::str::FromStr;
25
26
pub mod parse;
27
28
#[cfg(feature = "python")]
29
mod python;
30
31
#[cfg(feature = "python")]
32
use pyo3::prelude::pyclass;
33
34
#[cfg(not(feature = "std"))]
35
#[allow(unused_imports)] // Import is indeed used.
36
use num_traits::Float;
37
38
#[cfg(kani)]
39
mod kani_verif;
40
41
pub const DAYS_PER_CENTURY_U64: u64 = 36_525;
42
pub const NANOSECONDS_PER_MICROSECOND: u64 = 1_000;
43
pub const NANOSECONDS_PER_MILLISECOND: u64 = 1_000 * NANOSECONDS_PER_MICROSECOND;
44
pub const NANOSECONDS_PER_SECOND: u64 = 1_000 * NANOSECONDS_PER_MILLISECOND;
45
pub(crate) const NANOSECONDS_PER_SECOND_U32: u32 = 1_000_000_000;
46
pub const NANOSECONDS_PER_MINUTE: u64 = 60 * NANOSECONDS_PER_SECOND;
47
pub const NANOSECONDS_PER_HOUR: u64 = 60 * NANOSECONDS_PER_MINUTE;
48
pub const NANOSECONDS_PER_DAY: u64 = 24 * NANOSECONDS_PER_HOUR;
49
pub const NANOSECONDS_PER_CENTURY: u64 = DAYS_PER_CENTURY_U64 * NANOSECONDS_PER_DAY;
50
51
pub mod ops;
52
53
/// Defines generally usable durations for nanosecond precision valid for 32,768 centuries in either direction, and only on 80 bits / 10 octets.
54
///
55
/// **Important conventions:**
56
/// 1. The negative durations can be mentally modeled "BC" years. One hours before 01 Jan 0000, it was "-1" years but  365 days and 23h into the current day.
57
///    It was decided that the nanoseconds corresponds to the nanoseconds _into_ the current century. In other words,
58
///    a duration with centuries = -1 and nanoseconds = 0 is _a greater duration_ (further from zero) than centuries = -1 and nanoseconds = 1.
59
///    Duration zero minus one nanosecond returns a century of -1 and a nanosecond set to the number of nanoseconds in one century minus one.
60
///    That difference is exactly 1 nanoseconds, where the former duration is "closer to zero" than the latter.
61
///    As such, the largest negative duration that can be represented sets the centuries to i16::MAX and its nanoseconds to NANOSECONDS_PER_CENTURY.
62
/// 2. Negative and positive durations are distinct: -15 minutes != 15 minutes. Use the signum function to check the sign, and abs() to get the absolute value.
63
///
64
/// :type string_repr: str
65
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)]
66
#[repr(C)]
67
#[cfg_attr(feature = "python", pyclass)]
68
#[cfg_attr(feature = "python", pyo3(module = "hifitime"))]
69
pub struct Duration {
70
    pub(crate) centuries: i16,
71
    pub(crate) nanoseconds: u64,
72
}
73
74
impl Hash for Duration {
75
0
    fn hash<H: Hasher>(&self, hasher: &mut H) {
76
0
        self.centuries.hash(hasher);
77
0
        self.nanoseconds.hash(hasher);
78
0
    }
79
}
80
81
impl Default for Duration {
82
0
    fn default() -> Self {
83
0
        Duration::ZERO
84
0
    }
85
}
86
87
#[cfg(feature = "serde")]
88
impl Serialize for Duration {
89
0
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
90
0
    where
91
0
        S: Serializer,
92
    {
93
0
        let s = self.to_string();
94
0
        serializer.serialize_str(&s)
95
0
    }
Unexecuted instantiation: <hifitime::duration::Duration as serde_core::ser::Serialize>::serialize::<serde_lexpr::value::ser::Serializer>
Unexecuted instantiation: <hifitime::duration::Duration as serde_core::ser::Serialize>::serialize::<_>
96
}
97
98
#[cfg(feature = "serde")]
99
impl<'de> Deserialize<'de> for Duration {
100
0
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
101
0
    where
102
0
        D: Deserializer<'de>,
103
    {
104
0
        let s = String::deserialize(deserializer)?;
105
0
        Duration::from_str(&s).map_err(serde::de::Error::custom)
106
0
    }
Unexecuted instantiation: <hifitime::duration::Duration as serde_core::de::Deserialize>::deserialize::<serde::private::de::missing_field::MissingFieldDeserializer<serde_lexpr::error::Error>>
Unexecuted instantiation: <hifitime::duration::Duration as serde_core::de::Deserialize>::deserialize::<&mut serde_lexpr::value::de::Deserializer>
Unexecuted instantiation: <hifitime::duration::Duration as serde_core::de::Deserialize>::deserialize::<_>
107
}
108
109
// Defines the methods that should be classmethods in Python, but must be redefined as per https://github.com/PyO3/pyo3/issues/1003#issuecomment-844433346
110
impl Duration {
111
    /// A duration of exactly zero nanoseconds
112
    pub const ZERO: Self = Self {
113
        centuries: 0,
114
        nanoseconds: 0,
115
    };
116
117
    /// Maximum duration that can be represented
118
    pub const MAX: Self = Self {
119
        centuries: i16::MAX,
120
        nanoseconds: NANOSECONDS_PER_CENTURY,
121
    };
122
123
    /// Minimum duration that can be represented
124
    pub const MIN: Self = Self {
125
        centuries: i16::MIN,
126
        nanoseconds: 0,
127
    };
128
129
    /// Smallest duration that can be represented
130
    pub const EPSILON: Self = Self {
131
        centuries: 0,
132
        nanoseconds: 1,
133
    };
134
135
    /// Minimum positive duration is one nanoseconds
136
    pub const MIN_POSITIVE: Self = Self::EPSILON;
137
138
    /// Minimum negative duration is minus one nanosecond
139
    pub const MIN_NEGATIVE: Self = Self {
140
        centuries: -1,
141
        nanoseconds: NANOSECONDS_PER_CENTURY - 1,
142
    };
143
144
    #[must_use]
145
    /// Create a normalized duration from its parts
146
    #[cfg_attr(kani, kani::ensures(|result: &Self| {
147
        result.nanoseconds < NANOSECONDS_PER_CENTURY
148
            || result.parts_are_equal(Self::MAX)
149
            || result.parts_are_equal(Self::MIN)
150
    }))]
151
275M
    pub const fn from_parts(centuries: i16, nanoseconds: u64) -> Self {
152
275M
        Self {
153
275M
            centuries,
154
275M
            nanoseconds,
155
275M
        }
156
275M
        .as_normalized()
157
275M
    }
158
159
    #[must_use]
160
    /// Converts the total nanoseconds as i128 into this Duration (saving 48 bits)
161
    #[cfg_attr(kani, kani::ensures(|result: &Self| {
162
        result.nanoseconds < NANOSECONDS_PER_CENTURY
163
            || result.parts_are_equal(Self::MAX)
164
            || result.parts_are_equal(Self::MIN)
165
    }))]
166
142k
    pub const fn from_total_nanoseconds(nanos: i128) -> Self {
167
        // In this function, we simply check that the input data can be casted. The `normalize` function will check whether more work needs to be done.
168
142k
        if nanos == 0 {
169
27.1k
            Self::ZERO
170
        } else {
171
115k
            let centuries_i128 = nanos.div_euclid(NANOSECONDS_PER_CENTURY as i128);
172
115k
            let remaining_nanos_i128 = nanos.rem_euclid(NANOSECONDS_PER_CENTURY as i128);
173
115k
            if centuries_i128 > (i16::MAX as i128) {
174
40.2k
                Self::MAX
175
75.1k
            } else if centuries_i128 < (i16::MIN as i128) {
176
11.2k
                Self::MIN
177
            } else {
178
                // We know that the centuries fit, and we know that the nanos are less than the number
179
                // of nanos per centuries, and rem_euclid guarantees that it's positive, so the
180
                // casting will work fine every time.
181
63.8k
                Self::from_parts(centuries_i128 as i16, remaining_nanos_i128 as u64)
182
            }
183
        }
184
142k
    }
185
186
    #[must_use]
187
    /// Create a new duration from the truncated nanoseconds (+/- 2927.1 years of duration)
188
    #[cfg_attr(kani, kani::ensures(|result: &Self| {
189
        result.nanoseconds < NANOSECONDS_PER_CENTURY
190
            || result.parts_are_equal(Self::MAX)
191
            || result.parts_are_equal(Self::MIN)
192
    }))]
193
275M
    pub const fn from_truncated_nanoseconds(nanos: i64) -> Self {
194
275M
        if nanos < 0 {
195
46.3k
            let ns = nanos.unsigned_abs();
196
            // Note: i64::MIN corresponds to a duration just past -3 centuries, so we can't hit the Duration::MIN here.
197
46.3k
            let extra_centuries = ns.div_euclid(NANOSECONDS_PER_CENTURY);
198
46.3k
            let rem_nanos = ns.rem_euclid(NANOSECONDS_PER_CENTURY);
199
46.3k
            Self::from_parts(
200
46.3k
                -1 - (extra_centuries as i16),
201
46.3k
                NANOSECONDS_PER_CENTURY - rem_nanos,
202
            )
203
        } else {
204
275M
            Self::from_parts(0, nanos.unsigned_abs())
205
        }
206
275M
    }
207
208
    /// Creates a new duration from the provided number of days
209
    #[must_use]
210
    #[cfg_attr(kani, kani::ensures(|result: &Self| {
211
        result.nanoseconds < NANOSECONDS_PER_CENTURY
212
            || result.parts_are_equal(Self::MAX)
213
            || result.parts_are_equal(Self::MIN)
214
    }))]
215
    #[cfg_attr(kani, kani::requires(value.is_finite()))]
216
0
    pub const fn from_days(value: f64) -> Self {
217
0
        Unit::Day.const_multiply(value)
218
0
    }
219
220
    /// Creates a new duration from the provided number of hours
221
    #[must_use]
222
    #[cfg_attr(kani, kani::ensures(|result: &Self| {
223
        result.nanoseconds < NANOSECONDS_PER_CENTURY
224
            || result.parts_are_equal(Self::MAX)
225
            || result.parts_are_equal(Self::MIN)
226
    }))]
227
    #[cfg_attr(kani, kani::requires(value.is_finite()))]
228
0
    pub const fn from_hours(value: f64) -> Self {
229
0
        Unit::Hour.const_multiply(value)
230
0
    }
231
232
    /// Creates a new duration from the provided number of seconds
233
    #[must_use]
234
    #[cfg_attr(kani, kani::requires(value.is_finite()))]
235
    #[cfg_attr(kani, kani::ensures(|result: &Self| {
236
        result.nanoseconds < NANOSECONDS_PER_CENTURY
237
            || result.parts_are_equal(Self::MAX)
238
            || result.parts_are_equal(Self::MIN)
239
    }))]
240
0
    pub const fn from_seconds(value: f64) -> Self {
241
0
        Unit::Second.const_multiply(value)
242
0
    }
243
244
    /// Creates a new duration from the provided number of milliseconds
245
    #[must_use]
246
    #[cfg_attr(kani, kani::ensures(|result: &Self| {
247
        result.nanoseconds < NANOSECONDS_PER_CENTURY
248
            || result.parts_are_equal(Self::MAX)
249
            || result.parts_are_equal(Self::MIN)
250
    }))]
251
    #[cfg_attr(kani, kani::requires(value.is_finite()))]
252
0
    pub const fn from_milliseconds(value: f64) -> Self {
253
0
        Unit::Millisecond.const_multiply(value)
254
0
    }
255
256
    /// Creates a new duration from the provided number of microsecond
257
    #[must_use]
258
    #[cfg_attr(kani, kani::ensures(|result: &Self| {
259
        result.nanoseconds < NANOSECONDS_PER_CENTURY
260
            || result.parts_are_equal(Self::MAX)
261
            || result.parts_are_equal(Self::MIN)
262
    }))]
263
    #[cfg_attr(kani, kani::requires(value.is_finite()))]
264
0
    pub const fn from_microseconds(value: f64) -> Self {
265
0
        Unit::Microsecond.const_multiply(value)
266
0
    }
267
268
    /// Creates a new duration from the provided number of nanoseconds
269
    #[must_use]
270
    #[cfg_attr(kani, kani::ensures(|result: &Self| {
271
        result.nanoseconds < NANOSECONDS_PER_CENTURY
272
            || result.parts_are_equal(Self::MAX)
273
            || result.parts_are_equal(Self::MIN)
274
    }))]
275
    #[cfg_attr(kani, kani::requires(value.is_finite()))]
276
0
    pub const fn from_nanoseconds(value: f64) -> Self {
277
0
        Unit::Nanosecond.const_multiply(value)
278
0
    }
279
280
    /// Creates a new duration from its parts. Set the sign to a negative number for the duration to be negative.
281
    #[allow(clippy::too_many_arguments)]
282
    #[must_use]
283
    #[cfg_attr(kani, kani::ensures(|result: &Self| {
284
        result.nanoseconds < NANOSECONDS_PER_CENTURY
285
            || result.parts_are_equal(Self::MAX)
286
            || result.parts_are_equal(Self::MIN)
287
    }))]
288
0
    pub fn compose(
289
0
        sign: i8,
290
0
        days: u64,
291
0
        hours: u64,
292
0
        minutes: u64,
293
0
        seconds: u64,
294
0
        milliseconds: u64,
295
0
        microseconds: u64,
296
0
        nanoseconds: u64,
297
0
    ) -> Self {
298
0
        Self::compose_f64(
299
0
            sign,
300
0
            days as f64,
301
0
            hours as f64,
302
0
            minutes as f64,
303
0
            seconds as f64,
304
0
            milliseconds as f64,
305
0
            microseconds as f64,
306
0
            nanoseconds as f64,
307
        )
308
0
    }
309
310
    /// Creates a new duration from its parts. Set the sign to a negative number for the duration to be negative.
311
    #[allow(clippy::too_many_arguments)]
312
    #[must_use]
313
0
    pub fn compose_f64(
314
0
        sign: i8,
315
0
        days: f64,
316
0
        hours: f64,
317
0
        minutes: f64,
318
0
        seconds: f64,
319
0
        milliseconds: f64,
320
0
        microseconds: f64,
321
0
        nanoseconds: f64,
322
0
    ) -> Self {
323
0
        let me: Self = days.days()
324
0
            + hours.hours()
325
0
            + minutes.minutes()
326
0
            + seconds.seconds()
327
0
            + milliseconds.milliseconds()
328
0
            + microseconds.microseconds()
329
0
            + nanoseconds.nanoseconds();
330
0
        if sign < 0 {
331
0
            -me
332
        } else {
333
0
            me
334
        }
335
0
    }
336
337
    /// Initializes a Duration from a timezone offset
338
    #[must_use]
339
    #[cfg_attr(kani, kani::ensures(|result: &Self| {
340
        result.nanoseconds < NANOSECONDS_PER_CENTURY
341
            || result.parts_are_equal(Self::MAX)
342
            || result.parts_are_equal(Self::MIN)
343
    }))]
344
0
    pub fn from_tz_offset(sign: i8, hours: i64, minutes: i64) -> Self {
345
0
        let dur = hours * Unit::Hour + minutes * Unit::Minute;
346
0
        if sign < 0 {
347
0
            -dur
348
        } else {
349
0
            dur
350
        }
351
0
    }
352
}
353
354
impl Duration {
355
    /// Change the value of a [Duration] to its normalized equivalent.
356
824M
    fn normalize(&mut self) {
357
824M
        *self = self.as_normalized();
358
824M
    }
359
360
    /// Return the normalized equivalent of a [Duration].
361
    #[cfg_attr(kani, kani::ensures(|result: &Self| {
362
        result.nanoseconds < NANOSECONDS_PER_CENTURY
363
            || result.parts_are_equal(Self::MAX)
364
            || result.parts_are_equal(Self::MIN)
365
    }))]
366
1.10G
    const fn as_normalized(self) -> Self {
367
1.10G
        let mut normalized_self = self;
368
369
1.10G
        let extra_centuries = self.nanoseconds / NANOSECONDS_PER_CENTURY;
370
371
        // We can skip this whole step if the division shows that we didn't overflow the number of nanoseconds per century
372
1.10G
        if extra_centuries > 0 {
373
305M
            let rem_nanos = self.nanoseconds % NANOSECONDS_PER_CENTURY;
374
375
305M
            if self.centuries == i16::MAX {
376
305M
                if self.nanoseconds.saturating_add(rem_nanos) > Self::MAX.nanoseconds {
377
152M
                    // Saturated max
378
152M
                    normalized_self = Self::MAX;
379
152M
                }
380
                // Else, we're near the MAX but we're within the MAX in nanoseconds, so let's not do anything here.
381
151k
            } else if !self.parts_are_equal(Self::MIN) {
382
                // The bounds are valid as is, no wrapping needed when rem_nanos is not zero.
383
151k
                match self.centuries.checked_add(extra_centuries as i16) {
384
151k
                    Some(centuries) => {
385
151k
                        normalized_self.centuries = centuries;
386
151k
                        normalized_self.nanoseconds = rem_nanos;
387
151k
                    }
388
                    None => {
389
0
                        if self.centuries >= 0 {
390
0
                            // Saturated max again
391
0
                            normalized_self = Self::MAX;
392
0
                        } else {
393
0
                            // Saturated min
394
0
                            normalized_self = Self::MIN;
395
0
                        }
396
                    }
397
                }
398
0
            }
399
794M
        }
400
401
1.10G
        normalized_self
402
1.10G
    }
403
404
    /// `const`-compatible equality check between `self` and `other`.
405
    ///
406
    /// Note that this only checks whether the fields of `self` and `other` are
407
    /// the same, not whether they would (if [`Self::normalize`]d) represent
408
    /// the same duration.
409
151k
    const fn parts_are_equal(&self, other: Duration) -> bool {
410
151k
        self.centuries == other.centuries && self.nanoseconds == other.nanoseconds
411
151k
    }
412
413
    #[must_use]
414
    /// Returns the centuries and nanoseconds of this duration
415
    /// NOTE: These items are not public to prevent incorrect durations from being created by modifying the values of the structure directly.
416
    #[cfg_attr(kani, kani::ensures(|result| result.0 == self.centuries && result.1 == self.nanoseconds))]
417
71.9k
    pub const fn to_parts(&self) -> (i16, u64) {
418
71.9k
        (self.centuries, self.nanoseconds)
419
71.9k
    }
420
421
    /// Returns the total nanoseconds in a signed 128 bit integer
422
    #[must_use]
423
    #[cfg_attr(kani, kani::ensures(|result| {
424
        *result == i128::from(self.centuries) * i128::from(NANOSECONDS_PER_CENTURY)
425
                 + i128::from(self.nanoseconds)
426
    }))]
427
267k
    pub fn total_nanoseconds(&self) -> i128 {
428
267k
        i128::from(self.centuries) * i128::from(NANOSECONDS_PER_CENTURY)
429
267k
            + i128::from(self.nanoseconds)
430
267k
    }
431
432
    /// Returns the truncated nanoseconds in a signed 64 bit integer, if the duration fits.
433
0
    pub fn try_truncated_nanoseconds(&self) -> Result<i64, HifitimeError> {
434
0
        let total = self.total_nanoseconds();
435
0
        if total > i64::MAX as i128 {
436
0
            Err(HifitimeError::Duration {
437
0
                source: DurationError::Overflow,
438
0
            })
439
0
        } else if total < i64::MIN as i128 {
440
0
            Err(HifitimeError::Duration {
441
0
                source: DurationError::Underflow,
442
0
            })
443
        } else {
444
0
            Ok(total as i64)
445
        }
446
0
    }
447
448
    /// Returns the truncated nanoseconds in a signed 64 bit integer, if the duration fits.
449
    /// WARNING: This function will NOT fail and will return the i64::MIN or i64::MAX depending on
450
    /// the sign of the centuries if the Duration does not fit on aa i64
451
    #[must_use]
452
0
    pub fn truncated_nanoseconds(&self) -> i64 {
453
0
        match self.try_truncated_nanoseconds() {
454
0
            Ok(val) => val,
455
            Err(_) => {
456
0
                if self.centuries < 0 {
457
0
                    i64::MIN
458
                } else {
459
0
                    i64::MAX
460
                }
461
            }
462
        }
463
0
    }
464
465
    /// Returns this duration in seconds f64.
466
    /// For high fidelity comparisons, it is recommended to keep using the Duration structure.
467
    #[must_use]
468
    #[cfg_attr(kani, kani::ensures(|result: &f64| result.is_finite() && result.abs() < 1.1e14))]
469
1.69M
    pub fn to_seconds(&self) -> f64 {
470
        // Compute the seconds and nanoseconds that we know this fits on a 64bit float
471
1.69M
        let seconds = self.nanoseconds.div_euclid(NANOSECONDS_PER_SECOND);
472
1.69M
        let subseconds = self.nanoseconds.rem_euclid(NANOSECONDS_PER_SECOND);
473
1.69M
        if self.centuries == 0 {
474
1.58M
            (seconds as f64) + (subseconds as f64) * 1e-9
475
        } else {
476
108k
            f64::from(self.centuries) * SECONDS_PER_CENTURY
477
108k
                + (seconds as f64)
478
108k
                + (subseconds as f64) * 1e-9
479
        }
480
1.69M
    }
481
482
    #[must_use]
483
377k
    pub fn to_unit(&self, unit: Unit) -> f64 {
484
377k
        self.to_seconds() * unit.from_seconds()
485
377k
    }
486
487
    /// Returns the absolute value of this duration
488
    #[must_use]
489
    #[cfg_attr(kani, kani::ensures(|result: &Self| !result.centuries.is_negative()))]
490
96.2k
    pub fn abs(&self) -> Self {
491
96.2k
        if self.centuries.is_negative() {
492
2.14k
            -*self
493
        } else {
494
94.0k
            *self
495
        }
496
96.2k
    }
497
498
    /// Returns the sign of this duration
499
    /// + 0 if the number is zero
500
    /// + 1 if the number is positive
501
    /// + -1 if the number is negative
502
    #[must_use]
503
    #[cfg_attr(kani, kani::ensures(|result| *result == -1 || *result == 0 || *result == 1))]
504
98.3k
    pub const fn signum(&self) -> i8 {
505
98.3k
        self.centuries.signum() as i8
506
98.3k
    }
507
508
    /// Decomposes a Duration in its sign, days, hours, minutes, seconds, ms, us, ns
509
    #[must_use]
510
    #[cfg_attr(kani, kani::ensures(|result: &(i8, u64, u64, u64, u64, u64, u64, u64)| {
511
        let (sign, _days, hours, minutes, seconds, ms, us, ns) = *result;
512
        (sign == -1 || sign == 0 || sign == 1)
513
            && hours < 24
514
            && minutes < 60
515
            && seconds < 60
516
            && ms < 1000
517
            && us < 1000
518
            && ns < 1000
519
    }))]
520
53.8k
    pub fn decompose(&self) -> (i8, u64, u64, u64, u64, u64, u64, u64) {
521
53.8k
        let mut me = *self;
522
53.8k
        let sign = me.signum();
523
53.8k
        me = me.abs();
524
53.8k
        let days = me.to_unit(Unit::Day).floor();
525
53.8k
        me -= days.days();
526
53.8k
        let hours = me.to_unit(Unit::Hour).floor();
527
53.8k
        me -= hours.hours();
528
53.8k
        let minutes = me.to_unit(Unit::Minute).floor();
529
53.8k
        me -= minutes.minutes();
530
53.8k
        let seconds = me.to_unit(Unit::Second).floor();
531
53.8k
        me -= seconds.seconds();
532
53.8k
        let milliseconds = me.to_unit(Unit::Millisecond).floor();
533
53.8k
        me -= milliseconds.milliseconds();
534
53.8k
        let microseconds = me.to_unit(Unit::Microsecond).floor();
535
53.8k
        me -= microseconds.microseconds();
536
53.8k
        let nanoseconds = me.to_unit(Unit::Nanosecond).round();
537
538
        // Everything should fit in the expected types now
539
53.8k
        (
540
53.8k
            sign,
541
53.8k
            days as u64,
542
53.8k
            hours as u64,
543
53.8k
            minutes as u64,
544
53.8k
            seconds as u64,
545
53.8k
            milliseconds as u64,
546
53.8k
            microseconds as u64,
547
53.8k
            nanoseconds as u64,
548
53.8k
        )
549
53.8k
    }
550
551
    /// Returns the subdivision of duration in this unit, if such is available. Does not work with Week or Century.
552
    ///
553
    /// # Example
554
    /// ```
555
    /// use hifitime::{Duration, TimeUnits, Unit};
556
    ///
557
    /// let two_hours_three_min = 2.hours() + 3.minutes();
558
    /// assert_eq!(two_hours_three_min.subdivision(Unit::Hour), Some(2.hours()));
559
    /// assert_eq!(two_hours_three_min.subdivision(Unit::Minute), Some(3.minutes()));
560
    /// assert_eq!(two_hours_three_min.subdivision(Unit::Second), Some(Duration::ZERO));
561
    /// assert_eq!(two_hours_three_min.subdivision(Unit::Week), None);
562
    /// ```
563
    #[must_use]
564
    #[cfg_attr(kani, kani::ensures(|result: &Option<Duration>| {
565
        match result {
566
            Some(d) => {
567
                d.nanoseconds < NANOSECONDS_PER_CENTURY
568
                    || d.parts_are_equal(Self::MAX)
569
                    || d.parts_are_equal(Self::MIN)
570
            }
571
            None => true,
572
        }
573
    }))]
574
49.3k
    pub fn subdivision(&self, unit: Unit) -> Option<Duration> {
575
49.3k
        let (_, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds) =
576
49.3k
            self.decompose();
577
578
49.3k
        match unit {
579
0
            Unit::Nanosecond => Some((nanoseconds as i64) * unit),
580
0
            Unit::Microsecond => Some((microseconds as i64) * unit),
581
0
            Unit::Millisecond => Some((milliseconds as i64) * unit),
582
49.3k
            Unit::Second => Some((seconds as i64) * unit),
583
0
            Unit::Minute => Some((minutes as i64) * unit),
584
0
            Unit::Hour => Some((hours as i64) * unit),
585
0
            Unit::Day => Some((days as i64) * unit),
586
0
            Unit::Week | Unit::Century => None,
587
        }
588
49.3k
    }
589
590
    /// Floors this duration to the closest duration from the bottom
591
    ///
592
    /// # Example
593
    /// ```
594
    /// use hifitime::{Duration, TimeUnits};
595
    ///
596
    /// let two_hours_three_min = 2.hours() + 3.minutes();
597
    /// assert_eq!(two_hours_three_min.floor(1.hours()), 2.hours());
598
    /// assert_eq!(two_hours_three_min.floor(30.minutes()), 2.hours());
599
    /// // This is zero because we floor by a duration longer than the current duration, rounding it down
600
    /// assert_eq!(two_hours_three_min.floor(4.hours()), 0.hours());
601
    /// assert_eq!(two_hours_three_min.floor(1.seconds()), two_hours_three_min);
602
    /// assert_eq!(two_hours_three_min.floor(1.hours() + 1.minutes()), 2.hours() + 2.minutes());
603
    /// assert_eq!(two_hours_three_min.floor(1.hours() + 5.minutes()), 1.hours() + 5.minutes());
604
    /// ```
605
    #[cfg_attr(kani, kani::ensures(|result: &Self| {
606
        result.nanoseconds < NANOSECONDS_PER_CENTURY
607
            || result.parts_are_equal(Self::MAX)
608
            || result.parts_are_equal(Self::MIN)
609
    }))]
610
42.3k
    pub fn floor(&self, duration: Self) -> Self {
611
42.3k
        Self::from_total_nanoseconds(if duration.total_nanoseconds() == 0 {
612
0
            0
613
        } else {
614
42.3k
            self.total_nanoseconds() - self.total_nanoseconds() % duration.total_nanoseconds()
615
        })
616
42.3k
    }
617
618
    /// Ceils this duration to the closest provided duration
619
    ///
620
    /// This simply floors then adds the requested duration
621
    ///
622
    /// # Example
623
    /// ```
624
    /// use hifitime::{Duration, TimeUnits};
625
    ///
626
    /// let two_hours_three_min = 2.hours() + 3.minutes();
627
    /// assert_eq!(two_hours_three_min.ceil(1.hours()), 3.hours());
628
    /// assert_eq!(two_hours_three_min.ceil(30.minutes()), 2.hours() + 30.minutes());
629
    /// assert_eq!(two_hours_three_min.ceil(4.hours()), 4.hours());
630
    /// assert_eq!(two_hours_three_min.ceil(1.seconds()), two_hours_three_min + 1.seconds());
631
    /// assert_eq!(two_hours_three_min.ceil(1.hours() + 5.minutes()), 2.hours() + 10.minutes());
632
    /// ```
633
21.1k
    pub fn ceil(&self, duration: Self) -> Self {
634
21.1k
        let floored = self.floor(duration);
635
21.1k
        match floored
636
21.1k
            .total_nanoseconds()
637
21.1k
            .checked_add(duration.abs().total_nanoseconds())
638
        {
639
21.1k
            Some(total_ns) => Self::from_total_nanoseconds(total_ns),
640
0
            None => Self::MAX,
641
        }
642
21.1k
    }
643
644
    /// Rounds this duration to the closest provided duration
645
    ///
646
    /// This performs both a `ceil` and `floor` and returns the value which is the closest to current one.
647
    /// # Example
648
    /// ```
649
    /// use hifitime::{Duration, TimeUnits};
650
    ///
651
    /// let two_hours_three_min = 2.hours() + 3.minutes();
652
    /// assert_eq!(two_hours_three_min.round(1.hours()), 2.hours());
653
    /// assert_eq!(two_hours_three_min.round(30.minutes()), 2.hours());
654
    /// assert_eq!(two_hours_three_min.round(4.hours()), 4.hours());
655
    /// assert_eq!(two_hours_three_min.round(1.seconds()), two_hours_three_min);
656
    /// assert_eq!(two_hours_three_min.round(1.hours() + 5.minutes()), 2.hours() + 10.minutes());
657
    /// ```
658
21.1k
    pub fn round(&self, duration: Self) -> Self {
659
21.1k
        let floored = self.floor(duration);
660
21.1k
        let ceiled = self.ceil(duration);
661
21.1k
        if *self - floored < (ceiled - *self).abs() {
662
17.6k
            floored
663
        } else {
664
3.54k
            ceiled
665
        }
666
21.1k
    }
667
668
    /// Rounds this duration to the largest units represented in this duration.
669
    ///
670
    /// This is useful to provide an approximate human duration. Under the hood, this function uses `round`,
671
    /// so the "tipping point" of the rounding is half way to the next increment of the greatest unit.
672
    /// As shown below, one example is that 35 hours and 59 minutes rounds to 1 day, but 36 hours and 1 minute rounds
673
    /// to 2 days because 2 days is closer to 36h 1 min than 36h 1 min is to 1 day.
674
    ///
675
    /// # Example
676
    ///
677
    /// ```
678
    /// use hifitime::{Duration, TimeUnits};
679
    ///
680
    /// assert_eq!((2.hours() + 3.minutes()).approx(), 2.hours());
681
    /// assert_eq!((24.hours() + 3.minutes()).approx(), 1.days());
682
    /// assert_eq!((35.hours() + 59.minutes()).approx(), 1.days());
683
    /// assert_eq!((36.hours() + 1.minutes()).approx(), 2.days());
684
    /// assert_eq!((47.hours() + 3.minutes()).approx(), 2.days());
685
    /// assert_eq!((49.hours() + 3.minutes()).approx(), 2.days());
686
    /// ```
687
0
    pub fn approx(&self) -> Self {
688
0
        let (_, days, hours, minutes, seconds, milli, us, _) = self.decompose();
689
690
0
        let round_to = if days > 0 {
691
0
            1 * Unit::Day
692
0
        } else if hours > 0 {
693
0
            1 * Unit::Hour
694
0
        } else if minutes > 0 {
695
0
            1 * Unit::Minute
696
0
        } else if seconds > 0 {
697
0
            1 * Unit::Second
698
0
        } else if milli > 0 {
699
0
            1 * Unit::Millisecond
700
0
        } else if us > 0 {
701
0
            1 * Unit::Microsecond
702
        } else {
703
0
            1 * Unit::Nanosecond
704
        };
705
706
0
        self.round(round_to)
707
0
    }
708
709
    // Returns the minimum of the two durations.
710
    ///
711
    /// ```
712
    /// use hifitime::TimeUnits;
713
    ///
714
    /// let d0 = 20.seconds();
715
    /// let d1 = 21.seconds();
716
    ///
717
    /// assert_eq!(d0, d1.min(d0));
718
    /// assert_eq!(d0, d0.min(d1));
719
    /// ```
720
    #[cfg_attr(kani, kani::ensures(|result: &Self| *result <= self && *result <= other && (*result == self || *result == other)))]
721
0
    pub fn min(self, other: Self) -> Self {
722
0
        if self < other {
723
0
            self
724
        } else {
725
0
            other
726
        }
727
0
    }
728
729
    /// Returns the maximum of the two durations.
730
    ///
731
    /// ```
732
    /// use hifitime::TimeUnits;
733
    ///
734
    /// let d0 = 20.seconds();
735
    /// let d1 = 21.seconds();
736
    ///
737
    /// assert_eq!(d1, d1.max(d0));
738
    /// assert_eq!(d1, d0.max(d1));
739
    /// ```
740
    #[cfg_attr(kani, kani::ensures(|result: &Self| *result >= self && *result >= other && (*result == self || *result == other)))]
741
0
    pub fn max(self, other: Self) -> Self {
742
0
        if self > other {
743
0
            self
744
        } else {
745
0
            other
746
        }
747
0
    }
748
749
    /// Returns whether this is a negative or positive duration.
750
    #[cfg_attr(kani, kani::ensures(|result| *result == self.centuries.is_negative()))]
751
0
    pub const fn is_negative(&self) -> bool {
752
0
        self.centuries.is_negative()
753
0
    }
754
}
755
756
impl fmt::Display for Duration {
757
    // Prints this duration with automatic selection of the units, i.e. everything that isn't zero is ignored
758
11.6k
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
759
11.6k
        if self.total_nanoseconds() == 0 {
760
7.09k
            write!(f, "0 ns")
761
        } else {
762
4.58k
            let (sign, days, hours, minutes, seconds, milli, us, nano) = self.decompose();
763
4.58k
            if sign == -1 {
764
2.14k
                write!(f, "-")?;
765
2.43k
            }
766
767
4.58k
            let values = [days, hours, minutes, seconds, milli, us, nano];
768
4.58k
            let units = [
769
4.58k
                if days > 1 { "days" } else { "day" },
770
4.58k
                "h",
771
4.58k
                "min",
772
4.58k
                "s",
773
4.58k
                "ms",
774
4.58k
                "μs",
775
4.58k
                "ns",
776
            ];
777
778
4.58k
            let mut insert_space = false;
779
32.0k
            for (val, unit) in values.iter().zip(units.iter()) {
780
32.0k
                if *val > 0 {
781
7.91k
                    if insert_space {
782
3.33k
                        write!(f, " ")?;
783
4.58k
                    }
784
7.91k
                    write!(f, "{val} {unit}")?;
785
7.91k
                    insert_space = true;
786
24.1k
                }
787
            }
788
4.58k
            Ok(())
789
        }
790
11.6k
    }
791
}
792
793
impl fmt::LowerExp for Duration {
794
    // Prints the duration with appropriate units
795
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
796
0
        let seconds_f64 = self.to_seconds();
797
0
        let seconds_f64_abs = seconds_f64.abs();
798
0
        if seconds_f64_abs < 1e-5 {
799
0
            fmt::Display::fmt(&(seconds_f64 * 1e9), f)?;
800
0
            write!(f, " ns")
801
0
        } else if seconds_f64_abs < 1e-2 {
802
0
            fmt::Display::fmt(&(seconds_f64 * 1e3), f)?;
803
0
            write!(f, " ms")
804
0
        } else if seconds_f64_abs < 3.0 * SECONDS_PER_MINUTE {
805
0
            fmt::Display::fmt(&(seconds_f64), f)?;
806
0
            write!(f, " s")
807
0
        } else if seconds_f64_abs < SECONDS_PER_HOUR {
808
0
            fmt::Display::fmt(&(seconds_f64 / SECONDS_PER_MINUTE), f)?;
809
0
            write!(f, " min")
810
0
        } else if seconds_f64_abs < SECONDS_PER_DAY {
811
0
            fmt::Display::fmt(&(seconds_f64 / SECONDS_PER_HOUR), f)?;
812
0
            write!(f, " h")
813
        } else {
814
0
            fmt::Display::fmt(&(seconds_f64 / SECONDS_PER_DAY), f)?;
815
0
            write!(f, " days")
816
        }
817
0
    }
818
}
819
820
impl PartialEq<Unit> for Duration {
821
    #[allow(clippy::identity_op)]
822
0
    fn eq(&self, unit: &Unit) -> bool {
823
0
        *self == *unit * 1
824
0
    }
825
}
826
827
impl PartialOrd<Unit> for Duration {
828
    #[allow(clippy::identity_op, clippy::comparison_chain)]
829
0
    fn partial_cmp(&self, unit: &Unit) -> Option<Ordering> {
830
0
        let unit_deref = *unit;
831
0
        let unit_as_duration: Duration = unit_deref * 1;
832
0
        if self < &unit_as_duration {
833
0
            Some(Ordering::Less)
834
0
        } else if self > &unit_as_duration {
835
0
            Some(Ordering::Greater)
836
        } else {
837
0
            Some(Ordering::Equal)
838
        }
839
0
    }
840
}
841
842
impl From<Duration> for core::time::Duration {
843
    /// Converts a [`Duration`] into a [`core::time::Duration`]
844
    ///
845
    /// # Limitations
846
    /// 1. If the [`Duration`] is negative, this will return a [`core::time::Duration::ZERO`].
847
    /// 2. If the [`Duration`] is [`Duration::MAX`], this will return the equivalent of [`core::time::Duration::from_secs(103407943680000)`]
848
0
    fn from(hf_duration: Duration) -> Self {
849
        use crate::NANOSECONDS_PER_SECOND;
850
0
        if hf_duration.signum().is_negative() {
851
0
            core::time::Duration::ZERO
852
        } else {
853
0
            let unsigned_nanos = hf_duration.total_nanoseconds() as u128;
854
0
            let secs: u64 = (unsigned_nanos / NANOSECONDS_PER_SECOND as u128)
855
0
                .try_into()
856
0
                .unwrap_or(u64::MAX);
857
0
            let subsec_nanos = (unsigned_nanos % NANOSECONDS_PER_SECOND as u128) as u32;
858
859
0
            core::time::Duration::new(secs, subsec_nanos)
860
        }
861
0
    }
862
}
863
864
impl From<core::time::Duration> for Duration {
865
    /// Converts a [`core::time::Duration`] into a [`Duration`]
866
    ///
867
    /// # Limitations
868
    /// 1. If the [`core::time::Duration`] is larger than [`Duration::MAX`], this will return [`Duration::MAX`]
869
23.9k
    fn from(core_duration: core::time::Duration) -> Self {
870
23.9k
        Duration::from_total_nanoseconds(core_duration.as_nanos() as i128)
871
23.9k
    }
872
}
873
874
#[cfg(test)]
875
mod ut_duration {
876
    use super::{Duration, TimeUnits, Unit, NANOSECONDS_PER_CENTURY};
877
878
    #[test]
879
    #[cfg(feature = "serde")]
880
    fn test_serdes() {
881
        for (dt, content) in [
882
            (Duration::from_seconds(10.1), r#""10 s 100 ms""#),
883
            (1.0_f64.days() + 99.nanoseconds(), r#""1 day 99 ns""#),
884
            (
885
                1.0_f64.centuries() + 99.seconds(),
886
                r#""36525 days 1 min 39 s""#,
887
            ),
888
        ] {
889
            assert_eq!(content, serde_json::to_string(&dt).unwrap());
890
            let parsed: Duration = serde_json::from_str(content).unwrap();
891
            assert_eq!(dt, parsed);
892
        }
893
    }
894
895
    #[test]
896
    fn test_bounds() {
897
        let min = Duration::MIN;
898
        assert_eq!(min.centuries, i16::MIN);
899
        assert_eq!(min.nanoseconds, 0);
900
901
        let max = Duration::MAX;
902
        assert_eq!(max.centuries, i16::MAX);
903
        assert_eq!(max.nanoseconds, NANOSECONDS_PER_CENTURY);
904
905
        let min_p = Duration::MIN_POSITIVE;
906
        assert_eq!(min_p.centuries, 0);
907
        assert_eq!(min_p.nanoseconds, 1);
908
909
        let min_n = Duration::MIN_NEGATIVE;
910
        assert_eq!(min_n.centuries, -1);
911
        assert_eq!(min_n.nanoseconds, NANOSECONDS_PER_CENTURY - 1);
912
913
        let min_n1 = Duration::MIN - 1 * Unit::Nanosecond;
914
        assert_eq!(min_n1, Duration::MIN);
915
916
        let max_n1 = Duration::MAX - 1 * Unit::Nanosecond;
917
        assert_eq!(max_n1.centuries, i16::MAX);
918
        assert_eq!(max_n1.nanoseconds, NANOSECONDS_PER_CENTURY - 1);
919
    }
920
921
    #[test]
922
    fn test_decompose() {
923
        let d = -73000.days();
924
        let out_days = d.to_unit(Unit::Day);
925
        assert_eq!(out_days, -73000.0);
926
        let (sign, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds) =
927
            d.decompose();
928
        assert_eq!(sign, -1);
929
        assert_eq!(days, 73000);
930
        assert_eq!(hours, 0);
931
        assert_eq!(minutes, 0);
932
        assert_eq!(seconds, 0);
933
        assert_eq!(milliseconds, 0);
934
        assert_eq!(microseconds, 0);
935
        assert_eq!(nanoseconds, 0);
936
    }
937
938
    #[test]
939
    fn test_conversion() {
940
        let d = Duration::MIN;
941
        let core_d: core::time::Duration = d.into();
942
        assert_eq!(core_d, core::time::Duration::ZERO);
943
944
        let d = Duration::MAX;
945
        let core_d: core::time::Duration = d.into();
946
        assert_eq!(core_d, core::time::Duration::from_secs(103407943680000));
947
    }
948
}