use core::fmt;
use core::ops::{Add, AddAssign, Sub, SubAssign};
use core::str::FromStr;
use core::time::Duration;
#[cfg(feature = "std")]
use std::time::SystemTimeError;
#[cfg(feature = "std")]
use humantime::TimestampError;
#[cfg(feature = "std")]
use {
humantime::format_rfc3339_nanos,
std::time::{SystemTime, UNIX_EPOCH},
};
const MAX_NB_SEC: u64 = (1u64 << 32) - 1;
const FRAC_PER_SEC: u64 = 1u64 << 32;
const FRAC_MASK: u64 = 0xFFFF_FFFFu64;
const NANO_PER_SEC: u64 = 1_000_000_000;
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct NTP64(pub u64);
impl NTP64 {
#[inline]
pub fn as_u64(&self) -> u64 {
self.0
}
#[inline]
pub fn as_secs_f64(&self) -> f64 {
let secs: f64 = self.as_secs() as f64;
let subsec: f64 = ((self.0 & FRAC_MASK) as f64) / FRAC_PER_SEC as f64;
secs + subsec
}
#[inline]
pub fn as_secs(&self) -> u32 {
(self.0 >> 32) as u32
}
#[inline]
pub fn as_nanos(&self) -> u64 {
let secs_as_nanos = (self.as_secs() as u64) * NANO_PER_SEC;
let subsec_nanos = self.subsec_nanos() as u64;
secs_as_nanos + subsec_nanos
}
#[inline]
pub fn subsec_nanos(&self) -> u32 {
let frac = self.0 & FRAC_MASK;
(frac * NANO_PER_SEC).div_ceil(FRAC_PER_SEC) as u32
}
#[inline]
pub fn to_duration(self) -> Duration {
Duration::new(self.as_secs().into(), self.subsec_nanos())
}
#[inline]
#[cfg(feature = "std")]
pub fn to_system_time(self) -> SystemTime {
UNIX_EPOCH + self.to_duration()
}
#[cfg(feature = "std")]
pub fn to_string_rfc3339_lossy(&self) -> String {
format_rfc3339_nanos(self.to_system_time()).to_string()
}
#[cfg(feature = "std")]
pub fn parse_rfc3339(s: &str) -> Result<Self, ParseNTP64Error> {
match humantime::parse_rfc3339(s) {
Ok(time) => time
.duration_since(UNIX_EPOCH)
.map(NTP64::from)
.map_err(ParseNTP64Error::SystemTimeError),
Err(e) => Err(ParseNTP64Error::InvalidRFC3339(e)),
}
}
}
impl Add for NTP64 {
type Output = Self;
#[inline]
fn add(self, other: Self) -> Self {
Self(self.0 + other.0)
}
}
impl Add<NTP64> for &NTP64 {
type Output = <NTP64 as Add<NTP64>>::Output;
#[inline]
fn add(self, other: NTP64) -> <NTP64 as Add<NTP64>>::Output {
Add::add(*self, other)
}
}
impl Add<&NTP64> for NTP64 {
type Output = <NTP64 as Add<NTP64>>::Output;
#[inline]
fn add(self, other: &NTP64) -> <NTP64 as Add<NTP64>>::Output {
Add::add(self, *other)
}
}
impl Add<&NTP64> for &NTP64 {
type Output = <NTP64 as Add<NTP64>>::Output;
#[inline]
fn add(self, other: &NTP64) -> <NTP64 as Add<NTP64>>::Output {
Add::add(*self, *other)
}
}
impl Add<u64> for NTP64 {
type Output = Self;
#[inline]
fn add(self, other: u64) -> Self {
Self(self.0 + other)
}
}
impl AddAssign<u64> for NTP64 {
#[inline]
fn add_assign(&mut self, other: u64) {
*self = Self(self.0 + other);
}
}
impl Sub for NTP64 {
type Output = Self;
#[inline]
fn sub(self, other: Self) -> Self {
Self(self.0 - other.0)
}
}
impl Sub<NTP64> for &NTP64 {
type Output = <NTP64 as Sub<NTP64>>::Output;
#[inline]
fn sub(self, other: NTP64) -> <NTP64 as Sub<NTP64>>::Output {
Sub::sub(*self, other)
}
}
impl Sub<&NTP64> for NTP64 {
type Output = <NTP64 as Sub<NTP64>>::Output;
#[inline]
fn sub(self, other: &NTP64) -> <NTP64 as Sub<NTP64>>::Output {
Sub::sub(self, *other)
}
}
impl Sub<&NTP64> for &NTP64 {
type Output = <NTP64 as Sub<NTP64>>::Output;
#[inline]
fn sub(self, other: &NTP64) -> <NTP64 as Sub<NTP64>>::Output {
Sub::sub(*self, *other)
}
}
impl Sub<u64> for NTP64 {
type Output = Self;
#[inline]
fn sub(self, other: u64) -> Self {
Self(self.0 - other)
}
}
impl SubAssign<u64> for NTP64 {
#[inline]
fn sub_assign(&mut self, other: u64) {
*self = Self(self.0 - other);
}
}
impl fmt::Display for NTP64 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if f.alternate() {
#[cfg(feature = "std")]
return write!(f, "{}", format_rfc3339_nanos(self.to_system_time()));
#[cfg(not(feature = "std"))]
return write!(f, "{}", self.0);
} else {
write!(f, "{}", self.0)
}
}
}
impl fmt::Debug for NTP64 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<Duration> for NTP64 {
fn from(duration: Duration) -> NTP64 {
let secs = duration.as_secs();
assert!(secs <= MAX_NB_SEC);
let nanos: u64 = duration.subsec_nanos().into();
NTP64((secs << 32) + ((nanos * FRAC_PER_SEC) / NANO_PER_SEC))
}
}
#[cfg(all(feature = "nix", target_family = "unix"))]
impl From<nix::sys::time::TimeSpec> for NTP64 {
fn from(ts: nix::sys::time::TimeSpec) -> Self {
Self::from(Duration::from(ts))
}
}
impl FromStr for NTP64 {
type Err = ParseNTP64Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
u64::from_str(s)
.map(NTP64)
.map_err(|_| ParseNTP64Error::ParseIntError)
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ParseNTP64Error {
ParseIntError,
#[cfg(feature = "std")]
SystemTimeError(SystemTimeError),
#[cfg(feature = "std")]
InvalidRFC3339(TimestampError),
}
impl fmt::Display for ParseNTP64Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseNTP64Error::ParseIntError => {
write!(f, "Invalid NTP6 time (not an unsigned 64 bits integer)")
}
#[cfg(feature = "std")]
ParseNTP64Error::SystemTimeError(e) => write!(f, "Invalid NTP6 time ({e})"),
#[cfg(feature = "std")]
ParseNTP64Error::InvalidRFC3339(e) => write!(f, "Invalid NTP6 time ({e})"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseNTP64Error {}
#[cfg(test)]
mod tests {
#[test]
fn as_secs_f64() {
use crate::*;
let epoch = NTP64::default();
assert_eq!(epoch.as_secs_f64(), 0f64);
let epoch_plus_1 = NTP64(1);
assert!(epoch_plus_1 > epoch);
assert!(epoch_plus_1.as_secs_f64() > epoch.as_secs_f64());
let epoch_plus_counter_max = NTP64(CMASK);
#[cfg(feature = "std")]
println!(
"Time precision = {} ns",
epoch_plus_counter_max.as_secs_f64() * (ntp64::NANO_PER_SEC as f64)
);
assert!(epoch_plus_counter_max.as_secs_f64() < 0.0000000035f64);
}
#[test]
fn as_nanos() {
use crate::*;
let t = NTP64::from(Duration::new(42, 84));
assert_eq!(t.as_nanos(), 42_000_000_084);
}
#[cfg(feature = "std")]
#[test]
fn bijective_to_string() {
use crate::*;
use core::str::FromStr;
use rand::prelude::*;
let mut rng = rand::thread_rng();
for _ in 0u64..10000 {
let t = NTP64(rng.gen());
assert_eq!(t, NTP64::from_str(&t.to_string()).unwrap());
}
}
#[cfg(feature = "std")]
#[test]
fn rfc3339_conversion() {
use crate::*;
use regex::Regex;
let rfc3339_regex = Regex::new(
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$"
).unwrap();
let now = SystemTime::now();
let t = NTP64::from(now.duration_since(UNIX_EPOCH).unwrap());
let rfc3339 = t.to_string_rfc3339_lossy();
assert_eq!(rfc3339, humantime::format_rfc3339_nanos(now).to_string());
assert!(rfc3339_regex.is_match(&rfc3339));
let rfc3339_2 = format!("{t:#}");
assert_eq!(rfc3339_2, humantime::format_rfc3339_nanos(now).to_string());
assert!(rfc3339_regex.is_match(&rfc3339_2));
}
#[test]
fn duration_conversion() {
use super::*;
let zero = NTP64::from(Duration::ZERO);
assert_eq!(zero.as_u64(), 0u64);
assert_eq!(zero.as_secs_f64(), 0f64);
let one_sec = NTP64::from(Duration::from_secs(1));
assert_eq!(one_sec.as_u64(), 1u64 << 32);
assert_eq!(one_sec.as_secs_f64(), 1f64);
}
#[cfg(all(feature = "nix", target_family = "unix"))]
#[test]
fn from_timespec() {
use super::*;
let ts = nix::sys::time::TimeSpec::new(42, 84);
let t = NTP64::from(ts);
assert_eq!(t.as_secs(), 42);
assert_eq!(t.subsec_nanos(), 84);
}
}