Time module extensions #9
154
src/time/cds.rs
154
src/time/cds.rs
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user