reaching target coverage
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit

This commit is contained in:
Robin Müller 2023-01-15 17:42:23 +01:00
parent 4c280b22c8
commit 73575bd00f
No known key found for this signature in database
GPG Key ID: 71B58F8A3CDFA9AC
3 changed files with 326 additions and 55 deletions

View File

@ -1,7 +1,9 @@
//! Module to generate or read CCSDS Day Segmented (CDS) timestamps as specified in //! Module to generate or read CCSDS Day Segmented (CDS) timestamps as specified in
//! [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf) section 3.3 . //! [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf) section 3.3 .
//! //!
//! The core data structure to do this is the [cds::TimeProvider] struct. //! 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 super::*;
use crate::private::Sealed; use crate::private::Sealed;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
@ -22,7 +24,16 @@ pub const MAX_DAYS_24_BITS: u32 = 2_u32.pow(24) - 1;
/// Generic trait implemented by token structs to specify the length of day field at type /// Generic trait implemented by token structs to specify the length of day field at type
/// level. This trait is only meant to be implemented in this crate and therefore sealed. /// level. This trait is only meant to be implemented in this crate and therefore sealed.
pub trait ProvidesDaysLength: Sealed { pub trait ProvidesDaysLength: Sealed {
type FieldType: Copy + Clone + TryFrom<i32> + TryFrom<u32> + From<u16> + Into<u32> + Into<i64>; type FieldType: Debug
+ Copy
+ Clone
+ PartialEq
+ Eq
+ TryFrom<i32>
+ TryFrom<u32>
+ From<u16>
+ Into<u32>
+ Into<i64>;
} }
/// Type level token to be used as a generic parameter to [TimeProvider]. /// Type level token to be used as a generic parameter to [TimeProvider].
@ -112,7 +123,7 @@ pub fn precision_from_pfield(pfield: u8) -> SubmillisPrecision {
/// ///
/// If you do not want to perform a forward check of the days length field with /// If you do not want to perform a forward check of the days length field with
/// [length_of_day_segment_from_pfield] and you have [alloc] support, you can also /// [length_of_day_segment_from_pfield] and you have [alloc] support, you can also
/// use [TimeProvider::from_bytes_dyn] to retrieve the correct instance as a [DynCdsTimeProvider] /// use [get_dyn_time_provider_from_bytes] to retrieve the correct instance as a [DynCdsTimeProvider]
/// trait object. /// trait object.
/// ///
/// Custom epochs are not supported yet. /// Custom epochs are not supported yet.
@ -123,6 +134,7 @@ pub fn precision_from_pfield(pfield: u8) -> SubmillisPrecision {
/// # Example /// # Example
/// ///
/// ``` /// ```
/// use core::time::Duration;
/// use spacepackets::time::cds::{TimeProvider, length_of_day_segment_from_pfield, LengthOfDaySegment}; /// use spacepackets::time::cds::{TimeProvider, length_of_day_segment_from_pfield, LengthOfDaySegment};
/// use spacepackets::time::{TimeWriter, CcsdsTimeCodes, CcsdsTimeProvider}; /// use spacepackets::time::{TimeWriter, CcsdsTimeCodes, CcsdsTimeProvider};
/// ///
@ -140,6 +152,11 @@ pub fn precision_from_pfield(pfield: u8) -> SubmillisPrecision {
/// let stamp_deserialized = read_result.unwrap(); /// let stamp_deserialized = read_result.unwrap();
/// assert_eq!(stamp_deserialized.len_as_bytes(), 7); /// assert_eq!(stamp_deserialized.len_as_bytes(), 7);
/// } /// }
/// // It is possible to add a Duration offset to a timestamp provider. Add 5 minutes offset here
/// let offset = Duration::from_secs(60 * 5);
/// let former_unix_seconds = timestamp_now.unix_seconds();
/// 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, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@ -150,7 +167,7 @@ pub struct TimeProvider<DaysLen: ProvidesDaysLength = DaysLen16Bits> {
submillis_precision: Option<SubmillisPrecision>, submillis_precision: Option<SubmillisPrecision>,
/// This is not strictly necessary but still cached because it significantly simplifies the /// This is not strictly necessary but still cached because it significantly simplifies the
/// calculation of [`DateTime<Utc>`]. /// calculation of [`DateTime<Utc>`].
unix_seconds: i64, unix_stamp: UnixTimeStamp,
} }
/// Common properties for all CDS time providers. /// Common properties for all CDS time providers.
@ -541,12 +558,13 @@ impl<ProvidesDaysLen: ProvidesDaysLength> TimeProvider<ProvidesDaysLen> {
#[inline] #[inline]
fn calc_unix_seconds(&mut self, unix_days_seconds: i64, ms_of_day: u32) { fn calc_unix_seconds(&mut self, unix_days_seconds: i64, ms_of_day: u32) {
self.unix_seconds = unix_days_seconds; self.unix_stamp.unix_seconds = unix_days_seconds;
self.unix_stamp.subsecond_millis = Some((ms_of_day % 1000) as u16);
let seconds_of_day = (ms_of_day / 1000) as i64; let seconds_of_day = (ms_of_day / 1000) as i64;
if self.unix_seconds < 0 { if self.unix_stamp.unix_seconds < 0 {
self.unix_seconds -= seconds_of_day; self.unix_stamp.unix_seconds -= seconds_of_day;
} else { } else {
self.unix_seconds += seconds_of_day; self.unix_stamp.unix_seconds += seconds_of_day;
} }
} }
@ -555,7 +573,8 @@ impl<ProvidesDaysLen: ProvidesDaysLength> TimeProvider<ProvidesDaysLen> {
ns_since_last_second < 10_u32.pow(9), ns_since_last_second < 10_u32.pow(9),
"Invalid MS since last second" "Invalid MS since last second"
); );
if let LocalResult::Single(val) = Utc.timestamp_opt(self.unix_seconds, ns_since_last_second) if let LocalResult::Single(val) =
Utc.timestamp_opt(self.unix_stamp.unix_seconds, ns_since_last_second)
{ {
return Some(val); return Some(val);
} }
@ -586,7 +605,7 @@ impl<ProvidesDaysLen: ProvidesDaysLength> TimeProvider<ProvidesDaysLen> {
pfield: Self::generate_p_field(days_len, None), pfield: Self::generate_p_field(days_len, None),
ccsds_days, ccsds_days,
ms_of_day, ms_of_day,
unix_seconds: 0, unix_stamp: Default::default(),
submillis_precision: None, submillis_precision: None,
}; };
let unix_days_seconds = ccsds_to_unix_days(i64::from(ccsds_days)) * SECONDS_PER_DAY as i64; let unix_days_seconds = ccsds_to_unix_days(i64::from(ccsds_days)) * SECONDS_PER_DAY as i64;
@ -662,7 +681,7 @@ impl<ProvidesDaysLen: ProvidesDaysLength> TimeProvider<ProvidesDaysLen> {
pfield: Self::generate_p_field(days_len, converter.submillis_precision()), pfield: Self::generate_p_field(days_len, converter.submillis_precision()),
ccsds_days, ccsds_days,
ms_of_day: converter.ms_of_day(), ms_of_day: converter.ms_of_day(),
unix_seconds: 0, unix_stamp: Default::default(),
submillis_precision: converter.submillis_precision(), submillis_precision: converter.submillis_precision(),
}; };
provider.setup(converter.unix_days_seconds(), converter.ms_of_day()); provider.setup(converter.unix_days_seconds(), converter.ms_of_day());
@ -762,12 +781,12 @@ impl TimeProvider<DaysLen24Bits> {
} }
/// Like [Self::from_dt_with_u24_days] but with microsecond sub-millisecond precision. /// Like [Self::from_dt_with_u24_days] but with microsecond sub-millisecond precision.
pub fn from_dt_with_u24_days_us_prec(dt: &DateTime<Utc>) -> Result<Self, TimestampError> { pub fn from_dt_with_u24_days_us_precision(dt: &DateTime<Utc>) -> Result<Self, TimestampError> {
Self::from_dt_generic_us_prec(dt, LengthOfDaySegment::Long24Bits) Self::from_dt_generic_us_prec(dt, LengthOfDaySegment::Long24Bits)
} }
/// Like [Self::from_dt_with_u24_days] but with picoseconds sub-millisecond precision. /// Like [Self::from_dt_with_u24_days] but with picoseconds sub-millisecond precision.
pub fn from_dt_with_u24_days_ps_prec(dt: &DateTime<Utc>) -> Result<Self, TimestampError> { pub fn from_dt_with_u24_days_ps_precision(dt: &DateTime<Utc>) -> Result<Self, TimestampError> {
Self::from_dt_generic_ps_prec(dt, LengthOfDaySegment::Long24Bits) Self::from_dt_generic_ps_prec(dt, LengthOfDaySegment::Long24Bits)
} }
@ -1063,8 +1082,18 @@ impl<ProvidesDaysLen: ProvidesDaysLength> CcsdsTimeProvider for TimeProvider<Pro
CcsdsTimeCodes::Cds CcsdsTimeCodes::Cds
} }
#[inline]
fn unix_seconds(&self) -> i64 { fn unix_seconds(&self) -> i64 {
self.unix_seconds self.unix_stamp.unix_seconds
}
#[inline]
fn subsecond_millis(&self) -> Option<u16> {
self.unix_stamp.subsecond_millis
}
#[inline]
fn unix_stamp(&self) -> UnixTimeStamp {
self.unix_stamp
} }
fn date_time(&self) -> Option<DateTime<Utc>> { fn date_time(&self) -> Option<DateTime<Utc>> {
@ -1152,11 +1181,18 @@ mod tests {
#[test] #[test]
fn test_time_stamp_zero_args() { fn test_time_stamp_zero_args() {
let time_stamper = TimeProvider::new_with_u16_days(0, 0); let time_stamper = TimeProvider::new_with_u16_days(0, 0);
let unix_stamp = time_stamper.unix_stamp();
assert_eq!( assert_eq!(
time_stamper.unix_seconds(), unix_stamp.unix_seconds,
(DAYS_CCSDS_TO_UNIX * SECONDS_PER_DAY as i32) as i64 (DAYS_CCSDS_TO_UNIX * SECONDS_PER_DAY as i32) as i64
); );
let subsecond_millis = unix_stamp.subsecond_millis;
assert!(subsecond_millis.is_some());
assert_eq!(subsecond_millis.unwrap(), 0);
assert_eq!(time_stamper.submillis_precision(), None); assert_eq!(time_stamper.submillis_precision(), None);
assert!(time_stamper.subsecond_millis().is_some());
assert_eq!(time_stamper.subsecond_millis().unwrap(), 0);
assert_eq!(time_stamper.ccdsd_time_code(), CcsdsTimeCodes::Cds); assert_eq!(time_stamper.ccdsd_time_code(), CcsdsTimeCodes::Cds);
assert_eq!( assert_eq!(
time_stamper.p_field(), time_stamper.p_field(),
@ -1174,7 +1210,7 @@ mod tests {
#[test] #[test]
fn test_time_stamp_unix_epoch() { fn test_time_stamp_unix_epoch() {
let time_stamper = TimeProvider::new_with_u16_days((-DAYS_CCSDS_TO_UNIX) as u16, 0); let time_stamper = TimeProvider::new_with_u16_days((-DAYS_CCSDS_TO_UNIX) as u16, 0);
assert_eq!(time_stamper.unix_seconds(), 0); assert_eq!(time_stamper.unix_stamp().unix_seconds, 0);
assert_eq!(time_stamper.submillis_precision(), None); assert_eq!(time_stamper.submillis_precision(), None);
let date_time = time_stamper.date_time().unwrap(); let date_time = time_stamper.date_time().unwrap();
assert_eq!(date_time.year(), 1970); assert_eq!(date_time.year(), 1970);
@ -1183,11 +1219,17 @@ mod tests {
assert_eq!(date_time.hour(), 0); assert_eq!(date_time.hour(), 0);
assert_eq!(date_time.minute(), 0); assert_eq!(date_time.minute(), 0);
assert_eq!(date_time.second(), 0); assert_eq!(date_time.second(), 0);
let time_stamper = TimeProvider::new_with_u16_days((-DAYS_CCSDS_TO_UNIX) as u16, 40);
assert!(time_stamper.subsecond_millis().is_some());
assert_eq!(time_stamper.subsecond_millis().unwrap(), 40);
let time_stamper = TimeProvider::new_with_u16_days((-DAYS_CCSDS_TO_UNIX) as u16, 1040);
assert!(time_stamper.subsecond_millis().is_some());
assert_eq!(time_stamper.subsecond_millis().unwrap(), 40);
} }
#[test] #[test]
fn test_large_days_field_write() { fn test_large_days_field_write() {
let time_stamper = TimeProvider::new_with_u24_days(0x108020_u32, 0); let time_stamper = TimeProvider::new_with_u24_days(0x108020_u32, 0x10203040);
assert!(time_stamper.is_ok()); assert!(time_stamper.is_ok());
let time_stamper = time_stamper.unwrap(); let time_stamper = time_stamper.unwrap();
assert_eq!(time_stamper.len_as_bytes(), 8); assert_eq!(time_stamper.len_as_bytes(), 8);
@ -1200,7 +1242,7 @@ mod tests {
assert_eq!(buf[2], 0x80); assert_eq!(buf[2], 0x80);
assert_eq!(buf[3], 0x20); assert_eq!(buf[3], 0x20);
let ms = u32::from_be_bytes(buf[4..8].try_into().unwrap()); let ms = u32::from_be_bytes(buf[4..8].try_into().unwrap());
assert_eq!(ms, 0); assert_eq!(ms, 0x10203040);
assert_eq!((buf[0] >> 2) & 0b1, 1); assert_eq!((buf[0] >> 2) & 0b1, 1);
} }
@ -1230,9 +1272,8 @@ mod tests {
let faulty_ctor = TimeProvider::<DaysLen16Bits>::from_bytes(&buf); let faulty_ctor = TimeProvider::<DaysLen16Bits>::from_bytes(&buf);
assert!(faulty_ctor.is_err()); assert!(faulty_ctor.is_err());
let error = faulty_ctor.unwrap_err(); let error = faulty_ctor.unwrap_err();
if let TimestampError::CdsError(cds::CdsError::InvalidCtorForDaysOfLenInPreamble( if let TimestampError::CdsError(CdsError::InvalidCtorForDaysOfLenInPreamble(len_of_day)) =
len_of_day, error
)) = error
{ {
assert_eq!(len_of_day, LengthOfDaySegment::Long24Bits); assert_eq!(len_of_day, LengthOfDaySegment::Long24Bits);
} else { } else {
@ -1497,66 +1538,117 @@ mod tests {
} }
#[test] #[test]
fn test_creation_from_dt_u16_days() { fn read_u24_stamp_with_us_submillis_precision() {
let subsec_millis = 250; let mut time_stamper = TimeProvider::new_with_u24_days(u16::MAX as u32 + 1, 0).unwrap();
time_stamper.set_submillis_precision(SubmillisPrecision::Microseconds(500));
let mut write_buf: [u8; 16] = [0; 16];
let written = time_stamper
.write_to_bytes(&mut write_buf)
.expect("Writing timestamp failed");
// 1 byte pfield + 3 bytes days + 4 bytes ms of day + 2 bytes us precision
assert_eq!(written, 10);
let stamp_deserialized = TimeProvider::from_bytes_with_u24_days(&write_buf);
assert!(stamp_deserialized.is_ok());
let stamp_deserialized = stamp_deserialized.unwrap();
assert_eq!(stamp_deserialized.len_as_bytes(), 10);
assert_eq!(stamp_deserialized.ccsds_days(), u16::MAX as u32 + 1);
assert!(stamp_deserialized.submillis_precision().is_some());
let submillis_rec = stamp_deserialized.submillis_precision().unwrap();
if let SubmillisPrecision::Microseconds(us) = submillis_rec {
assert_eq!(us, 500);
} else {
panic!("Wrong precision field detected");
}
}
#[test]
fn read_u24_stamp_with_ps_submillis_precision() {
let mut time_stamper = TimeProvider::new_with_u24_days(u16::MAX as u32 + 1, 0).unwrap();
time_stamper.set_submillis_precision(SubmillisPrecision::Picoseconds(5e8 as u32));
let mut write_buf: [u8; 16] = [0; 16];
let written = time_stamper
.write_to_bytes(&mut write_buf)
.expect("Writing timestamp failed");
// 1 byte pfield + 3 bytes days + 4 bytes ms of day + 4 bytes us precision
assert_eq!(written, 12);
let stamp_deserialized = TimeProvider::from_bytes_with_u24_days(&write_buf);
assert!(stamp_deserialized.is_ok());
let stamp_deserialized = stamp_deserialized.unwrap();
assert_eq!(stamp_deserialized.len_as_bytes(), 12);
assert_eq!(stamp_deserialized.ccsds_days(), u16::MAX as u32 + 1);
assert!(stamp_deserialized.submillis_precision().is_some());
let submillis_rec = stamp_deserialized.submillis_precision().unwrap();
if let SubmillisPrecision::Picoseconds(ps) = submillis_rec {
assert_eq!(ps, 5e8 as u32);
} else {
panic!("Wrong precision field detected");
}
}
fn generic_dt_case_0_no_prec(subsec_millis: u32) -> DateTime<Utc> {
let naivedatetime_utc = NaiveDate::from_ymd_opt(2023, 01, 14) let naivedatetime_utc = NaiveDate::from_ymd_opt(2023, 01, 14)
.unwrap() .unwrap()
.and_hms_milli_opt(16, 49, 30, subsec_millis) .and_hms_milli_opt(16, 49, 30, subsec_millis)
.unwrap(); .unwrap();
let datetime_utc = DateTime::<Utc>::from_utc(naivedatetime_utc, Utc); DateTime::<Utc>::from_utc(naivedatetime_utc, Utc)
let time_provider = TimeProvider::from_dt_with_u16_days(&datetime_utc).unwrap(); }
fn generic_check_dt_case_0<DaysLen: ProvidesDaysLength>(
time_provider: &TimeProvider<DaysLen>,
subsec_millis: u32,
datetime_utc: DateTime<Utc>,
) {
// https://www.timeanddate.com/date/durationresult.html?d1=01&m1=01&y1=1958&d2=14&m2=01&y2=2023 // 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. // Leap years need to be accounted for as well.
assert_eq!(time_provider.ccsds_days(), 23754); assert_eq!(time_provider.ccsds_days, 23754.into());
assert_eq!( assert_eq!(
time_provider.ms_of_day, time_provider.ms_of_day,
30 * 1000 + 49 * 60 * 1000 + 16 * 60 * 60 * 1000 + subsec_millis 30 * 1000 + 49 * 60 * 1000 + 16 * 60 * 60 * 1000 + subsec_millis
); );
assert_eq!(time_provider.date_time().unwrap(), datetime_utc); assert_eq!(time_provider.date_time().unwrap(), datetime_utc);
}
#[test]
fn test_creation_from_dt_u16_days() {
let subsec_millis = 250;
let datetime_utc = generic_dt_case_0_no_prec(subsec_millis);
let time_provider = TimeProvider::from_dt_with_u16_days(&datetime_utc).unwrap();
generic_check_dt_case_0(&time_provider, subsec_millis, datetime_utc);
let time_provider_2: TimeProvider<DaysLen16Bits> = let time_provider_2: TimeProvider<DaysLen16Bits> =
datetime_utc.try_into().expect("conversion failed"); datetime_utc.try_into().expect("conversion failed");
// Test the TryInto trait impl // Test the TryInto trait impl
assert_eq!(time_provider, time_provider_2); assert_eq!(time_provider, time_provider_2);
} }
#[test] #[test]
fn test_creation_from_dt_u24_days() { fn test_creation_from_dt_u24_days() {
let subsec_millis = 250; let subsec_millis = 250;
let naivedatetime_utc = NaiveDate::from_ymd_opt(2023, 01, 14) let datetime_utc = generic_dt_case_0_no_prec(subsec_millis);
.unwrap()
.and_hms_milli_opt(16, 49, 30, subsec_millis)
.unwrap();
let datetime_utc = DateTime::<Utc>::from_utc(naivedatetime_utc, Utc);
let time_provider = TimeProvider::from_dt_with_u24_days(&datetime_utc).unwrap(); let time_provider = TimeProvider::from_dt_with_u24_days(&datetime_utc).unwrap();
// https://www.timeanddate.com/date/durationresult.html?d1=01&m1=01&y1=1958&d2=14&m2=01&y2=2023 generic_check_dt_case_0(&time_provider, subsec_millis, datetime_utc);
// Leap years need to be accounted for as well.
assert_eq!(time_provider.ccsds_days, 23754);
assert_eq!(
time_provider.ms_of_day,
30 * 1000 + 49 * 60 * 1000 + 16 * 60 * 60 * 1000 + subsec_millis
);
assert_eq!(time_provider.date_time().unwrap(), datetime_utc);
let time_provider_2: TimeProvider<DaysLen24Bits> = let time_provider_2: TimeProvider<DaysLen24Bits> =
datetime_utc.try_into().expect("conversion failed"); datetime_utc.try_into().expect("conversion failed");
// Test the TryInto trait impl // Test the TryInto trait impl
assert_eq!(time_provider, time_provider_2); assert_eq!(time_provider, time_provider_2);
} }
#[test] fn generic_dt_case_1_us_prec(subsec_millis: u32) -> DateTime<Utc> {
fn test_creation_from_dt_us_prec() {
// 250 ms + 500 us // 250 ms + 500 us
let subsec_millis = 250;
let subsec_micros = subsec_millis * 1000 + 500; let subsec_micros = subsec_millis * 1000 + 500;
let naivedatetime_utc = NaiveDate::from_ymd_opt(2023, 01, 14) let naivedatetime_utc = NaiveDate::from_ymd_opt(2023, 01, 14)
.unwrap() .unwrap()
.and_hms_micro_opt(16, 49, 30, subsec_micros) .and_hms_micro_opt(16, 49, 30, subsec_micros)
.unwrap(); .unwrap();
let datetime_utc = DateTime::<Utc>::from_utc(naivedatetime_utc, Utc); DateTime::<Utc>::from_utc(naivedatetime_utc, Utc)
let time_provider = }
TimeProvider::from_dt_with_u16_days_us_precision(&datetime_utc).unwrap();
fn generic_check_dt_case_1_us_prec<DaysLen: ProvidesDaysLength>(
time_provider: &TimeProvider<DaysLen>,
subsec_millis: u32,
datetime_utc: DateTime<Utc>,
) {
// https://www.timeanddate.com/date/durationresult.html?d1=01&m1=01&y1=1958&d2=14&m2=01&y2=2023 // 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. // Leap years need to be accounted for as well.
assert_eq!(time_provider.ccsds_days, 23754); assert_eq!(time_provider.ccsds_days, 23754.into());
assert_eq!( assert_eq!(
time_provider.ms_of_day, time_provider.ms_of_day,
30 * 1000 + 49 * 60 * 1000 + 16 * 60 * 60 * 1000 + subsec_millis 30 * 1000 + 49 * 60 * 1000 + 16 * 60 * 60 * 1000 + subsec_millis
@ -1572,21 +1664,46 @@ mod tests {
} }
#[test] #[test]
fn test_creation_from_dt_ps_prec() { fn test_creation_from_dt_u16_days_us_prec() {
// 250 ms + 500 us
let subsec_millis = 250; let subsec_millis = 250;
let datetime_utc = generic_dt_case_1_us_prec(subsec_millis);
let time_provider =
TimeProvider::from_dt_with_u16_days_us_precision(&datetime_utc).unwrap();
generic_check_dt_case_1_us_prec(&time_provider, subsec_millis, datetime_utc);
}
#[test]
fn test_creation_from_dt_u24_days_us_prec() {
let subsec_millis = 250;
let datetime_utc = generic_dt_case_1_us_prec(subsec_millis);
let time_provider =
TimeProvider::from_dt_with_u24_days_us_precision(&datetime_utc).unwrap();
generic_check_dt_case_1_us_prec(&time_provider, subsec_millis, datetime_utc);
}
fn generic_dt_case_2_ps_prec(subsec_millis: u32) -> (DateTime<Utc>, u32) {
// 250 ms + 500 us
let subsec_nanos = subsec_millis * 1000 * 1000 + 500 * 1000; let subsec_nanos = subsec_millis * 1000 * 1000 + 500 * 1000;
let submilli_nanos = subsec_nanos % 10_u32.pow(6); let submilli_nanos = subsec_nanos % 10_u32.pow(6);
let naivedatetime_utc = NaiveDate::from_ymd_opt(2023, 01, 14) let naivedatetime_utc = NaiveDate::from_ymd_opt(2023, 01, 14)
.unwrap() .unwrap()
.and_hms_nano_opt(16, 49, 30, subsec_nanos) .and_hms_nano_opt(16, 49, 30, subsec_nanos)
.unwrap(); .unwrap();
let datetime_utc = DateTime::<Utc>::from_utc(naivedatetime_utc, Utc); (
let time_provider = DateTime::<Utc>::from_utc(naivedatetime_utc, Utc),
TimeProvider::from_dt_with_u16_days_ps_precision(&datetime_utc).unwrap(); submilli_nanos,
)
}
fn generic_check_dt_case_2_ps_prec<DaysLen: ProvidesDaysLength>(
time_provider: &TimeProvider<DaysLen>,
subsec_millis: u32,
submilli_nanos: u32,
datetime_utc: DateTime<Utc>,
) {
// https://www.timeanddate.com/date/durationresult.html?d1=01&m1=01&y1=1958&d2=14&m2=01&y2=2023 // 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. // Leap years need to be accounted for as well.
assert_eq!(time_provider.ccsds_days, 23754); assert_eq!(time_provider.ccsds_days, 23754.into());
assert_eq!( assert_eq!(
time_provider.ms_of_day, time_provider.ms_of_day,
30 * 1000 + 49 * 60 * 1000 + 16 * 60 * 60 * 1000 + subsec_millis 30 * 1000 + 49 * 60 * 1000 + 16 * 60 * 60 * 1000 + subsec_millis
@ -1601,6 +1718,34 @@ mod tests {
assert_eq!(time_provider.date_time().unwrap(), datetime_utc); assert_eq!(time_provider.date_time().unwrap(), datetime_utc);
} }
#[test]
fn test_creation_from_dt_u16_days_ps_prec() {
let subsec_millis = 250;
let (datetime_utc, submilli_nanos) = generic_dt_case_2_ps_prec(subsec_millis);
let time_provider =
TimeProvider::from_dt_with_u16_days_ps_precision(&datetime_utc).unwrap();
generic_check_dt_case_2_ps_prec(
&time_provider,
subsec_millis,
submilli_nanos,
datetime_utc,
);
}
#[test]
fn test_creation_from_dt_u24_days_ps_prec() {
let subsec_millis = 250;
let (datetime_utc, submilli_nanos) = generic_dt_case_2_ps_prec(subsec_millis);
let time_provider =
TimeProvider::from_dt_with_u24_days_ps_precision(&datetime_utc).unwrap();
generic_check_dt_case_2_ps_prec(
&time_provider,
subsec_millis,
submilli_nanos,
datetime_utc,
);
}
#[test] #[test]
fn test_creation_from_unix_stamp_0() { fn test_creation_from_unix_stamp_0() {
let unix_secs = 0; let unix_secs = 0;
@ -1766,6 +1911,21 @@ mod tests {
} }
} }
#[test]
fn test_new_u24_days_too_large() {
let time_provider = TimeProvider::new_with_u24_days(2_u32.pow(24), 0);
assert!(time_provider.is_err());
let e = time_provider.unwrap_err();
if let CdsError::InvalidCcsdsDays(days) = e {
assert_eq!(days, 2_u32.pow(24) as i64);
} else {
panic!("unexpected error {}", e)
}
}
#[test]
fn test_dt_u24_days() {}
#[test] #[test]
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
fn test_serialization() { fn test_serialization() {

View File

@ -42,12 +42,14 @@ impl TryFrom<u8> for FractionalResolution {
/// Please note that this function will panic if the fractional value is not smaller than /// Please note that this function will panic if the fractional value is not smaller than
/// the maximum number of fractions allowed for the particular resolution. /// the maximum number of fractions allowed for the particular resolution.
/// (e.g. passing 270 when the resolution only allows 255 values). /// (e.g. passing 270 when the resolution only allows 255 values).
#[inline]
pub fn convert_fractional_part_to_ns(fractional_part: FractionalPart) -> u64 { pub fn convert_fractional_part_to_ns(fractional_part: FractionalPart) -> u64 {
let div = fractional_res_to_div(fractional_part.0); let div = fractional_res_to_div(fractional_part.0);
assert!(fractional_part.1 < div); assert!(fractional_part.1 < div);
10_u64.pow(9) * fractional_part.1 as u64 / div as u64 10_u64.pow(9) * fractional_part.1 as u64 / div as u64
} }
#[inline(always)]
pub const fn fractional_res_to_div(res: FractionalResolution) -> u32 { pub const fn fractional_res_to_div(res: FractionalResolution) -> u32 {
2_u32.pow(8 * res as u32) - 1 2_u32.pow(8 * res as u32) - 1
} }
@ -351,6 +353,11 @@ impl TimeProviderCcsdsEpoch {
pfield & 0b11 pfield & 0b11
} }
#[inline]
fn unix_seconds(&self) -> i64 {
ccsds_epoch_to_unix_epoch(self.counter.1 as u64) as i64
}
/// This returns the length of the individual components of the CUC timestamp in addition /// This returns the length of the individual components of the CUC timestamp in addition
/// to the total size. /// to the total size.
/// ///
@ -543,10 +550,28 @@ impl CcsdsTimeProvider for TimeProviderCcsdsEpoch {
CcsdsTimeCodes::CucCcsdsEpoch CcsdsTimeCodes::CucCcsdsEpoch
} }
fn unix_seconds(&self) -> i64 {
self.unix_seconds()
}
fn subsecond_millis(&self) -> Option<u16> {
if let Some(fractions) = self.fractions {
if fractions.0 == FractionalResolution::Seconds {
return None;
}
// Rounding down here is the correct approach.
return Some((convert_fractional_part_to_ns(fractions) / 10_u32.pow(6) as u64) as u16);
}
None
}
/// Please note that this function only works as intended if the time counter resolution /// Please note that this function only works as intended if the time counter resolution
/// is one second. /// is one second.
fn unix_seconds(&self) -> i64 { fn unix_stamp(&self) -> UnixTimeStamp {
ccsds_epoch_to_unix_epoch(self.counter.1 as u64) as i64 UnixTimeStamp {
unix_seconds: self.unix_seconds(),
subsecond_millis: None,
}
} }
fn date_time(&self) -> Option<DateTime<Utc>> { fn date_time(&self) -> Option<DateTime<Utc>> {

View File

@ -204,6 +204,9 @@ pub trait TimeReader {
} }
/// Trait for generic CCSDS time providers. /// Trait for generic CCSDS time providers.
///
/// The UNIX helper methods and the [date_time] method are not strictly necessary but extremely
/// practical because they are a very common and simple exchange format for time information.
pub trait CcsdsTimeProvider { pub trait CcsdsTimeProvider {
fn len_as_bytes(&self) -> usize; fn len_as_bytes(&self) -> usize;
@ -213,10 +216,73 @@ pub trait CcsdsTimeProvider {
/// in big endian format. /// in big endian format.
fn p_field(&self) -> (usize, [u8; 2]); fn p_field(&self) -> (usize, [u8; 2]);
fn ccdsd_time_code(&self) -> CcsdsTimeCodes; fn ccdsd_time_code(&self) -> CcsdsTimeCodes;
fn unix_seconds(&self) -> i64; fn unix_seconds(&self) -> i64;
fn subsecond_millis(&self) -> Option<u16>;
fn unix_stamp(&self) -> UnixTimeStamp {
UnixTimeStamp {
unix_seconds: self.unix_seconds(),
subsecond_millis: self.subsecond_millis(),
}
}
fn date_time(&self) -> Option<DateTime<Utc>>; fn date_time(&self) -> Option<DateTime<Utc>>;
} }
/// UNIX timestamp: Elapsed seconds since 01-01-1970 00:00:00.
///
/// Also can optionally include subsecond millisecond for greater accuracy.
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct UnixTimeStamp {
pub unix_seconds: i64,
subsecond_millis: Option<u16>,
}
impl UnixTimeStamp {
pub fn new(unix_seconds: i64) -> Self {
Self {
unix_seconds,
subsecond_millis: None,
}
}
/// Returns none if the subsecond millisecond value is larger than 999.
pub fn new_with_subsecond_millis(unix_seconds: i64, subsec_millis: u16) -> Option<Self> {
if subsec_millis > 999 {
return None;
}
Some(Self {
unix_seconds,
subsecond_millis: Some(subsec_millis),
})
}
pub fn subsecond_millis(&self) -> Option<u16> {
self.subsecond_millis
}
#[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub fn from_now() -> Result<Self, SystemTimeError> {
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
let epoch = now.as_secs();
Ok(UnixTimeStamp {
unix_seconds: epoch as i64,
subsecond_millis: Some(now.subsec_millis() as u16),
})
}
#[inline]
pub fn unix_seconds_f64(&self) -> f64 {
let mut secs = self.unix_seconds as f64;
if let Some(subsec_millis) = self.subsecond_millis {
secs += subsec_millis as f64 / 1000.0;
}
secs
}
}
#[cfg(all(test, feature = "std"))] #[cfg(all(test, feature = "std"))]
mod tests { mod tests {
use super::*; use super::*;
@ -245,4 +311,24 @@ mod tests {
let days_diff = (ccsds_epoch - unix_epoch) / SECONDS_PER_DAY as u64; let days_diff = (ccsds_epoch - unix_epoch) / SECONDS_PER_DAY as u64;
assert_eq!(days_diff, -DAYS_CCSDS_TO_UNIX as u64); assert_eq!(days_diff, -DAYS_CCSDS_TO_UNIX as u64);
} }
#[test]
fn basic_unix_stamp_test() {
let stamp = UnixTimeStamp::new(-200);
assert_eq!(stamp.unix_seconds, -200);
assert!(stamp.subsecond_millis().is_none());
let stamp = UnixTimeStamp::new(250);
assert_eq!(stamp.unix_seconds, 250);
assert!(stamp.subsecond_millis().is_none());
}
#[test]
fn basic_float_unix_stamp_test() {
let stamp = UnixTimeStamp::new_with_subsecond_millis(500, 600).unwrap();
assert!(stamp.subsecond_millis.is_some());
assert_eq!(stamp.unix_seconds, 500);
let subsec_millis = stamp.subsecond_millis().unwrap();
assert_eq!(subsec_millis, 600);
assert!((500.6 - stamp.unix_seconds_f64()).abs() < 0.0001);
}
} }