zerodds_time_service/
time_base.rs1pub type TimeT = u64;
29
30pub type InaccuracyT = u64;
34
35pub type TdfT = i16;
39
40pub const UTC_EPOCH_TO_UNIX_TICKS: TimeT = 122_192_928_000_000_000;
45
46pub const TICKS_PER_SECOND: u64 = 10_000_000;
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
52pub struct UtcT {
53 pub time: TimeT,
56 pub inacclo: u32,
58 pub inacchi: u16,
60 pub tdf: TdfT,
62}
63
64impl UtcT {
65 #[must_use]
68 pub const fn new(time: TimeT, inaccuracy: InaccuracyT, tdf: TdfT) -> Self {
69 let inacc = inaccuracy & 0x0000_FFFF_FFFF_FFFF;
70 Self {
71 time,
72 inacclo: (inacc & 0xFFFF_FFFF) as u32,
73 inacchi: ((inacc >> 32) & 0xFFFF) as u16,
74 tdf,
75 }
76 }
77
78 #[must_use]
81 pub const fn inaccuracy(self) -> InaccuracyT {
82 ((self.inacchi as u64) << 32) | (self.inacclo as u64)
83 }
84
85 pub const fn set_inaccuracy(&mut self, value: InaccuracyT) {
87 let v = value & 0x0000_FFFF_FFFF_FFFF;
88 self.inacclo = (v & 0xFFFF_FFFF) as u32;
89 self.inacchi = ((v >> 32) & 0xFFFF) as u16;
90 }
91
92 #[must_use]
96 pub const fn local_time(self) -> TimeT {
97 let tdf_ticks = (self.tdf as i64) * 600_000_000;
98 let signed = self.time as i64;
101 signed.wrapping_add(tdf_ticks) as TimeT
102 }
103
104 #[must_use]
107 pub fn to_wire(self) -> [u8; 16] {
108 let mut buf = [0u8; 16];
109 buf[0..8].copy_from_slice(&self.time.to_le_bytes());
110 buf[8..12].copy_from_slice(&self.inacclo.to_le_bytes());
111 buf[12..14].copy_from_slice(&self.inacchi.to_le_bytes());
112 buf[14..16].copy_from_slice(&self.tdf.to_le_bytes());
113 buf
114 }
115
116 #[must_use]
118 pub fn from_wire(buf: [u8; 16]) -> Self {
119 let mut time_b = [0u8; 8];
120 time_b.copy_from_slice(&buf[0..8]);
121 let mut lo = [0u8; 4];
122 lo.copy_from_slice(&buf[8..12]);
123 let mut hi = [0u8; 2];
124 hi.copy_from_slice(&buf[12..14]);
125 let mut tdf = [0u8; 2];
126 tdf.copy_from_slice(&buf[14..16]);
127 Self {
128 time: TimeT::from_le_bytes(time_b),
129 inacclo: u32::from_le_bytes(lo),
130 inacchi: u16::from_le_bytes(hi),
131 tdf: TdfT::from_le_bytes(tdf),
132 }
133 }
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
140pub struct IntervalT {
141 pub lower_bound: TimeT,
143 pub upper_bound: TimeT,
145}
146
147impl IntervalT {
148 #[must_use]
151 pub const fn new(lower_bound: TimeT, upper_bound: TimeT) -> Option<Self> {
152 if lower_bound > upper_bound {
153 None
154 } else {
155 Some(Self {
156 lower_bound,
157 upper_bound,
158 })
159 }
160 }
161
162 #[must_use]
164 pub fn to_wire(self) -> [u8; 16] {
165 let mut buf = [0u8; 16];
166 buf[0..8].copy_from_slice(&self.lower_bound.to_le_bytes());
167 buf[8..16].copy_from_slice(&self.upper_bound.to_le_bytes());
168 buf
169 }
170
171 #[must_use]
173 pub fn from_wire(buf: [u8; 16]) -> Self {
174 let mut lo = [0u8; 8];
175 lo.copy_from_slice(&buf[0..8]);
176 let mut hi = [0u8; 8];
177 hi.copy_from_slice(&buf[8..16]);
178 Self {
179 lower_bound: TimeT::from_le_bytes(lo),
180 upper_bound: TimeT::from_le_bytes(hi),
181 }
182 }
183}
184
185#[cfg(feature = "std")]
189#[must_use]
190pub fn current_time() -> TimeT {
191 use std::time::{SystemTime, UNIX_EPOCH};
192 match SystemTime::now().duration_since(UNIX_EPOCH) {
193 Ok(d) => {
194 let nanos = d.as_secs() as u128 * 1_000_000_000 + d.subsec_nanos() as u128;
195 let ticks_since_unix = (nanos / 100) as u64;
196 UTC_EPOCH_TO_UNIX_TICKS.wrapping_add(ticks_since_unix)
197 }
198 Err(_) => 0,
199 }
200}
201
202#[cfg(not(feature = "std"))]
208#[must_use]
209pub fn current_time() -> TimeT {
210 0
211}
212
213#[cfg(test)]
214#[allow(clippy::expect_used)]
215mod tests {
216 use super::*;
217
218 #[test]
219 fn utct_size_is_16_octets() {
220 let utc = UtcT::new(0x0123_4567_89AB_CDEF, 0xFFFF_FFFF_FFFF, 60);
222 let wire = utc.to_wire();
223 assert_eq!(wire.len(), 16);
224 }
225
226 #[test]
227 fn intervalt_size_is_16_octets() {
228 let i = IntervalT::new(0, 0).expect("ok");
230 let wire = i.to_wire();
231 assert_eq!(wire.len(), 16);
232 }
233
234 #[test]
235 fn inaccuracy_caps_at_48_bits() {
236 let utc = UtcT::new(0, u64::MAX, 0);
238 assert_eq!(utc.inaccuracy(), 0x0000_FFFF_FFFF_FFFF);
239 }
240
241 #[test]
242 fn utct_wire_roundtrip_preserves_all_fields() {
243 let original = UtcT::new(123_456_789_012_345_678, 0xAABB_CCDD_EEFF, -480);
244 let wire = original.to_wire();
245 let decoded = UtcT::from_wire(wire);
246 assert_eq!(decoded, original);
247 }
248
249 #[test]
250 fn intervalt_wire_roundtrip_preserves_bounds() {
251 let i = IntervalT::new(100, 200).expect("ok");
252 let decoded = IntervalT::from_wire(i.to_wire());
253 assert_eq!(decoded.lower_bound, 100);
254 assert_eq!(decoded.upper_bound, 200);
255 }
256
257 #[test]
258 fn intervalt_rejects_lower_greater_than_upper() {
259 assert!(IntervalT::new(200, 100).is_none());
261 }
262
263 #[test]
264 fn local_time_applies_tdf() {
265 let utc = UtcT::new(1_000_000_000, 0, 60);
269 let expected = 1_000_000_000_i64 + 60 * 600_000_000;
270 assert_eq!(utc.local_time(), expected as TimeT);
271 }
272
273 #[test]
274 fn local_time_negative_tdf_west_of_greenwich() {
275 let utc = UtcT::new(1_000_000_000_000, 0, -480); let expected = 1_000_000_000_000_i64 + (-480) * 600_000_000;
278 assert_eq!(utc.local_time(), expected as TimeT);
279 }
280
281 #[cfg(feature = "std")]
282 #[test]
283 fn current_time_is_recent_century() {
284 let t = current_time();
289 assert!(t > 130_000_000_000_000_000);
290 assert!(t < 200_000_000_000_000_000);
291 }
292}