#![warn(missing_docs, missing_debug_implementations, unreachable_pub)]
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]
mod date_time;
mod errors;
#[cfg(feature = "std")]
mod tai_clock;
use core::fmt;
use core::ops::{Add, AddAssign, Sub, SubAssign};
use core::str::FromStr;
use core::time::Duration;
use date_time::*;
pub use errors::{DateTimeError, OutOfRangeError, ParseDateTimeError};
#[cfg(feature = "std")]
pub use tai_clock::*;
const NANOS_PER_SEC: u32 = 1_000_000_000;
const UNIX_EPOCH_YEAR: i32 = 1970;
pub type MonotonicTime = TaiTime<0>;
pub type GpsTime = TaiTime<315_964_819>;
pub type GstTime = TaiTime<935_280_019>;
pub type BdtTime = TaiTime<1_136_073_633>;
pub type Tai1958Time = TaiTime<-378_691_200>;
pub type Tai1972Time = TaiTime<63_072_000>;
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct TaiTime<const EPOCH_REF: i64> {
secs: i64,
#[cfg_attr(feature = "serde", serde(deserialize_with = "validate_nanos"))]
nanos: u32,
}
impl<const EPOCH_REF: i64> TaiTime<EPOCH_REF> {
#[cfg(feature = "std")]
const EPOCH_REF: i64 = EPOCH_REF;
pub const EPOCH: Self = Self { secs: 0, nanos: 0 };
pub const MIN: Self = Self {
secs: i64::MIN,
nanos: 0,
};
pub const MAX: Self = Self {
secs: i64::MAX,
nanos: NANOS_PER_SEC - 1,
};
pub const fn new(secs: i64, subsec_nanos: u32) -> Option<Self> {
if subsec_nanos >= NANOS_PER_SEC {
return None;
}
Some(Self {
secs,
nanos: subsec_nanos,
})
}
#[cfg(all(
feature = "tai_clock",
any(
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
target_os = "linux"
)
))]
pub fn now() -> Self {
Self::try_now().expect("overflow when converting timestamp")
}
#[cfg(all(
feature = "tai_clock",
any(
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
target_os = "linux"
)
))]
pub fn try_now() -> Result<Self, OutOfRangeError> {
let time = nix::time::clock_gettime(nix::time::ClockId::CLOCK_TAI)
.expect("unexpected error while calling clock_gettime");
#[allow(clippy::useless_conversion)]
let secs: i64 = time.tv_sec().try_into().unwrap();
#[allow(clippy::useless_conversion)]
let subsec_nanos: u32 = time.tv_nsec().try_into().unwrap();
let t = MonotonicTime::new(secs, subsec_nanos).unwrap();
t.to_tai_time().ok_or(OutOfRangeError(()))
}
#[cfg(feature = "std")]
pub fn now_from_utc(leap_secs: i64) -> Self {
Self::from_system_time(&std::time::SystemTime::now(), leap_secs)
}
#[cfg(feature = "std")]
pub fn try_now_from_utc(leap_secs: i64) -> Result<Self, OutOfRangeError> {
Self::try_from_system_time(&std::time::SystemTime::now(), leap_secs)
}
pub const fn try_from_date_time(
year: i32,
month: u8,
day: u8,
hour: u8,
min: u8,
sec: u8,
nano: u32,
) -> Result<Self, DateTimeError> {
if month < 1 || month > 12 {
return Err(DateTimeError::InvalidMonth(month));
}
if day < 1 || day > days_in_month(year, month) {
return Err(DateTimeError::InvalidDayOfMonth(day));
}
if hour > 23 {
return Err(DateTimeError::InvalidHour(hour));
}
if min > 59 {
return Err(DateTimeError::InvalidMinute(min));
}
if sec > 59 {
return Err(DateTimeError::InvalidSecond(sec));
}
if nano > NANOS_PER_SEC {
return Err(DateTimeError::InvalidNanosecond(nano));
}
let days = days_from_year_0(year) - days_from_year_0(UNIX_EPOCH_YEAR)
+ day_of_year(year, month, day) as i64;
let secs = days * 86400 + hour as i64 * 3600 + min as i64 * 60 + sec as i64;
if let Some(secs) = secs.checked_sub(EPOCH_REF) {
Ok(Self { secs, nanos: nano })
} else {
Err(DateTimeError::OutOfRange)
}
}
pub const fn from_unix_timestamp(secs: i64, subsec_nanos: u32, leap_secs: i64) -> Self {
if let Ok(timestamp) = Self::try_from_unix_timestamp(secs, subsec_nanos, leap_secs) {
return timestamp;
}
panic!("overflow when converting timestamp");
}
pub const fn try_from_unix_timestamp(
secs: i64,
subsec_nanos: u32,
leap_secs: i64,
) -> Result<Self, OutOfRangeError> {
let (secs_carry, subsec_nanos) = if subsec_nanos < NANOS_PER_SEC {
(0, subsec_nanos)
} else {
let secs = subsec_nanos / NANOS_PER_SEC;
(secs as i64, subsec_nanos - secs * NANOS_PER_SEC)
};
if let Some(secs) = secs.checked_add(secs_carry)
&& let Some(secs) = secs.checked_add(leap_secs)
&& let Some(secs) = secs.checked_sub(EPOCH_REF)
{
return Ok(Self {
secs,
nanos: subsec_nanos,
});
}
Err(OutOfRangeError(()))
}
#[cfg(feature = "std")]
pub fn from_system_time(system_time: &std::time::SystemTime, leap_secs: i64) -> Self {
Self::try_from_system_time(system_time, leap_secs)
.expect("overflow when converting timestamp")
}
#[cfg(feature = "std")]
pub fn try_from_system_time(
system_time: &std::time::SystemTime,
leap_secs: i64,
) -> Result<Self, OutOfRangeError> {
let unix_time = system_time
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.unwrap();
leap_secs
.checked_sub(EPOCH_REF)
.and_then(|secs| Self::new(secs, 0).unwrap().checked_add(unix_time))
.ok_or(OutOfRangeError(()))
}
#[cfg(feature = "chrono")]
pub const fn from_chrono_date_time<Tz: chrono::TimeZone>(
date_time: &chrono::DateTime<Tz>,
leap_secs: i64,
) -> Self {
if let Ok(timestamp) = Self::try_from_chrono_date_time(date_time, leap_secs) {
return timestamp;
}
panic!("overflow when converting timestamp");
}
#[cfg(feature = "chrono")]
pub const fn try_from_chrono_date_time<Tz: chrono::TimeZone>(
date_time: &chrono::DateTime<Tz>,
leap_secs: i64,
) -> Result<Self, OutOfRangeError> {
Self::try_from_unix_timestamp(
date_time.timestamp(),
date_time.timestamp_subsec_nanos(),
leap_secs,
)
}
pub const fn as_secs(&self) -> i64 {
self.secs
}
pub const fn subsec_nanos(&self) -> u32 {
self.nanos
}
pub const fn as_unix_secs(&self, leap_secs: i64) -> Option<i64> {
match self.secs.checked_sub(leap_secs) {
Some(secs) => secs.checked_add(EPOCH_REF),
None => None,
}
}
pub const fn to_tai_time<const OTHER_EPOCH_REF: i64>(
&self,
) -> Option<TaiTime<OTHER_EPOCH_REF>> {
if let Some(secs) = EPOCH_REF.checked_sub(OTHER_EPOCH_REF)
&& let Some(secs) = secs.checked_add(self.secs)
{
return Some(TaiTime {
secs,
nanos: self.nanos,
});
}
None
}
#[cfg(feature = "std")]
pub fn to_system_time(&self, leap_secs: i64) -> Option<std::time::SystemTime> {
let secs: u64 = self
.as_unix_secs(leap_secs)
.and_then(|secs| secs.try_into().ok())?;
std::time::SystemTime::UNIX_EPOCH.checked_add(Duration::new(secs, self.subsec_nanos()))
}
#[cfg(feature = "chrono")]
pub fn to_chrono_date_time(&self, leap_secs: i64) -> Option<chrono::DateTime<chrono::Utc>> {
self.as_unix_secs(leap_secs)
.and_then(|secs| chrono::DateTime::from_timestamp(secs, self.nanos))
}
pub const fn checked_add(self, rhs: Duration) -> Option<Self> {
let mut secs = self.secs.wrapping_add(rhs.as_secs() as i64);
if secs < self.secs {
return None;
}
let mut nanos = self.nanos + rhs.subsec_nanos();
if nanos >= NANOS_PER_SEC {
secs = if let Some(s) = secs.checked_add(1) {
s
} else {
return None;
};
nanos -= NANOS_PER_SEC;
}
Some(Self { secs, nanos })
}
pub const fn checked_sub(self, rhs: Duration) -> Option<Self> {
let mut secs = self.secs.wrapping_sub(rhs.as_secs() as i64);
if secs > self.secs {
return None;
}
let nanos = if self.nanos < rhs.subsec_nanos() {
secs = if let Some(s) = secs.checked_sub(1) {
s
} else {
return None;
};
(self.nanos + NANOS_PER_SEC) - rhs.subsec_nanos()
} else {
self.nanos - rhs.subsec_nanos()
};
Some(Self { secs, nanos })
}
pub const fn duration_since(self, earlier: Self) -> Duration {
if let Some(duration) = self.checked_duration_since(earlier) {
return duration;
}
panic!("attempt to substract a timestamp from an earlier timestamp");
}
pub const fn checked_duration_since(self, earlier: Self) -> Option<Duration> {
let (secs, nanos) = if earlier.nanos > self.nanos {
if let Some(s) = self.secs.checked_sub(1) {
(s, self.nanos + NANOS_PER_SEC)
} else {
return None;
}
} else {
(self.secs, self.nanos)
};
if secs < earlier.secs {
return None;
}
let delta_secs = secs.wrapping_sub(earlier.secs) as u64;
let delta_nanos = nanos - earlier.nanos;
Some(Duration::new(delta_secs, delta_nanos))
}
}
impl<const EPOCH_REF: i64> Add<Duration> for TaiTime<EPOCH_REF> {
type Output = Self;
fn add(self, other: Duration) -> Self {
self.checked_add(other)
.expect("overflow when adding duration to timestamp")
}
}
impl<const EPOCH_REF: i64> Sub<Duration> for TaiTime<EPOCH_REF> {
type Output = Self;
fn sub(self, other: Duration) -> Self {
self.checked_sub(other)
.expect("overflow when subtracting duration from timestamp")
}
}
impl<const EPOCH_REF: i64> AddAssign<Duration> for TaiTime<EPOCH_REF> {
fn add_assign(&mut self, other: Duration) {
*self = *self + other;
}
}
impl<const EPOCH_REF: i64> SubAssign<Duration> for TaiTime<EPOCH_REF> {
fn sub_assign(&mut self, other: Duration) {
*self = *self - other;
}
}
impl<const EPOCH_REF: i64> FromStr for TaiTime<EPOCH_REF> {
type Err = ParseDateTimeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (year, month, day, hour, min, sec, nano) = parse_date_time(s)?;
Self::try_from_date_time(year, month, day, hour, min, sec, nano)
.map_err(ParseDateTimeError::RangeError)
}
}
impl<const EPOCH_REF: i64> fmt::Display for TaiTime<EPOCH_REF> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use core::str::from_utf8;
let secs_from_year_0: i128 =
self.secs as i128 + EPOCH_REF as i128 + days_from_year_0(1970) as i128 * 86400;
let (year, doy, mut sec) = secs_to_date_time(secs_from_year_0);
let (month, day) = month_and_day_of_month(year, doy);
let hour = sec / 3600;
sec -= hour * 3600;
let min = sec / 60;
sec -= min * 60;
write!(
f,
"{}{:04}-{:02}-{:02} {:02}:{:02}:{:02}",
if year < 0 { "-" } else { "" },
year.abs(),
month,
day,
hour,
min,
sec
)?;
fn split_last_digit(n: u32) -> (u32, u32) {
let left = (((n as u64) * 429496730u64) >> 32) as u32;
let right = n - left * 10;
(left, right)
}
match f.precision() {
Some(precision) if precision != 0 => {
let mut n = self.nanos;
let mut buffer = [0u8; 9];
for pos in (0..9).rev() {
let (new_n, digit) = split_last_digit(n);
n = new_n;
buffer[pos] = digit as u8 + 48; }
write!(f, ".{}", from_utf8(&buffer[0..precision.min(9)]).unwrap())?;
}
None => {
let mut n = self.nanos;
let mut buffer = [0u8; 9];
let mut precision = None;
for pos in (0..9).rev() {
let (new_n, digit) = split_last_digit(n);
if digit != 0 && precision.is_none() {
precision = Some(pos);
}
n = new_n;
buffer[pos] = digit as u8 + 48;
}
if let Some(precision) = precision {
write!(f, ".{}", from_utf8(&buffer[0..=precision]).unwrap())?;
}
}
_ => {} }
Ok(())
}
}
#[cfg(feature = "serde")]
fn validate_nanos<'de, D>(deserializer: D) -> Result<u32, D::Error>
where
D: serde::de::Deserializer<'de>,
{
let v: u32 = serde::de::Deserialize::deserialize(deserializer)?;
if v < NANOS_PER_SEC {
Ok(v)
} else {
Err(serde::de::Error::invalid_value(
serde::de::Unexpected::Unsigned(v as u64),
&"a number of nanoseconds less than 1000000000",
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn equality() {
let t0 = Tai1972Time::new(123, 123_456_789).unwrap();
let t1 = Tai1972Time::new(123, 123_456_789).unwrap();
let t2 = Tai1972Time::new(123, 123_456_790).unwrap();
let t3 = Tai1972Time::new(124, 123_456_789).unwrap();
assert_eq!(t0, t1);
assert_ne!(t0, t2);
assert_ne!(t0, t3);
}
#[test]
fn ordering() {
let t0 = Tai1972Time::new(0, 1).unwrap();
let t1 = Tai1972Time::new(1, 0).unwrap();
assert!(t1 > t0);
}
#[test]
fn epoch_smoke() {
const T_UNIX_SECS: i64 = 1_234_567_890;
let t_tai_1970 = MonotonicTime::new(1_234_567_924, 123_456_789).unwrap();
let t_tai_1958 = Tai1958Time::new(1_613_259_124, 123_456_789).unwrap();
let t_tai_1972 = Tai1972Time::new(1_171_495_924, 123_456_789).unwrap();
let t_gps = GpsTime::new(918_603_105, 123_456_789).unwrap();
let t_gst = GstTime::new(299_287_905, 123_456_789).unwrap();
let t_bdt = BdtTime::new(98_494_291, 123_456_789).unwrap();
assert_eq!(t_tai_1970.as_unix_secs(34).unwrap(), T_UNIX_SECS);
assert_eq!(t_tai_1958.as_unix_secs(34).unwrap(), T_UNIX_SECS);
assert_eq!(t_tai_1972.as_unix_secs(34).unwrap(), T_UNIX_SECS);
assert_eq!(t_gps.as_unix_secs(34).unwrap(), T_UNIX_SECS);
assert_eq!(t_gst.as_unix_secs(34).unwrap(), T_UNIX_SECS);
assert_eq!(t_bdt.as_unix_secs(34).unwrap(), T_UNIX_SECS);
assert_eq!(t_tai_1970.to_tai_time().unwrap(), t_tai_1958);
assert_eq!(t_tai_1970.to_tai_time().unwrap(), t_tai_1970);
assert_eq!(t_tai_1970.to_tai_time().unwrap(), t_tai_1972);
assert_eq!(t_tai_1970.to_tai_time().unwrap(), t_gps);
assert_eq!(t_tai_1970.to_tai_time().unwrap(), t_gst);
assert_eq!(t_tai_1970.to_tai_time().unwrap(), t_bdt);
}
#[cfg(all(
feature = "std",
feature = "tai_clock",
any(
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
target_os = "linux"
)
))]
#[test]
fn now_smoke() {
let tolerance = Duration::from_secs(100);
let now_utc_no_leap = GpsTime::now_from_utc(0);
let now_tai = GpsTime::now();
if now_utc_no_leap > now_tai {
assert!(now_utc_no_leap.duration_since(now_tai) < tolerance);
} else {
assert!(now_tai.duration_since(now_utc_no_leap) < tolerance);
}
}
#[cfg(feature = "std")]
#[test]
fn now_from_utc_smoke() {
const TAI_1972_START_OF_2022: i64 = 1_577_923_200;
const TAI_1972_START_OF_2050: i64 = 2_461_536_000;
let now_secs = Tai1972Time::now_from_utc(0).as_secs();
assert!(now_secs > TAI_1972_START_OF_2022);
assert!(now_secs < TAI_1972_START_OF_2050);
}
#[cfg(feature = "std")]
#[test]
fn from_system_time() {
let t_unix = Duration::new(978_352_496, 789_000_000);
let t_tai_1972 = Tai1972Time::new(915_280_528, 789_000_000).unwrap();
let system_time = std::time::SystemTime::UNIX_EPOCH + t_unix;
let t = Tai1972Time::from_system_time(&system_time, 32);
assert_eq!(t, t_tai_1972);
}
#[test]
fn from_unix_timestamp() {
const T_UNIX_SECS: i64 = 978_352_496;
const T_UNIX_NANOS: u32 = 789_000_000;
let t_tai_1972 = Tai1972Time::new(915_280_528, 789_000_000).unwrap();
let t = Tai1972Time::from_unix_timestamp(T_UNIX_SECS, T_UNIX_NANOS, 32);
assert_eq!(t, t_tai_1972);
}
#[test]
fn from_unix_timestamp_with_carry() {
const T_UNIX_SECS: i64 = 978_352_494;
const T_UNIX_NANOS: u32 = 2_789_000_000;
let t_tai_1972 = Tai1972Time::new(915_280_528, 789_000_000).unwrap();
let t = Tai1972Time::from_unix_timestamp(T_UNIX_SECS, T_UNIX_NANOS, 32);
assert_eq!(t, t_tai_1972);
}
#[cfg(feature = "chrono")]
#[test]
fn from_chrono_date_time() {
let t_tai_1972 = Tai1972Time::new(915_280_528, 789_000_000).unwrap();
let chrono_date_time =
chrono::DateTime::parse_from_rfc3339("2001-01-01T12:34:56.789Z").unwrap();
let t = Tai1972Time::from_chrono_date_time(&chrono_date_time, 32);
assert_eq!(t, t_tai_1972);
}
#[test]
fn as_secs_and_nanos() {
const T_TAI_1972_SECS: i64 = 852_081_857;
const T_TAI_1972_NANOS: u32 = 678_000_000;
let t = Tai1972Time::new(T_TAI_1972_SECS, T_TAI_1972_NANOS).unwrap();
assert_eq!(t.as_secs(), T_TAI_1972_SECS);
assert_eq!(t.subsec_nanos(), T_TAI_1972_NANOS);
}
#[test]
fn as_unix_secs() {
const T_UNIX_SECS: i64 = 915_153_825;
const T_TAI_1972_SECS: i64 = 852_081_857;
const T_TAI_1972_NANOS: u32 = 678_000_000;
let t = Tai1972Time::new(T_TAI_1972_SECS, T_TAI_1972_NANOS).unwrap();
assert_eq!(t.as_unix_secs(32).unwrap(), T_UNIX_SECS);
}
#[test]
fn to_tai_time() {
let t_gps = GpsTime::new(599_189_038, 678_000_000).unwrap();
let t_tai_1972 = Tai1972Time::new(852_081_857, 678_000_000).unwrap();
let t: Tai1972Time = t_gps.to_tai_time().unwrap();
assert_eq!(t, t_tai_1972);
}
#[cfg(feature = "std")]
#[test]
fn to_system_time() {
const T_UNIX: Duration = Duration::new(915_153_825, 678_000_000);
let t_tai_1972 = Tai1972Time::new(852_081_857, 678_000_000).unwrap();
assert_eq!(
t_tai_1972.to_system_time(32).unwrap(),
std::time::SystemTime::UNIX_EPOCH + T_UNIX
);
}
#[cfg(feature = "chrono")]
#[test]
fn to_chrono_date_time() {
let t_tai_1972 = Tai1972Time::new(852_081_857, 678_000_000).unwrap();
assert_eq!(
t_tai_1972.to_chrono_date_time(32).unwrap(),
chrono::DateTime::parse_from_rfc3339("1999-01-01T01:23:45.678Z").unwrap()
);
}
#[test]
fn invalid_nanoseconds() {
assert_eq!(Tai1958Time::new(123, 1_000_000_000), None);
}
#[test]
fn duration_since_smoke() {
let t0 = Tai1972Time::new(100, 100_000_000).unwrap();
let t1 = Tai1972Time::new(123, 223_456_789).unwrap();
assert_eq!(
t1.checked_duration_since(t0),
Some(Duration::new(23, 123_456_789))
);
}
#[test]
fn duration_with_carry() {
let t0 = Tai1972Time::new(100, 200_000_000).unwrap();
let t1 = Tai1972Time::new(101, 100_000_000).unwrap();
assert_eq!(
t1.checked_duration_since(t0),
Some(Duration::new(0, 900_000_000))
);
}
#[test]
fn duration_since_extreme() {
const MIN_TIME: Tai1972Time = TaiTime::MIN;
const MAX_TIME: Tai1972Time = TaiTime::MAX;
assert_eq!(
MAX_TIME.checked_duration_since(MIN_TIME),
Some(Duration::new(u64::MAX, NANOS_PER_SEC - 1))
);
}
#[test]
fn duration_since_invalid() {
let t0 = Tai1972Time::new(100, 0).unwrap();
let t1 = Tai1972Time::new(99, 0).unwrap();
assert_eq!(t1.checked_duration_since(t0), None);
}
#[test]
fn add_duration_smoke() {
let t = Tai1972Time::new(-100, 100_000_000).unwrap();
let dt = Duration::new(400, 300_000_000);
assert_eq!(t + dt, Tai1972Time::new(300, 400_000_000).unwrap());
}
#[test]
fn add_duration_with_carry() {
let t = Tai1972Time::new(-100, 900_000_000).unwrap();
let dt1 = Duration::new(400, 100_000_000);
let dt2 = Duration::new(400, 300_000_000);
assert_eq!(t + dt1, Tai1972Time::new(301, 0).unwrap());
assert_eq!(t + dt2, Tai1972Time::new(301, 200_000_000).unwrap());
}
#[test]
fn add_duration_extreme() {
let t = Tai1972Time::new(i64::MIN, 0).unwrap();
let dt = Duration::new(u64::MAX, NANOS_PER_SEC - 1);
assert_eq!(
t + dt,
Tai1972Time::new(i64::MAX, NANOS_PER_SEC - 1).unwrap()
);
}
#[test]
#[should_panic]
fn add_duration_overflow() {
let t = Tai1972Time::new(i64::MIN, 1).unwrap();
let dt = Duration::new(u64::MAX, NANOS_PER_SEC - 1);
let _ = t + dt;
}
#[test]
fn sub_duration_smoke() {
let t = Tai1972Time::new(100, 500_000_000).unwrap();
let dt = Duration::new(400, 300_000_000);
assert_eq!(t - dt, Tai1972Time::new(-300, 200_000_000).unwrap());
}
#[test]
fn sub_duration_with_carry() {
let t = Tai1972Time::new(100, 100_000_000).unwrap();
let dt1 = Duration::new(400, 100_000_000);
let dt2 = Duration::new(400, 300_000_000);
assert_eq!(t - dt1, Tai1972Time::new(-300, 0).unwrap());
assert_eq!(t - dt2, Tai1972Time::new(-301, 800_000_000).unwrap());
}
#[test]
fn sub_duration_extreme() {
let t = Tai1972Time::new(i64::MAX, NANOS_PER_SEC - 1).unwrap();
let dt = Duration::new(u64::MAX, NANOS_PER_SEC - 1);
assert_eq!(t - dt, Tai1972Time::new(i64::MIN, 0).unwrap());
}
#[test]
#[should_panic]
fn sub_duration_overflow() {
let t = Tai1972Time::new(i64::MAX, NANOS_PER_SEC - 2).unwrap();
let dt = Duration::new(u64::MAX, NANOS_PER_SEC - 1);
let _ = t - dt;
}
#[cfg(feature = "chrono")]
#[test]
fn date_time_year_count() {
use chrono::NaiveDate;
const TEST_MIN_YEAR: i32 = -801;
const TEST_MAX_YEAR: i32 = 801;
const CHRONO_MIN_YEAR: i32 = -0x3ffff;
const CHRONO_MAX_YEAR: i32 = 0x3fffe;
let gps_chrono_epoch = NaiveDate::from_ymd_opt(1980, 1, 6)
.unwrap()
.and_hms_opt(0, 0, 19)
.unwrap();
for year in (-TEST_MIN_YEAR..=TEST_MAX_YEAR).chain([CHRONO_MIN_YEAR, CHRONO_MAX_YEAR]) {
let chrono_date_time = NaiveDate::from_ymd_opt(year, 1, 1)
.unwrap()
.and_hms_opt(0, 0, 0)
.unwrap();
let chrono_gps_timestamp = (chrono_date_time - gps_chrono_epoch).num_seconds();
let tai_gps_timestamp = GpsTime::try_from_date_time(year, 1, 1, 0, 0, 0, 0)
.unwrap()
.as_secs();
assert_eq!(tai_gps_timestamp, chrono_gps_timestamp);
let chrono_date_time = NaiveDate::from_ymd_opt(year, 12, 31)
.unwrap()
.and_hms_opt(23, 59, 59)
.unwrap();
let chrono_gps_timestamp = (chrono_date_time - gps_chrono_epoch).num_seconds();
let tai_gps_timestamp = GpsTime::try_from_date_time(year, 12, 31, 23, 59, 59, 0)
.unwrap()
.as_secs();
assert_eq!(tai_gps_timestamp, chrono_gps_timestamp);
}
}
#[cfg(feature = "chrono")]
#[test]
fn date_time_day_count() {
use chrono::{Datelike, NaiveDate};
const TEST_YEARS: [i32; 6] = [-3000, -500, -1, 600, 723, 2400];
let bdt_chrono_epoch = NaiveDate::from_ymd_opt(2006, 1, 1)
.unwrap()
.and_hms_opt(0, 0, 33)
.unwrap();
for year in TEST_YEARS {
let mut chrono_date_time = NaiveDate::from_ymd_opt(year, 1, 1)
.unwrap()
.and_hms_opt(0, 0, 0)
.unwrap();
while chrono_date_time.year() == year {
let chrono_bdt_timestamp = (chrono_date_time - bdt_chrono_epoch).num_seconds();
let tai_bdt_timestamp = BdtTime::try_from_date_time(
year,
chrono_date_time.month() as u8,
chrono_date_time.day() as u8,
0,
0,
0,
0,
)
.unwrap()
.as_secs();
assert_eq!(tai_bdt_timestamp, chrono_bdt_timestamp);
chrono_date_time += Duration::from_secs(86399);
let chrono_bdt_timestamp = (chrono_date_time - bdt_chrono_epoch).num_seconds();
let tai_bdt_timestamp = BdtTime::try_from_date_time(
year,
chrono_date_time.month() as u8,
chrono_date_time.day() as u8,
23,
59,
59,
0,
)
.unwrap()
.as_secs();
assert_eq!(tai_bdt_timestamp, chrono_bdt_timestamp);
chrono_date_time += Duration::from_secs(1);
}
}
}
#[test]
fn date_time_second_count() {
const TEST_DAY: u8 = 12;
const TEST_MONTH: u8 = 3;
const TEST_YEAR: i32 = -4567;
let mut timestamp =
Tai1958Time::try_from_date_time(TEST_YEAR, TEST_MONTH, TEST_DAY, 0, 0, 0, 0)
.unwrap()
.as_secs();
for hour in 0..=23 {
for min in 0..=59 {
for sec in 0..=59 {
let t = Tai1958Time::try_from_date_time(
TEST_YEAR, TEST_MONTH, TEST_DAY, hour, min, sec, 0,
)
.unwrap();
assert_eq!(t.as_secs(), timestamp);
timestamp += 1;
}
}
}
}
#[test]
fn date_time_string_roundtrip() {
const TEST_DATES: &[(&str, (i32, u8, u8, u8, u8, u8, u32))] = &[
(
"-2147483647-01-01 00:00:00",
(-2147483647, 1, 1, 0, 0, 0, 0),
),
("-0000-01-01T00:00:00", (0, 1, 1, 0, 0, 0, 0)),
(
"2000-02-29T12:23:45.000000001",
(2000, 2, 29, 12, 23, 45, 1),
),
(
"+2345-10-11 12:13:14.123",
(2345, 10, 11, 12, 13, 14, 123_000_000),
),
(
"2147483647-12-31 23:59:59.999999999",
(2147483647, 12, 31, 23, 59, 59, 999_999_999),
),
];
for (date_time_str, date_time) in TEST_DATES {
let (year, month, day, hour, min, sec, nano) = *date_time;
let t0: GstTime = date_time_str.parse().unwrap();
let t1: GpsTime = t0.to_string().parse().unwrap();
assert_eq!(
t1,
GpsTime::try_from_date_time(year, month, day, hour, min, sec, nano).unwrap()
);
}
}
#[test]
fn date_time_invalid() {
const TEST_DATES: &[&str] = &[
"123-01-01 00:00:00",
"-1500-02-29 00:00:00",
"2001-06-31 00:00:00",
"1234-01-00 00:00:00",
"1234-00-01 00:00:00",
"1234-13-01 00:00:00",
"5678-09-10 24:00:00",
"5678-09-10 00:60:00",
"5678-09-10 00:00:60",
];
for date_time_str in TEST_DATES {
assert!(date_time_str.parse::<MonotonicTime>().is_err());
}
}
#[cfg(feature = "serde")]
#[test]
fn deserialize_from_seq() {
use serde_json;
let data = r#"[987654321, 123456789]"#;
let t: GpsTime = serde_json::from_str(data).unwrap();
assert_eq!(t, GpsTime::new(987654321, 123456789).unwrap());
}
#[cfg(feature = "serde")]
#[test]
fn deserialize_from_map() {
use serde_json;
let data = r#"{"secs": 987654321, "nanos": 123456789}"#;
let t: GpsTime = serde_json::from_str(data).unwrap();
assert_eq!(t, GpsTime::new(987654321, 123456789).unwrap());
}
#[cfg(feature = "serde")]
#[test]
fn deserialize_invalid_nanos() {
use serde_json;
let data = r#"{"secs": 987654321, "nanos": 1000000000}"#;
let t: Result<GpsTime, serde_json::Error> = serde_json::from_str(data);
assert!(t.is_err())
}
#[cfg(feature = "serde")]
#[test]
fn serialize_roundtrip() {
use serde_json;
let t0 = GpsTime::new(987654321, 123456789).unwrap();
let data = serde_json::to_string(&t0).unwrap();
let t1: GpsTime = serde_json::from_str(&data).unwrap();
assert_eq!(t0, t1);
}
#[cfg(feature = "schemars")]
#[test]
fn generate_json_schema() {
let schema = schemars::schema_for!(MonotonicTime);
let schema = schema.as_object().unwrap();
for field in &["secs", "nanos"] {
assert!(schema.get("properties").unwrap().get(field).is_some());
assert!(
schema
.get("required")
.unwrap()
.as_array()
.unwrap()
.iter()
.any(|a| a.as_str() == Some(field))
);
}
}
}