1use zerodds_cdr::{BufferReader, BufferWriter, DecodeError, EncodeError};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub struct Duration {
14 pub seconds: i32,
16 pub fraction: u32,
18}
19
20impl Duration {
21 pub const INFINITE: Self = Self {
23 seconds: i32::MAX,
24 fraction: u32::MAX,
25 };
26
27 pub const ZERO: Self = Self {
29 seconds: 0,
30 fraction: 0,
31 };
32
33 #[must_use]
35 pub const fn from_secs(seconds: i32) -> Self {
36 Self {
37 seconds,
38 fraction: 0,
39 }
40 }
41
42 #[must_use]
55 pub const fn from_millis(ms: i32) -> Self {
56 let seconds = ms.div_euclid(1000);
57 let remainder_ms = ms.rem_euclid(1000) as u32; let fraction = ((remainder_ms as u64 * (1u64 << 32)) / 1000) as u32;
59 Self { seconds, fraction }
60 }
61
62 #[must_use]
67 pub const fn from_micros(us: i32) -> Self {
68 let seconds = us.div_euclid(1_000_000);
69 let remainder_us = us.rem_euclid(1_000_000) as u32; let fraction = ((remainder_us as u64 * (1u64 << 32)) / 1_000_000) as u32;
71 Self { seconds, fraction }
72 }
73
74 #[must_use]
76 pub const fn is_infinite(self) -> bool {
77 self.seconds == i32::MAX && self.fraction == u32::MAX
78 }
79
80 #[must_use]
84 pub const fn to_nanos(self) -> u128 {
85 if self.is_infinite() {
86 return u128::MAX;
87 }
88 if self.seconds < 0 {
89 return 0;
90 }
91 let secs = self.seconds as u128;
92 let frac_nanos = (self.fraction as u128 * 1_000_000_000) >> 32;
94 secs * 1_000_000_000 + frac_nanos
95 }
96
97 #[must_use]
99 pub const fn is_zero(self) -> bool {
100 self.seconds == 0 && self.fraction == 0
101 }
102
103 pub fn encode_into(self, w: &mut BufferWriter) -> Result<(), EncodeError> {
108 w.write_u32(self.seconds as u32)?;
109 w.write_u32(self.fraction)
110 }
111
112 pub fn decode_from(r: &mut BufferReader<'_>) -> Result<Self, DecodeError> {
117 let seconds = r.read_u32()? as i32;
118 let fraction = r.read_u32()?;
119 Ok(Self { seconds, fraction })
120 }
121
122 #[must_use]
124 pub fn to_bytes_le(self) -> [u8; 8] {
125 let mut out = [0u8; 8];
126 out[..4].copy_from_slice(&self.seconds.to_le_bytes());
127 out[4..].copy_from_slice(&self.fraction.to_le_bytes());
128 out
129 }
130
131 #[must_use]
133 pub fn to_bytes_be(self) -> [u8; 8] {
134 let mut out = [0u8; 8];
135 out[..4].copy_from_slice(&self.seconds.to_be_bytes());
136 out[4..].copy_from_slice(&self.fraction.to_be_bytes());
137 out
138 }
139
140 #[must_use]
142 pub fn from_bytes_le(bytes: [u8; 8]) -> Self {
143 let seconds = i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
144 let fraction = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
145 Self { seconds, fraction }
146 }
147}
148
149impl Default for Duration {
150 fn default() -> Self {
151 Self::ZERO
152 }
153}
154
155#[cfg(test)]
156#[allow(clippy::unwrap_used)]
157mod tests {
158 use super::*;
159 use zerodds_cdr::Endianness;
160
161 #[test]
162 fn infinite_constant_matches_spec() {
163 assert_eq!(Duration::INFINITE.seconds, i32::MAX);
164 assert_eq!(Duration::INFINITE.fraction, u32::MAX);
165 assert!(Duration::INFINITE.is_infinite());
166 }
167
168 #[test]
169 fn zero_is_default_and_zero() {
170 assert_eq!(Duration::default(), Duration::ZERO);
171 assert!(Duration::ZERO.is_zero());
172 }
173
174 #[test]
175 fn from_secs_has_zero_fraction() {
176 let d = Duration::from_secs(42);
177 assert_eq!(d.seconds, 42);
178 assert_eq!(d.fraction, 0);
179 }
180
181 #[test]
182 fn from_millis_splits_correctly() {
183 let d = Duration::from_millis(1500);
184 assert_eq!(d.seconds, 1);
185 assert_eq!(d.fraction, 2_147_483_648);
187 }
188
189 #[test]
190 fn from_micros_keeps_sub_millisecond_precision() {
191 let d = Duration::from_micros(1_500_000);
193 assert_eq!(d.seconds, 1);
194 assert_eq!(d.fraction, 2_147_483_648);
195 let half_ms = Duration::from_micros(500);
197 assert_eq!(half_ms.seconds, 0);
198 assert!(half_ms.fraction > 0);
199 assert_eq!(
201 half_ms.fraction,
202 ((500u64 * (1u64 << 32)) / 1_000_000) as u32
203 );
204 }
205
206 #[test]
207 fn encode_decode_roundtrip() {
208 let d = Duration {
209 seconds: 7,
210 fraction: 0xCAFE_BABE,
211 };
212 let mut w = BufferWriter::new(Endianness::Little);
213 d.encode_into(&mut w).unwrap();
214 let bytes = w.into_bytes();
215 assert_eq!(bytes.len(), 8);
216 let mut r = BufferReader::new(&bytes, Endianness::Little);
217 let back = Duration::decode_from(&mut r).unwrap();
218 assert_eq!(back, d);
219 }
220
221 #[test]
222 fn to_from_bytes_le_roundtrip() {
223 let d = Duration {
224 seconds: -3,
225 fraction: 0xDEAD_BEEF,
226 };
227 let bytes = d.to_bytes_le();
228 let back = Duration::from_bytes_le(bytes);
229 assert_eq!(back, d);
230 }
231
232 #[test]
233 fn ord_compares_seconds_then_fraction() {
234 let a = Duration {
235 seconds: 1,
236 fraction: 0,
237 };
238 let b = Duration {
239 seconds: 1,
240 fraction: 1,
241 };
242 let c = Duration {
243 seconds: 2,
244 fraction: 0,
245 };
246 assert!(a < b);
247 assert!(b < c);
248 }
249}