Time module extensions #9
43
CHANGELOG.md
43
CHANGELOG.md
@ -8,6 +8,49 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
|
|
||||||
# [unreleased]
|
# [unreleased]
|
||||||
|
|
||||||
|
## Added
|
||||||
|
|
||||||
|
- New `UnixTimestamp` abstraction which contains the unix seconds as an `i64`
|
||||||
|
and an optional subsecond millisecond counter (`u16`)
|
||||||
|
- `MS_PER_DAY` constant.
|
||||||
|
|
||||||
|
### CDS time module
|
||||||
|
|
||||||
|
- Implement `Add<Duration>` and `AddAssign<Duration>` for time providers, which allows
|
||||||
|
easily adding offsets to the providers.
|
||||||
|
- Implement `TryFrom<DateTime<Utc>>` for time providers.
|
||||||
|
- `get_dyn_time_provider_from_bytes`: Requires `alloc` support and returns
|
||||||
|
the correct `TimeProvider` instance wrapped as a boxed trait object
|
||||||
|
`Box<DynCdsTimeProvider>` by checking the length of days field.
|
||||||
|
- Added constructor function to create the time provider
|
||||||
|
from `chrono::DateTime<Utc>` and a generic UNIX timestamp (`i64` seconds
|
||||||
|
and subsecond milliseconds).
|
||||||
|
- `MAX_DAYS_24_BITS` which contains maximum value which can be supplied
|
||||||
|
to the days field of a CDS time provider with 24 bits days field width.
|
||||||
|
- New `CdsTimestamp` trait which encapsulates common fields for all CDS time providers.
|
||||||
|
- `from_unix_secs_with_u24_days` and `from_unix_secs_with_u16_days` which create
|
||||||
|
the time provider from a `UnixTimestamp` reference.
|
||||||
|
- `from_dt_with_u16_days`, `from_dt_with_u24_days` and their `..._us_precision` and
|
||||||
|
`..._ps_precision` variants which allow to create time providers from
|
||||||
|
a `chrono::DateTime<Utc>`.
|
||||||
|
- Add `from_bytes_with_u24_days` and `from_bytes_with_u16_days` associated methods
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
|
||||||
|
- (breaking): Renamed `from_now_with_u24_days_and_us_prec` to `from_now_with_u24_days_us_precision`.
|
||||||
|
Also did the same for the `u16` variant.
|
||||||
|
- (breaking): Renamed `from_now_with_u24_days_and_ps_prec` to `from_now_with_u24_days_ps_precision`.
|
||||||
|
Also did the same for the `u16` variant.
|
||||||
|
- `CcsdsTimeProvider` trait (breaking):
|
||||||
|
- Add new `unix_stamp` method returning the new `UnixTimeStamp` struct.
|
||||||
|
- Add new `subsecond_millis` method returning counter `Option<u16>`.
|
||||||
|
- Default impl for `unix_stamp` which re-uses `subsecond_millis` and
|
||||||
|
existing `unix_seconds` method.
|
||||||
|
- `TimestampError` (breaking): Add `DateBeforeCcsdsEpoch` error type
|
||||||
|
because new CDS API allow supplying invalid date times before CCSDS epoch.
|
||||||
|
Make `TimestampError` with `#[non_exhaustive]` attribute to prevent
|
||||||
|
future breakages if new error variants are added.
|
||||||
|
|
||||||
# [v0.4.2] 14.01.2023
|
# [v0.4.2] 14.01.2023
|
||||||
|
|
||||||
## Fixed
|
## Fixed
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "spacepackets"
|
name = "spacepackets"
|
||||||
version = "0.4.2"
|
version = "0.5.0-rc.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.60"
|
rust-version = "1.60"
|
||||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||||
|
1261
src/time/cds.rs
1261
src/time/cds.rs
File diff suppressed because it is too large
Load Diff
@ -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,19 @@ impl CcsdsTimeProvider for TimeProviderCcsdsEpoch {
|
|||||||
CcsdsTimeCodes::CucCcsdsEpoch
|
CcsdsTimeCodes::CucCcsdsEpoch
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Please note that this function only works as intended if the time counter resolution
|
|
||||||
/// is one second.
|
|
||||||
fn unix_seconds(&self) -> i64 {
|
fn unix_seconds(&self) -> i64 {
|
||||||
ccsds_epoch_to_unix_epoch(self.counter.1 as u64) as 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
|
||||||
}
|
}
|
||||||
|
|
||||||
fn date_time(&self) -> Option<DateTime<Utc>> {
|
fn date_time(&self) -> Option<DateTime<Utc>> {
|
||||||
|
118
src/time/mod.rs
118
src/time/mod.rs
@ -20,6 +20,7 @@ pub mod cuc;
|
|||||||
|
|
||||||
pub const DAYS_CCSDS_TO_UNIX: i32 = -4383;
|
pub const DAYS_CCSDS_TO_UNIX: i32 = -4383;
|
||||||
pub const SECONDS_PER_DAY: u32 = 86400;
|
pub const SECONDS_PER_DAY: u32 = 86400;
|
||||||
|
pub const MS_PER_DAY: u32 = SECONDS_PER_DAY * 1000;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
@ -55,6 +56,7 @@ pub fn ccsds_time_code_from_p_field(pfield: u8) -> Result<CcsdsTimeCodes, u8> {
|
|||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[non_exhaustive]
|
||||||
pub enum TimestampError {
|
pub enum TimestampError {
|
||||||
/// Contains tuple where first value is the expected time code and the second
|
/// Contains tuple where first value is the expected time code and the second
|
||||||
/// value is the found raw value
|
/// value is the found raw value
|
||||||
@ -62,6 +64,7 @@ pub enum TimestampError {
|
|||||||
ByteConversionError(ByteConversionError),
|
ByteConversionError(ByteConversionError),
|
||||||
CdsError(cds::CdsError),
|
CdsError(cds::CdsError),
|
||||||
CucError(cuc::CucError),
|
CucError(cuc::CucError),
|
||||||
|
DateBeforeCcsdsEpoch(DateTime<Utc>),
|
||||||
CustomEpochNotSupported,
|
CustomEpochNotSupported,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,6 +121,9 @@ impl Display for TimestampError {
|
|||||||
TimestampError::ByteConversionError(e) => {
|
TimestampError::ByteConversionError(e) => {
|
||||||
write!(f, "byte conversion error {}", e)
|
write!(f, "byte conversion error {}", e)
|
||||||
}
|
}
|
||||||
|
TimestampError::DateBeforeCcsdsEpoch(e) => {
|
||||||
|
write!(f, "datetime with date before ccsds epoch: {}", e)
|
||||||
|
}
|
||||||
TimestampError::CustomEpochNotSupported => {
|
TimestampError::CustomEpochNotSupported => {
|
||||||
write!(f, "custom epochs are not supported")
|
write!(f, "custom epochs are not supported")
|
||||||
}
|
}
|
||||||
@ -199,6 +205,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;
|
||||||
|
|
||||||
@ -208,10 +217,99 @@ 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 {
|
||||||
|
/// Returns none if the subsecond millisecond value is larger than 999.
|
||||||
|
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),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn const_new(unix_seconds: i64, subsec_millis: u16) -> Self {
|
||||||
|
if subsec_millis > 999 {
|
||||||
|
panic!("subsec milliseconds exceeds 999");
|
||||||
|
}
|
||||||
|
Self {
|
||||||
|
unix_seconds,
|
||||||
|
subsecond_millis: Some(subsec_millis),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_only_seconds(unix_seconds: i64) -> Self {
|
||||||
|
Self {
|
||||||
|
unix_seconds,
|
||||||
|
subsecond_millis: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_date_time(&self) -> LocalResult<DateTime<Utc>> {
|
||||||
|
Utc.timestamp_opt(
|
||||||
|
self.unix_seconds,
|
||||||
|
self.subsecond_millis.unwrap_or(0) as u32 * 10_u32.pow(6),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DateTime<Utc>> for UnixTimestamp {
|
||||||
|
fn from(value: DateTime<Utc>) -> Self {
|
||||||
|
Self {
|
||||||
|
unix_seconds: value.timestamp(),
|
||||||
|
subsecond_millis: Some(value.timestamp_subsec_millis() as u16),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(all(test, feature = "std"))]
|
#[cfg(all(test, feature = "std"))]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -240,4 +338,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_only_seconds(-200);
|
||||||
|
assert_eq!(stamp.unix_seconds, -200);
|
||||||
|
assert!(stamp.subsecond_millis().is_none());
|
||||||
|
let stamp = UnixTimestamp::new_only_seconds(250);
|
||||||
|
assert_eq!(stamp.unix_seconds, 250);
|
||||||
|
assert!(stamp.subsecond_millis().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic_float_unix_stamp_test() {
|
||||||
|
let stamp = UnixTimestamp::new(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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user