#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(docsrs, feature(doc_auto_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))]
pub struct TaiTime<const EPOCH_REF: i64> {
secs: i64,
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) -> Self {
assert!(
subsec_nanos < NANOS_PER_SEC,
"invalid number of nanoseconds"
);
Self {
secs,
nanos: subsec_nanos,
}
}
#[cfg(all(feature = "tai_clock", target_os = "linux"))]
pub fn now() -> Self {
use core::mem::MaybeUninit;
let mut c_time: MaybeUninit<libc::timespec> = MaybeUninit::uninit();
let ret = unsafe { libc::clock_gettime(libc::CLOCK_TAI, c_time.as_mut_ptr()) };
assert_eq!(ret, 0);
let res = unsafe { c_time.assume_init() };
#[allow(clippy::useless_conversion)]
let secs: i64 = res.tv_sec.try_into().unwrap();
#[allow(clippy::useless_conversion)]
let subsec_nanos: u32 = res.tv_nsec.try_into().unwrap();
let t = MonotonicTime::new(secs, subsec_nanos);
t.to_tai_time()
}
#[cfg(feature = "std")]
pub fn now_from_utc(leap_secs: i64) -> Self {
Self::from_system_time(&std::time::SystemTime::now(), leap_secs)
}
pub const fn 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 {
assert!(
subsec_nanos < NANOS_PER_SEC,
"invalid number of nanoseconds"
);
if let Some(secs) = secs.checked_add(leap_secs) {
if let Some(secs) = secs.checked_sub(EPOCH_REF) {
return Self {
secs,
nanos: subsec_nanos,
};
}
}
panic!("overflow when converting timestamp");
}
#[cfg(feature = "std")]
pub fn from_system_time(system_time: &std::time::SystemTime, leap_secs: i64) -> Self {
let unix_time = system_time
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.unwrap();
Self::new(
leap_secs
.checked_sub(EPOCH_REF)
.expect("overflow when converting timestamp"),
0,
) + unix_time
}
#[cfg(feature = "chrono")]
pub const fn from_chrono_date_time<Tz: chrono::TimeZone>(
date_time: &chrono::DateTime<Tz>,
leap_secs: i64,
) -> Self {
let secs = date_time.timestamp();
let subsec_nanos = date_time.timestamp_subsec_nanos();
let (secs_carry, subsec_nanos) = if subsec_nanos < NANOS_PER_SEC {
(0, subsec_nanos)
} else {
(1, subsec_nanos - NANOS_PER_SEC)
};
if let Some(secs) = secs.checked_add(secs_carry) {
return Self::from_unix_timestamp(secs, subsec_nanos, leap_secs);
}
panic!("overflow when converting timestamp");
}
pub const fn as_secs(&self) -> i64 {
self.secs
}
pub const fn subsec_nanos(&self) -> u32 {
self.nanos
}
pub const fn to_unix_secs(&self, leap_secs: i64) -> i64 {
if let Some(secs) = self.try_to_unix_secs(leap_secs) {
return secs;
}
panic!("overflow when converting timestamp");
}
const fn try_to_unix_secs(&self, leap_secs: i64) -> Option<i64> {
if let Some(secs) = self.secs.checked_sub(leap_secs) {
if let Some(secs) = secs.checked_add(EPOCH_REF) {
return Some(secs);
}
}
None
}
pub const fn to_tai_time<const OTHER_EPOCH_REF: i64>(&self) -> TaiTime<OTHER_EPOCH_REF> {
TaiTime {
secs: (EPOCH_REF - OTHER_EPOCH_REF) + self.secs,
nanos: self.nanos,
}
}
#[cfg(feature = "std")]
pub fn to_system_time(&self, leap_secs: i64) -> Result<std::time::SystemTime, OutOfRangeError> {
let secs: u64 = self
.try_to_unix_secs(leap_secs)
.and_then(|secs| secs.try_into().ok())
.ok_or(OutOfRangeError(()))?;
std::time::SystemTime::UNIX_EPOCH
.checked_add(Duration::new(secs, self.subsec_nanos()))
.ok_or(OutOfRangeError(()))
}
#[cfg(feature = "chrono")]
pub fn to_chrono_date_time(
&self,
leap_secs: i64,
) -> Result<chrono::DateTime<chrono::Utc>, OutOfRangeError> {
self.try_to_unix_secs(leap_secs)
.and_then(|secs| chrono::DateTime::from_timestamp(secs, self.nanos))
.ok_or(OutOfRangeError(()))
}
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::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 {
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
)?;
if self.nanos != 0 {
write!(f, ".{:09}", self.nanos)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn equality() {
let t0 = Tai1972Time::new(123, 123_456_789);
let t1 = Tai1972Time::new(123, 123_456_789);
let t2 = Tai1972Time::new(123, 123_456_790);
let t3 = Tai1972Time::new(124, 123_456_789);
assert_eq!(t0, t1);
assert_ne!(t0, t2);
assert_ne!(t0, t3);
}
#[test]
fn ordering() {
let t0 = Tai1972Time::new(0, 1);
let t1 = Tai1972Time::new(1, 0);
assert!(t1 > t0);
}
#[test]
fn epoch_smoke() {
const T_UNIX_SECS: i64 = 1_234_567_890;
const T_TAI_1970: MonotonicTime = MonotonicTime::new(1_234_567_924, 123_456_789);
const T_TAI_1958: Tai1958Time = Tai1958Time::new(1_613_259_124, 123_456_789);
const T_TAI_1972: Tai1972Time = Tai1972Time::new(1_171_495_924, 123_456_789);
const T_GPS: GpsTime = GpsTime::new(918_603_105, 123_456_789);
const T_GST: GstTime = GstTime::new(299_287_905, 123_456_789);
const T_BDT: BdtTime = BdtTime::new(98_494_291, 123_456_789);
assert_eq!(T_TAI_1970.to_unix_secs(34), T_UNIX_SECS);
assert_eq!(T_TAI_1958.to_unix_secs(34), T_UNIX_SECS);
assert_eq!(T_TAI_1972.to_unix_secs(34), T_UNIX_SECS);
assert_eq!(T_GPS.to_unix_secs(34), T_UNIX_SECS);
assert_eq!(T_GST.to_unix_secs(34), T_UNIX_SECS);
assert_eq!(T_BDT.to_unix_secs(34), T_UNIX_SECS);
assert_eq!(T_TAI_1970.to_tai_time(), T_TAI_1958);
assert_eq!(T_TAI_1970.to_tai_time(), T_TAI_1970);
assert_eq!(T_TAI_1970.to_tai_time(), T_TAI_1972);
assert_eq!(T_TAI_1970.to_tai_time(), T_GPS);
assert_eq!(T_TAI_1970.to_tai_time(), T_GST);
assert_eq!(T_TAI_1970.to_tai_time(), T_BDT);
}
#[cfg(feature = "std")]
#[test]
fn now_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() {
const T_UNIX: Duration = Duration::new(978_352_496, 789_000_000);
const T_TAI_1972: Tai1972Time = Tai1972Time::new(915_280_528, 789_000_000);
let system_time = std::time::SystemTime::UNIX_EPOCH + T_UNIX;
let tai1792_time = Tai1972Time::from_system_time(&system_time, 32);
assert_eq!(tai1792_time, T_TAI_1972);
}
#[test]
fn from_unix_timestamp() {
const T_UNIX_SECS: i64 = 978_352_496;
const T_UNIX_NANOS: u32 = 789_000_000;
const T_TAI_1972: Tai1972Time = Tai1972Time::new(915_280_528, 789_000_000);
let tai1792_time = Tai1972Time::from_unix_timestamp(T_UNIX_SECS, T_UNIX_NANOS, 32);
assert_eq!(tai1792_time, T_TAI_1972);
}
#[cfg(feature = "chrono")]
#[test]
fn from_chrono_date_time() {
const T_TAI_1972: Tai1972Time = Tai1972Time::new(915_280_528, 789_000_000);
let chrono_date_time =
chrono::DateTime::parse_from_rfc3339("2001-01-01T12:34:56.789Z").unwrap();
let tai1792_time = Tai1972Time::from_chrono_date_time(&chrono_date_time, 32);
assert_eq!(tai1792_time, 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;
const T_TAI_1972: Tai1972Time = Tai1972Time::new(T_TAI_1972_SECS, T_TAI_1972_NANOS);
assert_eq!(T_TAI_1972.as_secs(), T_TAI_1972_SECS);
assert_eq!(T_TAI_1972.subsec_nanos(), T_TAI_1972_NANOS);
}
#[test]
fn to_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;
const T_TAI_1972: Tai1972Time = Tai1972Time::new(T_TAI_1972_SECS, T_TAI_1972_NANOS);
assert_eq!(T_TAI_1972.to_unix_secs(32), T_UNIX_SECS);
}
#[test]
fn to_tai_time() {
const T_GPS: GpsTime = GpsTime::new(599_189_038, 678_000_000);
const T_TAI_1972: Tai1972Time = Tai1972Time::new(852_081_857, 678_000_000);
let tai1792_time: Tai1972Time = T_GPS.to_tai_time();
assert_eq!(tai1792_time, T_TAI_1972);
}
#[cfg(feature = "std")]
#[test]
fn to_system_time() {
const T_UNIX: Duration = Duration::new(915_153_825, 678_000_000);
const T_TAI_1972: Tai1972Time = Tai1972Time::new(852_081_857, 678_000_000);
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() {
const T_TAI_1972: Tai1972Time = Tai1972Time::new(852_081_857, 678_000_000);
assert_eq!(
T_TAI_1972.to_chrono_date_time(32).unwrap(),
chrono::DateTime::parse_from_rfc3339("1999-01-01T01:23:45.678Z").unwrap()
);
}
#[test]
#[should_panic]
fn invalid_nanoseconds() {
Tai1958Time::new(123, 1_000_000_000);
}
#[test]
fn duration_since_smoke() {
let t0 = Tai1972Time::new(100, 100_000_000);
let t1 = Tai1972Time::new(123, 223_456_789);
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);
let t1 = Tai1972Time::new(101, 100_000_000);
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);
let t1 = Tai1972Time::new(99, 0);
assert_eq!(t1.checked_duration_since(t0), None);
}
#[test]
fn add_duration_smoke() {
let t = Tai1972Time::new(-100, 100_000_000);
let dt = Duration::new(400, 300_000_000);
assert_eq!(t + dt, Tai1972Time::new(300, 400_000_000));
}
#[test]
fn add_duration_with_carry() {
let t = Tai1972Time::new(-100, 900_000_000);
let dt1 = Duration::new(400, 100_000_000);
let dt2 = Duration::new(400, 300_000_000);
assert_eq!(t + dt1, Tai1972Time::new(301, 0));
assert_eq!(t + dt2, Tai1972Time::new(301, 200_000_000));
}
#[test]
fn add_duration_extreme() {
let t = Tai1972Time::new(i64::MIN, 0);
let dt = Duration::new(u64::MAX, NANOS_PER_SEC - 1);
assert_eq!(t + dt, Tai1972Time::new(i64::MAX, NANOS_PER_SEC - 1));
}
#[test]
#[should_panic]
fn add_duration_overflow() {
let t = Tai1972Time::new(i64::MIN, 1);
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);
let dt = Duration::new(400, 300_000_000);
assert_eq!(t - dt, Tai1972Time::new(-300, 200_000_000));
}
#[test]
fn sub_duration_with_carry() {
let t = Tai1972Time::new(100, 100_000_000);
let dt1 = Duration::new(400, 100_000_000);
let dt2 = Duration::new(400, 300_000_000);
assert_eq!(t - dt1, Tai1972Time::new(-300, 0));
assert_eq!(t - dt2, Tai1972Time::new(-301, 800_000_000));
}
#[test]
fn sub_duration_extreme() {
let t = Tai1972Time::new(i64::MAX, NANOS_PER_SEC - 1);
let dt = Duration::new(u64::MAX, NANOS_PER_SEC - 1);
assert_eq!(t - dt, Tai1972Time::new(i64::MIN, 0));
}
#[test]
#[should_panic]
fn sub_duration_overflow() {
let t = Tai1972Time::new(i64::MAX, NANOS_PER_SEC - 2);
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::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::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::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::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::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::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::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());
}
}
}