pub type TimeT = u64;
pub type InaccuracyT = u64;
pub type TdfT = i16;
pub const UTC_EPOCH_TO_UNIX_TICKS: TimeT = 122_192_928_000_000_000;
pub const TICKS_PER_SECOND: u64 = 10_000_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct UtcT {
pub time: TimeT,
pub inacclo: u32,
pub inacchi: u16,
pub tdf: TdfT,
}
impl UtcT {
#[must_use]
pub const fn new(time: TimeT, inaccuracy: InaccuracyT, tdf: TdfT) -> Self {
let inacc = inaccuracy & 0x0000_FFFF_FFFF_FFFF;
Self {
time,
inacclo: (inacc & 0xFFFF_FFFF) as u32,
inacchi: ((inacc >> 32) & 0xFFFF) as u16,
tdf,
}
}
#[must_use]
pub const fn inaccuracy(self) -> InaccuracyT {
((self.inacchi as u64) << 32) | (self.inacclo as u64)
}
pub const fn set_inaccuracy(&mut self, value: InaccuracyT) {
let v = value & 0x0000_FFFF_FFFF_FFFF;
self.inacclo = (v & 0xFFFF_FFFF) as u32;
self.inacchi = ((v >> 32) & 0xFFFF) as u16;
}
#[must_use]
pub const fn local_time(self) -> TimeT {
let tdf_ticks = (self.tdf as i64) * 600_000_000;
let signed = self.time as i64;
signed.wrapping_add(tdf_ticks) as TimeT
}
#[must_use]
pub fn to_wire(self) -> [u8; 16] {
let mut buf = [0u8; 16];
buf[0..8].copy_from_slice(&self.time.to_le_bytes());
buf[8..12].copy_from_slice(&self.inacclo.to_le_bytes());
buf[12..14].copy_from_slice(&self.inacchi.to_le_bytes());
buf[14..16].copy_from_slice(&self.tdf.to_le_bytes());
buf
}
#[must_use]
pub fn from_wire(buf: [u8; 16]) -> Self {
let mut time_b = [0u8; 8];
time_b.copy_from_slice(&buf[0..8]);
let mut lo = [0u8; 4];
lo.copy_from_slice(&buf[8..12]);
let mut hi = [0u8; 2];
hi.copy_from_slice(&buf[12..14]);
let mut tdf = [0u8; 2];
tdf.copy_from_slice(&buf[14..16]);
Self {
time: TimeT::from_le_bytes(time_b),
inacclo: u32::from_le_bytes(lo),
inacchi: u16::from_le_bytes(hi),
tdf: TdfT::from_le_bytes(tdf),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct IntervalT {
pub lower_bound: TimeT,
pub upper_bound: TimeT,
}
impl IntervalT {
#[must_use]
pub const fn new(lower_bound: TimeT, upper_bound: TimeT) -> Option<Self> {
if lower_bound > upper_bound {
None
} else {
Some(Self {
lower_bound,
upper_bound,
})
}
}
#[must_use]
pub fn to_wire(self) -> [u8; 16] {
let mut buf = [0u8; 16];
buf[0..8].copy_from_slice(&self.lower_bound.to_le_bytes());
buf[8..16].copy_from_slice(&self.upper_bound.to_le_bytes());
buf
}
#[must_use]
pub fn from_wire(buf: [u8; 16]) -> Self {
let mut lo = [0u8; 8];
lo.copy_from_slice(&buf[0..8]);
let mut hi = [0u8; 8];
hi.copy_from_slice(&buf[8..16]);
Self {
lower_bound: TimeT::from_le_bytes(lo),
upper_bound: TimeT::from_le_bytes(hi),
}
}
}
#[cfg(feature = "std")]
#[must_use]
pub fn current_time() -> TimeT {
use std::time::{SystemTime, UNIX_EPOCH};
match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(d) => {
let nanos = d.as_secs() as u128 * 1_000_000_000 + d.subsec_nanos() as u128;
let ticks_since_unix = (nanos / 100) as u64;
UTC_EPOCH_TO_UNIX_TICKS.wrapping_add(ticks_since_unix)
}
Err(_) => 0,
}
}
#[cfg(not(feature = "std"))]
#[must_use]
pub fn current_time() -> TimeT {
0
}
#[cfg(test)]
#[allow(clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn utct_size_is_16_octets() {
let utc = UtcT::new(0x0123_4567_89AB_CDEF, 0xFFFF_FFFF_FFFF, 60);
let wire = utc.to_wire();
assert_eq!(wire.len(), 16);
}
#[test]
fn intervalt_size_is_16_octets() {
let i = IntervalT::new(0, 0).expect("ok");
let wire = i.to_wire();
assert_eq!(wire.len(), 16);
}
#[test]
fn inaccuracy_caps_at_48_bits() {
let utc = UtcT::new(0, u64::MAX, 0);
assert_eq!(utc.inaccuracy(), 0x0000_FFFF_FFFF_FFFF);
}
#[test]
fn utct_wire_roundtrip_preserves_all_fields() {
let original = UtcT::new(123_456_789_012_345_678, 0xAABB_CCDD_EEFF, -480);
let wire = original.to_wire();
let decoded = UtcT::from_wire(wire);
assert_eq!(decoded, original);
}
#[test]
fn intervalt_wire_roundtrip_preserves_bounds() {
let i = IntervalT::new(100, 200).expect("ok");
let decoded = IntervalT::from_wire(i.to_wire());
assert_eq!(decoded.lower_bound, 100);
assert_eq!(decoded.upper_bound, 200);
}
#[test]
fn intervalt_rejects_lower_greater_than_upper() {
assert!(IntervalT::new(200, 100).is_none());
}
#[test]
fn local_time_applies_tdf() {
let utc = UtcT::new(1_000_000_000, 0, 60);
let expected = 1_000_000_000_i64 + 60 * 600_000_000;
assert_eq!(utc.local_time(), expected as TimeT);
}
#[test]
fn local_time_negative_tdf_west_of_greenwich() {
let utc = UtcT::new(1_000_000_000_000, 0, -480); let expected = 1_000_000_000_000_i64 + (-480) * 600_000_000;
assert_eq!(utc.local_time(), expected as TimeT);
}
#[cfg(feature = "std")]
#[test]
fn current_time_is_recent_century() {
let t = current_time();
assert!(t > 130_000_000_000_000_000);
assert!(t < 200_000_000_000_000_000);
}
}