crux_time/
duration.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
use serde::{Deserialize, Serialize};

use crate::{error::TimeResult, TimeError};

/// The number of nanoseconds in seconds.
pub(crate) const NANOS_PER_SEC: u32 = 1_000_000_000;
/// The number of nanoseconds in a millisecond.
const NANOS_PER_MILLI: u32 = 1_000_000;

/// Represents a duration of time, internally stored as nanoseconds
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Duration {
    nanos: u64,
}

impl Duration {
    /// Create a new `Duration` from the given number of nanoseconds.
    pub fn new(nanos: u64) -> Self {
        Self { nanos }
    }

    /// Create a new `Duration` from the given number of milliseconds.
    ///
    /// Errors with [`TimeError::InvalidDuration`] if the number of milliseconds
    /// would overflow when converted to nanoseconds.
    pub fn from_millis(millis: u64) -> TimeResult<Self> {
        let nanos = millis
            .checked_mul(NANOS_PER_MILLI as u64)
            .ok_or(TimeError::InvalidDuration)?;
        Ok(Self { nanos })
    }

    /// Create a new `Duration` from the given number of seconds.
    ///
    /// Errors with [`TimeError::InvalidDuration`] if the number of seconds
    /// would overflow when converted to nanoseconds.
    pub fn from_secs(seconds: u64) -> TimeResult<Self> {
        let nanos = seconds
            .checked_mul(NANOS_PER_SEC as u64)
            .ok_or(TimeError::InvalidDuration)?;
        Ok(Self { nanos })
    }
}

#[cfg(feature = "chrono")]
impl TryFrom<chrono::TimeDelta> for Duration {
    type Error = TimeError;

    fn try_from(value: chrono::TimeDelta) -> Result<Self, Self::Error> {
        let nanos = value.num_nanoseconds().ok_or(TimeError::InvalidDuration)? as u64;
        Ok(Self { nanos })
    }
}

#[cfg(feature = "chrono")]
impl TryFrom<Duration> for chrono::TimeDelta {
    type Error = TimeError;

    fn try_from(value: Duration) -> Result<Self, Self::Error> {
        let nanos = value
            .nanos
            .try_into()
            .map_err(|_| TimeError::InvalidDuration)?;
        Ok(chrono::TimeDelta::nanoseconds(nanos))
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn duration_from_millis() {
        let duration = Duration::from_millis(1_000).unwrap();
        assert_eq!(duration.nanos, 1_000_000_000);
    }

    #[test]
    fn duration_from_secs() {
        let duration = Duration::from_secs(1).unwrap();
        assert_eq!(duration.nanos, 1_000_000_000);
    }
}

#[cfg(feature = "chrono")]
#[cfg(test)]
mod chrono_test {
    use super::*;

    #[test]
    fn duration_to_timedelta() {
        let duration = Duration::new(1_000_000_000);
        let chrono_duration: chrono::TimeDelta = duration.try_into().unwrap();
        assert_eq!(chrono_duration.num_nanoseconds().unwrap(), 1_000_000_000);
    }

    #[test]
    fn timedelta_to_duration() {
        let chrono_duration = chrono::TimeDelta::nanoseconds(1_000_000_000);
        let duration: Duration = chrono_duration.try_into().unwrap();
        assert_eq!(duration.nanos, 1_000_000_000);
    }
}