add new Conversion helper

This commit is contained in:
Robin Müller 2023-01-14 13:22:32 +01:00
parent b350f8fe03
commit 3ba68b4e64
No known key found for this signature in database
GPG Key ID: 71B58F8A3CDFA9AC

View File

@ -4,6 +4,7 @@
//! The core data structure to do this is the [cds::TimeProvider] struct. //! The core data structure to do this is the [cds::TimeProvider] struct.
use super::*; use super::*;
use crate::private::Sealed; use crate::private::Sealed;
use chrono::Datelike;
use core::fmt::Debug; use core::fmt::Debug;
use core::ops::Add; use core::ops::Add;
use core::time::Duration; use core::time::Duration;
@ -135,15 +136,56 @@ pub struct TimeProvider<DaysLen: ProvidesDaysLength = DaysLen16Bits> {
ccsds_days: DaysLen::FieldType, ccsds_days: DaysLen::FieldType,
ms_of_day: u32, ms_of_day: u32,
submillis_precision: Option<SubmillisPrecision>, submillis_precision: Option<SubmillisPrecision>,
/// This is not strictly necessary but still cached because it significantly simplifies the
/// calculation of [`DateTime<Utc>`].
unix_seconds: i64, unix_seconds: i64,
} }
/// Helper struct which generates fields for the CDS time provider from a datetime.
struct ConversionFromDatetime {
ccsds_days: u32,
ms_of_day: 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,
submillis_prec: Option<SubmillisPrecision>,
}
fn calc_unix_days_and_secs_of_day(unix_seconds: i64) -> (i64, i64) {
let secs_of_day = unix_seconds % SECONDS_PER_DAY as i64;
let unix_days = if secs_of_day > 0 {
(unix_seconds - secs_of_day) / SECONDS_PER_DAY as i64
} else {
(unix_seconds + secs_of_day) / SECONDS_PER_DAY as i64
};
(unix_days, secs_of_day)
}
impl ConversionFromDatetime {
fn new(dt: DateTime<Utc>) -> Self {
// The CDS timestamp does not support timestamps before the CCSDS epoch.
assert!(dt.year() >= 1958);
let unix_seconds = dt.timestamp();
let (unix_days, _) = calc_unix_days_and_secs_of_day(unix_seconds);
// This should always be positive now.
let ccsds_days = unix_to_ccsds_days(unix_days) as u32;
let unix_ms = dt.timestamp_millis();
let mut ms_of_day = unix_ms % MS_PER_DAY as i64;
if ms_of_day < 0 {
ms_of_day = -ms_of_day;
}
Self {
ccsds_days,
ms_of_day: ms_of_day as u32,
unix_days_seconds: unix_days * SECONDS_PER_DAY as i64,
submillis_prec: None,
}
}
}
#[cfg(feature = "std")] #[cfg(feature = "std")]
struct ConversionFromNow { struct ConversionFromNow {
ccsds_days: i32, base: ConversionFromDatetime,
ms_of_day: u64,
unix_days_seconds: u64,
submillis_prec: Option<SubmillisPrecision>,
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
@ -163,8 +205,8 @@ impl ConversionFromNow {
fn new_generic(mut prec: Option<SubmillisPrecision>) -> Result<Self, SystemTimeError> { fn new_generic(mut prec: Option<SubmillisPrecision>) -> Result<Self, SystemTimeError> {
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
let epoch = now.as_secs(); let epoch = now.as_secs();
let secs_of_day = epoch % SECONDS_PER_DAY as u64; // Both values should now be positive
let unix_days_seconds = epoch - secs_of_day; let (unix_days, secs_of_day) = calc_unix_days_and_secs_of_day(epoch as i64);
if let Some(submilli_prec) = prec { if let Some(submilli_prec) = prec {
match submilli_prec { match submilli_prec {
SubmillisPrecision::Microseconds(_) => { SubmillisPrecision::Microseconds(_) => {
@ -181,11 +223,12 @@ impl ConversionFromNow {
} }
} }
Ok(Self { Ok(Self {
ms_of_day: secs_of_day * 1000 + now.subsec_millis() as u64, base: ConversionFromDatetime {
ccsds_days: unix_to_ccsds_days((unix_days_seconds / SECONDS_PER_DAY as u64) as i64) ms_of_day: secs_of_day as u32 * 1000 + now.subsec_millis(),
as i32, ccsds_days: unix_to_ccsds_days(unix_days) as u32,
unix_days_seconds, unix_days_seconds: unix_days * SECONDS_PER_DAY as i64,
submillis_prec: prec, submillis_prec: prec,
},
}) })
} }
} }
@ -296,11 +339,11 @@ impl<ProvidesDaysLen: ProvidesDaysLength> TimeProvider<ProvidesDaysLen> {
init_len init_len
} }
fn setup(&mut self, unix_days_seconds: i64, ms_of_day: u64) { fn setup(&mut self, unix_days_seconds: i64, ms_of_day: u32) {
self.calc_unix_seconds(unix_days_seconds, ms_of_day); self.calc_unix_seconds(unix_days_seconds, ms_of_day);
} }
fn calc_unix_seconds(&mut self, unix_days_seconds: i64, ms_of_day: u64) { fn calc_unix_seconds(&mut self, unix_days_seconds: i64, ms_of_day: u32) {
self.unix_seconds = unix_days_seconds; self.unix_seconds = unix_days_seconds;
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_seconds < 0 {
@ -310,10 +353,13 @@ impl<ProvidesDaysLen: ProvidesDaysLength> TimeProvider<ProvidesDaysLen> {
} }
} }
fn calc_date_time(&self, ms_since_last_second: u32) -> Option<DateTime<Utc>> { fn calc_date_time(&self, ns_since_last_second: u32) -> Option<DateTime<Utc>> {
assert!(ms_since_last_second < 1000, "Invalid MS since last second"); assert!(
let ns_since_last_sec = ms_since_last_second * 1e6 as u32; ns_since_last_second < 10_u32.pow(9),
if let LocalResult::Single(val) = Utc.timestamp_opt(self.unix_seconds, ns_since_last_sec) { "Invalid MS since last second"
);
if let LocalResult::Single(val) = Utc.timestamp_opt(self.unix_seconds, ns_since_last_second)
{
return Some(val); return Some(val);
} }
None None
@ -357,24 +403,27 @@ impl<ProvidesDaysLen: ProvidesDaysLength> TimeProvider<ProvidesDaysLen> {
conversion_from_now: ConversionFromNow, conversion_from_now: ConversionFromNow,
) -> Result<Self, StdTimestampError> ) -> Result<Self, StdTimestampError>
where where
<ProvidesDaysLen::FieldType as TryFrom<i32>>::Error: Debug, <ProvidesDaysLen as ProvidesDaysLength>::FieldType: TryFrom<u32>,
{ {
let ccsds_days: ProvidesDaysLen::FieldType = let ccsds_days: ProvidesDaysLen::FieldType = conversion_from_now
conversion_from_now.ccsds_days.try_into().map_err(|_| { .base
.ccsds_days
.try_into()
.map_err(|_| {
StdTimestampError::TimestampError( StdTimestampError::TimestampError(
CdsError::InvalidCcsdsDays(conversion_from_now.ccsds_days.into()).into(), CdsError::InvalidCcsdsDays(conversion_from_now.base.ccsds_days.into()).into(),
) )
})?; })?;
let mut provider = Self { let mut provider = Self {
pfield: Self::generate_p_field(days_len, conversion_from_now.submillis_prec), pfield: Self::generate_p_field(days_len, conversion_from_now.base.submillis_prec),
ccsds_days, ccsds_days,
ms_of_day: conversion_from_now.ms_of_day as u32, ms_of_day: conversion_from_now.base.ms_of_day as u32,
unix_seconds: 0, unix_seconds: 0,
submillis_precision: conversion_from_now.submillis_prec, submillis_precision: conversion_from_now.base.submillis_prec,
}; };
provider.setup( provider.setup(
conversion_from_now.unix_days_seconds as i64, conversion_from_now.base.unix_days_seconds as i64,
conversion_from_now.ms_of_day, conversion_from_now.base.ms_of_day,
); );
Ok(provider) Ok(provider)
} }
@ -415,26 +464,34 @@ impl<ProvidesDaysLen: ProvidesDaysLength> TimeProvider<ProvidesDaysLen> {
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub fn update_from_now(&mut self) -> Result<(), StdTimestampError> pub fn update_from_now(&mut self) -> Result<(), StdTimestampError>
where where
<ProvidesDaysLen::FieldType as TryFrom<i32>>::Error: Debug, <ProvidesDaysLen as ProvidesDaysLength>::FieldType: From<u32>,
{ {
let conversion_from_now = self.generic_conversion_from_now()?; let conversion_from_now = self.generic_conversion_from_now()?;
let ccsds_days: ProvidesDaysLen::FieldType = let ccsds_days: ProvidesDaysLen::FieldType = conversion_from_now
conversion_from_now.ccsds_days.try_into().map_err(|_| { .base
.ccsds_days
.try_into()
.map_err(|_| {
StdTimestampError::TimestampError( StdTimestampError::TimestampError(
CdsError::InvalidCcsdsDays(conversion_from_now.ccsds_days as i64).into(), CdsError::InvalidCcsdsDays(conversion_from_now.base.ccsds_days as i64).into(),
) )
})?; })?;
self.ccsds_days = ccsds_days; self.ccsds_days = ccsds_days;
self.ms_of_day = conversion_from_now.ms_of_day as u32; self.ms_of_day = conversion_from_now.base.ms_of_day as u32;
self.setup( self.setup(
conversion_from_now.unix_days_seconds as i64, conversion_from_now.base.unix_days_seconds as i64,
conversion_from_now.ms_of_day, conversion_from_now.base.ms_of_day,
); );
Ok(()) Ok(())
} }
} }
impl TimeProvider<DaysLen24Bits> { impl TimeProvider<DaysLen24Bits> {
pub fn new_with_u24_days_from_unix_stamp(unix_seconds: i64) {
//let seconds_of_day = unix_seconds % SECONDS_PER_DAY;
//let unix_days = (unix_seconds - seconds_of_day) / SECONDS_PER_DAY;
}
/// Generate a new timestamp provider with the days field width set to 24 bits /// Generate a new timestamp provider with the days field width set to 24 bits
pub fn new_with_u24_days(ccsds_days: u32, ms_of_day: u32) -> Result<Self, CdsError> { pub fn new_with_u24_days(ccsds_days: u32, ms_of_day: u32) -> Result<Self, CdsError> {
if ccsds_days > 2_u32.pow(24) { if ccsds_days > 2_u32.pow(24) {
@ -594,6 +651,7 @@ fn add_for_max_ccsds_days_val<T: ProvidesDaysLength>(
); );
(next_ccsds_days, next_ms_of_day, precision) (next_ccsds_days, next_ms_of_day, precision)
} }
/// Allows adding an duration in form of an offset. Please note that the CCSDS days will rollover /// Allows adding an duration in form of an offset. Please note that the CCSDS days will rollover
/// when they overflow, because addition needs to be infallible. The user needs to check for a /// when they overflow, because addition needs to be infallible. The user needs to check for a
/// days overflow when this is a possibility and might be a problem. /// days overflow when this is a possibility and might be a problem.
@ -625,6 +683,15 @@ impl Add<Duration> for TimeProvider<DaysLen24Bits> {
} }
} }
impl TryFrom<DateTime<Utc>> for TimeProvider<DaysLen16Bits> {
type Error = ();
fn try_from(dt: DateTime<Utc>) -> Result<Self, Self::Error> {
//todo!()
//let unix_ms = dt.timestamp_millis();
Ok(Self::new_with_u16_days(0, 0))
}
}
impl<ProvidesDaysLen: ProvidesDaysLength> CcsdsTimeProvider for TimeProvider<ProvidesDaysLen> { impl<ProvidesDaysLen: ProvidesDaysLength> CcsdsTimeProvider for TimeProvider<ProvidesDaysLen> {
fn len_as_bytes(&self) -> usize { fn len_as_bytes(&self) -> usize {
Self::calc_stamp_len(self.pfield) Self::calc_stamp_len(self.pfield)
@ -643,7 +710,19 @@ impl<ProvidesDaysLen: ProvidesDaysLength> CcsdsTimeProvider for TimeProvider<Pro
} }
fn date_time(&self) -> Option<DateTime<Utc>> { fn date_time(&self) -> Option<DateTime<Utc>> {
self.calc_date_time(self.ms_of_day % 1000) let mut ns_since_last_sec = (self.ms_of_day % 1000) * 10_u32.pow(6);
if let Some(precision) = self.submillis_precision {
match precision {
SubmillisPrecision::Microseconds(us) => {
ns_since_last_sec += us as u32 * 1000;
}
SubmillisPrecision::Picoseconds(ps) => {
ns_since_last_sec += ps / 1000;
}
_ => (),
}
}
self.calc_date_time(ns_since_last_sec)
} }
} }
@ -710,7 +789,7 @@ mod tests {
use chrono::{Datelike, Timelike}; use chrono::{Datelike, Timelike};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use postcard::{from_bytes, to_allocvec}; use postcard::{from_bytes, to_allocvec};
use std::format; use std::{format, println};
#[test] #[test]
fn test_time_stamp_zero_args() { fn test_time_stamp_zero_args() {
@ -980,6 +1059,13 @@ mod tests {
assert_eq!(write_buf[7..11], cross_check.to_be_bytes()); assert_eq!(write_buf[7..11], cross_check.to_be_bytes());
} }
#[test]
fn test() {
let negative_unix_seconds = -(SECONDS_PER_DAY as i32) - 12;
let test = negative_unix_seconds % SECONDS_PER_DAY as i32;
println!("test: {}", test)
}
#[test] #[test]
fn read_stamp_with_ps_submillis_precision() { fn read_stamp_with_ps_submillis_precision() {
let mut time_stamper = TimeProvider::new_with_u16_days(0, 0); let mut time_stamper = TimeProvider::new_with_u16_days(0, 0);