1use alloc::string::String;
12use core::fmt;
13use core::ops::{Add, AddAssign, Sub, SubAssign};
14use core::time::Duration;
15use serde::{Deserialize, Serialize};
16
17#[cfg(feature = "std")]
18use {
19 core::str::FromStr,
20 humantime::format_rfc3339_nanos,
21 std::time::{SystemTime, UNIX_EPOCH},
22};
23
24const MAX_NB_SEC: u64 = (1u64 << 32) - 1;
26const FRAC_PER_SEC: u64 = 1u64 << 32;
28const FRAC_MASK: u64 = 0xFFFF_FFFFu64;
30
31const NANO_PER_SEC: u64 = 1_000_000_000;
33
34#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default, Deserialize, Serialize)]
70#[cfg_attr(feature = "defmt", derive(defmt::Format))]
71pub struct NTP64(pub u64);
72
73impl NTP64 {
74 #[inline]
76 pub fn as_u64(&self) -> u64 {
77 self.0
78 }
79
80 #[inline]
87 pub fn as_secs_f64(&self) -> f64 {
88 let secs: f64 = self.as_secs() as f64;
89 let subsec: f64 = ((self.0 & FRAC_MASK) as f64) / FRAC_PER_SEC as f64;
90 secs + subsec
91 }
92
93 #[inline]
95 pub fn as_secs(&self) -> u32 {
96 (self.0 >> 32) as u32
97 }
98
99 #[inline]
101 pub fn as_nanos(&self) -> u64 {
102 let secs_as_nanos = (self.as_secs() as u64) * NANO_PER_SEC;
103 let subsec_nanos = self.subsec_nanos() as u64;
104 secs_as_nanos + subsec_nanos
105 }
106
107 #[inline]
109 pub fn subsec_nanos(&self) -> u32 {
110 let frac = self.0 & FRAC_MASK;
111 (frac * NANO_PER_SEC).div_ceil(FRAC_PER_SEC) as u32
113 }
114
115 #[inline]
117 pub fn to_duration(self) -> Duration {
118 Duration::new(self.as_secs().into(), self.subsec_nanos())
119 }
120
121 #[inline]
123 #[cfg(feature = "std")]
124 pub fn to_system_time(self) -> SystemTime {
125 UNIX_EPOCH + self.to_duration()
126 }
127
128 #[cfg(feature = "std")]
131 pub fn to_string_rfc3339_lossy(&self) -> String {
132 format_rfc3339_nanos(self.to_system_time()).to_string()
133 }
134
135 #[cfg(feature = "std")]
137 pub fn parse_rfc3339(s: &str) -> Result<Self, ParseNTP64Error> {
138 match humantime::parse_rfc3339(s) {
139 Ok(time) => time
140 .duration_since(UNIX_EPOCH)
141 .map(NTP64::from)
142 .map_err(|e| ParseNTP64Error {
143 cause: format!("Failed to parse '{s}' : {e}"),
144 }),
145 Err(_) => Err(ParseNTP64Error {
146 cause: format!("Failed to parse '{s}' : invalid RFC3339 format"),
147 }),
148 }
149 }
150}
151
152impl Add for NTP64 {
153 type Output = Self;
154
155 #[inline]
156 fn add(self, other: Self) -> Self {
157 Self(self.0 + other.0)
158 }
159}
160
161impl Add<NTP64> for &NTP64 {
162 type Output = <NTP64 as Add<NTP64>>::Output;
163
164 #[inline]
165 fn add(self, other: NTP64) -> <NTP64 as Add<NTP64>>::Output {
166 Add::add(*self, other)
167 }
168}
169
170impl Add<&NTP64> for NTP64 {
171 type Output = <NTP64 as Add<NTP64>>::Output;
172
173 #[inline]
174 fn add(self, other: &NTP64) -> <NTP64 as Add<NTP64>>::Output {
175 Add::add(self, *other)
176 }
177}
178
179impl Add<&NTP64> for &NTP64 {
180 type Output = <NTP64 as Add<NTP64>>::Output;
181
182 #[inline]
183 fn add(self, other: &NTP64) -> <NTP64 as Add<NTP64>>::Output {
184 Add::add(*self, *other)
185 }
186}
187
188impl Add<u64> for NTP64 {
189 type Output = Self;
190
191 #[inline]
192 fn add(self, other: u64) -> Self {
193 Self(self.0 + other)
194 }
195}
196
197impl AddAssign<u64> for NTP64 {
198 #[inline]
199 fn add_assign(&mut self, other: u64) {
200 *self = Self(self.0 + other);
201 }
202}
203
204impl Sub for NTP64 {
205 type Output = Self;
206
207 #[inline]
208 fn sub(self, other: Self) -> Self {
209 Self(self.0 - other.0)
210 }
211}
212
213impl Sub<NTP64> for &NTP64 {
214 type Output = <NTP64 as Sub<NTP64>>::Output;
215
216 #[inline]
217 fn sub(self, other: NTP64) -> <NTP64 as Sub<NTP64>>::Output {
218 Sub::sub(*self, other)
219 }
220}
221
222impl Sub<&NTP64> for NTP64 {
223 type Output = <NTP64 as Sub<NTP64>>::Output;
224
225 #[inline]
226 fn sub(self, other: &NTP64) -> <NTP64 as Sub<NTP64>>::Output {
227 Sub::sub(self, *other)
228 }
229}
230
231impl Sub<&NTP64> for &NTP64 {
232 type Output = <NTP64 as Sub<NTP64>>::Output;
233
234 #[inline]
235 fn sub(self, other: &NTP64) -> <NTP64 as Sub<NTP64>>::Output {
236 Sub::sub(*self, *other)
237 }
238}
239
240impl Sub<u64> for NTP64 {
241 type Output = Self;
242
243 #[inline]
244 fn sub(self, other: u64) -> Self {
245 Self(self.0 - other)
246 }
247}
248
249impl SubAssign<u64> for NTP64 {
250 #[inline]
251 fn sub_assign(&mut self, other: u64) {
252 *self = Self(self.0 - other);
253 }
254}
255
256impl fmt::Display for NTP64 {
257 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
269 if f.alternate() {
271 #[cfg(feature = "std")]
272 return write!(f, "{}", format_rfc3339_nanos(self.to_system_time()));
273 #[cfg(not(feature = "std"))]
274 return write!(f, "{}", self.0);
275 } else {
276 write!(f, "{}", self.0)
277 }
278 }
279}
280
281impl fmt::Debug for NTP64 {
282 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
283 write!(f, "{}", self.0)
284 }
285}
286
287impl From<Duration> for NTP64 {
288 fn from(duration: Duration) -> NTP64 {
289 let secs = duration.as_secs();
290 assert!(secs <= MAX_NB_SEC);
291 let nanos: u64 = duration.subsec_nanos().into();
292 NTP64((secs << 32) + ((nanos * FRAC_PER_SEC) / NANO_PER_SEC))
293 }
294}
295
296#[cfg(all(feature = "nix", target_family = "unix"))]
297impl From<nix::sys::time::TimeSpec> for NTP64 {
298 fn from(ts: nix::sys::time::TimeSpec) -> Self {
299 Self::from(Duration::from(ts))
300 }
301}
302
303#[cfg(feature = "std")]
304impl FromStr for NTP64 {
305 type Err = ParseNTP64Error;
306
307 fn from_str(s: &str) -> Result<Self, Self::Err> {
308 u64::from_str(s).map(NTP64).map_err(|_| ParseNTP64Error {
309 cause: format!("Invalid NTP64 time : '{s}' (must be a u64)"),
310 })
311 }
312}
313
314#[derive(Debug, Clone, PartialEq, Eq)]
315#[cfg_attr(feature = "defmt", derive(defmt::Format))]
316pub struct ParseNTP64Error {
317 pub cause: String,
318}
319
320#[cfg(test)]
321mod tests {
322
323 #[test]
324 fn as_secs_f64() {
325 use crate::*;
326
327 let epoch = NTP64::default();
328 assert_eq!(epoch.as_secs_f64(), 0f64);
329
330 let epoch_plus_1 = NTP64(1);
331 assert!(epoch_plus_1 > epoch);
332 assert!(epoch_plus_1.as_secs_f64() > epoch.as_secs_f64());
333
334 let epoch_plus_counter_max = NTP64(CMASK);
336 println!(
337 "Time precision = {} ns",
338 epoch_plus_counter_max.as_secs_f64() * (ntp64::NANO_PER_SEC as f64)
339 );
340 assert!(epoch_plus_counter_max.as_secs_f64() < 0.0000000035f64);
341 }
342
343 #[test]
344 fn as_nanos() {
345 use crate::*;
346 let t = NTP64::from(Duration::new(42, 84));
347 assert_eq!(t.as_nanos(), 42_000_000_084);
348 }
349
350 #[test]
351 fn bijective_to_string() {
352 use crate::*;
353 use rand::prelude::*;
354 use std::str::FromStr;
355
356 let mut rng = rand::thread_rng();
357 for _ in 0u64..10000 {
358 let t = NTP64(rng.gen());
359 assert_eq!(t, NTP64::from_str(&t.to_string()).unwrap());
360 }
361 }
362
363 #[test]
364 fn rfc3339_conversion() {
365 use crate::*;
366 use regex::Regex;
367
368 let rfc3339_regex = Regex::new(
369 r"^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]Z$"
370 ).unwrap();
371
372 let now = SystemTime::now();
373 let t = NTP64::from(now.duration_since(UNIX_EPOCH).unwrap());
374
375 let rfc3339 = t.to_string_rfc3339_lossy();
376 assert_eq!(rfc3339, humantime::format_rfc3339_nanos(now).to_string());
377 assert!(rfc3339_regex.is_match(&rfc3339));
378
379 let rfc3339_2 = format!("{t:#}");
381 assert_eq!(rfc3339_2, humantime::format_rfc3339_nanos(now).to_string());
382 assert!(rfc3339_regex.is_match(&rfc3339_2));
383 }
384
385 #[test]
386 fn duration_conversion() {
387 use super::*;
388
389 let zero = NTP64::from(Duration::ZERO);
390 assert_eq!(zero.as_u64(), 0u64);
391 assert_eq!(zero.as_secs_f64(), 0f64);
392
393 let one_sec = NTP64::from(Duration::from_secs(1));
394 assert_eq!(one_sec.as_u64(), 1u64 << 32);
395 assert_eq!(one_sec.as_secs_f64(), 1f64);
396 }
397
398 #[cfg(all(feature = "nix", target_family = "unix"))]
399 #[test]
400 fn from_timespec() {
401 use super::*;
402 let ts = nix::sys::time::TimeSpec::new(42, 84);
403 let t = NTP64::from(ts);
404 assert_eq!(t.as_secs(), 42);
405 assert_eq!(t.subsec_nanos(), 84);
406 }
407}