diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fcfe92..52b21a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/). # [unreleased] +## Added + +- `time::cds::TimeProvider` + - Add `Ord` and `PartialOrd`, use custom `PartialEq` impl to account for precision correctly. + - Add `precision_as_ns` function which converts microsecond and picosecond precision values + into nanoseconds. + - Add conversion trait to convert `cds::TimeProvider` into + `cds::TimeProvider` and vice-versa. +- `time::UnixTimestamp` + - Add `Ord` and `PartialOrd` implementations. + # [v0.5.0] 2023-01-20 The timestamp of `PusTm` is now optional. See Added and Changed section for details. diff --git a/src/time/cds.rs b/src/time/cds.rs index a6c3c0f..c0e6834 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -14,6 +14,7 @@ use core::any::Any; use core::fmt::Debug; use core::ops::{Add, AddAssign}; use core::time::Duration; +use core::cmp::Ordering; use delegate::delegate; /// Base value for the preamble field for a time field parser to determine the time field type. @@ -158,7 +159,7 @@ pub fn precision_from_pfield(pfield: u8) -> SubmillisPrecision { /// let timestamp_in_5_minutes = timestamp_now + offset; /// assert_eq!(timestamp_in_5_minutes.unix_seconds(), former_unix_seconds + 5 * 60); /// ``` -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct TimeProvider { pfield: u8, @@ -459,6 +460,7 @@ impl CdsCommon for TimeProvider TimeProvider { + /// Please note that a precision value of 0 will be converted to [None] (no precision). pub fn set_submillis_precision(&mut self, prec: SubmillisPrecision) { self.pfield &= !(0b11); if let SubmillisPrecision::Absent = prec { @@ -486,6 +488,25 @@ impl TimeProvider { self.ccsds_days } + /// Maps the submillisecond precision to a nanosecond value. This will reduce precision when + /// using picosecond resolution, but significantly simplifies comparison of timestamps. + pub fn precision_as_ns(&self) -> Option { + if let Some(prec) = self.submillis_precision { + match prec { + SubmillisPrecision::Microseconds(us) => { + return Some(us as u32 * 1000); + } + SubmillisPrecision::Picoseconds(ps) => { + return Some(ps / 1000); + } + _ => { + return None; + } + } + } + None + } + fn generic_raw_read_checks( buf: &[u8], days_len: LengthOfDaySegment, @@ -1177,6 +1198,75 @@ impl TimeWriter for TimeProvider { } } +impl PartialEq for TimeProvider { + fn eq(&self, other: &Self) -> bool { + if self.ccsds_days == other.ccsds_days + && self.ms_of_day == other.ms_of_day + && self.precision_as_ns().unwrap_or(0) == other.precision_as_ns().unwrap_or(0) + { + return true; + } + false + } +} + +impl PartialOrd for TimeProvider { + fn partial_cmp(&self, other: &Self) -> Option { + if self == other { + return Some(Ordering::Equal); + } + match self.ccsds_days_as_u32().cmp(&other.ccsds_days_as_u32()) { + Ordering::Less => return Some(Ordering::Less), + Ordering::Greater => return Some(Ordering::Greater), + _ => (), + } + match self.ms_of_day().cmp(&other.ms_of_day()) { + Ordering::Less => return Some(Ordering::Less), + Ordering::Greater => return Some(Ordering::Greater), + _ => (), + } + match self + .precision_as_ns() + .unwrap_or(0) + .cmp(&other.precision_as_ns().unwrap_or(0)) + { + Ordering::Less => return Some(Ordering::Less), + Ordering::Greater => return Some(Ordering::Greater), + _ => (), + } + Some(Ordering::Equal) + } +} + +impl Ord for TimeProvider { + fn cmp(&self, other: &Self) -> Ordering { + PartialOrd::partial_cmp(self, other).unwrap() + } +} + +impl From> for TimeProvider { + fn from(value: TimeProvider) -> Self { + // This function only fails if the days value exceeds 24 bits, which is not possible here, + // so it is okay to unwrap. + Self::new_with_u24_days(value.ccsds_days_as_u32(), value.ms_of_day()).unwrap() + } +} + +/// This conversion can fail if the days value exceeds 16 bits. +impl TryFrom> for TimeProvider { + type Error = CdsError; + fn try_from(value: TimeProvider) -> Result { + let ccsds_days = value.ccsds_days_as_u32(); + if ccsds_days > u16::MAX as u32 { + return Err(CdsError::InvalidCcsdsDays(ccsds_days as i64)); + } + Ok(Self::new_with_u16_days( + ccsds_days as u16, + value.ms_of_day(), + )) + } +} + #[cfg(test)] mod tests { use super::*; @@ -2066,6 +2156,43 @@ mod tests { } } + #[test] + fn test_eq() { + let stamp0 = TimeProvider::new_with_u16_days(0, 0); + let mut buf: [u8; 7] = [0; 7]; + stamp0.write_to_bytes(&mut buf).unwrap(); + let stamp1 = TimeProvider::from_bytes_with_u16_days(&buf).unwrap(); + assert_eq!(stamp0, stamp1); + assert!(!(stamp0 < stamp1)); + assert!(!(stamp1 > stamp0)); + } + + #[test] + fn test_ord() { + let stamp0 = TimeProvider::new_with_u24_days(0, 0).unwrap(); + let stamp1 = TimeProvider::new_with_u24_days(0, 50000).unwrap(); + let mut stamp2 = TimeProvider::new_with_u24_days(0, 50000).unwrap(); + stamp2.set_submillis_precision(SubmillisPrecision::Microseconds(500)); + let stamp3 = TimeProvider::new_with_u24_days(1, 0).unwrap(); + assert!(stamp1 > stamp0); + assert!(stamp2 > stamp0); + assert!(stamp2 > stamp1); + assert!(stamp3 > stamp0); + assert!(stamp3 > stamp1); + assert!(stamp3 > stamp2); + } + + #[test] + fn test_conversion() { + let mut stamp_small = TimeProvider::new_with_u16_days(u16::MAX, 500); + let stamp_larger: TimeProvider = stamp_small.into(); + assert_eq!(stamp_larger.ccsds_days_as_u32(), u16::MAX as u32); + assert_eq!(stamp_larger.ms_of_day(), 500); + stamp_small = stamp_larger.try_into().unwrap(); + assert_eq!(stamp_small.ccsds_days_as_u32(), u16::MAX as u32); + assert_eq!(stamp_small.ms_of_day(), 500); + } + #[test] #[cfg(feature = "serde")] fn test_serialization() { diff --git a/src/time/mod.rs b/src/time/mod.rs index c8bddee..50cbee8 100644 --- a/src/time/mod.rs +++ b/src/time/mod.rs @@ -2,6 +2,7 @@ use crate::{ByteConversionError, SizeMissmatch}; use chrono::{DateTime, LocalResult, TimeZone, Utc}; use core::fmt::{Display, Formatter}; +use core::cmp::Ordering; #[allow(unused_imports)] #[cfg(not(feature = "std"))] @@ -232,7 +233,8 @@ pub trait CcsdsTimeProvider { /// UNIX timestamp: Elapsed seconds since 01-01-1970 00:00:00. /// -/// Also can optionally include subsecond millisecond for greater accuracy. +/// Also can optionally include subsecond millisecond for greater accuracy. Please note that a +/// subsecond millisecond value of 0 gets converted to [None]. #[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct UnixTimestamp { @@ -241,24 +243,28 @@ pub struct UnixTimestamp { } impl UnixTimestamp { - /// Returns none if the subsecond millisecond value is larger than 999. + /// Returns none if the subsecond millisecond value is larger than 999. 0 is converted to + /// a [None] value. pub fn new(unix_seconds: i64, subsec_millis: u16) -> Option { if subsec_millis > 999 { return None; } - Some(Self { - unix_seconds, - subsecond_millis: Some(subsec_millis), - }) + Some(Self::const_new(unix_seconds, subsec_millis)) } + /// Like [Self::new] but const. Panics if the subsecond value is larger than 999. pub const fn const_new(unix_seconds: i64, subsec_millis: u16) -> Self { if subsec_millis > 999 { panic!("subsec milliseconds exceeds 999"); } + let subsecond_millis = if subsec_millis == 0 { + None + } else { + Some(subsec_millis) + }; Self { unix_seconds, - subsecond_millis: Some(subsec_millis), + subsecond_millis, } } @@ -310,6 +316,48 @@ impl From> for UnixTimestamp { } } +impl PartialOrd for UnixTimestamp { + fn partial_cmp(&self, other: &Self) -> Option { + if self == other { + return Some(Ordering::Equal); + } + match self.unix_seconds.cmp(&other.unix_seconds) { + Ordering::Less => return Some(Ordering::Less), + Ordering::Greater => return Some(Ordering::Greater), + _ => (), + } + + match self + .subsecond_millis() + .unwrap_or(0) + .cmp(&other.subsecond_millis().unwrap_or(0)) + { + Ordering::Less => { + return if self.unix_seconds < 0 { + Some(Ordering::Greater) + } else { + Some(Ordering::Less) + } + } + Ordering::Greater => { + return if self.unix_seconds < 0 { + Some(Ordering::Less) + } else { + Some(Ordering::Greater) + } + } + Ordering::Equal => (), + } + Some(Ordering::Equal) + } +} + +impl Ord for UnixTimestamp { + fn cmp(&self, other: &Self) -> Ordering { + PartialOrd::partial_cmp(self, other).unwrap() + } +} + #[cfg(all(test, feature = "std"))] mod tests { use super::*; @@ -358,4 +406,59 @@ mod tests { assert_eq!(subsec_millis, 600); 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 stamp2 = UnixTimestamp::new_only_seconds(6); + assert!(stamp1 > stamp0); + assert!(stamp2 > stamp0); + assert!(stamp2 > stamp1); + } + + #[test] + fn test_ord_smaller() { + let stamp0 = UnixTimestamp::new_only_seconds(5); + let stamp1 = UnixTimestamp::new(5, 500).unwrap(); + let stamp2 = UnixTimestamp::new_only_seconds(6); + assert!(stamp0 < stamp1); + assert!(stamp0 < stamp2); + assert!(stamp1 < stamp2); + } + + #[test] + fn test_ord_larger_neg_numbers() { + let stamp0 = UnixTimestamp::new_only_seconds(-5); + let stamp1 = UnixTimestamp::new(-5, 500).unwrap(); + let stamp2 = UnixTimestamp::new_only_seconds(-6); + assert!(stamp0 > stamp1); + assert!(stamp0 > stamp2); + assert!(stamp1 > stamp2); + assert!(stamp1 >= stamp2); + assert!(stamp0 >= stamp1); + } + + #[test] + fn test_ord_smaller_neg_numbers() { + let stamp0 = UnixTimestamp::new_only_seconds(-5); + let stamp1 = UnixTimestamp::new(-5, 500).unwrap(); + let stamp2 = UnixTimestamp::new_only_seconds(-6); + assert!(stamp2 < stamp1); + assert!(stamp2 < stamp0); + assert!(stamp1 < stamp0); + assert!(stamp1 <= stamp0); + assert!(stamp2 <= stamp1); + } + + #[test] + fn test_eq() { + let stamp0 = UnixTimestamp::new(5, 0).unwrap(); + let stamp1 = UnixTimestamp::new_only_seconds(5); + assert_eq!(stamp0, stamp1); + assert!(stamp0 <= stamp1); + assert!(stamp0 >= stamp1); + assert!(!(stamp0 < stamp1)); + assert!(!(stamp0 > stamp1)); + } }