UnixTimestamp now has nanosecond precision
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
aae246dca6
commit
05b6503851
14
CHANGELOG.md
14
CHANGELOG.md
@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
# [unreleased]
|
||||
|
||||
Major API changes for the time API. If you are using the time API, it is strongly recommended
|
||||
to check all the API changes.
|
||||
|
||||
## Fixed
|
||||
|
||||
- CUC timestamp was fixed to include leap second corrections because it is based on the TAI
|
||||
@ -20,6 +23,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- Added basic support conversions to the `time` library. Introduce new `chrono` and `timelib`
|
||||
feature gate
|
||||
|
||||
## Changed
|
||||
|
||||
- `UnixTimestamp::new` renamed to `UnixTimestamp::new_checked`.
|
||||
- `UnixTimestamp` now has a nanosecond subsecond precision. The `new` constructor now expects
|
||||
nanoseconds as the second argument.
|
||||
- Added new `UnixTimestamp::new_subsec_millis` and `UnixTimestamp::new_subsec_millis_checked` API
|
||||
to still allow creating a timestamp with only millisecond subsecond resolution.
|
||||
- `CcsdsTimeProvider` now has a new `subsecond_nanos` method in addition to a default
|
||||
implementation for the `subsecond_millis` method.
|
||||
- `CcsdsTimeProvider::date_time` renamed to `CcsdsTimeProvider::chrono_date_time`
|
||||
|
||||
# [v0.11.0-rc.0] 2024-03-04
|
||||
|
||||
## Added
|
||||
|
@ -48,8 +48,9 @@ optional = true
|
||||
version = "0.2"
|
||||
default-features = false
|
||||
|
||||
[dev-dependencies.postcard]
|
||||
version = "1"
|
||||
[dev-dependencies]
|
||||
postcard = "1"
|
||||
chrono = "0.4"
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
|
350
src/time/cds.rs
350
src/time/cds.rs
@ -4,20 +4,40 @@
|
||||
//! 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 crate::private::Sealed;
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::boxed::Box;
|
||||
#[cfg(feature = "chrono")]
|
||||
use chrono::Datelike;
|
||||
#[cfg(feature = "alloc")]
|
||||
use core::any::Any;
|
||||
use crate::ByteConversionError;
|
||||
use core::cmp::Ordering;
|
||||
use core::fmt::Debug;
|
||||
use core::fmt::{Debug, Display, Formatter};
|
||||
use core::ops::{Add, AddAssign};
|
||||
use core::time::Duration;
|
||||
|
||||
use delegate::delegate;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use super::StdTimestampError;
|
||||
#[cfg(feature = "std")]
|
||||
use std::error::Error;
|
||||
#[cfg(feature = "std")]
|
||||
use std::time::{SystemTime, SystemTimeError};
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
use chrono::Datelike;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
use super::ccsds_time_code_from_p_field;
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::boxed::Box;
|
||||
#[cfg(feature = "alloc")]
|
||||
use core::any::Any;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use super::{
|
||||
ccsds_to_unix_days, unix_to_ccsds_days, CcsdsTimeCodes, CcsdsTimeProvider, TimeReader,
|
||||
TimeWriter, TimestampError, UnixTimestamp, MS_PER_DAY, SECONDS_PER_DAY,
|
||||
};
|
||||
|
||||
/// Base value for the preamble field for a time field parser to determine the time field type.
|
||||
pub const P_FIELD_BASE: u8 = (CcsdsTimeCodes::Cds as u8) << 4;
|
||||
pub const MIN_CDS_FIELD_LEN: usize = 7;
|
||||
@ -165,7 +185,6 @@ pub struct TimeProvider<DaysLen: ProvidesDaysLength = DaysLen16Bits> {
|
||||
pfield: u8,
|
||||
ccsds_days: DaysLen::FieldType,
|
||||
ms_of_day: u32,
|
||||
// submillis_precision: SubmillisPrecision,
|
||||
submillis: u32,
|
||||
/// This is not strictly necessary but still cached because it significantly simplifies the
|
||||
/// calculation of [`DateTime<Utc>`].
|
||||
@ -195,36 +214,47 @@ trait CdsConverter: CdsCommon {
|
||||
struct ConversionFromUnix {
|
||||
ccsds_days: u32,
|
||||
ms_of_day: u32,
|
||||
submilis_prec: SubmillisPrecision,
|
||||
submillis: 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,
|
||||
}
|
||||
|
||||
impl ConversionFromUnix {
|
||||
fn new(unix_seconds: i64, subsec_millis: u32) -> Result<Self, TimestampError> {
|
||||
fn new(
|
||||
unix_seconds: i64,
|
||||
subsec_nanos: u32,
|
||||
precision: SubmillisPrecision,
|
||||
) -> Result<Self, TimestampError> {
|
||||
let (unix_days, secs_of_day) = calc_unix_days_and_secs_of_day(unix_seconds);
|
||||
let ccsds_days = unix_to_ccsds_days(unix_days);
|
||||
if ccsds_days == 0 && (secs_of_day > 0 || subsec_millis > 0) || ccsds_days < 0 {
|
||||
let millis = if unix_seconds < 0 {
|
||||
unix_seconds * 1000 - subsec_millis as i64
|
||||
} else {
|
||||
unix_seconds * 1000 + subsec_millis as i64
|
||||
};
|
||||
if ccsds_days == 0 && (secs_of_day > 0 || subsec_nanos > 0) || ccsds_days < 0 {
|
||||
return Err(TimestampError::DateBeforeCcsdsEpoch(
|
||||
Utc.timestamp_millis_opt(millis).unwrap(),
|
||||
UnixTimestamp::new_checked(unix_seconds, subsec_nanos)
|
||||
.expect("unix timestamp creation failed"),
|
||||
));
|
||||
}
|
||||
let ms_of_day = secs_of_day * 1000 + subsec_nanos / 10_u32.pow(6);
|
||||
|
||||
let submillis = match precision {
|
||||
SubmillisPrecision::Microseconds => (subsec_nanos / 1_000) % 1000,
|
||||
SubmillisPrecision::Picoseconds => (subsec_nanos % 10_u32.pow(6)) * 1000,
|
||||
_ => 0,
|
||||
};
|
||||
Ok(Self {
|
||||
ccsds_days: unix_to_ccsds_days(unix_days) as u32,
|
||||
ms_of_day: secs_of_day * 1000 + subsec_millis,
|
||||
ms_of_day,
|
||||
unix_days_seconds: unix_days * SECONDS_PER_DAY as i64,
|
||||
submilis_prec: precision,
|
||||
submillis,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl CdsCommon for ConversionFromUnix {
|
||||
fn submillis_precision(&self) -> SubmillisPrecision {
|
||||
SubmillisPrecision::Absent
|
||||
self.submilis_prec
|
||||
}
|
||||
|
||||
fn ms_of_day(&self) -> u32 {
|
||||
@ -236,7 +266,7 @@ impl CdsCommon for ConversionFromUnix {
|
||||
}
|
||||
|
||||
fn submillis(&self) -> u32 {
|
||||
0
|
||||
self.submillis
|
||||
}
|
||||
}
|
||||
|
||||
@ -246,13 +276,13 @@ impl CdsConverter for ConversionFromUnix {
|
||||
}
|
||||
}
|
||||
/// Helper struct which generates fields for the CDS time provider from a datetime.
|
||||
struct ConversionFromDatetime {
|
||||
struct ConversionFromChronoDatetime {
|
||||
unix_conversion: ConversionFromUnix,
|
||||
submillis_prec: SubmillisPrecision,
|
||||
submillis: u32,
|
||||
}
|
||||
|
||||
impl CdsCommon for ConversionFromDatetime {
|
||||
impl CdsCommon for ConversionFromChronoDatetime {
|
||||
fn submillis_precision(&self) -> SubmillisPrecision {
|
||||
self.submillis_prec
|
||||
}
|
||||
@ -269,7 +299,7 @@ impl CdsCommon for ConversionFromDatetime {
|
||||
}
|
||||
}
|
||||
|
||||
impl CdsConverter for ConversionFromDatetime {
|
||||
impl CdsConverter for ConversionFromChronoDatetime {
|
||||
delegate! {to self.unix_conversion { fn unix_days_seconds(&self) -> i64; }}
|
||||
}
|
||||
|
||||
@ -286,27 +316,38 @@ fn calc_unix_days_and_secs_of_day(unix_seconds: i64) -> (i64, u32) {
|
||||
(unix_days, secs_of_day as u32)
|
||||
}
|
||||
|
||||
impl ConversionFromDatetime {
|
||||
fn new(dt: &DateTime<Utc>) -> Result<Self, TimestampError> {
|
||||
#[cfg(feature = "chrono")]
|
||||
impl ConversionFromChronoDatetime {
|
||||
fn new(dt: &chrono::DateTime<chrono::Utc>) -> Result<Self, TimestampError> {
|
||||
Self::new_generic(dt, SubmillisPrecision::Absent)
|
||||
}
|
||||
|
||||
fn new_with_submillis_us_prec(dt: &DateTime<Utc>) -> Result<Self, TimestampError> {
|
||||
fn new_with_submillis_us_prec(
|
||||
dt: &chrono::DateTime<chrono::Utc>,
|
||||
) -> Result<Self, TimestampError> {
|
||||
Self::new_generic(dt, SubmillisPrecision::Microseconds)
|
||||
}
|
||||
|
||||
fn new_with_submillis_ps_prec(dt: &DateTime<Utc>) -> Result<Self, TimestampError> {
|
||||
fn new_with_submillis_ps_prec(
|
||||
dt: &chrono::DateTime<chrono::Utc>,
|
||||
) -> Result<Self, TimestampError> {
|
||||
Self::new_generic(dt, SubmillisPrecision::Picoseconds)
|
||||
}
|
||||
|
||||
fn new_generic(dt: &DateTime<Utc>, prec: SubmillisPrecision) -> Result<Self, TimestampError> {
|
||||
#[cfg(feature = "chrono")]
|
||||
fn new_generic(
|
||||
dt: &chrono::DateTime<chrono::Utc>,
|
||||
prec: SubmillisPrecision,
|
||||
) -> Result<Self, TimestampError> {
|
||||
// The CDS timestamp does not support timestamps before the CCSDS epoch.
|
||||
if dt.year() < 1958 {
|
||||
return Err(TimestampError::DateBeforeCcsdsEpoch(*dt));
|
||||
return Err(TimestampError::DateBeforeCcsdsEpoch(UnixTimestamp::from(
|
||||
*dt,
|
||||
)));
|
||||
}
|
||||
// The contained values in the conversion should be all positive now
|
||||
let unix_conversion =
|
||||
ConversionFromUnix::new(dt.timestamp(), dt.timestamp_subsec_millis())?;
|
||||
ConversionFromUnix::new(dt.timestamp(), dt.timestamp_subsec_nanos(), prec)?;
|
||||
let mut submillis = 0;
|
||||
match prec {
|
||||
SubmillisPrecision::Microseconds => {
|
||||
@ -351,7 +392,8 @@ impl ConversionFromNow {
|
||||
let epoch = now.as_secs();
|
||||
// This should always return a value with valid (non-negative) CCSDS days,
|
||||
// so it is okay to unwrap
|
||||
let unix_conversion = ConversionFromUnix::new(epoch as i64, now.subsec_millis()).unwrap();
|
||||
let unix_conversion =
|
||||
ConversionFromUnix::new(epoch as i64, now.subsec_nanos(), prec).unwrap();
|
||||
let mut submillis = 0;
|
||||
|
||||
match prec {
|
||||
@ -589,20 +631,6 @@ impl<ProvidesDaysLen: ProvidesDaysLength> TimeProvider<ProvidesDaysLen> {
|
||||
self.calc_unix_seconds(unix_days_seconds, ms_of_day);
|
||||
}
|
||||
|
||||
fn calc_ns_since_last_second(&self) -> u32 {
|
||||
let mut ns_since_last_sec = (self.ms_of_day % 1000) * 10_u32.pow(6);
|
||||
match self.submillis_precision() {
|
||||
SubmillisPrecision::Microseconds => {
|
||||
ns_since_last_sec += self.submillis() * 1000;
|
||||
}
|
||||
SubmillisPrecision::Picoseconds => {
|
||||
ns_since_last_sec += self.submillis() / 1000;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
ns_since_last_sec
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn calc_unix_seconds(&mut self, mut unix_days_seconds: i64, ms_of_day: u32) {
|
||||
let seconds_of_day = (ms_of_day / 1000) as i64;
|
||||
@ -611,35 +639,11 @@ impl<ProvidesDaysLen: ProvidesDaysLength> TimeProvider<ProvidesDaysLen> {
|
||||
} else {
|
||||
unix_days_seconds += seconds_of_day;
|
||||
}
|
||||
self.unix_stamp = UnixTimestamp::const_new(unix_days_seconds, (ms_of_day % 1000) as u16);
|
||||
}
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
fn calc_chrono_date_time(
|
||||
&self,
|
||||
ns_since_last_second: u32,
|
||||
) -> Option<chrono::DateTime<chrono::Utc>> {
|
||||
assert!(
|
||||
ns_since_last_second < 10_u32.pow(9),
|
||||
"Invalid MS since last second"
|
||||
);
|
||||
if let LocalResult::Single(val) =
|
||||
Utc.timestamp_opt(self.unix_stamp.unix_seconds, ns_since_last_second)
|
||||
{
|
||||
return Some(val);
|
||||
let mut subsec_nanos = (ms_of_day % 1000) * 10_u32.pow(6);
|
||||
if let Some(precision) = self.precision_as_ns() {
|
||||
subsec_nanos += precision;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(feature = "timelib")]
|
||||
fn calc_timelib_date_time(
|
||||
&self,
|
||||
ns_since_last_second: u32,
|
||||
) -> Result<time::OffsetDateTime, time::error::ComponentRange> {
|
||||
Ok(
|
||||
time::OffsetDateTime::from_unix_timestamp(self.unix_stamp.unix_seconds)?
|
||||
+ time::Duration::nanoseconds(ns_since_last_second.into()),
|
||||
)
|
||||
self.unix_stamp = UnixTimestamp::new(unix_days_seconds, subsec_nanos);
|
||||
}
|
||||
|
||||
fn length_check(&self, buf: &[u8], len_as_bytes: usize) -> Result<(), TimestampError> {
|
||||
@ -674,36 +678,43 @@ impl<ProvidesDaysLen: ProvidesDaysLength> TimeProvider<ProvidesDaysLen> {
|
||||
Ok(provider)
|
||||
}
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
fn from_dt_generic(
|
||||
dt: &DateTime<Utc>,
|
||||
dt: &chrono::DateTime<chrono::Utc>,
|
||||
days_len: LengthOfDaySegment,
|
||||
) -> Result<Self, TimestampError> {
|
||||
let conv_from_dt = ConversionFromDatetime::new(dt)?;
|
||||
let conv_from_dt = ConversionFromChronoDatetime::new(dt)?;
|
||||
Self::generic_from_conversion(days_len, conv_from_dt)
|
||||
}
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
fn from_dt_generic_us_prec(
|
||||
dt: &DateTime<Utc>,
|
||||
dt: &chrono::DateTime<chrono::Utc>,
|
||||
days_len: LengthOfDaySegment,
|
||||
) -> Result<Self, TimestampError> {
|
||||
let conv_from_dt = ConversionFromDatetime::new_with_submillis_us_prec(dt)?;
|
||||
let conv_from_dt = ConversionFromChronoDatetime::new_with_submillis_us_prec(dt)?;
|
||||
Self::generic_from_conversion(days_len, conv_from_dt)
|
||||
}
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
fn from_dt_generic_ps_prec(
|
||||
dt: &DateTime<Utc>,
|
||||
dt: &chrono::DateTime<chrono::Utc>,
|
||||
days_len: LengthOfDaySegment,
|
||||
) -> Result<Self, TimestampError> {
|
||||
let conv_from_dt = ConversionFromDatetime::new_with_submillis_ps_prec(dt)?;
|
||||
let conv_from_dt = ConversionFromChronoDatetime::new_with_submillis_ps_prec(dt)?;
|
||||
Self::generic_from_conversion(days_len, conv_from_dt)
|
||||
}
|
||||
|
||||
fn from_unix_generic(
|
||||
unix_stamp: &UnixTimestamp,
|
||||
days_len: LengthOfDaySegment,
|
||||
submillis_prec: SubmillisPrecision,
|
||||
) -> Result<Self, TimestampError> {
|
||||
let conv_from_dt =
|
||||
ConversionFromUnix::new(unix_stamp.unix_seconds, unix_stamp.subsecond_millis as u32)?;
|
||||
let conv_from_dt = ConversionFromUnix::new(
|
||||
unix_stamp.unix_seconds,
|
||||
unix_stamp.subsecond_nanos,
|
||||
submillis_prec,
|
||||
)?;
|
||||
Self::generic_from_conversion(days_len, conv_from_dt)
|
||||
}
|
||||
|
||||
@ -818,7 +829,10 @@ impl TimeProvider<DaysLen24Bits> {
|
||||
/// This function will return [TimestampError::DateBeforeCcsdsEpoch] or
|
||||
/// [TimestampError::Cds] if the time is before the CCSDS epoch (1958-01-01T00:00:00+00:00)
|
||||
/// or the CCSDS days value exceeds the allowed bit width (24 bits).
|
||||
pub fn from_dt_with_u24_days(dt: &DateTime<Utc>) -> Result<Self, TimestampError> {
|
||||
#[cfg(feature = "chrono")]
|
||||
pub fn from_dt_with_u24_days(
|
||||
dt: &chrono::DateTime<chrono::Utc>,
|
||||
) -> Result<Self, TimestampError> {
|
||||
Self::from_dt_generic(dt, LengthOfDaySegment::Long24Bits)
|
||||
}
|
||||
|
||||
@ -829,19 +843,26 @@ impl TimeProvider<DaysLen24Bits> {
|
||||
/// This function will return [TimestampError::DateBeforeCcsdsEpoch] or
|
||||
/// [TimestampError::Cds] if the time is before the CCSDS epoch (1958-01-01T00:00:00+00:00)
|
||||
/// or the CCSDS days value exceeds the allowed bit width (24 bits).
|
||||
pub fn from_unix_secs_with_u24_days(
|
||||
pub fn from_unix_stamp_with_u24_days(
|
||||
unix_stamp: &UnixTimestamp,
|
||||
submillis_prec: SubmillisPrecision,
|
||||
) -> Result<Self, TimestampError> {
|
||||
Self::from_unix_generic(unix_stamp, LengthOfDaySegment::Long24Bits)
|
||||
Self::from_unix_generic(unix_stamp, LengthOfDaySegment::Long24Bits, submillis_prec)
|
||||
}
|
||||
|
||||
/// Like [Self::from_dt_with_u24_days] but with microsecond sub-millisecond precision.
|
||||
pub fn from_dt_with_u24_days_us_precision(dt: &DateTime<Utc>) -> Result<Self, TimestampError> {
|
||||
#[cfg(feature = "chrono")]
|
||||
pub fn from_dt_with_u24_days_us_precision(
|
||||
dt: &chrono::DateTime<chrono::Utc>,
|
||||
) -> Result<Self, TimestampError> {
|
||||
Self::from_dt_generic_us_prec(dt, LengthOfDaySegment::Long24Bits)
|
||||
}
|
||||
|
||||
/// Like [Self::from_dt_with_u24_days] but with picoseconds sub-millisecond precision.
|
||||
pub fn from_dt_with_u24_days_ps_precision(dt: &DateTime<Utc>) -> Result<Self, TimestampError> {
|
||||
#[cfg(feature = "chrono")]
|
||||
pub fn from_dt_with_u24_days_ps_precision(
|
||||
dt: &chrono::DateTime<chrono::Utc>,
|
||||
) -> Result<Self, TimestampError> {
|
||||
Self::from_dt_generic_ps_prec(dt, LengthOfDaySegment::Long24Bits)
|
||||
}
|
||||
|
||||
@ -898,7 +919,10 @@ impl TimeProvider<DaysLen16Bits> {
|
||||
/// This function will return a [TimestampError::DateBeforeCcsdsEpoch] or a
|
||||
/// [TimestampError::Cds] if the time is before the CCSDS epoch (01-01-1958 00:00:00) or
|
||||
/// the CCSDS days value exceeds the allowed bit width (16 bits).
|
||||
pub fn from_dt_with_u16_days(dt: &DateTime<Utc>) -> Result<Self, TimestampError> {
|
||||
#[cfg(feature = "chrono")]
|
||||
pub fn from_dt_with_u16_days(
|
||||
dt: &chrono::DateTime<chrono::Utc>,
|
||||
) -> Result<Self, TimestampError> {
|
||||
Self::from_dt_generic(dt, LengthOfDaySegment::Short16Bits)
|
||||
}
|
||||
|
||||
@ -916,19 +940,26 @@ impl TimeProvider<DaysLen16Bits> {
|
||||
/// This function will return [TimestampError::DateBeforeCcsdsEpoch] or
|
||||
/// [TimestampError::Cds] if the time is before the CCSDS epoch (1958-01-01T00:00:00+00:00)
|
||||
/// or the CCSDS days value exceeds the allowed bit width (24 bits).
|
||||
pub fn from_unix_secs_with_u16_days(
|
||||
pub fn from_unix_stamp_with_u16_days(
|
||||
unix_stamp: &UnixTimestamp,
|
||||
submillis_prec: SubmillisPrecision,
|
||||
) -> Result<Self, TimestampError> {
|
||||
Self::from_unix_generic(unix_stamp, LengthOfDaySegment::Short16Bits)
|
||||
Self::from_unix_generic(unix_stamp, LengthOfDaySegment::Short16Bits, submillis_prec)
|
||||
}
|
||||
|
||||
/// Like [Self::from_dt_with_u16_days] but with microsecond sub-millisecond precision.
|
||||
pub fn from_dt_with_u16_days_us_precision(dt: &DateTime<Utc>) -> Result<Self, TimestampError> {
|
||||
#[cfg(feature = "chrono")]
|
||||
pub fn from_dt_with_u16_days_us_precision(
|
||||
dt: &chrono::DateTime<chrono::Utc>,
|
||||
) -> Result<Self, TimestampError> {
|
||||
Self::from_dt_generic_us_prec(dt, LengthOfDaySegment::Short16Bits)
|
||||
}
|
||||
|
||||
/// Like [Self::from_dt_with_u16_days] but with picoseconds sub-millisecond precision.
|
||||
pub fn from_dt_with_u16_days_ps_precision(dt: &DateTime<Utc>) -> Result<Self, TimestampError> {
|
||||
#[cfg(feature = "chrono")]
|
||||
pub fn from_dt_with_u16_days_ps_precision(
|
||||
dt: &chrono::DateTime<chrono::Utc>,
|
||||
) -> Result<Self, TimestampError> {
|
||||
Self::from_dt_generic_ps_prec(dt, LengthOfDaySegment::Short16Bits)
|
||||
}
|
||||
|
||||
@ -1140,20 +1171,22 @@ impl AddAssign<Duration> for TimeProvider<DaysLen24Bits> {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<DateTime<Utc>> for TimeProvider<DaysLen16Bits> {
|
||||
#[cfg(feature = "chrono")]
|
||||
impl TryFrom<chrono::DateTime<chrono::Utc>> for TimeProvider<DaysLen16Bits> {
|
||||
type Error = TimestampError;
|
||||
|
||||
fn try_from(dt: DateTime<Utc>) -> Result<Self, Self::Error> {
|
||||
let conversion = ConversionFromDatetime::new(&dt)?;
|
||||
fn try_from(dt: chrono::DateTime<chrono::Utc>) -> Result<Self, Self::Error> {
|
||||
let conversion = ConversionFromChronoDatetime::new(&dt)?;
|
||||
Self::generic_from_conversion(LengthOfDaySegment::Short16Bits, conversion)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<DateTime<Utc>> for TimeProvider<DaysLen24Bits> {
|
||||
#[cfg(feature = "chrono")]
|
||||
impl TryFrom<chrono::DateTime<chrono::Utc>> for TimeProvider<DaysLen24Bits> {
|
||||
type Error = TimestampError;
|
||||
|
||||
fn try_from(dt: DateTime<Utc>) -> Result<Self, Self::Error> {
|
||||
let conversion = ConversionFromDatetime::new(&dt)?;
|
||||
fn try_from(dt: chrono::DateTime<chrono::Utc>) -> Result<Self, Self::Error> {
|
||||
let conversion = ConversionFromChronoDatetime::new(&dt)?;
|
||||
Self::generic_from_conversion(LengthOfDaySegment::Long24Bits, conversion)
|
||||
}
|
||||
}
|
||||
@ -1176,26 +1209,14 @@ impl<ProvidesDaysLen: ProvidesDaysLength> CcsdsTimeProvider for TimeProvider<Pro
|
||||
self.unix_stamp.unix_seconds
|
||||
}
|
||||
#[inline]
|
||||
fn subsecond_millis(&self) -> u16 {
|
||||
self.unix_stamp.subsecond_millis
|
||||
fn subsecond_nanos(&self) -> u32 {
|
||||
self.unix_stamp.subsecond_nanos
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn unix_stamp(&self) -> UnixTimestamp {
|
||||
self.unix_stamp
|
||||
}
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "chrono")))]
|
||||
fn chrono_date_time(&self) -> Option<chrono::DateTime<chrono::Utc>> {
|
||||
self.calc_chrono_date_time(self.calc_ns_since_last_second())
|
||||
}
|
||||
|
||||
#[cfg(feature = "timelib")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "timelib")))]
|
||||
fn timelib_date_time(&self) -> Result<time::OffsetDateTime, time::error::ComponentRange> {
|
||||
self.calc_timelib_date_time(self.calc_ns_since_last_second())
|
||||
}
|
||||
}
|
||||
|
||||
impl TimeReader for TimeProvider<DaysLen16Bits> {
|
||||
@ -1330,6 +1351,7 @@ impl TryFrom<TimeProvider<DaysLen24Bits>> for TimeProvider<DaysLen16Bits> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::time::TimestampError::{ByteConversion, InvalidTimeCode};
|
||||
use crate::time::{UnixTimestamp, DAYS_CCSDS_TO_UNIX, MS_PER_DAY};
|
||||
use crate::ByteConversionError::{FromSliceTooSmall, ToSliceTooSmall};
|
||||
use alloc::string::ToString;
|
||||
use chrono::{Datelike, NaiveDate, Timelike};
|
||||
@ -1345,13 +1367,13 @@ mod tests {
|
||||
unix_stamp.unix_seconds,
|
||||
(DAYS_CCSDS_TO_UNIX * SECONDS_PER_DAY as i32) as i64
|
||||
);
|
||||
let subsecond_millis = unix_stamp.subsecond_millis;
|
||||
let subsecond_millis = unix_stamp.subsecond_nanos;
|
||||
assert_eq!(subsecond_millis, 0);
|
||||
assert_eq!(
|
||||
time_stamper.submillis_precision(),
|
||||
SubmillisPrecision::Absent
|
||||
);
|
||||
assert_eq!(time_stamper.subsecond_millis(), 0);
|
||||
assert_eq!(time_stamper.subsecond_nanos(), 0);
|
||||
assert_eq!(time_stamper.ccdsd_time_code(), CcsdsTimeCodes::Cds);
|
||||
assert_eq!(
|
||||
time_stamper.p_field(),
|
||||
@ -1382,8 +1404,10 @@ mod tests {
|
||||
assert_eq!(date_time.minute(), 0);
|
||||
assert_eq!(date_time.second(), 0);
|
||||
let time_stamper = TimeProvider::new_with_u16_days((-DAYS_CCSDS_TO_UNIX) as u16, 40);
|
||||
assert_eq!(time_stamper.subsecond_nanos(), 40 * 10_u32.pow(6));
|
||||
assert_eq!(time_stamper.subsecond_millis(), 40);
|
||||
let time_stamper = TimeProvider::new_with_u16_days((-DAYS_CCSDS_TO_UNIX) as u16, 1040);
|
||||
assert_eq!(time_stamper.subsecond_nanos(), 40 * 10_u32.pow(6));
|
||||
assert_eq!(time_stamper.subsecond_millis(), 40);
|
||||
}
|
||||
|
||||
@ -1565,7 +1589,7 @@ mod tests {
|
||||
|
||||
fn generic_now_test<T: ProvidesDaysLength>(
|
||||
timestamp_now: TimeProvider<T>,
|
||||
compare_stamp: DateTime<Utc>,
|
||||
compare_stamp: chrono::DateTime<chrono::Utc>,
|
||||
) {
|
||||
let dt = timestamp_now.chrono_date_time().unwrap();
|
||||
if compare_stamp.year() > dt.year() {
|
||||
@ -1591,35 +1615,35 @@ mod tests {
|
||||
#[test]
|
||||
fn test_time_now() {
|
||||
let timestamp_now = TimeProvider::from_now_with_u16_days().unwrap();
|
||||
let compare_stamp = Utc::now();
|
||||
let compare_stamp = chrono::Utc::now();
|
||||
generic_now_test(timestamp_now, compare_stamp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_now_us_prec() {
|
||||
let timestamp_now = TimeProvider::from_now_with_u16_days_us_precision().unwrap();
|
||||
let compare_stamp = Utc::now();
|
||||
let compare_stamp = chrono::Utc::now();
|
||||
generic_now_test(timestamp_now, compare_stamp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_now_ps_prec() {
|
||||
let timestamp_now = TimeProvider::from_now_with_u16_days_ps_precision().unwrap();
|
||||
let compare_stamp = Utc::now();
|
||||
let compare_stamp = chrono::Utc::now();
|
||||
generic_now_test(timestamp_now, compare_stamp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_now_ps_prec_u16_days() {
|
||||
let timestamp_now = TimeProvider::from_now_with_u16_days_ps_precision().unwrap();
|
||||
let compare_stamp = Utc::now();
|
||||
let compare_stamp = chrono::Utc::now();
|
||||
generic_now_test(timestamp_now, compare_stamp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_now_ps_prec_u24_days() {
|
||||
let timestamp_now = TimeProvider::from_now_with_u24_days_ps_precision().unwrap();
|
||||
let compare_stamp = Utc::now();
|
||||
let compare_stamp = chrono::Utc::now();
|
||||
generic_now_test(timestamp_now, compare_stamp);
|
||||
}
|
||||
|
||||
@ -1743,19 +1767,19 @@ mod tests {
|
||||
assert_eq!(stamp_deserialized.submillis(), 5e8 as u32);
|
||||
}
|
||||
|
||||
fn generic_dt_case_0_no_prec(subsec_millis: u32) -> DateTime<Utc> {
|
||||
fn generic_dt_case_0_no_prec(subsec_millis: u32) -> chrono::DateTime<chrono::Utc> {
|
||||
NaiveDate::from_ymd_opt(2023, 1, 14)
|
||||
.unwrap()
|
||||
.and_hms_milli_opt(16, 49, 30, subsec_millis)
|
||||
.unwrap()
|
||||
.and_local_timezone(Utc)
|
||||
.and_local_timezone(chrono::Utc)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn generic_check_dt_case_0<DaysLen: ProvidesDaysLength>(
|
||||
time_provider: &TimeProvider<DaysLen>,
|
||||
subsec_millis: u32,
|
||||
datetime_utc: DateTime<Utc>,
|
||||
datetime_utc: chrono::DateTime<chrono::Utc>,
|
||||
) {
|
||||
// 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.
|
||||
@ -1790,21 +1814,21 @@ mod tests {
|
||||
assert_eq!(time_provider, time_provider_2);
|
||||
}
|
||||
|
||||
fn generic_dt_case_1_us_prec(subsec_millis: u32) -> DateTime<Utc> {
|
||||
fn generic_dt_case_1_us_prec(subsec_millis: u32) -> chrono::DateTime<chrono::Utc> {
|
||||
// 250 ms + 500 us
|
||||
let subsec_micros = subsec_millis * 1000 + 500;
|
||||
NaiveDate::from_ymd_opt(2023, 1, 14)
|
||||
.unwrap()
|
||||
.and_hms_micro_opt(16, 49, 30, subsec_micros)
|
||||
.unwrap()
|
||||
.and_local_timezone(Utc)
|
||||
.and_local_timezone(chrono::Utc)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn generic_check_dt_case_1_us_prec<DaysLen: ProvidesDaysLength>(
|
||||
time_provider: &TimeProvider<DaysLen>,
|
||||
subsec_millis: u32,
|
||||
datetime_utc: DateTime<Utc>,
|
||||
datetime_utc: chrono::DateTime<chrono::Utc>,
|
||||
) {
|
||||
// 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.
|
||||
@ -1839,7 +1863,7 @@ mod tests {
|
||||
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) {
|
||||
fn generic_dt_case_2_ps_prec(subsec_millis: u32) -> (chrono::DateTime<chrono::Utc>, u32) {
|
||||
// 250 ms + 500 us
|
||||
let subsec_nanos = subsec_millis * 1000 * 1000 + 500 * 1000;
|
||||
let submilli_nanos = subsec_nanos % 10_u32.pow(6);
|
||||
@ -1848,7 +1872,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.and_hms_nano_opt(16, 49, 30, subsec_nanos)
|
||||
.unwrap()
|
||||
.and_local_timezone(Utc)
|
||||
.and_local_timezone(chrono::Utc)
|
||||
.unwrap(),
|
||||
submilli_nanos,
|
||||
)
|
||||
@ -1858,7 +1882,7 @@ mod tests {
|
||||
time_provider: &TimeProvider<DaysLen>,
|
||||
subsec_millis: u32,
|
||||
submilli_nanos: u32,
|
||||
datetime_utc: DateTime<Utc>,
|
||||
datetime_utc: chrono::DateTime<chrono::Utc>,
|
||||
) {
|
||||
// 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.
|
||||
@ -1907,10 +1931,10 @@ mod tests {
|
||||
fn test_creation_from_unix_stamp_0_u16_days() {
|
||||
let unix_secs = 0;
|
||||
let subsec_millis = 0;
|
||||
let time_provider = TimeProvider::from_unix_secs_with_u16_days(&UnixTimestamp::const_new(
|
||||
unix_secs,
|
||||
subsec_millis,
|
||||
))
|
||||
let time_provider = TimeProvider::from_unix_stamp_with_u16_days(
|
||||
&UnixTimestamp::new(unix_secs, subsec_millis),
|
||||
SubmillisPrecision::Absent,
|
||||
)
|
||||
.expect("creating provider from unix stamp failed");
|
||||
assert_eq!(time_provider.ccsds_days, -DAYS_CCSDS_TO_UNIX as u16)
|
||||
}
|
||||
@ -1919,10 +1943,10 @@ mod tests {
|
||||
fn test_creation_from_unix_stamp_0_u24_days() {
|
||||
let unix_secs = 0;
|
||||
let subsec_millis = 0;
|
||||
let time_provider = TimeProvider::from_unix_secs_with_u24_days(&UnixTimestamp::const_new(
|
||||
unix_secs,
|
||||
subsec_millis,
|
||||
))
|
||||
let time_provider = TimeProvider::from_unix_stamp_with_u24_days(
|
||||
&UnixTimestamp::new(unix_secs, subsec_millis),
|
||||
SubmillisPrecision::Absent,
|
||||
)
|
||||
.expect("creating provider from unix stamp failed");
|
||||
assert_eq!(time_provider.ccsds_days, (-DAYS_CCSDS_TO_UNIX) as u32)
|
||||
}
|
||||
@ -1934,10 +1958,13 @@ mod tests {
|
||||
.unwrap()
|
||||
.and_hms_milli_opt(16, 49, 30, subsec_millis)
|
||||
.unwrap()
|
||||
.and_local_timezone(Utc)
|
||||
.and_local_timezone(chrono::Utc)
|
||||
.unwrap();
|
||||
let time_provider = TimeProvider::from_unix_secs_with_u16_days(&datetime_utc.into())
|
||||
.expect("creating provider from unix stamp failed");
|
||||
let time_provider = TimeProvider::from_unix_stamp_with_u16_days(
|
||||
&datetime_utc.into(),
|
||||
SubmillisPrecision::Absent,
|
||||
)
|
||||
.expect("creating provider from unix stamp failed");
|
||||
// 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.
|
||||
assert_eq!(time_provider.ccsds_days, 23754);
|
||||
@ -1953,10 +1980,10 @@ mod tests {
|
||||
fn test_creation_0_ccsds_days() {
|
||||
let unix_secs = DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64;
|
||||
let subsec_millis = 0;
|
||||
let time_provider = TimeProvider::from_unix_secs_with_u16_days(&UnixTimestamp::const_new(
|
||||
unix_secs,
|
||||
subsec_millis,
|
||||
))
|
||||
let time_provider = TimeProvider::from_unix_stamp_with_u16_days(
|
||||
&UnixTimestamp::new(unix_secs, subsec_millis),
|
||||
SubmillisPrecision::Absent,
|
||||
)
|
||||
.expect("creating provider from unix stamp failed");
|
||||
assert_eq!(time_provider.ccsds_days, 0)
|
||||
}
|
||||
@ -1965,10 +1992,10 @@ mod tests {
|
||||
fn test_invalid_creation_from_unix_stamp_days_too_large() {
|
||||
let invalid_unix_secs: i64 = (u16::MAX as i64 + 1) * SECONDS_PER_DAY as i64;
|
||||
let subsec_millis = 0;
|
||||
match TimeProvider::from_unix_secs_with_u16_days(&UnixTimestamp::const_new(
|
||||
invalid_unix_secs,
|
||||
subsec_millis,
|
||||
)) {
|
||||
match TimeProvider::from_unix_stamp_with_u16_days(
|
||||
&UnixTimestamp::new(invalid_unix_secs, subsec_millis),
|
||||
SubmillisPrecision::Absent,
|
||||
) {
|
||||
Ok(_) => {
|
||||
panic!("creation should not succeed")
|
||||
}
|
||||
@ -1992,21 +2019,26 @@ mod tests {
|
||||
// precisely 31-12-1957 23:59:55
|
||||
let unix_secs = DAYS_CCSDS_TO_UNIX * SECONDS_PER_DAY as i32 - 5;
|
||||
let subsec_millis = 0;
|
||||
match TimeProvider::from_unix_secs_with_u16_days(&UnixTimestamp::const_new(
|
||||
unix_secs as i64,
|
||||
subsec_millis,
|
||||
)) {
|
||||
match TimeProvider::from_unix_stamp_with_u16_days(
|
||||
&UnixTimestamp::new(unix_secs as i64, subsec_millis),
|
||||
SubmillisPrecision::Absent,
|
||||
) {
|
||||
Ok(_) => {
|
||||
panic!("creation should not succeed")
|
||||
}
|
||||
Err(e) => {
|
||||
if let TimestampError::DateBeforeCcsdsEpoch(dt) = e {
|
||||
assert_eq!(dt.year(), 1957);
|
||||
assert_eq!(dt.month(), 12);
|
||||
assert_eq!(dt.day(), 31);
|
||||
assert_eq!(dt.hour(), 23);
|
||||
assert_eq!(dt.minute(), 59);
|
||||
assert_eq!(dt.second(), 55);
|
||||
let dt = dt.as_date_time();
|
||||
if let chrono::LocalResult::Single(dt) = dt {
|
||||
assert_eq!(dt.year(), 1957);
|
||||
assert_eq!(dt.month(), 12);
|
||||
assert_eq!(dt.day(), 31);
|
||||
assert_eq!(dt.hour(), 23);
|
||||
assert_eq!(dt.minute(), 59);
|
||||
assert_eq!(dt.second(), 55);
|
||||
} else {
|
||||
panic!("unexpected error {}", e)
|
||||
}
|
||||
} else {
|
||||
panic!("unexpected error {}", e)
|
||||
}
|
||||
@ -2222,12 +2254,12 @@ mod tests {
|
||||
.unwrap()
|
||||
.and_hms_milli_opt(23, 59, 59, 999)
|
||||
.unwrap()
|
||||
.and_local_timezone(Utc)
|
||||
.and_local_timezone(chrono::Utc)
|
||||
.unwrap();
|
||||
let time_provider = TimeProvider::from_dt_with_u24_days(&datetime_utc);
|
||||
assert!(time_provider.is_err());
|
||||
if let TimestampError::DateBeforeCcsdsEpoch(dt) = time_provider.unwrap_err() {
|
||||
assert_eq!(dt, datetime_utc);
|
||||
assert_eq!(dt, datetime_utc.into());
|
||||
}
|
||||
}
|
||||
|
||||
|
106
src/time/cuc.rs
106
src/time/cuc.rs
@ -2,15 +2,29 @@
|
||||
//! [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf) section 3.2 .
|
||||
//!
|
||||
//! The core data structure to do this is the [TimeProviderCcsdsEpoch] struct.
|
||||
use super::*;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use core::fmt::{Debug, Display, Formatter};
|
||||
use core::ops::{Add, AddAssign};
|
||||
use core::time::Duration;
|
||||
use core::u64;
|
||||
|
||||
use crate::ByteConversionError;
|
||||
|
||||
use super::{CcsdsTimeCodes, TimestampError, unix_epoch_to_ccsds_epoch, UnixTimestamp, ccsds_epoch_to_unix_epoch, TimeReader, TimeWriter, CcsdsTimeProvider};
|
||||
#[cfg(feature = "std")]
|
||||
use super::StdTimestampError;
|
||||
#[cfg(feature = "std")]
|
||||
use std::error::Error;
|
||||
#[cfg(feature = "std")]
|
||||
use std::time::SystemTime;
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
use chrono::Datelike;
|
||||
|
||||
use core::fmt::Debug;
|
||||
use core::ops::{Add, AddAssign};
|
||||
use core::time::Duration;
|
||||
use core::u64;
|
||||
#[cfg(feature = "alloc")]
|
||||
use super::ccsds_time_code_from_p_field;
|
||||
|
||||
const MIN_CUC_LEN: usize = 2;
|
||||
|
||||
@ -301,6 +315,7 @@ impl TimeProviderCcsdsEpoch {
|
||||
#[cfg(feature = "std")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
|
||||
pub fn update_from_now(&mut self) -> Result<(), StdTimestampError> {
|
||||
|
||||
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
|
||||
self.counter.1 = unix_epoch_to_ccsds_epoch(now.as_secs() as i64) as u32;
|
||||
self.counter
|
||||
@ -325,7 +340,9 @@ impl TimeProviderCcsdsEpoch {
|
||||
) -> Result<Self, TimestampError> {
|
||||
// Year before CCSDS epoch is invalid.
|
||||
if dt.year() < 1958 {
|
||||
return Err(TimestampError::DateBeforeCcsdsEpoch(*dt));
|
||||
return Err(TimestampError::DateBeforeCcsdsEpoch(UnixTimestamp::from(
|
||||
*dt,
|
||||
)));
|
||||
}
|
||||
let counter = dt
|
||||
.timestamp()
|
||||
@ -349,9 +366,7 @@ impl TimeProviderCcsdsEpoch {
|
||||
let ccsds_epoch = unix_epoch_to_ccsds_epoch(unix_stamp.unix_seconds);
|
||||
// Negative CCSDS epoch is invalid.
|
||||
if ccsds_epoch < 0 {
|
||||
return Err(TimestampError::DateBeforeCcsdsEpoch(
|
||||
unix_stamp.as_date_time().unwrap(),
|
||||
));
|
||||
return Err(TimestampError::DateBeforeCcsdsEpoch(*unix_stamp));
|
||||
}
|
||||
ccsds_epoch
|
||||
.checked_add(i64::from(leap_seconds))
|
||||
@ -682,43 +697,16 @@ impl CcsdsTimeProvider for TimeProviderCcsdsEpoch {
|
||||
self.unix_seconds()
|
||||
}
|
||||
|
||||
fn subsecond_millis(&self) -> u16 {
|
||||
fn subsecond_nanos(&self) -> u32 {
|
||||
if let Some(fractions) = self.fractions {
|
||||
if fractions.0 == FractionalResolution::Seconds {
|
||||
return 0;
|
||||
}
|
||||
// Rounding down here is the correct approach.
|
||||
return (convert_fractional_part_to_ns(fractions) / 10_u32.pow(6) as u64) as u16;
|
||||
return convert_fractional_part_to_ns(fractions) as u32;
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "chrono")))]
|
||||
fn chrono_date_time(&self) -> Option<chrono::DateTime<chrono::Utc>> {
|
||||
let mut ns = 0;
|
||||
if let Some(fractional_part) = self.fractions {
|
||||
ns = convert_fractional_part_to_ns(fractional_part);
|
||||
}
|
||||
if let LocalResult::Single(res) = chrono::Utc.timestamp_opt(self.unix_seconds(), ns as u32)
|
||||
{
|
||||
return Some(res);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(feature = "timelib")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "timelib")))]
|
||||
fn timelib_date_time(&self) -> Result<time::OffsetDateTime, time::error::ComponentRange> {
|
||||
let mut ns = 0;
|
||||
if let Some(fractional_part) = self.fractions {
|
||||
ns = convert_fractional_part_to_ns(fractional_part);
|
||||
}
|
||||
Ok(
|
||||
time::OffsetDateTime::from_unix_timestamp(self.unix_seconds())?
|
||||
+ time::Duration::nanoseconds(ns as i64),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_provider_values_after_duration_addition(
|
||||
@ -818,9 +806,11 @@ impl Add<Duration> for &TimeProviderCcsdsEpoch {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::time::{UnixTimestamp, DAYS_CCSDS_TO_UNIX, SECONDS_PER_DAY};
|
||||
|
||||
use super::*;
|
||||
use alloc::string::ToString;
|
||||
use chrono::{Datelike, Timelike};
|
||||
use chrono::{Datelike, TimeZone, Timelike};
|
||||
#[allow(unused_imports)]
|
||||
use std::println;
|
||||
|
||||
@ -840,14 +830,14 @@ mod tests {
|
||||
let fractions = zero_cuc.width_fractions_pair();
|
||||
assert!(fractions.is_none());
|
||||
let dt = zero_cuc.chrono_date_time();
|
||||
assert!(dt.is_some());
|
||||
let dt = dt.unwrap();
|
||||
assert_eq!(dt.year(), 1958);
|
||||
assert_eq!(dt.month(), 1);
|
||||
assert_eq!(dt.day(), 1);
|
||||
assert_eq!(dt.hour(), 0);
|
||||
assert_eq!(dt.minute(), 0);
|
||||
assert_eq!(dt.second(), 0);
|
||||
if let chrono::LocalResult::Single(dt) = dt {
|
||||
assert_eq!(dt.year(), 1958);
|
||||
assert_eq!(dt.month(), 1);
|
||||
assert_eq!(dt.day(), 1);
|
||||
assert_eq!(dt.hour(), 0);
|
||||
assert_eq!(dt.minute(), 0);
|
||||
assert_eq!(dt.second(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -862,7 +852,7 @@ mod tests {
|
||||
let zero_cuc = zero_cuc.unwrap();
|
||||
let res = zero_cuc.write_to_bytes(&mut buf);
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(zero_cuc.subsecond_millis(), 0);
|
||||
assert_eq!(zero_cuc.subsecond_nanos(), 0);
|
||||
assert_eq!(zero_cuc.len_as_bytes(), 5);
|
||||
assert_eq!(pfield_len(buf[0]), 1);
|
||||
let written = res.unwrap();
|
||||
@ -880,17 +870,19 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_datetime_now() {
|
||||
let now = Utc::now();
|
||||
let now = chrono::Utc::now();
|
||||
let cuc_now = TimeProviderCcsdsEpoch::from_now(FractionalResolution::SixtyNs, LEAP_SECONDS);
|
||||
assert!(cuc_now.is_ok());
|
||||
let cuc_now = cuc_now.unwrap();
|
||||
let dt_opt = cuc_now.chrono_date_time();
|
||||
assert!(dt_opt.is_some());
|
||||
let dt = dt_opt.unwrap();
|
||||
let diff = dt - now;
|
||||
assert!(diff.num_milliseconds() < 1000);
|
||||
println!("datetime from cuc: {}", dt);
|
||||
println!("datetime now: {}", now);
|
||||
if let chrono::LocalResult::Single(dt) = dt_opt {
|
||||
let diff = dt - now;
|
||||
assert!(diff.num_milliseconds() < 1000);
|
||||
println!("datetime from cuc: {}", dt);
|
||||
println!("datetime now: {}", now);
|
||||
} else {
|
||||
panic!("date time creation from now failed")
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1316,7 +1308,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_from_dt() {
|
||||
let dt = Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap();
|
||||
let dt = chrono::Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap();
|
||||
let cuc = TimeProviderCcsdsEpoch::from_chrono_date_time(
|
||||
&dt,
|
||||
FractionalResolution::Seconds,
|
||||
@ -1335,7 +1327,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn from_unix_stamp() {
|
||||
let unix_stamp = UnixTimestamp::new(0, 0).unwrap();
|
||||
let unix_stamp = UnixTimestamp::new(0, 0);
|
||||
let cuc = TimeProviderCcsdsEpoch::from_unix_stamp(
|
||||
&unix_stamp,
|
||||
FractionalResolution::Seconds,
|
||||
|
137
src/time/mod.rs
137
src/time/mod.rs
@ -1,5 +1,6 @@
|
||||
//! CCSDS Time Code Formats according to [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
|
||||
use crate::ByteConversionError;
|
||||
#[cfg(feature = "chrono")]
|
||||
use chrono::{DateTime, LocalResult, TimeZone, Utc};
|
||||
use core::cmp::Ordering;
|
||||
use core::fmt::{Display, Formatter};
|
||||
@ -13,6 +14,7 @@ use num_traits::float::FloatCore;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use std::error::Error;
|
||||
#[cfg(feature = "std")]
|
||||
@ -68,7 +70,7 @@ pub enum TimestampError {
|
||||
ByteConversion(ByteConversionError),
|
||||
Cds(cds::CdsError),
|
||||
Cuc(cuc::CucError),
|
||||
DateBeforeCcsdsEpoch(DateTime<Utc>),
|
||||
DateBeforeCcsdsEpoch(UnixTimestamp),
|
||||
CustomEpochNotSupported,
|
||||
}
|
||||
|
||||
@ -91,7 +93,7 @@ impl Display for TimestampError {
|
||||
write!(f, "time stamp: {e}")
|
||||
}
|
||||
TimestampError::DateBeforeCcsdsEpoch(e) => {
|
||||
write!(f, "datetime with date before ccsds epoch: {e}")
|
||||
write!(f, "datetime with date before ccsds epoch: {e:?}")
|
||||
}
|
||||
TimestampError::CustomEpochNotSupported => {
|
||||
write!(f, "custom epochs are not supported")
|
||||
@ -231,59 +233,101 @@ pub trait CcsdsTimeProvider {
|
||||
fn ccdsd_time_code(&self) -> CcsdsTimeCodes;
|
||||
|
||||
fn unix_seconds(&self) -> i64;
|
||||
fn subsecond_millis(&self) -> u16;
|
||||
fn subsecond_nanos(&self) -> u32;
|
||||
|
||||
fn subsecond_millis(&self) -> u16 {
|
||||
(self.subsecond_nanos() / 1_000_000) as u16
|
||||
}
|
||||
|
||||
fn unix_stamp(&self) -> UnixTimestamp {
|
||||
UnixTimestamp::const_new(self.unix_seconds(), self.subsecond_millis())
|
||||
UnixTimestamp::new(self.unix_seconds(), self.subsecond_nanos())
|
||||
}
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "chrono")))]
|
||||
fn chrono_date_time(&self) -> Option<chrono::DateTime<chrono::Utc>>;
|
||||
fn chrono_date_time(&self) -> chrono::LocalResult<chrono::DateTime<chrono::Utc>> {
|
||||
chrono::Utc.timestamp_opt(self.unix_seconds(), self.subsecond_nanos())
|
||||
}
|
||||
|
||||
#[cfg(feature = "timelib")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "timelib")))]
|
||||
fn timelib_date_time(&self) -> Result<time::OffsetDateTime, time::error::ComponentRange>;
|
||||
fn timelib_date_time(&self) -> Result<time::OffsetDateTime, time::error::ComponentRange> {
|
||||
Ok(
|
||||
time::OffsetDateTime::from_unix_timestamp(self.unix_seconds())?
|
||||
+ time::Duration::nanoseconds(self.subsecond_nanos().into()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// UNIX timestamp: Elapsed seconds since 1970-01-01T00:00:00+00:00.
|
||||
///
|
||||
/// Also can optionally include subsecond millisecond for greater accuracy.
|
||||
/// It is assumed that leap second correction was already applied and the reference time is UTC.
|
||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct UnixTimestamp {
|
||||
pub unix_seconds: i64,
|
||||
subsecond_millis: u16,
|
||||
subsecond_nanos: u32,
|
||||
}
|
||||
|
||||
impl UnixTimestamp {
|
||||
/// Returns [None] if the subsecond millisecond value is larger than 999.
|
||||
pub fn new(unix_seconds: i64, subsec_millis: u16) -> Option<Self> {
|
||||
/// Returns [None] if the subsecond nanosecond value is invalid (larger than fraction of a
|
||||
/// second)
|
||||
pub fn new_checked(unix_seconds: i64, subsec_nanos: u32) -> Option<Self> {
|
||||
if subsec_nanos > 1_000_000_000 - 1 {
|
||||
return None;
|
||||
}
|
||||
Some(Self::new(unix_seconds, subsec_nanos))
|
||||
}
|
||||
|
||||
/// Returns [None] if the subsecond millisecond value is invalid (larger than fraction of a
|
||||
/// second)
|
||||
pub fn new_subsec_millis_checked(unix_seconds: i64, subsec_millis: u16) -> Option<Self> {
|
||||
if subsec_millis > 999 {
|
||||
return None;
|
||||
}
|
||||
Some(Self::const_new(unix_seconds, subsec_millis))
|
||||
Self::new_checked(unix_seconds, subsec_millis as u32 * 1_000_000)
|
||||
}
|
||||
|
||||
/// Like [Self::new] but const. Panics if the subsecond value is larger than 999.
|
||||
pub const fn const_new(unix_seconds: i64, subsecond_millis: u16) -> Self {
|
||||
if subsecond_millis > 999 {
|
||||
panic!("subsec milliseconds exceeds 999");
|
||||
/// This function will panic if the subsecond value is larger than the fraction of a second.
|
||||
/// Use [new_checked] if you want to handle this case without a panic.
|
||||
pub const fn new(unix_seconds: i64, subsecond_nanos: u32) -> Self {
|
||||
// This could actually be used to model leap seconds, but we will ignore that for now.
|
||||
if subsecond_nanos > 1_000_000_000 - 1 {
|
||||
panic!("invalid subsecond nanos value");
|
||||
}
|
||||
Self {
|
||||
unix_seconds,
|
||||
subsecond_millis,
|
||||
subsecond_nanos,
|
||||
}
|
||||
}
|
||||
|
||||
/// This function will panic if the subsecond value is larger than the fraction of a second.
|
||||
/// Use [new_subsecond_millis_checked] if you want to handle this case without a panic.
|
||||
pub const fn new_subsec_millis(unix_seconds: i64, subsecond_millis: u16) -> Self {
|
||||
// This could actually be used to model leap seconds, but we will ignore that for now.
|
||||
if subsecond_millis > 999 {
|
||||
panic!("invalid subsecond millisecond value");
|
||||
}
|
||||
Self {
|
||||
unix_seconds,
|
||||
subsecond_nanos: subsecond_millis as u32 * 1_000_000,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_only_seconds(unix_seconds: i64) -> Self {
|
||||
Self {
|
||||
unix_seconds,
|
||||
subsecond_millis: 0,
|
||||
subsecond_nanos: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subsecond_millis(&self) -> u16 {
|
||||
self.subsecond_millis
|
||||
(self.subsecond_nanos / 1_000_000) as u16
|
||||
}
|
||||
|
||||
pub fn subsecond_nanos(&self) -> u32 {
|
||||
self.subsecond_nanos
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
@ -291,19 +335,16 @@ impl UnixTimestamp {
|
||||
pub fn from_now() -> Result<Self, SystemTimeError> {
|
||||
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
|
||||
let epoch = now.as_secs();
|
||||
Ok(Self::const_new(epoch as i64, now.subsec_millis() as u16))
|
||||
Ok(Self::new(epoch as i64, now.subsec_nanos()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn unix_seconds_f64(&self) -> f64 {
|
||||
self.unix_seconds as f64 + (self.subsecond_millis as f64 / 1000.0)
|
||||
self.unix_seconds as f64 + (self.subsecond_nanos as f64 / 1_000_000_000.0)
|
||||
}
|
||||
|
||||
pub fn as_date_time(&self) -> LocalResult<DateTime<Utc>> {
|
||||
Utc.timestamp_opt(
|
||||
self.unix_seconds,
|
||||
self.subsecond_millis as u32 * 10_u32.pow(6),
|
||||
)
|
||||
Utc.timestamp_opt(self.unix_seconds, self.subsecond_nanos)
|
||||
}
|
||||
|
||||
// Calculate the difference in milliseconds between two UnixTimestamps
|
||||
@ -313,16 +354,16 @@ impl UnixTimestamp {
|
||||
let milliseconds_difference = seconds_difference * 1000;
|
||||
|
||||
// Calculate the difference in subsecond milliseconds directly
|
||||
let subsecond_difference = self.subsecond_millis as i64 - other.subsecond_millis as i64;
|
||||
let subsecond_difference_nanos = self.subsecond_nanos as i64 - other.subsecond_nanos as i64;
|
||||
|
||||
// Combine the differences
|
||||
milliseconds_difference + subsecond_difference
|
||||
milliseconds_difference + (subsecond_difference_nanos / 1_000_000)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DateTime<Utc>> for UnixTimestamp {
|
||||
fn from(value: DateTime<Utc>) -> Self {
|
||||
Self::const_new(value.timestamp(), value.timestamp_subsec_millis() as u16)
|
||||
Self::new(value.timestamp(), value.timestamp_subsec_nanos())
|
||||
}
|
||||
}
|
||||
|
||||
@ -395,7 +436,7 @@ fn get_new_stamp_after_addition(
|
||||
current_stamp: &UnixTimestamp,
|
||||
duration: Duration,
|
||||
) -> UnixTimestamp {
|
||||
let mut new_subsec_millis = current_stamp.subsecond_millis() + duration.subsec_millis() as u16;
|
||||
let mut new_subsec_nanos = current_stamp.subsecond_nanos() + duration.subsec_nanos();
|
||||
let mut new_unix_seconds = current_stamp.unix_seconds;
|
||||
let mut increment_seconds = |value: u32| {
|
||||
if new_unix_seconds < 0 {
|
||||
@ -408,8 +449,8 @@ fn get_new_stamp_after_addition(
|
||||
.expect("new unix seconds would exceed i64::MAX");
|
||||
}
|
||||
};
|
||||
if new_subsec_millis >= 1000 {
|
||||
new_subsec_millis -= 1000;
|
||||
if new_subsec_nanos >= 1_000_000_000 {
|
||||
new_subsec_nanos -= 1_000_000_000;
|
||||
increment_seconds(1);
|
||||
}
|
||||
increment_seconds(
|
||||
@ -418,7 +459,7 @@ fn get_new_stamp_after_addition(
|
||||
.try_into()
|
||||
.expect("duration seconds exceeds u32::MAX"),
|
||||
);
|
||||
UnixTimestamp::const_new(new_unix_seconds, new_subsec_millis)
|
||||
UnixTimestamp::new(new_unix_seconds, new_subsec_nanos)
|
||||
}
|
||||
|
||||
/// Please note that this operation will panic on the following conditions:
|
||||
@ -457,10 +498,15 @@ impl Add<Duration> for &UnixTimestamp {
|
||||
mod tests {
|
||||
use alloc::string::ToString;
|
||||
use chrono::{Datelike, Timelike};
|
||||
use std::format;
|
||||
use std::{format, println};
|
||||
|
||||
use super::{cuc::CucError, *};
|
||||
|
||||
#[allow(dead_code)]
|
||||
const UNIX_STAMP_CONST: UnixTimestamp = UnixTimestamp::new(5, 999_999_999);
|
||||
#[allow(dead_code)]
|
||||
const UNIX_STAMP_CONST_2: UnixTimestamp = UnixTimestamp::new_subsec_millis(5, 999);
|
||||
|
||||
#[test]
|
||||
fn test_days_conversion() {
|
||||
assert_eq!(unix_to_ccsds_days(DAYS_CCSDS_TO_UNIX.into()), 0);
|
||||
@ -506,17 +552,18 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn basic_float_unix_stamp_test() {
|
||||
let stamp = UnixTimestamp::new(500, 600).unwrap();
|
||||
let stamp = UnixTimestamp::new_subsec_millis_checked(500, 600).unwrap();
|
||||
assert_eq!(stamp.unix_seconds, 500);
|
||||
let subsec_millis = stamp.subsecond_millis();
|
||||
assert_eq!(subsec_millis, 600);
|
||||
println!("{:?}", (500.6 - stamp.unix_seconds_f64()).to_string());
|
||||
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 stamp1 = UnixTimestamp::new_subsec_millis_checked(5, 500).unwrap();
|
||||
let stamp2 = UnixTimestamp::new_only_seconds(6);
|
||||
assert!(stamp1 > stamp0);
|
||||
assert!(stamp2 > stamp0);
|
||||
@ -526,7 +573,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_ord_smaller() {
|
||||
let stamp0 = UnixTimestamp::new_only_seconds(5);
|
||||
let stamp1 = UnixTimestamp::new(5, 500).unwrap();
|
||||
let stamp1 = UnixTimestamp::new_subsec_millis_checked(5, 500).unwrap();
|
||||
let stamp2 = UnixTimestamp::new_only_seconds(6);
|
||||
assert!(stamp0 < stamp1);
|
||||
assert!(stamp0 < stamp2);
|
||||
@ -536,7 +583,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_ord_larger_neg_numbers() {
|
||||
let stamp0 = UnixTimestamp::new_only_seconds(-5);
|
||||
let stamp1 = UnixTimestamp::new(-5, 500).unwrap();
|
||||
let stamp1 = UnixTimestamp::new_subsec_millis_checked(-5, 500).unwrap();
|
||||
let stamp2 = UnixTimestamp::new_only_seconds(-6);
|
||||
assert!(stamp0 > stamp1);
|
||||
assert!(stamp0 > stamp2);
|
||||
@ -548,7 +595,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_ord_smaller_neg_numbers() {
|
||||
let stamp0 = UnixTimestamp::new_only_seconds(-5);
|
||||
let stamp1 = UnixTimestamp::new(-5, 500).unwrap();
|
||||
let stamp1 = UnixTimestamp::new_subsec_millis_checked(-5, 500).unwrap();
|
||||
let stamp2 = UnixTimestamp::new_only_seconds(-6);
|
||||
assert!(stamp2 < stamp1);
|
||||
assert!(stamp2 < stamp0);
|
||||
@ -560,7 +607,7 @@ mod tests {
|
||||
#[allow(clippy::nonminimal_bool)]
|
||||
#[test]
|
||||
fn test_eq() {
|
||||
let stamp0 = UnixTimestamp::new(5, 0).unwrap();
|
||||
let stamp0 = UnixTimestamp::new(5, 0);
|
||||
let stamp1 = UnixTimestamp::new_only_seconds(5);
|
||||
assert_eq!(stamp0, stamp1);
|
||||
assert!(stamp0 <= stamp1);
|
||||
@ -582,7 +629,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_addition_on_ref() {
|
||||
let stamp0 = &UnixTimestamp::new(20, 500).unwrap();
|
||||
let stamp0 = &UnixTimestamp::new_subsec_millis_checked(20, 500).unwrap();
|
||||
let stamp1 = stamp0 + Duration::from_millis(2500);
|
||||
assert_eq!(stamp1.unix_seconds, 23);
|
||||
assert_eq!(stamp1.subsecond_millis(), 0);
|
||||
@ -609,19 +656,19 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_stamp_diff_positive_0() {
|
||||
let stamp_later = UnixTimestamp::new(2, 0).unwrap();
|
||||
let stamp_later = UnixTimestamp::new(2, 0);
|
||||
let StampDiff {
|
||||
positive_duration,
|
||||
duration_absolute,
|
||||
} = stamp_later - UnixTimestamp::new(1, 0).unwrap();
|
||||
} = stamp_later - UnixTimestamp::new(1, 0);
|
||||
assert!(positive_duration);
|
||||
assert_eq!(duration_absolute, Duration::from_secs(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stamp_diff_positive_1() {
|
||||
let stamp_later = UnixTimestamp::new(3, 800).unwrap();
|
||||
let stamp_earlier = UnixTimestamp::new(1, 900).unwrap();
|
||||
let stamp_later = UnixTimestamp::new(3, 800 * 1_000_000);
|
||||
let stamp_earlier = UnixTimestamp::new_subsec_millis_checked(1, 900).unwrap();
|
||||
let StampDiff {
|
||||
positive_duration,
|
||||
duration_absolute,
|
||||
@ -632,8 +679,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_stamp_diff_negative() {
|
||||
let stamp_later = UnixTimestamp::new(3, 800).unwrap();
|
||||
let stamp_earlier = UnixTimestamp::new(1, 900).unwrap();
|
||||
let stamp_later = UnixTimestamp::new_subsec_millis_checked(3, 800).unwrap();
|
||||
let stamp_earlier = UnixTimestamp::new_subsec_millis_checked(1, 900).unwrap();
|
||||
let StampDiff {
|
||||
positive_duration,
|
||||
duration_absolute,
|
||||
@ -644,7 +691,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_addition_spillover() {
|
||||
let mut stamp0 = UnixTimestamp::new(1, 900).unwrap();
|
||||
let mut stamp0 = UnixTimestamp::new_subsec_millis_checked(1, 900).unwrap();
|
||||
stamp0 += Duration::from_millis(100);
|
||||
assert_eq!(stamp0.unix_seconds, 2);
|
||||
assert_eq!(stamp0.subsecond_millis(), 0);
|
||||
|
Loading…
Reference in New Issue
Block a user