1use core::fmt;
12use core::ops::{Add, AddAssign, Sub, SubAssign};
13use core::str::FromStr;
14use core::time::Duration;
15#[cfg(feature = "std")]
16use std::time::SystemTimeError;
17
18#[cfg(feature = "std")]
19use humantime::TimestampError;
20
21#[cfg(feature = "std")]
22use {
23 humantime::format_rfc3339_nanos,
24 std::time::{SystemTime, UNIX_EPOCH},
25};
26
27const MAX_NB_SEC: u64 = (1u64 << 32) - 1;
29const FRAC_PER_SEC: u64 = 1u64 << 32;
31const FRAC_MASK: u64 = 0xFFFF_FFFFu64;
33
34const NANO_PER_SEC: u64 = 1_000_000_000;
36
37#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)]
73#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
74#[cfg_attr(feature = "defmt", derive(defmt::Format))]
75pub struct NTP64(pub u64);
76
77impl NTP64 {
78 #[inline]
80 pub fn as_u64(&self) -> u64 {
81 self.0
82 }
83
84 #[inline]
91 pub fn as_secs_f64(&self) -> f64 {
92 let secs: f64 = self.as_secs() as f64;
93 let subsec: f64 = ((self.0 & FRAC_MASK) as f64) / FRAC_PER_SEC as f64;
94 secs + subsec
95 }
96
97 #[inline]
99 pub fn as_secs(&self) -> u32 {
100 (self.0 >> 32) as u32
101 }
102
103 #[inline]
105 pub fn as_nanos(&self) -> u64 {
106 let secs_as_nanos = (self.as_secs() as u64) * NANO_PER_SEC;
107 let subsec_nanos = self.subsec_nanos() as u64;
108 secs_as_nanos + subsec_nanos
109 }
110
111 #[inline]
113 pub fn subsec_nanos(&self) -> u32 {
114 let frac = self.0 & FRAC_MASK;
115 (frac * NANO_PER_SEC).div_ceil(FRAC_PER_SEC) as u32
117 }
118
119 #[inline]
121 pub fn to_duration(self) -> Duration {
122 Duration::new(self.as_secs().into(), self.subsec_nanos())
123 }
124
125 #[inline]
127 #[cfg(feature = "std")]
128 pub fn to_system_time(self) -> SystemTime {
129 UNIX_EPOCH + self.to_duration()
130 }
131
132 #[cfg(feature = "std")]
135 pub fn to_string_rfc3339_lossy(&self) -> String {
136 format_rfc3339_nanos(self.to_system_time()).to_string()
137 }
138
139 #[cfg(feature = "std")]
141 pub fn parse_rfc3339(s: &str) -> Result<Self, ParseNTP64Error> {
142 match humantime::parse_rfc3339(s) {
143 Ok(time) => time
144 .duration_since(UNIX_EPOCH)
145 .map(NTP64::from)
146 .map_err(ParseNTP64Error::SystemTimeError),
147 Err(e) => Err(ParseNTP64Error::InvalidRFC3339(e)),
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
303impl FromStr for NTP64 {
304 type Err = ParseNTP64Error;
305
306 fn from_str(s: &str) -> Result<Self, Self::Err> {
307 u64::from_str(s)
308 .map(NTP64)
309 .map_err(|_| ParseNTP64Error::ParseIntError)
310 }
311}
312
313#[derive(Debug, Clone)]
314#[cfg_attr(feature = "defmt", derive(defmt::Format))]
315pub enum ParseNTP64Error {
316 ParseIntError,
317 #[cfg(feature = "std")]
318 SystemTimeError(SystemTimeError),
319 #[cfg(feature = "std")]
320 InvalidRFC3339(TimestampError),
321}
322
323impl fmt::Display for ParseNTP64Error {
324 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
325 match self {
326 ParseNTP64Error::ParseIntError => {
327 write!(f, "Invalid NTP6 time (not an unsigned 64 bits integer)")
328 }
329 #[cfg(feature = "std")]
330 ParseNTP64Error::SystemTimeError(e) => write!(f, "Invalid NTP6 time ({e})"),
331 #[cfg(feature = "std")]
332 ParseNTP64Error::InvalidRFC3339(e) => write!(f, "Invalid NTP6 time ({e})"),
333 }
334 }
335}
336
337#[cfg(feature = "std")]
338impl std::error::Error for ParseNTP64Error {}
339
340#[cfg(test)]
341mod tests {
342
343 #[test]
344 fn as_secs_f64() {
345 use crate::*;
346
347 let epoch = NTP64::default();
348 assert_eq!(epoch.as_secs_f64(), 0f64);
349
350 let epoch_plus_1 = NTP64(1);
351 assert!(epoch_plus_1 > epoch);
352 assert!(epoch_plus_1.as_secs_f64() > epoch.as_secs_f64());
353
354 let epoch_plus_counter_max = NTP64(CMASK);
356 #[cfg(feature = "std")]
357 println!(
358 "Time precision = {} ns",
359 epoch_plus_counter_max.as_secs_f64() * (ntp64::NANO_PER_SEC as f64)
360 );
361 assert!(epoch_plus_counter_max.as_secs_f64() < 0.0000000035f64);
362 }
363
364 #[test]
365 fn as_nanos() {
366 use crate::*;
367 let t = NTP64::from(Duration::new(42, 84));
368 assert_eq!(t.as_nanos(), 42_000_000_084);
369 }
370
371 #[cfg(feature = "std")]
372 #[test]
373 fn bijective_to_string() {
374 use crate::*;
375 use core::str::FromStr;
376 use rand::prelude::*;
377
378 let mut rng = rand::thread_rng();
379 for _ in 0u64..10000 {
380 let t = NTP64(rng.gen());
381 assert_eq!(t, NTP64::from_str(&t.to_string()).unwrap());
382 }
383 }
384
385 #[cfg(feature = "std")]
386 #[test]
387 fn rfc3339_conversion() {
388 use crate::*;
389 use regex::Regex;
390
391 let rfc3339_regex = Regex::new(
392 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$"
393 ).unwrap();
394
395 let now = SystemTime::now();
396 let t = NTP64::from(now.duration_since(UNIX_EPOCH).unwrap());
397
398 let rfc3339 = t.to_string_rfc3339_lossy();
399 assert_eq!(rfc3339, humantime::format_rfc3339_nanos(now).to_string());
400 assert!(rfc3339_regex.is_match(&rfc3339));
401
402 let rfc3339_2 = format!("{t:#}");
404 assert_eq!(rfc3339_2, humantime::format_rfc3339_nanos(now).to_string());
405 assert!(rfc3339_regex.is_match(&rfc3339_2));
406 }
407
408 #[test]
409 fn duration_conversion() {
410 use super::*;
411
412 let zero = NTP64::from(Duration::ZERO);
413 assert_eq!(zero.as_u64(), 0u64);
414 assert_eq!(zero.as_secs_f64(), 0f64);
415
416 let one_sec = NTP64::from(Duration::from_secs(1));
417 assert_eq!(one_sec.as_u64(), 1u64 << 32);
418 assert_eq!(one_sec.as_secs_f64(), 1f64);
419 }
420
421 #[cfg(all(feature = "nix", target_family = "unix"))]
422 #[test]
423 fn from_timespec() {
424 use super::*;
425 let ts = nix::sys::time::TimeSpec::new(42, 84);
426 let t = NTP64::from(ts);
427 assert_eq!(t.as_secs(), 42);
428 assert_eq!(t.subsec_nanos(), 84);
429 }
430}