Improve time module
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
This commit is contained in:
parent
55862a2433
commit
ef55bc4a6e
11
CHANGELOG.md
11
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<DaysLen16Bits>` into
|
||||
`cds::TimeProvider<DaysLen24Bits>` 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.
|
||||
|
129
src/time/cds.rs
129
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<DaysLen: ProvidesDaysLength = DaysLen16Bits> {
|
||||
pfield: u8,
|
||||
@ -459,6 +460,7 @@ impl<ProvidesDaysLen: ProvidesDaysLength> CdsCommon for TimeProvider<ProvidesDay
|
||||
}
|
||||
|
||||
impl<ProvidesDaysLen: ProvidesDaysLength> TimeProvider<ProvidesDaysLen> {
|
||||
/// 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<ProvidesDaysLen: ProvidesDaysLength> TimeProvider<ProvidesDaysLen> {
|
||||
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<u32> {
|
||||
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<DaysLen24Bits> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<DaysLenProvider: ProvidesDaysLength> PartialEq for TimeProvider<DaysLenProvider> {
|
||||
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<DaysLenProvider: ProvidesDaysLength> PartialOrd for TimeProvider<DaysLenProvider> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
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<DaysLenProvider: ProvidesDaysLength + Eq> Ord for TimeProvider<DaysLenProvider> {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
PartialOrd::partial_cmp(self, other).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TimeProvider<DaysLen16Bits>> for TimeProvider<DaysLen24Bits> {
|
||||
fn from(value: TimeProvider<DaysLen16Bits>) -> 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<TimeProvider<DaysLen24Bits>> for TimeProvider<DaysLen16Bits> {
|
||||
type Error = CdsError;
|
||||
fn try_from(value: TimeProvider<DaysLen24Bits>) -> Result<Self, CdsError> {
|
||||
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<DaysLen24Bits> = 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() {
|
||||
|
117
src/time/mod.rs
117
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<Self> {
|
||||
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<DateTime<Utc>> for UnixTimestamp {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for UnixTimestamp {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user