Skip to main content

cbor_core/
epoch_time.rs

1use std::time::{Duration, SystemTime};
2
3use crate::{Error, Result, Tag, Value};
4
5#[derive(Debug, Clone, Copy, PartialEq)]
6enum Inner {
7    Int(u64),
8    Float(f64),
9}
10
11/// Helper for validated epoch time construction.
12///
13/// Wraps a non-negative integer or finite float in the range 0 to
14/// 253402300799, as required by the CBOR::Core draft. Implements
15/// `TryFrom` for all integer and float primitives as well as
16/// [`SystemTime`], so that [`Value::epoch_time`] can accept all of
17/// these through a single `TryInto<EpochTime>` bound.
18///
19/// Whole-second values are stored as integers; sub-second values
20/// (from floats or `SystemTime` with nanoseconds) are stored as
21/// floats. Converting to [`Value`] produces a tag 1 wrapper.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
23pub struct EpochTime(Inner);
24
25impl Eq for Inner {} // our f64 is always finite
26
27impl Ord for Inner {
28    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
29        match (self, other) {
30            (Self::Int(a), Self::Int(b)) => a.cmp(b),
31            (Self::Float(a), Self::Float(b)) => a.total_cmp(b),
32            // Max epoch value (253402300799) fits in 38 bits, well within f64's
33            // 52-bit mantissa, so the cast is always exact.
34            (Self::Int(a), Self::Float(b)) => (*a as f64).total_cmp(b),
35            (Self::Float(a), Self::Int(b)) => a.total_cmp(&(*b as f64)),
36        }
37    }
38}
39
40impl PartialOrd for Inner {
41    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
42        Some(self.cmp(other))
43    }
44}
45
46impl std::hash::Hash for Inner {
47    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
48        core::mem::discriminant(self).hash(state);
49        match self {
50            Inner::Int(x) => x.hash(state),
51            Inner::Float(x) => x.to_bits().hash(state),
52        }
53    }
54}
55
56impl From<EpochTime> for Value {
57    fn from(value: EpochTime) -> Self {
58        match value.0 {
59            Inner::Int(int) => Self::tag(Tag::EPOCH_TIME, int),
60            Inner::Float(float) => Self::tag(Tag::EPOCH_TIME, float),
61        }
62    }
63}
64
65impl TryFrom<SystemTime> for EpochTime {
66    type Error = Error;
67
68    fn try_from(value: SystemTime) -> Result<Self> {
69        let time = value
70            .duration_since(SystemTime::UNIX_EPOCH)
71            .or(Err(Error::InvalidValue))?;
72
73        if time > Duration::from_secs(253402300799) {
74            Err(Error::InvalidValue)
75        } else if time.subsec_nanos() == 0 {
76            Ok(Self(Inner::Int(time.as_secs())))
77        } else {
78            Ok(Self(Inner::Float(time.as_secs_f64())))
79        }
80    }
81}
82
83fn from_int<T: TryInto<u64>>(value: T) -> Result<EpochTime> {
84    let value = value.try_into().or(Err(Error::InvalidValue))?;
85
86    if (0..=253402300799).contains(&value) {
87        Ok(EpochTime(Inner::Int(value)))
88    } else {
89        Err(Error::InvalidValue)
90    }
91}
92
93impl TryFrom<u8> for EpochTime {
94    type Error = Error;
95    fn try_from(value: u8) -> Result<Self> {
96        from_int(value)
97    }
98}
99impl TryFrom<u16> for EpochTime {
100    type Error = Error;
101    fn try_from(value: u16) -> Result<Self> {
102        from_int(value)
103    }
104}
105impl TryFrom<u32> for EpochTime {
106    type Error = Error;
107    fn try_from(value: u32) -> Result<Self> {
108        from_int(value)
109    }
110}
111impl TryFrom<u64> for EpochTime {
112    type Error = Error;
113    fn try_from(value: u64) -> Result<Self> {
114        from_int(value)
115    }
116}
117impl TryFrom<u128> for EpochTime {
118    type Error = Error;
119    fn try_from(value: u128) -> Result<Self> {
120        from_int(value)
121    }
122}
123impl TryFrom<usize> for EpochTime {
124    type Error = Error;
125    fn try_from(value: usize) -> Result<Self> {
126        from_int(value)
127    }
128}
129impl TryFrom<i8> for EpochTime {
130    type Error = Error;
131    fn try_from(value: i8) -> Result<Self> {
132        from_int(value)
133    }
134}
135impl TryFrom<i16> for EpochTime {
136    type Error = Error;
137    fn try_from(value: i16) -> Result<Self> {
138        from_int(value)
139    }
140}
141impl TryFrom<i32> for EpochTime {
142    type Error = Error;
143    fn try_from(value: i32) -> Result<Self> {
144        from_int(value)
145    }
146}
147impl TryFrom<i64> for EpochTime {
148    type Error = Error;
149    fn try_from(value: i64) -> Result<Self> {
150        from_int(value)
151    }
152}
153impl TryFrom<i128> for EpochTime {
154    type Error = Error;
155    fn try_from(value: i128) -> Result<Self> {
156        from_int(value)
157    }
158}
159impl TryFrom<isize> for EpochTime {
160    type Error = Error;
161    fn try_from(value: isize) -> Result<Self> {
162        from_int(value)
163    }
164}
165
166impl TryFrom<f32> for EpochTime {
167    type Error = Error;
168
169    fn try_from(value: f32) -> Result<Self> {
170        Self::try_from(f64::from(value))
171    }
172}
173impl TryFrom<f64> for EpochTime {
174    type Error = Error;
175
176    fn try_from(value: f64) -> Result<Self> {
177        if value.is_finite() && (0.0..=253402300799.0).contains(&value) {
178            Ok(Self(Inner::Float(value)))
179        } else {
180            Err(Error::InvalidValue)
181        }
182    }
183}