From 05b650385142fb06825e8dcfc2206b1c97ba633d Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 14 Mar 2024 10:13:29 +0100 Subject: [PATCH] UnixTimestamp now has nanosecond precision --- CHANGELOG.md | 14 ++ Cargo.toml | 5 +- src/time/cds.rs | 350 ++++++++++++++++++++++++++---------------------- src/time/cuc.rs | 106 +++++++-------- src/time/mod.rs | 137 ++++++++++++------- 5 files changed, 349 insertions(+), 263 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc87192..4fd96ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). # [unreleased] +Major API changes for the time API. If you are using the time API, it is strongly recommended +to check all the API changes. + ## Fixed - CUC timestamp was fixed to include leap second corrections because it is based on the TAI @@ -20,6 +23,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Added basic support conversions to the `time` library. Introduce new `chrono` and `timelib` feature gate +## Changed + +- `UnixTimestamp::new` renamed to `UnixTimestamp::new_checked`. +- `UnixTimestamp` now has a nanosecond subsecond precision. The `new` constructor now expects + nanoseconds as the second argument. +- Added new `UnixTimestamp::new_subsec_millis` and `UnixTimestamp::new_subsec_millis_checked` API + to still allow creating a timestamp with only millisecond subsecond resolution. +- `CcsdsTimeProvider` now has a new `subsecond_nanos` method in addition to a default + implementation for the `subsecond_millis` method. +- `CcsdsTimeProvider::date_time` renamed to `CcsdsTimeProvider::chrono_date_time` + # [v0.11.0-rc.0] 2024-03-04 ## Added diff --git a/Cargo.toml b/Cargo.toml index c0333c0..c0d6a8d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,8 +48,9 @@ optional = true version = "0.2" default-features = false -[dev-dependencies.postcard] -version = "1" +[dev-dependencies] +postcard = "1" +chrono = "0.4" [features] default = ["std"] diff --git a/src/time/cds.rs b/src/time/cds.rs index fcb3a72..f83a908 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -4,20 +4,40 @@ //! The core data structure to do this is the [TimeProvider] struct and the //! [get_dyn_time_provider_from_bytes] function to retrieve correct instances of the //! struct from a bytestream. -use super::*; use crate::private::Sealed; -#[cfg(feature = "alloc")] -use alloc::boxed::Box; -#[cfg(feature = "chrono")] -use chrono::Datelike; -#[cfg(feature = "alloc")] -use core::any::Any; +use crate::ByteConversionError; use core::cmp::Ordering; -use core::fmt::Debug; +use core::fmt::{Debug, Display, Formatter}; use core::ops::{Add, AddAssign}; use core::time::Duration; + use delegate::delegate; +#[cfg(feature = "std")] +use super::StdTimestampError; +#[cfg(feature = "std")] +use std::error::Error; +#[cfg(feature = "std")] +use std::time::{SystemTime, SystemTimeError}; + +#[cfg(feature = "chrono")] +use chrono::Datelike; + +#[cfg(feature = "alloc")] +use super::ccsds_time_code_from_p_field; +#[cfg(feature = "alloc")] +use alloc::boxed::Box; +#[cfg(feature = "alloc")] +use core::any::Any; + +#[cfg(feature = "serde")] +use serde::{Serialize, Deserialize}; + +use super::{ + ccsds_to_unix_days, unix_to_ccsds_days, CcsdsTimeCodes, CcsdsTimeProvider, TimeReader, + TimeWriter, TimestampError, UnixTimestamp, MS_PER_DAY, SECONDS_PER_DAY, +}; + /// Base value for the preamble field for a time field parser to determine the time field type. pub const P_FIELD_BASE: u8 = (CcsdsTimeCodes::Cds as u8) << 4; pub const MIN_CDS_FIELD_LEN: usize = 7; @@ -165,7 +185,6 @@ pub struct TimeProvider { pfield: u8, ccsds_days: DaysLen::FieldType, ms_of_day: u32, - // submillis_precision: SubmillisPrecision, submillis: u32, /// This is not strictly necessary but still cached because it significantly simplifies the /// calculation of [`DateTime`]. @@ -195,36 +214,47 @@ trait CdsConverter: CdsCommon { struct ConversionFromUnix { ccsds_days: u32, ms_of_day: u32, + submilis_prec: SubmillisPrecision, + submillis: u32, /// This is a side-product of the calculation of the CCSDS days. It is useful for /// re-calculating the datetime at a later point and therefore supplied as well. unix_days_seconds: i64, } impl ConversionFromUnix { - fn new(unix_seconds: i64, subsec_millis: u32) -> Result { + fn new( + unix_seconds: i64, + subsec_nanos: u32, + precision: SubmillisPrecision, + ) -> Result { let (unix_days, secs_of_day) = calc_unix_days_and_secs_of_day(unix_seconds); let ccsds_days = unix_to_ccsds_days(unix_days); - if ccsds_days == 0 && (secs_of_day > 0 || subsec_millis > 0) || ccsds_days < 0 { - let millis = if unix_seconds < 0 { - unix_seconds * 1000 - subsec_millis as i64 - } else { - unix_seconds * 1000 + subsec_millis as i64 - }; + if ccsds_days == 0 && (secs_of_day > 0 || subsec_nanos > 0) || ccsds_days < 0 { return Err(TimestampError::DateBeforeCcsdsEpoch( - Utc.timestamp_millis_opt(millis).unwrap(), + UnixTimestamp::new_checked(unix_seconds, subsec_nanos) + .expect("unix timestamp creation failed"), )); } + let ms_of_day = secs_of_day * 1000 + subsec_nanos / 10_u32.pow(6); + + let submillis = match precision { + SubmillisPrecision::Microseconds => (subsec_nanos / 1_000) % 1000, + SubmillisPrecision::Picoseconds => (subsec_nanos % 10_u32.pow(6)) * 1000, + _ => 0, + }; Ok(Self { ccsds_days: unix_to_ccsds_days(unix_days) as u32, - ms_of_day: secs_of_day * 1000 + subsec_millis, + ms_of_day, unix_days_seconds: unix_days * SECONDS_PER_DAY as i64, + submilis_prec: precision, + submillis, }) } } impl CdsCommon for ConversionFromUnix { fn submillis_precision(&self) -> SubmillisPrecision { - SubmillisPrecision::Absent + self.submilis_prec } fn ms_of_day(&self) -> u32 { @@ -236,7 +266,7 @@ impl CdsCommon for ConversionFromUnix { } fn submillis(&self) -> u32 { - 0 + self.submillis } } @@ -246,13 +276,13 @@ impl CdsConverter for ConversionFromUnix { } } /// Helper struct which generates fields for the CDS time provider from a datetime. -struct ConversionFromDatetime { +struct ConversionFromChronoDatetime { unix_conversion: ConversionFromUnix, submillis_prec: SubmillisPrecision, submillis: u32, } -impl CdsCommon for ConversionFromDatetime { +impl CdsCommon for ConversionFromChronoDatetime { fn submillis_precision(&self) -> SubmillisPrecision { self.submillis_prec } @@ -269,7 +299,7 @@ impl CdsCommon for ConversionFromDatetime { } } -impl CdsConverter for ConversionFromDatetime { +impl CdsConverter for ConversionFromChronoDatetime { delegate! {to self.unix_conversion { fn unix_days_seconds(&self) -> i64; }} } @@ -286,27 +316,38 @@ fn calc_unix_days_and_secs_of_day(unix_seconds: i64) -> (i64, u32) { (unix_days, secs_of_day as u32) } -impl ConversionFromDatetime { - fn new(dt: &DateTime) -> Result { +#[cfg(feature = "chrono")] +impl ConversionFromChronoDatetime { + fn new(dt: &chrono::DateTime) -> Result { Self::new_generic(dt, SubmillisPrecision::Absent) } - fn new_with_submillis_us_prec(dt: &DateTime) -> Result { + fn new_with_submillis_us_prec( + dt: &chrono::DateTime, + ) -> Result { Self::new_generic(dt, SubmillisPrecision::Microseconds) } - fn new_with_submillis_ps_prec(dt: &DateTime) -> Result { + fn new_with_submillis_ps_prec( + dt: &chrono::DateTime, + ) -> Result { Self::new_generic(dt, SubmillisPrecision::Picoseconds) } - fn new_generic(dt: &DateTime, prec: SubmillisPrecision) -> Result { + #[cfg(feature = "chrono")] + fn new_generic( + dt: &chrono::DateTime, + prec: SubmillisPrecision, + ) -> Result { // The CDS timestamp does not support timestamps before the CCSDS epoch. if dt.year() < 1958 { - return Err(TimestampError::DateBeforeCcsdsEpoch(*dt)); + return Err(TimestampError::DateBeforeCcsdsEpoch(UnixTimestamp::from( + *dt, + ))); } // The contained values in the conversion should be all positive now let unix_conversion = - ConversionFromUnix::new(dt.timestamp(), dt.timestamp_subsec_millis())?; + ConversionFromUnix::new(dt.timestamp(), dt.timestamp_subsec_nanos(), prec)?; let mut submillis = 0; match prec { SubmillisPrecision::Microseconds => { @@ -351,7 +392,8 @@ impl ConversionFromNow { let epoch = now.as_secs(); // This should always return a value with valid (non-negative) CCSDS days, // so it is okay to unwrap - let unix_conversion = ConversionFromUnix::new(epoch as i64, now.subsec_millis()).unwrap(); + let unix_conversion = + ConversionFromUnix::new(epoch as i64, now.subsec_nanos(), prec).unwrap(); let mut submillis = 0; match prec { @@ -589,20 +631,6 @@ impl TimeProvider { self.calc_unix_seconds(unix_days_seconds, ms_of_day); } - fn calc_ns_since_last_second(&self) -> u32 { - let mut ns_since_last_sec = (self.ms_of_day % 1000) * 10_u32.pow(6); - match self.submillis_precision() { - SubmillisPrecision::Microseconds => { - ns_since_last_sec += self.submillis() * 1000; - } - SubmillisPrecision::Picoseconds => { - ns_since_last_sec += self.submillis() / 1000; - } - _ => (), - } - ns_since_last_sec - } - #[inline] fn calc_unix_seconds(&mut self, mut unix_days_seconds: i64, ms_of_day: u32) { let seconds_of_day = (ms_of_day / 1000) as i64; @@ -611,35 +639,11 @@ impl TimeProvider { } else { unix_days_seconds += seconds_of_day; } - self.unix_stamp = UnixTimestamp::const_new(unix_days_seconds, (ms_of_day % 1000) as u16); - } - - #[cfg(feature = "chrono")] - fn calc_chrono_date_time( - &self, - ns_since_last_second: u32, - ) -> Option> { - assert!( - ns_since_last_second < 10_u32.pow(9), - "Invalid MS since last second" - ); - if let LocalResult::Single(val) = - Utc.timestamp_opt(self.unix_stamp.unix_seconds, ns_since_last_second) - { - return Some(val); + let mut subsec_nanos = (ms_of_day % 1000) * 10_u32.pow(6); + if let Some(precision) = self.precision_as_ns() { + subsec_nanos += precision; } - None - } - - #[cfg(feature = "timelib")] - fn calc_timelib_date_time( - &self, - ns_since_last_second: u32, - ) -> Result { - Ok( - time::OffsetDateTime::from_unix_timestamp(self.unix_stamp.unix_seconds)? - + time::Duration::nanoseconds(ns_since_last_second.into()), - ) + self.unix_stamp = UnixTimestamp::new(unix_days_seconds, subsec_nanos); } fn length_check(&self, buf: &[u8], len_as_bytes: usize) -> Result<(), TimestampError> { @@ -674,36 +678,43 @@ impl TimeProvider { Ok(provider) } + #[cfg(feature = "chrono")] fn from_dt_generic( - dt: &DateTime, + dt: &chrono::DateTime, days_len: LengthOfDaySegment, ) -> Result { - let conv_from_dt = ConversionFromDatetime::new(dt)?; + let conv_from_dt = ConversionFromChronoDatetime::new(dt)?; Self::generic_from_conversion(days_len, conv_from_dt) } + #[cfg(feature = "chrono")] fn from_dt_generic_us_prec( - dt: &DateTime, + dt: &chrono::DateTime, days_len: LengthOfDaySegment, ) -> Result { - let conv_from_dt = ConversionFromDatetime::new_with_submillis_us_prec(dt)?; + let conv_from_dt = ConversionFromChronoDatetime::new_with_submillis_us_prec(dt)?; Self::generic_from_conversion(days_len, conv_from_dt) } + #[cfg(feature = "chrono")] fn from_dt_generic_ps_prec( - dt: &DateTime, + dt: &chrono::DateTime, days_len: LengthOfDaySegment, ) -> Result { - let conv_from_dt = ConversionFromDatetime::new_with_submillis_ps_prec(dt)?; + let conv_from_dt = ConversionFromChronoDatetime::new_with_submillis_ps_prec(dt)?; Self::generic_from_conversion(days_len, conv_from_dt) } fn from_unix_generic( unix_stamp: &UnixTimestamp, days_len: LengthOfDaySegment, + submillis_prec: SubmillisPrecision, ) -> Result { - let conv_from_dt = - ConversionFromUnix::new(unix_stamp.unix_seconds, unix_stamp.subsecond_millis as u32)?; + let conv_from_dt = ConversionFromUnix::new( + unix_stamp.unix_seconds, + unix_stamp.subsecond_nanos, + submillis_prec, + )?; Self::generic_from_conversion(days_len, conv_from_dt) } @@ -818,7 +829,10 @@ impl TimeProvider { /// This function will return [TimestampError::DateBeforeCcsdsEpoch] or /// [TimestampError::Cds] if the time is before the CCSDS epoch (1958-01-01T00:00:00+00:00) /// or the CCSDS days value exceeds the allowed bit width (24 bits). - pub fn from_dt_with_u24_days(dt: &DateTime) -> Result { + #[cfg(feature = "chrono")] + pub fn from_dt_with_u24_days( + dt: &chrono::DateTime, + ) -> Result { Self::from_dt_generic(dt, LengthOfDaySegment::Long24Bits) } @@ -829,19 +843,26 @@ impl TimeProvider { /// This function will return [TimestampError::DateBeforeCcsdsEpoch] or /// [TimestampError::Cds] if the time is before the CCSDS epoch (1958-01-01T00:00:00+00:00) /// or the CCSDS days value exceeds the allowed bit width (24 bits). - pub fn from_unix_secs_with_u24_days( + pub fn from_unix_stamp_with_u24_days( unix_stamp: &UnixTimestamp, + submillis_prec: SubmillisPrecision, ) -> Result { - Self::from_unix_generic(unix_stamp, LengthOfDaySegment::Long24Bits) + Self::from_unix_generic(unix_stamp, LengthOfDaySegment::Long24Bits, submillis_prec) } /// Like [Self::from_dt_with_u24_days] but with microsecond sub-millisecond precision. - pub fn from_dt_with_u24_days_us_precision(dt: &DateTime) -> Result { + #[cfg(feature = "chrono")] + pub fn from_dt_with_u24_days_us_precision( + dt: &chrono::DateTime, + ) -> Result { Self::from_dt_generic_us_prec(dt, LengthOfDaySegment::Long24Bits) } /// Like [Self::from_dt_with_u24_days] but with picoseconds sub-millisecond precision. - pub fn from_dt_with_u24_days_ps_precision(dt: &DateTime) -> Result { + #[cfg(feature = "chrono")] + pub fn from_dt_with_u24_days_ps_precision( + dt: &chrono::DateTime, + ) -> Result { Self::from_dt_generic_ps_prec(dt, LengthOfDaySegment::Long24Bits) } @@ -898,7 +919,10 @@ impl TimeProvider { /// This function will return a [TimestampError::DateBeforeCcsdsEpoch] or a /// [TimestampError::Cds] if the time is before the CCSDS epoch (01-01-1958 00:00:00) or /// the CCSDS days value exceeds the allowed bit width (16 bits). - pub fn from_dt_with_u16_days(dt: &DateTime) -> Result { + #[cfg(feature = "chrono")] + pub fn from_dt_with_u16_days( + dt: &chrono::DateTime, + ) -> Result { Self::from_dt_generic(dt, LengthOfDaySegment::Short16Bits) } @@ -916,19 +940,26 @@ impl TimeProvider { /// This function will return [TimestampError::DateBeforeCcsdsEpoch] or /// [TimestampError::Cds] if the time is before the CCSDS epoch (1958-01-01T00:00:00+00:00) /// or the CCSDS days value exceeds the allowed bit width (24 bits). - pub fn from_unix_secs_with_u16_days( + pub fn from_unix_stamp_with_u16_days( unix_stamp: &UnixTimestamp, + submillis_prec: SubmillisPrecision, ) -> Result { - Self::from_unix_generic(unix_stamp, LengthOfDaySegment::Short16Bits) + Self::from_unix_generic(unix_stamp, LengthOfDaySegment::Short16Bits, submillis_prec) } /// Like [Self::from_dt_with_u16_days] but with microsecond sub-millisecond precision. - pub fn from_dt_with_u16_days_us_precision(dt: &DateTime) -> Result { + #[cfg(feature = "chrono")] + pub fn from_dt_with_u16_days_us_precision( + dt: &chrono::DateTime, + ) -> Result { Self::from_dt_generic_us_prec(dt, LengthOfDaySegment::Short16Bits) } /// Like [Self::from_dt_with_u16_days] but with picoseconds sub-millisecond precision. - pub fn from_dt_with_u16_days_ps_precision(dt: &DateTime) -> Result { + #[cfg(feature = "chrono")] + pub fn from_dt_with_u16_days_ps_precision( + dt: &chrono::DateTime, + ) -> Result { Self::from_dt_generic_ps_prec(dt, LengthOfDaySegment::Short16Bits) } @@ -1140,20 +1171,22 @@ impl AddAssign for TimeProvider { } } -impl TryFrom> for TimeProvider { +#[cfg(feature = "chrono")] +impl TryFrom> for TimeProvider { type Error = TimestampError; - fn try_from(dt: DateTime) -> Result { - let conversion = ConversionFromDatetime::new(&dt)?; + fn try_from(dt: chrono::DateTime) -> Result { + let conversion = ConversionFromChronoDatetime::new(&dt)?; Self::generic_from_conversion(LengthOfDaySegment::Short16Bits, conversion) } } -impl TryFrom> for TimeProvider { +#[cfg(feature = "chrono")] +impl TryFrom> for TimeProvider { type Error = TimestampError; - fn try_from(dt: DateTime) -> Result { - let conversion = ConversionFromDatetime::new(&dt)?; + fn try_from(dt: chrono::DateTime) -> Result { + let conversion = ConversionFromChronoDatetime::new(&dt)?; Self::generic_from_conversion(LengthOfDaySegment::Long24Bits, conversion) } } @@ -1176,26 +1209,14 @@ impl CcsdsTimeProvider for TimeProvider u16 { - self.unix_stamp.subsecond_millis + fn subsecond_nanos(&self) -> u32 { + self.unix_stamp.subsecond_nanos } #[inline] fn unix_stamp(&self) -> UnixTimestamp { self.unix_stamp } - - #[cfg(feature = "chrono")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "chrono")))] - fn chrono_date_time(&self) -> Option> { - self.calc_chrono_date_time(self.calc_ns_since_last_second()) - } - - #[cfg(feature = "timelib")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "timelib")))] - fn timelib_date_time(&self) -> Result { - self.calc_timelib_date_time(self.calc_ns_since_last_second()) - } } impl TimeReader for TimeProvider { @@ -1330,6 +1351,7 @@ impl TryFrom> for TimeProvider { mod tests { use super::*; use crate::time::TimestampError::{ByteConversion, InvalidTimeCode}; + use crate::time::{UnixTimestamp, DAYS_CCSDS_TO_UNIX, MS_PER_DAY}; use crate::ByteConversionError::{FromSliceTooSmall, ToSliceTooSmall}; use alloc::string::ToString; use chrono::{Datelike, NaiveDate, Timelike}; @@ -1345,13 +1367,13 @@ mod tests { unix_stamp.unix_seconds, (DAYS_CCSDS_TO_UNIX * SECONDS_PER_DAY as i32) as i64 ); - let subsecond_millis = unix_stamp.subsecond_millis; + let subsecond_millis = unix_stamp.subsecond_nanos; assert_eq!(subsecond_millis, 0); assert_eq!( time_stamper.submillis_precision(), SubmillisPrecision::Absent ); - assert_eq!(time_stamper.subsecond_millis(), 0); + assert_eq!(time_stamper.subsecond_nanos(), 0); assert_eq!(time_stamper.ccdsd_time_code(), CcsdsTimeCodes::Cds); assert_eq!( time_stamper.p_field(), @@ -1382,8 +1404,10 @@ mod tests { assert_eq!(date_time.minute(), 0); assert_eq!(date_time.second(), 0); let time_stamper = TimeProvider::new_with_u16_days((-DAYS_CCSDS_TO_UNIX) as u16, 40); + assert_eq!(time_stamper.subsecond_nanos(), 40 * 10_u32.pow(6)); assert_eq!(time_stamper.subsecond_millis(), 40); let time_stamper = TimeProvider::new_with_u16_days((-DAYS_CCSDS_TO_UNIX) as u16, 1040); + assert_eq!(time_stamper.subsecond_nanos(), 40 * 10_u32.pow(6)); assert_eq!(time_stamper.subsecond_millis(), 40); } @@ -1565,7 +1589,7 @@ mod tests { fn generic_now_test( timestamp_now: TimeProvider, - compare_stamp: DateTime, + compare_stamp: chrono::DateTime, ) { let dt = timestamp_now.chrono_date_time().unwrap(); if compare_stamp.year() > dt.year() { @@ -1591,35 +1615,35 @@ mod tests { #[test] fn test_time_now() { let timestamp_now = TimeProvider::from_now_with_u16_days().unwrap(); - let compare_stamp = Utc::now(); + let compare_stamp = chrono::Utc::now(); generic_now_test(timestamp_now, compare_stamp); } #[test] fn test_time_now_us_prec() { let timestamp_now = TimeProvider::from_now_with_u16_days_us_precision().unwrap(); - let compare_stamp = Utc::now(); + let compare_stamp = chrono::Utc::now(); generic_now_test(timestamp_now, compare_stamp); } #[test] fn test_time_now_ps_prec() { let timestamp_now = TimeProvider::from_now_with_u16_days_ps_precision().unwrap(); - let compare_stamp = Utc::now(); + let compare_stamp = chrono::Utc::now(); generic_now_test(timestamp_now, compare_stamp); } #[test] fn test_time_now_ps_prec_u16_days() { let timestamp_now = TimeProvider::from_now_with_u16_days_ps_precision().unwrap(); - let compare_stamp = Utc::now(); + let compare_stamp = chrono::Utc::now(); generic_now_test(timestamp_now, compare_stamp); } #[test] fn test_time_now_ps_prec_u24_days() { let timestamp_now = TimeProvider::from_now_with_u24_days_ps_precision().unwrap(); - let compare_stamp = Utc::now(); + let compare_stamp = chrono::Utc::now(); generic_now_test(timestamp_now, compare_stamp); } @@ -1743,19 +1767,19 @@ mod tests { assert_eq!(stamp_deserialized.submillis(), 5e8 as u32); } - fn generic_dt_case_0_no_prec(subsec_millis: u32) -> DateTime { + fn generic_dt_case_0_no_prec(subsec_millis: u32) -> chrono::DateTime { NaiveDate::from_ymd_opt(2023, 1, 14) .unwrap() .and_hms_milli_opt(16, 49, 30, subsec_millis) .unwrap() - .and_local_timezone(Utc) + .and_local_timezone(chrono::Utc) .unwrap() } fn generic_check_dt_case_0( time_provider: &TimeProvider, subsec_millis: u32, - datetime_utc: DateTime, + datetime_utc: chrono::DateTime, ) { // https://www.timeanddate.com/date/durationresult.html?d1=01&m1=01&y1=1958&d2=14&m2=01&y2=2023 // Leap years need to be accounted for as well. @@ -1790,21 +1814,21 @@ mod tests { assert_eq!(time_provider, time_provider_2); } - fn generic_dt_case_1_us_prec(subsec_millis: u32) -> DateTime { + fn generic_dt_case_1_us_prec(subsec_millis: u32) -> chrono::DateTime { // 250 ms + 500 us let subsec_micros = subsec_millis * 1000 + 500; NaiveDate::from_ymd_opt(2023, 1, 14) .unwrap() .and_hms_micro_opt(16, 49, 30, subsec_micros) .unwrap() - .and_local_timezone(Utc) + .and_local_timezone(chrono::Utc) .unwrap() } fn generic_check_dt_case_1_us_prec( time_provider: &TimeProvider, subsec_millis: u32, - datetime_utc: DateTime, + datetime_utc: chrono::DateTime, ) { // https://www.timeanddate.com/date/durationresult.html?d1=01&m1=01&y1=1958&d2=14&m2=01&y2=2023 // Leap years need to be accounted for as well. @@ -1839,7 +1863,7 @@ mod tests { generic_check_dt_case_1_us_prec(&time_provider, subsec_millis, datetime_utc); } - fn generic_dt_case_2_ps_prec(subsec_millis: u32) -> (DateTime, u32) { + fn generic_dt_case_2_ps_prec(subsec_millis: u32) -> (chrono::DateTime, u32) { // 250 ms + 500 us let subsec_nanos = subsec_millis * 1000 * 1000 + 500 * 1000; let submilli_nanos = subsec_nanos % 10_u32.pow(6); @@ -1848,7 +1872,7 @@ mod tests { .unwrap() .and_hms_nano_opt(16, 49, 30, subsec_nanos) .unwrap() - .and_local_timezone(Utc) + .and_local_timezone(chrono::Utc) .unwrap(), submilli_nanos, ) @@ -1858,7 +1882,7 @@ mod tests { time_provider: &TimeProvider, subsec_millis: u32, submilli_nanos: u32, - datetime_utc: DateTime, + datetime_utc: chrono::DateTime, ) { // https://www.timeanddate.com/date/durationresult.html?d1=01&m1=01&y1=1958&d2=14&m2=01&y2=2023 // Leap years need to be accounted for as well. @@ -1907,10 +1931,10 @@ mod tests { fn test_creation_from_unix_stamp_0_u16_days() { let unix_secs = 0; let subsec_millis = 0; - let time_provider = TimeProvider::from_unix_secs_with_u16_days(&UnixTimestamp::const_new( - unix_secs, - subsec_millis, - )) + let time_provider = TimeProvider::from_unix_stamp_with_u16_days( + &UnixTimestamp::new(unix_secs, subsec_millis), + SubmillisPrecision::Absent, + ) .expect("creating provider from unix stamp failed"); assert_eq!(time_provider.ccsds_days, -DAYS_CCSDS_TO_UNIX as u16) } @@ -1919,10 +1943,10 @@ mod tests { fn test_creation_from_unix_stamp_0_u24_days() { let unix_secs = 0; let subsec_millis = 0; - let time_provider = TimeProvider::from_unix_secs_with_u24_days(&UnixTimestamp::const_new( - unix_secs, - subsec_millis, - )) + let time_provider = TimeProvider::from_unix_stamp_with_u24_days( + &UnixTimestamp::new(unix_secs, subsec_millis), + SubmillisPrecision::Absent, + ) .expect("creating provider from unix stamp failed"); assert_eq!(time_provider.ccsds_days, (-DAYS_CCSDS_TO_UNIX) as u32) } @@ -1934,10 +1958,13 @@ mod tests { .unwrap() .and_hms_milli_opt(16, 49, 30, subsec_millis) .unwrap() - .and_local_timezone(Utc) + .and_local_timezone(chrono::Utc) .unwrap(); - let time_provider = TimeProvider::from_unix_secs_with_u16_days(&datetime_utc.into()) - .expect("creating provider from unix stamp failed"); + let time_provider = TimeProvider::from_unix_stamp_with_u16_days( + &datetime_utc.into(), + SubmillisPrecision::Absent, + ) + .expect("creating provider from unix stamp failed"); // https://www.timeanddate.com/date/durationresult.html?d1=01&m1=01&y1=1958&d2=14&m2=01&y2=2023 // Leap years need to be accounted for as well. assert_eq!(time_provider.ccsds_days, 23754); @@ -1953,10 +1980,10 @@ mod tests { fn test_creation_0_ccsds_days() { let unix_secs = DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64; let subsec_millis = 0; - let time_provider = TimeProvider::from_unix_secs_with_u16_days(&UnixTimestamp::const_new( - unix_secs, - subsec_millis, - )) + let time_provider = TimeProvider::from_unix_stamp_with_u16_days( + &UnixTimestamp::new(unix_secs, subsec_millis), + SubmillisPrecision::Absent, + ) .expect("creating provider from unix stamp failed"); assert_eq!(time_provider.ccsds_days, 0) } @@ -1965,10 +1992,10 @@ mod tests { fn test_invalid_creation_from_unix_stamp_days_too_large() { let invalid_unix_secs: i64 = (u16::MAX as i64 + 1) * SECONDS_PER_DAY as i64; let subsec_millis = 0; - match TimeProvider::from_unix_secs_with_u16_days(&UnixTimestamp::const_new( - invalid_unix_secs, - subsec_millis, - )) { + match TimeProvider::from_unix_stamp_with_u16_days( + &UnixTimestamp::new(invalid_unix_secs, subsec_millis), + SubmillisPrecision::Absent, + ) { Ok(_) => { panic!("creation should not succeed") } @@ -1992,21 +2019,26 @@ mod tests { // precisely 31-12-1957 23:59:55 let unix_secs = DAYS_CCSDS_TO_UNIX * SECONDS_PER_DAY as i32 - 5; let subsec_millis = 0; - match TimeProvider::from_unix_secs_with_u16_days(&UnixTimestamp::const_new( - unix_secs as i64, - subsec_millis, - )) { + match TimeProvider::from_unix_stamp_with_u16_days( + &UnixTimestamp::new(unix_secs as i64, subsec_millis), + SubmillisPrecision::Absent, + ) { Ok(_) => { panic!("creation should not succeed") } Err(e) => { if let TimestampError::DateBeforeCcsdsEpoch(dt) = e { - assert_eq!(dt.year(), 1957); - assert_eq!(dt.month(), 12); - assert_eq!(dt.day(), 31); - assert_eq!(dt.hour(), 23); - assert_eq!(dt.minute(), 59); - assert_eq!(dt.second(), 55); + let dt = dt.as_date_time(); + if let chrono::LocalResult::Single(dt) = dt { + assert_eq!(dt.year(), 1957); + assert_eq!(dt.month(), 12); + assert_eq!(dt.day(), 31); + assert_eq!(dt.hour(), 23); + assert_eq!(dt.minute(), 59); + assert_eq!(dt.second(), 55); + } else { + panic!("unexpected error {}", e) + } } else { panic!("unexpected error {}", e) } @@ -2222,12 +2254,12 @@ mod tests { .unwrap() .and_hms_milli_opt(23, 59, 59, 999) .unwrap() - .and_local_timezone(Utc) + .and_local_timezone(chrono::Utc) .unwrap(); let time_provider = TimeProvider::from_dt_with_u24_days(&datetime_utc); assert!(time_provider.is_err()); if let TimestampError::DateBeforeCcsdsEpoch(dt) = time_provider.unwrap_err() { - assert_eq!(dt, datetime_utc); + assert_eq!(dt, datetime_utc.into()); } } diff --git a/src/time/cuc.rs b/src/time/cuc.rs index 382ec84..db855e5 100644 --- a/src/time/cuc.rs +++ b/src/time/cuc.rs @@ -2,15 +2,29 @@ //! [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf) section 3.2 . //! //! The core data structure to do this is the [TimeProviderCcsdsEpoch] struct. -use super::*; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use core::fmt::{Debug, Display, Formatter}; +use core::ops::{Add, AddAssign}; +use core::time::Duration; +use core::u64; + +use crate::ByteConversionError; + +use super::{CcsdsTimeCodes, TimestampError, unix_epoch_to_ccsds_epoch, UnixTimestamp, ccsds_epoch_to_unix_epoch, TimeReader, TimeWriter, CcsdsTimeProvider}; +#[cfg(feature = "std")] +use super::StdTimestampError; +#[cfg(feature = "std")] +use std::error::Error; +#[cfg(feature = "std")] +use std::time::SystemTime; #[cfg(feature = "chrono")] use chrono::Datelike; -use core::fmt::Debug; -use core::ops::{Add, AddAssign}; -use core::time::Duration; -use core::u64; +#[cfg(feature = "alloc")] +use super::ccsds_time_code_from_p_field; const MIN_CUC_LEN: usize = 2; @@ -301,6 +315,7 @@ impl TimeProviderCcsdsEpoch { #[cfg(feature = "std")] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] pub fn update_from_now(&mut self) -> Result<(), StdTimestampError> { + let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; self.counter.1 = unix_epoch_to_ccsds_epoch(now.as_secs() as i64) as u32; self.counter @@ -325,7 +340,9 @@ impl TimeProviderCcsdsEpoch { ) -> Result { // Year before CCSDS epoch is invalid. if dt.year() < 1958 { - return Err(TimestampError::DateBeforeCcsdsEpoch(*dt)); + return Err(TimestampError::DateBeforeCcsdsEpoch(UnixTimestamp::from( + *dt, + ))); } let counter = dt .timestamp() @@ -349,9 +366,7 @@ impl TimeProviderCcsdsEpoch { let ccsds_epoch = unix_epoch_to_ccsds_epoch(unix_stamp.unix_seconds); // Negative CCSDS epoch is invalid. if ccsds_epoch < 0 { - return Err(TimestampError::DateBeforeCcsdsEpoch( - unix_stamp.as_date_time().unwrap(), - )); + return Err(TimestampError::DateBeforeCcsdsEpoch(*unix_stamp)); } ccsds_epoch .checked_add(i64::from(leap_seconds)) @@ -682,43 +697,16 @@ impl CcsdsTimeProvider for TimeProviderCcsdsEpoch { self.unix_seconds() } - fn subsecond_millis(&self) -> u16 { + fn subsecond_nanos(&self) -> u32 { if let Some(fractions) = self.fractions { if fractions.0 == FractionalResolution::Seconds { return 0; } // Rounding down here is the correct approach. - return (convert_fractional_part_to_ns(fractions) / 10_u32.pow(6) as u64) as u16; + return convert_fractional_part_to_ns(fractions) as u32; } 0 } - - #[cfg(feature = "chrono")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "chrono")))] - fn chrono_date_time(&self) -> Option> { - let mut ns = 0; - if let Some(fractional_part) = self.fractions { - ns = convert_fractional_part_to_ns(fractional_part); - } - if let LocalResult::Single(res) = chrono::Utc.timestamp_opt(self.unix_seconds(), ns as u32) - { - return Some(res); - } - None - } - - #[cfg(feature = "timelib")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "timelib")))] - fn timelib_date_time(&self) -> Result { - let mut ns = 0; - if let Some(fractional_part) = self.fractions { - ns = convert_fractional_part_to_ns(fractional_part); - } - Ok( - time::OffsetDateTime::from_unix_timestamp(self.unix_seconds())? - + time::Duration::nanoseconds(ns as i64), - ) - } } fn get_provider_values_after_duration_addition( @@ -818,9 +806,11 @@ impl Add for &TimeProviderCcsdsEpoch { #[cfg(test)] mod tests { + use crate::time::{UnixTimestamp, DAYS_CCSDS_TO_UNIX, SECONDS_PER_DAY}; + use super::*; use alloc::string::ToString; - use chrono::{Datelike, Timelike}; + use chrono::{Datelike, TimeZone, Timelike}; #[allow(unused_imports)] use std::println; @@ -840,14 +830,14 @@ mod tests { let fractions = zero_cuc.width_fractions_pair(); assert!(fractions.is_none()); let dt = zero_cuc.chrono_date_time(); - assert!(dt.is_some()); - let dt = dt.unwrap(); - assert_eq!(dt.year(), 1958); - assert_eq!(dt.month(), 1); - assert_eq!(dt.day(), 1); - assert_eq!(dt.hour(), 0); - assert_eq!(dt.minute(), 0); - assert_eq!(dt.second(), 0); + if let chrono::LocalResult::Single(dt) = dt { + assert_eq!(dt.year(), 1958); + assert_eq!(dt.month(), 1); + assert_eq!(dt.day(), 1); + assert_eq!(dt.hour(), 0); + assert_eq!(dt.minute(), 0); + assert_eq!(dt.second(), 0); + } } #[test] @@ -862,7 +852,7 @@ mod tests { let zero_cuc = zero_cuc.unwrap(); let res = zero_cuc.write_to_bytes(&mut buf); assert!(res.is_ok()); - assert_eq!(zero_cuc.subsecond_millis(), 0); + assert_eq!(zero_cuc.subsecond_nanos(), 0); assert_eq!(zero_cuc.len_as_bytes(), 5); assert_eq!(pfield_len(buf[0]), 1); let written = res.unwrap(); @@ -880,17 +870,19 @@ mod tests { #[test] fn test_datetime_now() { - let now = Utc::now(); + let now = chrono::Utc::now(); let cuc_now = TimeProviderCcsdsEpoch::from_now(FractionalResolution::SixtyNs, LEAP_SECONDS); assert!(cuc_now.is_ok()); let cuc_now = cuc_now.unwrap(); let dt_opt = cuc_now.chrono_date_time(); - assert!(dt_opt.is_some()); - let dt = dt_opt.unwrap(); - let diff = dt - now; - assert!(diff.num_milliseconds() < 1000); - println!("datetime from cuc: {}", dt); - println!("datetime now: {}", now); + if let chrono::LocalResult::Single(dt) = dt_opt { + let diff = dt - now; + assert!(diff.num_milliseconds() < 1000); + println!("datetime from cuc: {}", dt); + println!("datetime now: {}", now); + } else { + panic!("date time creation from now failed") + } } #[test] @@ -1316,7 +1308,7 @@ mod tests { #[test] fn test_from_dt() { - let dt = Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap(); + let dt = chrono::Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap(); let cuc = TimeProviderCcsdsEpoch::from_chrono_date_time( &dt, FractionalResolution::Seconds, @@ -1335,7 +1327,7 @@ mod tests { #[test] fn from_unix_stamp() { - let unix_stamp = UnixTimestamp::new(0, 0).unwrap(); + let unix_stamp = UnixTimestamp::new(0, 0); let cuc = TimeProviderCcsdsEpoch::from_unix_stamp( &unix_stamp, FractionalResolution::Seconds, diff --git a/src/time/mod.rs b/src/time/mod.rs index c5d4734..004772d 100644 --- a/src/time/mod.rs +++ b/src/time/mod.rs @@ -1,5 +1,6 @@ //! CCSDS Time Code Formats according to [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf) use crate::ByteConversionError; +#[cfg(feature = "chrono")] use chrono::{DateTime, LocalResult, TimeZone, Utc}; use core::cmp::Ordering; use core::fmt::{Display, Formatter}; @@ -13,6 +14,7 @@ use num_traits::float::FloatCore; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; + #[cfg(feature = "std")] use std::error::Error; #[cfg(feature = "std")] @@ -68,7 +70,7 @@ pub enum TimestampError { ByteConversion(ByteConversionError), Cds(cds::CdsError), Cuc(cuc::CucError), - DateBeforeCcsdsEpoch(DateTime), + DateBeforeCcsdsEpoch(UnixTimestamp), CustomEpochNotSupported, } @@ -91,7 +93,7 @@ impl Display for TimestampError { write!(f, "time stamp: {e}") } TimestampError::DateBeforeCcsdsEpoch(e) => { - write!(f, "datetime with date before ccsds epoch: {e}") + write!(f, "datetime with date before ccsds epoch: {e:?}") } TimestampError::CustomEpochNotSupported => { write!(f, "custom epochs are not supported") @@ -231,59 +233,101 @@ pub trait CcsdsTimeProvider { fn ccdsd_time_code(&self) -> CcsdsTimeCodes; fn unix_seconds(&self) -> i64; - fn subsecond_millis(&self) -> u16; + fn subsecond_nanos(&self) -> u32; + + fn subsecond_millis(&self) -> u16 { + (self.subsecond_nanos() / 1_000_000) as u16 + } + fn unix_stamp(&self) -> UnixTimestamp { - UnixTimestamp::const_new(self.unix_seconds(), self.subsecond_millis()) + UnixTimestamp::new(self.unix_seconds(), self.subsecond_nanos()) } #[cfg(feature = "chrono")] #[cfg_attr(doc_cfg, doc(cfg(feature = "chrono")))] - fn chrono_date_time(&self) -> Option>; + fn chrono_date_time(&self) -> chrono::LocalResult> { + chrono::Utc.timestamp_opt(self.unix_seconds(), self.subsecond_nanos()) + } #[cfg(feature = "timelib")] #[cfg_attr(doc_cfg, doc(cfg(feature = "timelib")))] - fn timelib_date_time(&self) -> Result; + fn timelib_date_time(&self) -> Result { + Ok( + time::OffsetDateTime::from_unix_timestamp(self.unix_seconds())? + + time::Duration::nanoseconds(self.subsecond_nanos().into()), + ) + } } /// UNIX timestamp: Elapsed seconds since 1970-01-01T00:00:00+00:00. /// /// Also can optionally include subsecond millisecond for greater accuracy. +/// It is assumed that leap second correction was already applied and the reference time is UTC. #[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct UnixTimestamp { pub unix_seconds: i64, - subsecond_millis: u16, + subsecond_nanos: u32, } impl UnixTimestamp { - /// Returns [None] if the subsecond millisecond value is larger than 999. - pub fn new(unix_seconds: i64, subsec_millis: u16) -> Option { + /// Returns [None] if the subsecond nanosecond value is invalid (larger than fraction of a + /// second) + pub fn new_checked(unix_seconds: i64, subsec_nanos: u32) -> Option { + if subsec_nanos > 1_000_000_000 - 1 { + return None; + } + Some(Self::new(unix_seconds, subsec_nanos)) + } + + /// Returns [None] if the subsecond millisecond value is invalid (larger than fraction of a + /// second) + pub fn new_subsec_millis_checked(unix_seconds: i64, subsec_millis: u16) -> Option { if subsec_millis > 999 { return None; } - Some(Self::const_new(unix_seconds, subsec_millis)) + Self::new_checked(unix_seconds, subsec_millis as u32 * 1_000_000) } - /// Like [Self::new] but const. Panics if the subsecond value is larger than 999. - pub const fn const_new(unix_seconds: i64, subsecond_millis: u16) -> Self { - if subsecond_millis > 999 { - panic!("subsec milliseconds exceeds 999"); + /// This function will panic if the subsecond value is larger than the fraction of a second. + /// Use [new_checked] if you want to handle this case without a panic. + pub const fn new(unix_seconds: i64, subsecond_nanos: u32) -> Self { + // This could actually be used to model leap seconds, but we will ignore that for now. + if subsecond_nanos > 1_000_000_000 - 1 { + panic!("invalid subsecond nanos value"); } Self { unix_seconds, - subsecond_millis, + subsecond_nanos, + } + } + + /// This function will panic if the subsecond value is larger than the fraction of a second. + /// Use [new_subsecond_millis_checked] if you want to handle this case without a panic. + pub const fn new_subsec_millis(unix_seconds: i64, subsecond_millis: u16) -> Self { + // This could actually be used to model leap seconds, but we will ignore that for now. + if subsecond_millis > 999 { + panic!("invalid subsecond millisecond value"); + } + Self { + unix_seconds, + subsecond_nanos: subsecond_millis as u32 * 1_000_000, } } pub fn new_only_seconds(unix_seconds: i64) -> Self { Self { unix_seconds, - subsecond_millis: 0, + subsecond_nanos: 0, } } pub fn subsecond_millis(&self) -> u16 { - self.subsecond_millis + (self.subsecond_nanos / 1_000_000) as u16 + } + + pub fn subsecond_nanos(&self) -> u32 { + self.subsecond_nanos } #[cfg(feature = "std")] @@ -291,19 +335,16 @@ impl UnixTimestamp { pub fn from_now() -> Result { let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; let epoch = now.as_secs(); - Ok(Self::const_new(epoch as i64, now.subsec_millis() as u16)) + Ok(Self::new(epoch as i64, now.subsec_nanos())) } #[inline] pub fn unix_seconds_f64(&self) -> f64 { - self.unix_seconds as f64 + (self.subsecond_millis as f64 / 1000.0) + self.unix_seconds as f64 + (self.subsecond_nanos as f64 / 1_000_000_000.0) } pub fn as_date_time(&self) -> LocalResult> { - Utc.timestamp_opt( - self.unix_seconds, - self.subsecond_millis as u32 * 10_u32.pow(6), - ) + Utc.timestamp_opt(self.unix_seconds, self.subsecond_nanos) } // Calculate the difference in milliseconds between two UnixTimestamps @@ -313,16 +354,16 @@ impl UnixTimestamp { let milliseconds_difference = seconds_difference * 1000; // Calculate the difference in subsecond milliseconds directly - let subsecond_difference = self.subsecond_millis as i64 - other.subsecond_millis as i64; + let subsecond_difference_nanos = self.subsecond_nanos as i64 - other.subsecond_nanos as i64; // Combine the differences - milliseconds_difference + subsecond_difference + milliseconds_difference + (subsecond_difference_nanos / 1_000_000) } } impl From> for UnixTimestamp { fn from(value: DateTime) -> Self { - Self::const_new(value.timestamp(), value.timestamp_subsec_millis() as u16) + Self::new(value.timestamp(), value.timestamp_subsec_nanos()) } } @@ -395,7 +436,7 @@ fn get_new_stamp_after_addition( current_stamp: &UnixTimestamp, duration: Duration, ) -> UnixTimestamp { - let mut new_subsec_millis = current_stamp.subsecond_millis() + duration.subsec_millis() as u16; + let mut new_subsec_nanos = current_stamp.subsecond_nanos() + duration.subsec_nanos(); let mut new_unix_seconds = current_stamp.unix_seconds; let mut increment_seconds = |value: u32| { if new_unix_seconds < 0 { @@ -408,8 +449,8 @@ fn get_new_stamp_after_addition( .expect("new unix seconds would exceed i64::MAX"); } }; - if new_subsec_millis >= 1000 { - new_subsec_millis -= 1000; + if new_subsec_nanos >= 1_000_000_000 { + new_subsec_nanos -= 1_000_000_000; increment_seconds(1); } increment_seconds( @@ -418,7 +459,7 @@ fn get_new_stamp_after_addition( .try_into() .expect("duration seconds exceeds u32::MAX"), ); - UnixTimestamp::const_new(new_unix_seconds, new_subsec_millis) + UnixTimestamp::new(new_unix_seconds, new_subsec_nanos) } /// Please note that this operation will panic on the following conditions: @@ -457,10 +498,15 @@ impl Add for &UnixTimestamp { mod tests { use alloc::string::ToString; use chrono::{Datelike, Timelike}; - use std::format; + use std::{format, println}; use super::{cuc::CucError, *}; + #[allow(dead_code)] + const UNIX_STAMP_CONST: UnixTimestamp = UnixTimestamp::new(5, 999_999_999); + #[allow(dead_code)] + const UNIX_STAMP_CONST_2: UnixTimestamp = UnixTimestamp::new_subsec_millis(5, 999); + #[test] fn test_days_conversion() { assert_eq!(unix_to_ccsds_days(DAYS_CCSDS_TO_UNIX.into()), 0); @@ -506,17 +552,18 @@ mod tests { #[test] fn basic_float_unix_stamp_test() { - let stamp = UnixTimestamp::new(500, 600).unwrap(); + let stamp = UnixTimestamp::new_subsec_millis_checked(500, 600).unwrap(); assert_eq!(stamp.unix_seconds, 500); let subsec_millis = stamp.subsecond_millis(); assert_eq!(subsec_millis, 600); + println!("{:?}", (500.6 - stamp.unix_seconds_f64()).to_string()); assert!((500.6 - stamp.unix_seconds_f64()).abs() < 0.0001); } #[test] fn test_ord_larger() { let stamp0 = UnixTimestamp::new_only_seconds(5); - let stamp1 = UnixTimestamp::new(5, 500).unwrap(); + let stamp1 = UnixTimestamp::new_subsec_millis_checked(5, 500).unwrap(); let stamp2 = UnixTimestamp::new_only_seconds(6); assert!(stamp1 > stamp0); assert!(stamp2 > stamp0); @@ -526,7 +573,7 @@ mod tests { #[test] fn test_ord_smaller() { let stamp0 = UnixTimestamp::new_only_seconds(5); - let stamp1 = UnixTimestamp::new(5, 500).unwrap(); + let stamp1 = UnixTimestamp::new_subsec_millis_checked(5, 500).unwrap(); let stamp2 = UnixTimestamp::new_only_seconds(6); assert!(stamp0 < stamp1); assert!(stamp0 < stamp2); @@ -536,7 +583,7 @@ mod tests { #[test] fn test_ord_larger_neg_numbers() { let stamp0 = UnixTimestamp::new_only_seconds(-5); - let stamp1 = UnixTimestamp::new(-5, 500).unwrap(); + let stamp1 = UnixTimestamp::new_subsec_millis_checked(-5, 500).unwrap(); let stamp2 = UnixTimestamp::new_only_seconds(-6); assert!(stamp0 > stamp1); assert!(stamp0 > stamp2); @@ -548,7 +595,7 @@ mod tests { #[test] fn test_ord_smaller_neg_numbers() { let stamp0 = UnixTimestamp::new_only_seconds(-5); - let stamp1 = UnixTimestamp::new(-5, 500).unwrap(); + let stamp1 = UnixTimestamp::new_subsec_millis_checked(-5, 500).unwrap(); let stamp2 = UnixTimestamp::new_only_seconds(-6); assert!(stamp2 < stamp1); assert!(stamp2 < stamp0); @@ -560,7 +607,7 @@ mod tests { #[allow(clippy::nonminimal_bool)] #[test] fn test_eq() { - let stamp0 = UnixTimestamp::new(5, 0).unwrap(); + let stamp0 = UnixTimestamp::new(5, 0); let stamp1 = UnixTimestamp::new_only_seconds(5); assert_eq!(stamp0, stamp1); assert!(stamp0 <= stamp1); @@ -582,7 +629,7 @@ mod tests { #[test] fn test_addition_on_ref() { - let stamp0 = &UnixTimestamp::new(20, 500).unwrap(); + let stamp0 = &UnixTimestamp::new_subsec_millis_checked(20, 500).unwrap(); let stamp1 = stamp0 + Duration::from_millis(2500); assert_eq!(stamp1.unix_seconds, 23); assert_eq!(stamp1.subsecond_millis(), 0); @@ -609,19 +656,19 @@ mod tests { #[test] fn test_stamp_diff_positive_0() { - let stamp_later = UnixTimestamp::new(2, 0).unwrap(); + let stamp_later = UnixTimestamp::new(2, 0); let StampDiff { positive_duration, duration_absolute, - } = stamp_later - UnixTimestamp::new(1, 0).unwrap(); + } = stamp_later - UnixTimestamp::new(1, 0); assert!(positive_duration); assert_eq!(duration_absolute, Duration::from_secs(1)); } #[test] fn test_stamp_diff_positive_1() { - let stamp_later = UnixTimestamp::new(3, 800).unwrap(); - let stamp_earlier = UnixTimestamp::new(1, 900).unwrap(); + let stamp_later = UnixTimestamp::new(3, 800 * 1_000_000); + let stamp_earlier = UnixTimestamp::new_subsec_millis_checked(1, 900).unwrap(); let StampDiff { positive_duration, duration_absolute, @@ -632,8 +679,8 @@ mod tests { #[test] fn test_stamp_diff_negative() { - let stamp_later = UnixTimestamp::new(3, 800).unwrap(); - let stamp_earlier = UnixTimestamp::new(1, 900).unwrap(); + let stamp_later = UnixTimestamp::new_subsec_millis_checked(3, 800).unwrap(); + let stamp_earlier = UnixTimestamp::new_subsec_millis_checked(1, 900).unwrap(); let StampDiff { positive_duration, duration_absolute, @@ -644,7 +691,7 @@ mod tests { #[test] fn test_addition_spillover() { - let mut stamp0 = UnixTimestamp::new(1, 900).unwrap(); + let mut stamp0 = UnixTimestamp::new_subsec_millis_checked(1, 900).unwrap(); stamp0 += Duration::from_millis(100); assert_eq!(stamp0.unix_seconds, 2); assert_eq!(stamp0.subsecond_millis(), 0);