use zerodds_cdr::{BufferReader, BufferWriter, DecodeError, EncodeError};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Duration {
pub seconds: i32,
pub fraction: u32,
}
impl Duration {
pub const INFINITE: Self = Self {
seconds: i32::MAX,
fraction: u32::MAX,
};
pub const ZERO: Self = Self {
seconds: 0,
fraction: 0,
};
#[must_use]
pub const fn from_secs(seconds: i32) -> Self {
Self {
seconds,
fraction: 0,
}
}
#[must_use]
pub const fn from_millis(ms: i32) -> Self {
let seconds = ms.div_euclid(1000);
let remainder_ms = ms.rem_euclid(1000) as u32; let fraction = ((remainder_ms as u64 * (1u64 << 32)) / 1000) as u32;
Self { seconds, fraction }
}
#[must_use]
pub const fn is_infinite(self) -> bool {
self.seconds == i32::MAX && self.fraction == u32::MAX
}
#[must_use]
pub const fn to_nanos(self) -> u128 {
if self.is_infinite() {
return u128::MAX;
}
if self.seconds < 0 {
return 0;
}
let secs = self.seconds as u128;
let frac_nanos = (self.fraction as u128 * 1_000_000_000) >> 32;
secs * 1_000_000_000 + frac_nanos
}
#[must_use]
pub const fn is_zero(self) -> bool {
self.seconds == 0 && self.fraction == 0
}
pub fn encode_into(self, w: &mut BufferWriter) -> Result<(), EncodeError> {
w.write_u32(self.seconds as u32)?;
w.write_u32(self.fraction)
}
pub fn decode_from(r: &mut BufferReader<'_>) -> Result<Self, DecodeError> {
let seconds = r.read_u32()? as i32;
let fraction = r.read_u32()?;
Ok(Self { seconds, fraction })
}
#[must_use]
pub fn to_bytes_le(self) -> [u8; 8] {
let mut out = [0u8; 8];
out[..4].copy_from_slice(&self.seconds.to_le_bytes());
out[4..].copy_from_slice(&self.fraction.to_le_bytes());
out
}
#[must_use]
pub fn from_bytes_le(bytes: [u8; 8]) -> Self {
let seconds = i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
let fraction = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
Self { seconds, fraction }
}
}
impl Default for Duration {
fn default() -> Self {
Self::ZERO
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use zerodds_cdr::Endianness;
#[test]
fn infinite_constant_matches_spec() {
assert_eq!(Duration::INFINITE.seconds, i32::MAX);
assert_eq!(Duration::INFINITE.fraction, u32::MAX);
assert!(Duration::INFINITE.is_infinite());
}
#[test]
fn zero_is_default_and_zero() {
assert_eq!(Duration::default(), Duration::ZERO);
assert!(Duration::ZERO.is_zero());
}
#[test]
fn from_secs_has_zero_fraction() {
let d = Duration::from_secs(42);
assert_eq!(d.seconds, 42);
assert_eq!(d.fraction, 0);
}
#[test]
fn from_millis_splits_correctly() {
let d = Duration::from_millis(1500);
assert_eq!(d.seconds, 1);
assert_eq!(d.fraction, 2_147_483_648);
}
#[test]
fn encode_decode_roundtrip() {
let d = Duration {
seconds: 7,
fraction: 0xCAFE_BABE,
};
let mut w = BufferWriter::new(Endianness::Little);
d.encode_into(&mut w).unwrap();
let bytes = w.into_bytes();
assert_eq!(bytes.len(), 8);
let mut r = BufferReader::new(&bytes, Endianness::Little);
let back = Duration::decode_from(&mut r).unwrap();
assert_eq!(back, d);
}
#[test]
fn to_from_bytes_le_roundtrip() {
let d = Duration {
seconds: -3,
fraction: 0xDEAD_BEEF,
};
let bytes = d.to_bytes_le();
let back = Duration::from_bytes_le(bytes);
assert_eq!(back, d);
}
#[test]
fn ord_compares_seconds_then_fraction() {
let a = Duration {
seconds: 1,
fraction: 0,
};
let b = Duration {
seconds: 1,
fraction: 1,
};
let c = Duration {
seconds: 2,
fraction: 0,
};
assert!(a < b);
assert!(b < c);
}
}