Major refactoring of the time API
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
Rust/spacepackets/pipeline/pr-main There was a failure building this commit

This commit is contained in:
Robin Müller 2024-03-14 12:51:19 +01:00
parent 6f5254bdbd
commit e0bd127d2f
Signed by: muellerr
GPG Key ID: A649FB78196E3849
8 changed files with 881 additions and 592 deletions

View File

@ -8,9 +8,41 @@ 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 in the **Changed** chapter.
## Fixed
- CUC timestamp was fixed to include leap second corrections because it is based on the TAI
time reference. The default CUC time object do not implement `CcsdsTimeProvider` anymore
because the trait methods require cached leap second information. This task is now performed
by the `cuc::CucTimeWithLeapSecs` which implements the trait.
## Added
- `From<$EcssEnum$TY> from $TY` for the ECSS enum type definitions.
- Added basic support conversions to the `time` library. Introduce new `chrono` and `timelib`
feature gate.
- Added `CcsdsTimeProvider::timelib_date_time`.
## Changed
- Renamed `CcsdsTimeProvider::date_time` to `CcsdsTimeProvider::chrono_date_time`
- Renamed `CcsdsTimeCodes` to `CcsdsTimeCode`
- Renamed `cds::TimeProvider` to `cds::CdsTime`
- Renamed `cuc::TimeProviderCcsdsEpoch` to `cuc::CucTime`
- `UnixTimestamp` renamed to `UnixTime`
- `UnixTime` seconds are now private and can be retrieved using the `secs` member method.
- `UnixTime::new` renamed to `UnixTime::new_checked`.
- `UnixTime` now has a nanosecond subsecond precision. The `new` constructor now expects
nanoseconds as the second argument.
- Added new `UnixTime::new_subsec_millis` and `UnixTime::new_subsec_millis_checked` API
to still allow creating a timestamp with only millisecond subsecond resolution.
- `CcsdsTimeProvider` now has a new `subsec_nanos` method in addition to a default
implementation for the `subsec_millis` method.
- `CcsdsTimeProvider::date_time` renamed to `CcsdsTimeProvider::chrono_date_time`.
- Added `UnixTime::MIN`, `UnixTime::MAX` and `UnixTime::EPOCH`.
- Added `UnixTime::timelib_date_time`.
# [v0.11.0-rc.0] 2024-03-04

View File

@ -34,22 +34,31 @@ optional = true
default-features = false
features = ["derive"]
[dependencies.time]
version = "0.3"
default-features = false
optional = true
[dependencies.chrono]
version = "0.4"
default-features = false
optional = true
[dependencies.num-traits]
version = "0.2"
default-features = false
[dev-dependencies.postcard]
version = "1"
[dev-dependencies]
postcard = "1"
chrono = "0.4"
[features]
default = ["std"]
std = ["chrono/std", "chrono/clock", "alloc", "thiserror"]
serde = ["dep:serde", "chrono/serde"]
alloc = ["postcard/alloc", "chrono/alloc"]
chrono = ["dep:chrono"]
timelib = ["dep:time"]
[package.metadata.docs.rs]
all-features = true

View File

@ -43,6 +43,8 @@ deserializing them with an appropriate `serde` provider like
## Optional Features
- [`serde`](https://serde.rs/): Adds `serde` support for most types by adding `Serialize` and `Deserialize` `derive`s
- [`chrono`](https://crates.io/crates/chrono): Add basic support for the `chrono` time library.
- [`timelib`](https://crates.io/crates/time): Add basic support for the `time` time library.
# Examples

View File

@ -1097,7 +1097,7 @@ mod tests {
use super::*;
use crate::ecss::PusVersion::PusC;
use crate::time::cds::TimeProvider;
use crate::time::cds::CdsTime;
#[cfg(feature = "serde")]
use crate::time::CcsdsTimeProvider;
use crate::SpHeader;
@ -1136,7 +1136,7 @@ mod tests {
#[test]
fn test_basic_simple_api() {
let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap();
let time_provider = TimeProvider::new_with_u16_days(0, 0);
let time_provider = CdsTime::new_with_u16_days(0, 0);
let mut stamp_buf: [u8; 8] = [0; 8];
let pus_tm =
PusTmCreator::new_simple(&mut sph, 17, 2, &time_provider, &mut stamp_buf, None, true)
@ -1534,7 +1534,7 @@ mod tests {
#[cfg(feature = "serde")]
fn test_serialization_creator_serde() {
let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap();
let time_provider = TimeProvider::new_with_u16_days(0, 0);
let time_provider = CdsTime::new_with_u16_days(0, 0);
let mut stamp_buf: [u8; 8] = [0; 8];
let pus_tm =
PusTmCreator::new_simple(&mut sph, 17, 2, &time_provider, &mut stamp_buf, None, true)
@ -1549,7 +1549,7 @@ mod tests {
#[cfg(feature = "serde")]
fn test_serialization_reader_serde() {
let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap();
let time_provider = TimeProvider::new_with_u16_days(0, 0);
let time_provider = CdsTime::new_with_u16_days(0, 0);
let mut stamp_buf: [u8; 8] = [0; 8];
let pus_tm =
PusTmCreator::new_simple(&mut sph, 17, 2, &time_provider, &mut stamp_buf, None, true)

View File

@ -2,11 +2,8 @@
//! [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf) section 3.5 .
//! See [chrono::DateTime::format] for a usage example of the generated
//! [chrono::format::DelayedFormat] structs.
#[cfg(feature = "alloc")]
use chrono::{
format::{DelayedFormat, StrftimeItems},
DateTime, Utc,
};
#[cfg(all(feature = "alloc", feature = "chrono"))]
pub use alloc_mod_chrono::*;
/// Tuple of format string and formatted size for time code A.
///
@ -34,36 +31,41 @@ pub const FMT_STR_CODE_B_WITH_SIZE: (&str, usize) = ("%Y-%jT%T%.3f", 21);
/// Three digits are used for the decimal fraction and a terminator is added at the end.
pub const FMT_STR_CODE_B_TERMINATED_WITH_SIZE: (&str, usize) = ("%Y-%jT%T%.3fZ", 22);
/// Generates a time code formatter using the [FMT_STR_CODE_A_WITH_SIZE] format.
#[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
pub fn generate_time_code_a(date: &DateTime<Utc>) -> DelayedFormat<StrftimeItems<'static>> {
date.format(FMT_STR_CODE_A_WITH_SIZE.0)
}
#[cfg(all(feature = "alloc", feature = "chrono"))]
pub mod alloc_mod_chrono {
use super::*;
use chrono::{
format::{DelayedFormat, StrftimeItems},
DateTime, Utc,
};
/// Generates a time code formatter using the [FMT_STR_CODE_A_TERMINATED_WITH_SIZE] format.
#[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
pub fn generate_time_code_a_terminated(
date: &DateTime<Utc>,
) -> DelayedFormat<StrftimeItems<'static>> {
date.format(FMT_STR_CODE_A_TERMINATED_WITH_SIZE.0)
}
/// Generates a time code formatter using the [FMT_STR_CODE_A_WITH_SIZE] format.
#[cfg_attr(doc_cfg, doc(cfg(all(feature = "alloc", feature = "chrono"))))]
pub fn generate_time_code_a(date: &DateTime<Utc>) -> DelayedFormat<StrftimeItems<'static>> {
date.format(FMT_STR_CODE_A_WITH_SIZE.0)
}
/// Generates a time code formatter using the [FMT_STR_CODE_B_WITH_SIZE] format.
#[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
pub fn generate_time_code_b(date: &DateTime<Utc>) -> DelayedFormat<StrftimeItems<'static>> {
date.format(FMT_STR_CODE_B_WITH_SIZE.0)
}
/// Generates a time code formatter using the [FMT_STR_CODE_A_TERMINATED_WITH_SIZE] format.
#[cfg_attr(doc_cfg, doc(cfg(all(feature = "alloc", feature = "chrono"))))]
pub fn generate_time_code_a_terminated(
date: &DateTime<Utc>,
) -> DelayedFormat<StrftimeItems<'static>> {
date.format(FMT_STR_CODE_A_TERMINATED_WITH_SIZE.0)
}
/// Generates a time code formatter using the [FMT_STR_CODE_B_TERMINATED_WITH_SIZE] format.
#[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
pub fn generate_time_code_b_terminated(
date: &DateTime<Utc>,
) -> DelayedFormat<StrftimeItems<'static>> {
date.format(FMT_STR_CODE_B_TERMINATED_WITH_SIZE.0)
/// Generates a time code formatter using the [FMT_STR_CODE_B_WITH_SIZE] format.
#[cfg_attr(doc_cfg, doc(cfg(all(feature = "alloc", feature = "chrono"))))]
pub fn generate_time_code_b(date: &DateTime<Utc>) -> DelayedFormat<StrftimeItems<'static>> {
date.format(FMT_STR_CODE_B_WITH_SIZE.0)
}
/// Generates a time code formatter using the [FMT_STR_CODE_B_TERMINATED_WITH_SIZE] format.
#[cfg_attr(doc_cfg, doc(cfg(all(feature = "alloc", feature = "chrono"))))]
pub fn generate_time_code_b_terminated(
date: &DateTime<Utc>,
) -> DelayedFormat<StrftimeItems<'static>> {
date.format(FMT_STR_CODE_B_TERMINATED_WITH_SIZE.0)
}
}
#[cfg(test)]
@ -77,7 +79,7 @@ mod tests {
let date = Utc::now();
let stamp_formatter = generate_time_code_a(&date);
let stamp = format!("{}", stamp_formatter);
let t_sep = stamp.find("T");
let t_sep = stamp.find('T');
assert!(t_sep.is_some());
assert_eq!(t_sep.unwrap(), 10);
assert_eq!(stamp.len(), FMT_STR_CODE_A_WITH_SIZE.1);
@ -88,10 +90,10 @@ mod tests {
let date = Utc::now();
let stamp_formatter = generate_time_code_a_terminated(&date);
let stamp = format!("{}", stamp_formatter);
let t_sep = stamp.find("T");
let t_sep = stamp.find('T');
assert!(t_sep.is_some());
assert_eq!(t_sep.unwrap(), 10);
let z_terminator = stamp.find("Z");
let z_terminator = stamp.find('Z');
assert!(z_terminator.is_some());
assert_eq!(
z_terminator.unwrap(),
@ -105,7 +107,7 @@ mod tests {
let date = Utc::now();
let stamp_formatter = generate_time_code_b(&date);
let stamp = format!("{}", stamp_formatter);
let t_sep = stamp.find("T");
let t_sep = stamp.find('T');
assert!(t_sep.is_some());
assert_eq!(t_sep.unwrap(), 8);
assert_eq!(stamp.len(), FMT_STR_CODE_B_WITH_SIZE.1);
@ -116,10 +118,10 @@ mod tests {
let date = Utc::now();
let stamp_formatter = generate_time_code_b_terminated(&date);
let stamp = format!("{}", stamp_formatter);
let t_sep = stamp.find("T");
let t_sep = stamp.find('T');
assert!(t_sep.is_some());
assert_eq!(t_sep.unwrap(), 8);
let z_terminator = stamp.find("Z");
let z_terminator = stamp.find('Z');
assert!(z_terminator.is_some());
assert_eq!(
z_terminator.unwrap(),

File diff suppressed because it is too large Load Diff

View File

@ -2,17 +2,37 @@
//! [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::*;
use chrono::Datelike;
use core::fmt::Debug;
#[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;
#[cfg(feature = "std")]
use super::StdTimestampError;
use super::{
ccsds_epoch_to_unix_epoch, unix_epoch_to_ccsds_epoch, CcsdsTimeCode, CcsdsTimeProvider,
TimeReader, TimeWriter, TimestampError, UnixTime,
};
#[cfg(feature = "std")]
use std::error::Error;
#[cfg(feature = "std")]
use std::time::SystemTime;
#[cfg(feature = "chrono")]
use chrono::Datelike;
#[cfg(feature = "alloc")]
use super::ccsds_time_code_from_p_field;
const MIN_CUC_LEN: usize = 2;
/// Base value for the preamble field for a time field parser to determine the time field type.
pub const P_FIELD_BASE: u8 = (CcsdsTimeCodes::CucCcsdsEpoch as u8) << 4;
pub const P_FIELD_BASE: u8 = (CcsdsTimeCode::CucCcsdsEpoch as u8) << 4;
/// Maximum length if the preamble field is not extended.
pub const MAX_CUC_LEN_SMALL_PREAMBLE: usize = 8;
@ -103,6 +123,7 @@ pub enum CucError {
resolution: FractionalResolution,
value: u64,
},
LeapSecondCorrectionError,
}
impl Display for CucError {
@ -120,6 +141,9 @@ impl Display for CucError {
"invalid cuc fractional part {value} for resolution {resolution:?}"
)
}
CucError::LeapSecondCorrectionError => {
write!(f, "error while correcting for leap seconds")
}
}
}
}
@ -147,14 +171,20 @@ pub struct FractionalPart(FractionalResolution, u32);
/// type (generally seconds) to 4 bytes and the width of the fractions type to 3 bytes. This limits
/// the maximum time stamp size to [MAX_CUC_LEN_SMALL_PREAMBLE] (8 bytes).
///
/// Please note that this object does not implement the [CcsdsTimeProvider] trait by itself because
/// leap seconds corrections need to be applied to support the trait methods. Instead, it needs
/// to be converted to a [CucTimeWithLeapSecs] object using the [Self::to_leap_sec_helper] method.
///
/// # Example
///
/// ```
/// use spacepackets::time::cuc::{FractionalResolution, TimeProviderCcsdsEpoch};
/// use spacepackets::time::cuc::{FractionalResolution, CucTime};
/// use spacepackets::time::{TimeWriter, CcsdsTimeCodes, TimeReader, CcsdsTimeProvider};
///
/// const LEAP_SECONDS: u32 = 37;
/// // Highest fractional resolution
/// let timestamp_now = TimeProviderCcsdsEpoch::from_now(FractionalResolution::SixtyNs).expect("creating cuc stamp failed");
/// let timestamp_now = CucTime::from_now(FractionalResolution::SixtyNs, LEAP_SECONDS)
/// .expect("creating cuc stamp failed");
/// let mut raw_stamp = [0; 16];
/// {
/// let written = timestamp_now.write_to_bytes(&mut raw_stamp).expect("writing timestamp failed");
@ -163,7 +193,7 @@ pub struct FractionalPart(FractionalResolution, u32);
/// assert_eq!(written, 8);
/// }
/// {
/// let read_result = TimeProviderCcsdsEpoch::from_bytes(&raw_stamp);
/// let read_result = TimeProviderCcsdsEpoch::from_bytes_with_leap_seconds(&raw_stamp, LEAP_SECONDS);
/// assert!(read_result.is_ok());
/// let stamp_deserialized = read_result.unwrap();
/// assert_eq!(stamp_deserialized, timestamp_now);
@ -171,12 +201,27 @@ pub struct FractionalPart(FractionalResolution, u32);
/// ```
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct TimeProviderCcsdsEpoch {
pub struct CucTime {
pfield: u8,
counter: WidthCounterPair,
fractions: Option<FractionalPart>,
}
/// This object is a wrapper object around the [CucTime] object which also tracks
/// the leap seconds. This is necessary to implement the [CcsdsTimeProvider] trait.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CucTimeWithLeapSecs {
pub time: CucTime,
pub leap_seconds: u32,
}
impl CucTimeWithLeapSecs {
pub fn new(time: CucTime, leap_seconds: u32) -> Self {
Self { time, leap_seconds }
}
}
#[inline]
pub fn pfield_len(pfield: u8) -> usize {
if ((pfield >> 7) & 0b1) == 1 {
@ -185,7 +230,7 @@ pub fn pfield_len(pfield: u8) -> usize {
1
}
impl TimeProviderCcsdsEpoch {
impl CucTime {
/// Create a time provider with a four byte counter and no fractional part.
pub fn new(counter: u32) -> Self {
// These values are definitely valid, so it is okay to unwrap here.
@ -239,11 +284,21 @@ impl TimeProviderCcsdsEpoch {
/// This function will return the current time as a CUC timestamp.
/// The counter width will always be set to 4 bytes because the normal CCSDS epoch will overflow
/// when using less than that.
///
/// The CUC timestamp uses TAI as the reference time system. Therefore, leap second corrections
/// must be applied on top of the UTC based time retrieved from the system in addition to the
/// conversion to the CCSDS epoch.
#[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub fn from_now(fraction_resolution: FractionalResolution) -> Result<Self, StdTimestampError> {
pub fn from_now(
fraction_resolution: FractionalResolution,
leap_seconds: u32,
) -> Result<Self, StdTimestampError> {
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
let ccsds_epoch = unix_epoch_to_ccsds_epoch(now.as_secs() as i64);
ccsds_epoch
.checked_add(i64::from(leap_seconds))
.ok_or(TimestampError::Cuc(CucError::LeapSecondCorrectionError))?;
if fraction_resolution == FractionalResolution::Seconds {
return Ok(Self::new(ccsds_epoch as u32));
}
@ -255,11 +310,19 @@ impl TimeProviderCcsdsEpoch {
/// Updates the current time stamp from the current time. The fractional field width remains
/// the same and will be updated accordingly.
///
/// The CUC timestamp uses TAI as the reference time system. Therefore, leap second corrections
/// must be applied on top of the UTC based time retrieved from the system in addition to the
/// conversion to the CCSDS epoch.
#[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, leap_seconds: u32) -> 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
.1
.checked_add(leap_seconds)
.ok_or(TimestampError::Cuc(CucError::LeapSecondCorrectionError))?;
if self.fractions.is_some() {
self.fractions = fractional_part_from_subsec_ns(
self.fractions.unwrap().0,
@ -269,16 +332,23 @@ impl TimeProviderCcsdsEpoch {
Ok(())
}
pub fn from_date_time(
dt: &DateTime<Utc>,
#[cfg(feature = "chrono")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "chrono")))]
pub fn from_chrono_date_time(
dt: &chrono::DateTime<chrono::Utc>,
res: FractionalResolution,
leap_seconds: u32,
) -> Result<Self, TimestampError> {
// Year before CCSDS epoch is invalid.
if dt.year() < 1958 {
return Err(TimestampError::DateBeforeCcsdsEpoch(*dt));
return Err(TimestampError::DateBeforeCcsdsEpoch(UnixTime::from(*dt)));
}
let counter = dt
.timestamp()
.checked_add(i64::from(leap_seconds))
.ok_or(TimestampError::Cuc(CucError::LeapSecondCorrectionError))?;
Self::new_generic(
WidthCounterPair(4, dt.timestamp() as u32),
WidthCounterPair(4, counter as u32),
fractional_part_from_subsec_ns(res, dt.timestamp_subsec_nanos() as u64),
)
.map_err(|e| e.into())
@ -287,20 +357,20 @@ impl TimeProviderCcsdsEpoch {
/// Generates a CUC timestamp from a UNIX timestamp with a width of 4. This width is able
/// to accomodate all possible UNIX timestamp values.
pub fn from_unix_stamp(
unix_stamp: &UnixTimestamp,
unix_stamp: &UnixTime,
res: FractionalResolution,
leap_seconds: u32,
) -> Result<Self, TimestampError> {
let ccsds_epoch = unix_epoch_to_ccsds_epoch(unix_stamp.unix_seconds);
let ccsds_epoch = unix_epoch_to_ccsds_epoch(unix_stamp.secs);
// 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));
}
let fractions = fractional_part_from_subsec_ns(
res,
unix_stamp.subsecond_millis() as u64 * 10_u64.pow(6),
);
ccsds_epoch
.checked_add(i64::from(leap_seconds))
.ok_or(TimestampError::Cuc(CucError::LeapSecondCorrectionError))?;
let fractions =
fractional_part_from_subsec_ns(res, unix_stamp.subsec_millis() as u64 * 10_u64.pow(6));
Self::new_generic(WidthCounterPair(4, ccsds_epoch as u32), fractions).map_err(|e| e.into())
}
@ -309,6 +379,10 @@ impl TimeProviderCcsdsEpoch {
Self::new_generic(WidthCounterPair(2, counter as u32), None).unwrap()
}
pub fn ccsds_time_code(&self) -> CcsdsTimeCode {
CcsdsTimeCode::CucCcsdsEpoch
}
pub fn width_counter_pair(&self) -> WidthCounterPair {
self.counter
}
@ -325,6 +399,10 @@ impl TimeProviderCcsdsEpoch {
self.fractions
}
pub fn to_leap_sec_helper(&self, leap_seconds: u32) -> CucTimeWithLeapSecs {
CucTimeWithLeapSecs::new(*self, leap_seconds)
}
pub fn set_fractions(&mut self, fractions: FractionalPart) -> Result<(), CucError> {
Self::verify_fractions_value(fractions)?;
self.fractions = Some(fractions);
@ -410,8 +488,15 @@ impl TimeProviderCcsdsEpoch {
}
#[inline]
fn unix_seconds(&self) -> i64 {
pub fn unix_secs(&self, leap_seconds: u32) -> i64 {
ccsds_epoch_to_unix_epoch(self.counter.1 as i64)
.checked_sub(leap_seconds as i64)
.unwrap()
}
#[inline]
pub fn subsec_millis(&self) -> u16 {
(self.subsec_nanos() / 1_000_000) as u16
}
/// This returns the length of the individual components of the CUC timestamp in addition
@ -455,13 +540,25 @@ impl TimeProviderCcsdsEpoch {
}
Ok(())
}
fn len_as_bytes(&self) -> usize {
Self::len_packed_from_pfield(self.pfield)
}
fn subsec_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) as u32;
}
0
}
}
impl TimeReader for TimeProviderCcsdsEpoch {
fn from_bytes(buf: &[u8]) -> Result<Self, TimestampError>
where
Self: Sized,
{
impl TimeReader for CucTime {
fn from_bytes(buf: &[u8]) -> Result<Self, TimestampError> {
if buf.len() < MIN_CUC_LEN {
return Err(TimestampError::ByteConversion(
ByteConversionError::FromSliceTooSmall {
@ -472,16 +569,16 @@ impl TimeReader for TimeProviderCcsdsEpoch {
}
match ccsds_time_code_from_p_field(buf[0]) {
Ok(code) => {
if code != CcsdsTimeCodes::CucCcsdsEpoch {
if code != CcsdsTimeCode::CucCcsdsEpoch {
return Err(TimestampError::InvalidTimeCode {
expected: CcsdsTimeCodes::CucCcsdsEpoch,
expected: CcsdsTimeCode::CucCcsdsEpoch,
found: code as u8,
});
}
}
Err(raw) => {
return Err(TimestampError::InvalidTimeCode {
expected: CcsdsTimeCodes::CucCcsdsEpoch,
expected: CcsdsTimeCode::CucCcsdsEpoch,
found: raw,
});
}
@ -541,7 +638,7 @@ impl TimeReader for TimeProviderCcsdsEpoch {
}
}
impl TimeWriter for TimeProviderCcsdsEpoch {
impl TimeWriter for CucTime {
fn write_to_bytes(&self, bytes: &mut [u8]) -> Result<usize, TimestampError> {
// Cross check the sizes of the counters against byte widths in the ctor
if bytes.len() < self.len_as_bytes() {
@ -593,50 +690,32 @@ impl TimeWriter for TimeProviderCcsdsEpoch {
}
}
impl CcsdsTimeProvider for TimeProviderCcsdsEpoch {
impl CcsdsTimeProvider for CucTimeWithLeapSecs {
fn len_as_bytes(&self) -> usize {
Self::len_packed_from_pfield(self.pfield)
self.time.len_as_bytes()
}
fn p_field(&self) -> (usize, [u8; 2]) {
(1, [self.pfield, 0])
(1, [self.time.pfield, 0])
}
fn ccdsd_time_code(&self) -> CcsdsTimeCodes {
CcsdsTimeCodes::CucCcsdsEpoch
fn ccdsd_time_code(&self) -> CcsdsTimeCode {
self.time.ccsds_time_code()
}
fn unix_seconds(&self) -> i64 {
self.unix_seconds()
fn unix_secs(&self) -> i64 {
ccsds_epoch_to_unix_epoch(self.time.counter.1 as i64)
.checked_sub(self.leap_seconds as i64)
.unwrap()
}
fn subsecond_millis(&self) -> u16 {
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;
}
0
}
fn date_time(&self) -> Option<DateTime<Utc>> {
let unix_seconds = self.unix_seconds();
let ns = if let Some(fractional_part) = self.fractions {
convert_fractional_part_to_ns(fractional_part)
} else {
0
};
if let LocalResult::Single(res) = Utc.timestamp_opt(unix_seconds, ns as u32) {
return Some(res);
}
None
fn subsec_nanos(&self) -> u32 {
self.time.subsec_nanos()
}
}
fn get_provider_values_after_duration_addition(
provider: &TimeProviderCcsdsEpoch,
provider: &CucTime,
duration: Duration,
) -> (u32, Option<FractionalPart>) {
let mut new_counter = provider.counter.1;
@ -685,7 +764,7 @@ fn get_provider_values_after_duration_addition(
(new_counter, fractional_part)
}
impl AddAssign<Duration> for TimeProviderCcsdsEpoch {
impl AddAssign<Duration> for CucTime {
fn add_assign(&mut self, duration: Duration) {
let (new_counter, new_fractional_part) =
get_provider_values_after_duration_addition(self, duration);
@ -696,7 +775,7 @@ impl AddAssign<Duration> for TimeProviderCcsdsEpoch {
}
}
impl Add<Duration> for TimeProviderCcsdsEpoch {
impl Add<Duration> for CucTime {
type Output = Self;
fn add(self, duration: Duration) -> Self::Output {
@ -710,8 +789,8 @@ impl Add<Duration> for TimeProviderCcsdsEpoch {
}
}
impl Add<Duration> for &TimeProviderCcsdsEpoch {
type Output = TimeProviderCcsdsEpoch;
impl Add<Duration> for &CucTime {
type Output = CucTime;
fn add(self, duration: Duration) -> Self::Output {
let (new_counter, new_fractional_part) =
@ -726,44 +805,50 @@ impl Add<Duration> for &TimeProviderCcsdsEpoch {
#[cfg(test)]
mod tests {
use crate::time::{UnixTime, 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;
const LEAP_SECONDS: u32 = 37;
#[test]
fn test_basic_zero_epoch() {
let zero_cuc = TimeProviderCcsdsEpoch::new(0);
// Do not include leap second corrections, which do not apply to dates before 1972.
let zero_cuc = CucTime::new(0);
assert_eq!(zero_cuc.len_as_bytes(), 5);
assert_eq!(zero_cuc.width(), zero_cuc.width_counter_pair().0);
assert_eq!(zero_cuc.counter(), zero_cuc.width_counter_pair().1);
assert_eq!(zero_cuc.ccdsd_time_code(), CcsdsTimeCodes::CucCcsdsEpoch);
let ccsds_cuc = zero_cuc.to_leap_sec_helper(0);
assert_eq!(ccsds_cuc.ccdsd_time_code(), CcsdsTimeCode::CucCcsdsEpoch);
let counter = zero_cuc.width_counter_pair();
assert_eq!(counter.0, 4);
assert_eq!(counter.1, 0);
let fractions = zero_cuc.width_fractions_pair();
assert!(fractions.is_none());
let dt = zero_cuc.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);
let dt = ccsds_cuc.chrono_date_time();
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]
fn test_write_no_fractions() {
let mut buf: [u8; 16] = [0; 16];
let zero_cuc = TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(4, 0x20102030), None);
let zero_cuc = CucTime::new_generic(WidthCounterPair(4, 0x20102030), None);
assert!(zero_cuc.is_ok());
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.subsec_nanos(), 0);
assert_eq!(zero_cuc.len_as_bytes(), 5);
assert_eq!(pfield_len(buf[0]), 1);
let written = res.unwrap();
@ -771,7 +856,7 @@ mod tests {
assert_eq!((buf[0] >> 7) & 0b1, 0);
let time_code = ccsds_time_code_from_p_field(buf[0]);
assert!(time_code.is_ok());
assert_eq!(time_code.unwrap(), CcsdsTimeCodes::CucCcsdsEpoch);
assert_eq!(time_code.unwrap(), CcsdsTimeCode::CucCcsdsEpoch);
assert_eq!((buf[0] >> 2) & 0b11, 0b11);
assert_eq!(buf[0] & 0b11, 0);
let raw_counter = u32::from_be_bytes(buf[1..5].try_into().unwrap());
@ -781,27 +866,28 @@ mod tests {
#[test]
fn test_datetime_now() {
let now = Utc::now();
let cuc_now = TimeProviderCcsdsEpoch::from_now(FractionalResolution::SixtyNs);
let now = chrono::Utc::now();
let cuc_now = CucTime::from_now(FractionalResolution::SixtyNs, LEAP_SECONDS);
assert!(cuc_now.is_ok());
let cuc_now = cuc_now.unwrap();
let dt_opt = cuc_now.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);
let ccsds_cuc = cuc_now.to_leap_sec_helper(LEAP_SECONDS);
let dt_opt = ccsds_cuc.chrono_date_time();
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]
fn test_read_no_fractions() {
let mut buf: [u8; 16] = [0; 16];
let zero_cuc =
TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(4, 0x20102030), None).unwrap();
let zero_cuc = CucTime::new_generic(WidthCounterPair(4, 0x20102030), None).unwrap();
zero_cuc.write_to_bytes(&mut buf).unwrap();
let cuc_read_back =
TimeProviderCcsdsEpoch::from_bytes(&buf).expect("reading cuc timestamp failed");
let cuc_read_back = CucTime::from_bytes(&buf).expect("reading cuc timestamp failed");
assert_eq!(cuc_read_back, zero_cuc);
assert_eq!(cuc_read_back.width_counter_pair().1, 0x20102030);
assert_eq!(cuc_read_back.width_fractions_pair(), None);
@ -811,7 +897,7 @@ mod tests {
fn invalid_read_len() {
let mut buf: [u8; 16] = [0; 16];
for i in 0..2 {
let res = TimeProviderCcsdsEpoch::from_bytes(&buf[0..i]);
let res = CucTime::from_bytes(&buf[0..i]);
assert!(res.is_err());
let err = res.unwrap_err();
if let TimestampError::ByteConversion(ByteConversionError::FromSliceTooSmall {
@ -823,10 +909,10 @@ mod tests {
assert_eq!(expected, 2);
}
}
let large_stamp = TimeProviderCcsdsEpoch::new_with_fine_fractions(22, 300).unwrap();
let large_stamp = CucTime::new_with_fine_fractions(22, 300).unwrap();
large_stamp.write_to_bytes(&mut buf).unwrap();
for i in 2..large_stamp.len_as_bytes() - 1 {
let res = TimeProviderCcsdsEpoch::from_bytes(&buf[0..i]);
let res = CucTime::from_bytes(&buf[0..i]);
assert!(res.is_err());
let err = res.unwrap_err();
if let TimestampError::ByteConversion(ByteConversionError::FromSliceTooSmall {
@ -843,7 +929,7 @@ mod tests {
#[test]
fn write_and_read_tiny_stamp() {
let mut buf = [0; 2];
let cuc = TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(1, 200), None);
let cuc = CucTime::new_generic(WidthCounterPair(1, 200), None);
assert!(cuc.is_ok());
let cuc = cuc.unwrap();
assert_eq!(cuc.len_as_bytes(), 2);
@ -852,7 +938,7 @@ mod tests {
let written = res.unwrap();
assert_eq!(written, 2);
assert_eq!(buf[1], 200);
let cuc_read_back = TimeProviderCcsdsEpoch::from_bytes(&buf);
let cuc_read_back = CucTime::from_bytes(&buf);
assert!(cuc_read_back.is_ok());
let cuc_read_back = cuc_read_back.unwrap();
assert_eq!(cuc_read_back, cuc);
@ -861,7 +947,7 @@ mod tests {
#[test]
fn write_slightly_larger_stamp() {
let mut buf = [0; 4];
let cuc = TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(2, 40000), None);
let cuc = CucTime::new_generic(WidthCounterPair(2, 40000), None);
assert!(cuc.is_ok());
let cuc = cuc.unwrap();
assert_eq!(cuc.len_as_bytes(), 3);
@ -870,7 +956,7 @@ mod tests {
let written = res.unwrap();
assert_eq!(written, 3);
assert_eq!(u16::from_be_bytes(buf[1..3].try_into().unwrap()), 40000);
let cuc_read_back = TimeProviderCcsdsEpoch::from_bytes(&buf);
let cuc_read_back = CucTime::from_bytes(&buf);
assert!(cuc_read_back.is_ok());
let cuc_read_back = cuc_read_back.unwrap();
assert_eq!(cuc_read_back, cuc);
@ -882,7 +968,7 @@ mod tests {
#[test]
fn write_read_three_byte_cntr_stamp() {
let mut buf = [0; 4];
let cuc = TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(3, 2_u32.pow(24) - 2), None);
let cuc = CucTime::new_generic(WidthCounterPair(3, 2_u32.pow(24) - 2), None);
assert!(cuc.is_ok());
let cuc = cuc.unwrap();
assert_eq!(cuc.len_as_bytes(), 4);
@ -893,7 +979,7 @@ mod tests {
let mut temp_buf = [0; 4];
temp_buf[1..4].copy_from_slice(&buf[1..4]);
assert_eq!(u32::from_be_bytes(temp_buf), 2_u32.pow(24) - 2);
let cuc_read_back = TimeProviderCcsdsEpoch::from_bytes(&buf);
let cuc_read_back = CucTime::from_bytes(&buf);
assert!(cuc_read_back.is_ok());
let cuc_read_back = cuc_read_back.unwrap();
assert_eq!(cuc_read_back, cuc);
@ -902,7 +988,7 @@ mod tests {
#[test]
fn test_write_invalid_buf() {
let mut buf: [u8; 16] = [0; 16];
let res = TimeProviderCcsdsEpoch::new_with_fine_fractions(0, 0);
let res = CucTime::new_with_fine_fractions(0, 0);
let cuc = res.unwrap();
for i in 0..cuc.len_as_bytes() - 1 {
let err = cuc.write_to_bytes(&mut buf[0..i]);
@ -923,13 +1009,13 @@ mod tests {
#[test]
fn invalid_ccsds_stamp_type() {
let mut buf: [u8; 16] = [0; 16];
buf[0] |= (CcsdsTimeCodes::CucAgencyEpoch as u8) << 4;
let res = TimeProviderCcsdsEpoch::from_bytes(&buf);
buf[0] |= (CcsdsTimeCode::CucAgencyEpoch as u8) << 4;
let res = CucTime::from_bytes(&buf);
assert!(res.is_err());
let err = res.unwrap_err();
if let TimestampError::InvalidTimeCode { expected, found } = err {
assert_eq!(expected, CcsdsTimeCodes::CucCcsdsEpoch);
assert_eq!(found, CcsdsTimeCodes::CucAgencyEpoch as u8);
assert_eq!(expected, CcsdsTimeCode::CucCcsdsEpoch);
assert_eq!(found, CcsdsTimeCode::CucAgencyEpoch as u8);
} else {
panic!("unexpected error: {}", err);
}
@ -938,7 +1024,7 @@ mod tests {
#[test]
fn test_write_with_coarse_fractions() {
let mut buf: [u8; 16] = [0; 16];
let cuc = TimeProviderCcsdsEpoch::new_with_coarse_fractions(0x30201060, 120);
let cuc = CucTime::new_with_coarse_fractions(0x30201060, 120);
assert!(cuc.fractions.is_some());
assert_eq!(cuc.fractions.unwrap().1, 120);
assert_eq!(cuc.fractions.unwrap().0, FractionalResolution::FourMs);
@ -957,10 +1043,10 @@ mod tests {
#[test]
fn test_read_with_coarse_fractions() {
let mut buf: [u8; 16] = [0; 16];
let cuc = TimeProviderCcsdsEpoch::new_with_coarse_fractions(0x30201060, 120);
let cuc = CucTime::new_with_coarse_fractions(0x30201060, 120);
let res = cuc.write_to_bytes(&mut buf);
assert!(res.is_ok());
let res = TimeProviderCcsdsEpoch::from_bytes(&buf);
let res = CucTime::from_bytes(&buf);
assert!(res.is_ok());
let read_back = res.unwrap();
assert_eq!(read_back, cuc);
@ -969,7 +1055,7 @@ mod tests {
#[test]
fn test_write_with_medium_fractions() {
let mut buf: [u8; 16] = [0; 16];
let cuc = TimeProviderCcsdsEpoch::new_with_medium_fractions(0x30303030, 30000);
let cuc = CucTime::new_with_medium_fractions(0x30303030, 30000);
let res = cuc.write_to_bytes(&mut buf);
assert!(res.is_ok());
let written = res.unwrap();
@ -981,10 +1067,10 @@ mod tests {
#[test]
fn test_read_with_medium_fractions() {
let mut buf: [u8; 16] = [0; 16];
let cuc = TimeProviderCcsdsEpoch::new_with_medium_fractions(0x30303030, 30000);
let cuc = CucTime::new_with_medium_fractions(0x30303030, 30000);
let res = cuc.write_to_bytes(&mut buf);
assert!(res.is_ok());
let res = TimeProviderCcsdsEpoch::from_bytes(&buf);
let res = CucTime::from_bytes(&buf);
assert!(res.is_ok());
let cuc_read_back = res.unwrap();
assert_eq!(cuc_read_back, cuc);
@ -993,8 +1079,7 @@ mod tests {
#[test]
fn test_write_with_fine_fractions() {
let mut buf: [u8; 16] = [0; 16];
let cuc =
TimeProviderCcsdsEpoch::new_with_fine_fractions(0x30303030, u16::MAX as u32 + 60000);
let cuc = CucTime::new_with_fine_fractions(0x30303030, u16::MAX as u32 + 60000);
assert!(cuc.is_ok());
let cuc = cuc.unwrap();
let res = cuc.write_to_bytes(&mut buf);
@ -1009,13 +1094,12 @@ mod tests {
#[test]
fn test_read_with_fine_fractions() {
let mut buf: [u8; 16] = [0; 16];
let cuc =
TimeProviderCcsdsEpoch::new_with_fine_fractions(0x30303030, u16::MAX as u32 + 60000);
let cuc = CucTime::new_with_fine_fractions(0x30303030, u16::MAX as u32 + 60000);
assert!(cuc.is_ok());
let cuc = cuc.unwrap();
let res = cuc.write_to_bytes(&mut buf);
assert!(res.is_ok());
let res = TimeProviderCcsdsEpoch::from_bytes(&buf);
let res = CucTime::from_bytes(&buf);
assert!(res.is_ok());
let cuc_read_back = res.unwrap();
assert_eq!(cuc_read_back, cuc);
@ -1089,7 +1173,7 @@ mod tests {
#[test]
fn update_fractions() {
let mut stamp = TimeProviderCcsdsEpoch::new(2000);
let mut stamp = CucTime::new(2000);
let res = stamp.set_fractions(FractionalPart(FractionalResolution::SixtyNs, 5000));
assert!(res.is_ok());
assert!(stamp.fractions.is_some());
@ -1100,13 +1184,13 @@ mod tests {
#[test]
fn set_fract_resolution() {
let mut stamp = TimeProviderCcsdsEpoch::new(2000);
let mut stamp = CucTime::new(2000);
stamp.set_fractional_resolution(FractionalResolution::SixtyNs);
assert!(stamp.fractions.is_some());
let fractions = stamp.fractions.unwrap();
assert_eq!(fractions.0, FractionalResolution::SixtyNs);
assert_eq!(fractions.1, 0);
let res = stamp.update_from_now();
let res = stamp.update_from_now(LEAP_SECONDS);
assert!(res.is_ok());
}
@ -1120,7 +1204,12 @@ mod tests {
assert_eq!(fractions.1, 2_u32.pow(3 * 8) - 2);
}
fn check_stamp_after_addition(cuc_stamp: &TimeProviderCcsdsEpoch) {
fn check_stamp_after_addition(cuc_stamp: &CucTime) {
let cuc_with_leaps = cuc_stamp.to_leap_sec_helper(LEAP_SECONDS);
assert_eq!(
cuc_with_leaps.ccdsd_time_code(),
CcsdsTimeCode::CucCcsdsEpoch
);
assert_eq!(cuc_stamp.width_counter_pair().1, 202);
let fractions = cuc_stamp.width_fractions_pair().unwrap().1;
let expected_val =
@ -1130,12 +1219,12 @@ mod tests {
// What I would roughly expect
assert_eq!(cuc_stamp2.counter.1, 203);
assert!(cuc_stamp2.fractions.unwrap().1 < 100);
assert!(cuc_stamp2.subsecond_millis() <= 1);
assert!(cuc_stamp2.subsec_millis() <= 1);
}
#[test]
fn add_duration_basic() {
let mut cuc_stamp = TimeProviderCcsdsEpoch::new(200);
let mut cuc_stamp = CucTime::new(200);
cuc_stamp.set_fractional_resolution(FractionalResolution::FifteenUs);
let duration = Duration::from_millis(2500);
cuc_stamp += duration;
@ -1144,7 +1233,7 @@ mod tests {
#[test]
fn add_duration_basic_on_ref() {
let mut cuc_stamp = TimeProviderCcsdsEpoch::new(200);
let mut cuc_stamp = CucTime::new(200);
cuc_stamp.set_fractional_resolution(FractionalResolution::FifteenUs);
let duration = Duration::from_millis(2500);
let new_stamp = cuc_stamp + duration;
@ -1153,7 +1242,7 @@ mod tests {
#[test]
fn add_duration_basic_no_fractions() {
let mut cuc_stamp = TimeProviderCcsdsEpoch::new(200);
let mut cuc_stamp = CucTime::new(200);
let duration = Duration::from_millis(2000);
cuc_stamp += duration;
assert_eq!(cuc_stamp.counter(), 202);
@ -1162,7 +1251,7 @@ mod tests {
#[test]
fn add_duration_basic_on_ref_no_fractions() {
let cuc_stamp = TimeProviderCcsdsEpoch::new(200);
let cuc_stamp = CucTime::new(200);
let duration = Duration::from_millis(2000);
let new_stamp = cuc_stamp + duration;
assert_eq!(new_stamp.counter(), 202);
@ -1170,8 +1259,7 @@ mod tests {
}
#[test]
fn add_duration_overflow() {
let mut cuc_stamp =
TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(1, 255), None).unwrap();
let mut cuc_stamp = CucTime::new_generic(WidthCounterPair(1, 255), None).unwrap();
let duration = Duration::from_secs(10);
cuc_stamp += duration;
assert_eq!(cuc_stamp.counter.1, 10);
@ -1179,7 +1267,7 @@ mod tests {
#[test]
fn test_invalid_width_param() {
let error = TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(8, 0), None);
let error = CucTime::new_generic(WidthCounterPair(8, 0), None);
assert!(error.is_err());
let error = error.unwrap_err();
if let CucError::InvalidCounterWidth(width) = error {
@ -1192,24 +1280,24 @@ mod tests {
#[test]
fn test_from_dt() {
let dt = Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap();
let cuc =
TimeProviderCcsdsEpoch::from_date_time(&dt, FractionalResolution::Seconds).unwrap();
assert_eq!(cuc.counter(), dt.timestamp() as u32);
let dt = chrono::Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap();
let cuc = CucTime::from_chrono_date_time(&dt, FractionalResolution::Seconds, LEAP_SECONDS)
.unwrap();
assert_eq!(cuc.counter(), dt.timestamp() as u32 + LEAP_SECONDS);
}
#[test]
fn test_new_u16_width() {
let cuc = TimeProviderCcsdsEpoch::new_u16_counter(0);
let cuc = CucTime::new_u16_counter(0);
assert_eq!(cuc.width(), 2);
assert_eq!(cuc.counter(), 0);
}
#[test]
fn from_unix_stamp() {
let unix_stamp = UnixTimestamp::new(0, 0).unwrap();
let unix_stamp = UnixTime::new(0, 0);
let cuc =
TimeProviderCcsdsEpoch::from_unix_stamp(&unix_stamp, FractionalResolution::Seconds)
CucTime::from_unix_stamp(&unix_stamp, FractionalResolution::Seconds, LEAP_SECONDS)
.expect("failed to create cuc from unix stamp");
assert_eq!(
cuc.counter(),
@ -1219,7 +1307,7 @@ mod tests {
#[test]
fn test_invalid_counter() {
let cuc_error = TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(1, 256), None);
let cuc_error = CucTime::new_generic(WidthCounterPair(1, 256), None);
assert!(cuc_error.is_err());
let cuc_error = cuc_error.unwrap_err();
if let CucError::InvalidCounter { width, counter } = cuc_error {
@ -1233,7 +1321,7 @@ mod tests {
#[test]
fn test_stamp_to_vec() {
let stamp = TimeProviderCcsdsEpoch::new_u16_counter(100);
let stamp = CucTime::new_u16_counter(100);
let stamp_vec = stamp.to_vec().unwrap();
let mut buf: [u8; 16] = [0; 16];
stamp.write_to_bytes(&mut buf).unwrap();

View File

@ -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")]
@ -27,10 +29,11 @@ pub mod cuc;
pub const DAYS_CCSDS_TO_UNIX: i32 = -4383;
pub const SECONDS_PER_DAY: u32 = 86400;
pub const MS_PER_DAY: u32 = SECONDS_PER_DAY * 1000;
pub const NANOS_PER_SECOND: u32 = 1_000_000_000;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum CcsdsTimeCodes {
pub enum CcsdsTimeCode {
CucCcsdsEpoch = 0b001,
CucAgencyEpoch = 0b010,
Cds = 0b100,
@ -38,16 +41,16 @@ pub enum CcsdsTimeCodes {
AgencyDefined = 0b110,
}
impl TryFrom<u8> for CcsdsTimeCodes {
impl TryFrom<u8> for CcsdsTimeCode {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
x if x == CcsdsTimeCodes::CucCcsdsEpoch as u8 => Ok(CcsdsTimeCodes::CucCcsdsEpoch),
x if x == CcsdsTimeCodes::CucAgencyEpoch as u8 => Ok(CcsdsTimeCodes::CucAgencyEpoch),
x if x == CcsdsTimeCodes::Cds as u8 => Ok(CcsdsTimeCodes::Cds),
x if x == CcsdsTimeCodes::Ccs as u8 => Ok(CcsdsTimeCodes::Ccs),
x if x == CcsdsTimeCodes::AgencyDefined as u8 => Ok(CcsdsTimeCodes::AgencyDefined),
x if x == CcsdsTimeCode::CucCcsdsEpoch as u8 => Ok(CcsdsTimeCode::CucCcsdsEpoch),
x if x == CcsdsTimeCode::CucAgencyEpoch as u8 => Ok(CcsdsTimeCode::CucAgencyEpoch),
x if x == CcsdsTimeCode::Cds as u8 => Ok(CcsdsTimeCode::Cds),
x if x == CcsdsTimeCode::Ccs as u8 => Ok(CcsdsTimeCode::Ccs),
x if x == CcsdsTimeCode::AgencyDefined as u8 => Ok(CcsdsTimeCode::AgencyDefined),
_ => Err(()),
}
}
@ -55,20 +58,20 @@ impl TryFrom<u8> for CcsdsTimeCodes {
/// Retrieve the CCSDS time code from the p-field. If no valid time code identifier is found, the
/// value of the raw time code identification field is returned.
pub fn ccsds_time_code_from_p_field(pfield: u8) -> Result<CcsdsTimeCodes, u8> {
pub fn ccsds_time_code_from_p_field(pfield: u8) -> Result<CcsdsTimeCode, u8> {
let raw_bits = (pfield >> 4) & 0b111;
CcsdsTimeCodes::try_from(raw_bits).map_err(|_| raw_bits)
CcsdsTimeCode::try_from(raw_bits).map_err(|_| raw_bits)
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum TimestampError {
InvalidTimeCode { expected: CcsdsTimeCodes, found: u8 },
InvalidTimeCode { expected: CcsdsTimeCode, found: u8 },
ByteConversion(ByteConversionError),
Cds(cds::CdsError),
Cuc(cuc::CucError),
DateBeforeCcsdsEpoch(DateTime<Utc>),
DateBeforeCcsdsEpoch(UnixTime),
CustomEpochNotSupported,
}
@ -91,7 +94,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")
@ -204,16 +207,15 @@ pub trait TimeWriter {
}
}
pub trait TimeReader {
fn from_bytes(buf: &[u8]) -> Result<Self, TimestampError>
where
Self: Sized;
pub trait TimeReader: Sized {
fn from_bytes(buf: &[u8]) -> Result<Self, TimestampError>;
}
/// Trait for generic CCSDS time providers.
///
/// The UNIX helper methods and the [Self::date_time] method are not strictly necessary but extremely
/// The UNIX helper methods and the helper method are not strictly necessary but extremely
/// practical because they are a very common and simple exchange format for time information.
/// Therefore, it was decided to keep them in this trait as well.
pub trait CcsdsTimeProvider {
fn len_as_bytes(&self) -> usize;
@ -222,56 +224,120 @@ pub trait CcsdsTimeProvider {
/// entry denotes the length of the pfield and the second entry is the value of the pfield
/// in big endian format.
fn p_field(&self) -> (usize, [u8; 2]);
fn ccdsd_time_code(&self) -> CcsdsTimeCodes;
fn ccdsd_time_code(&self) -> CcsdsTimeCode;
fn unix_seconds(&self) -> i64;
fn subsecond_millis(&self) -> u16;
fn unix_stamp(&self) -> UnixTimestamp {
UnixTimestamp::const_new(self.unix_seconds(), self.subsecond_millis())
fn unix_secs(&self) -> i64;
fn subsec_nanos(&self) -> u32;
fn subsec_millis(&self) -> u16 {
(self.subsec_nanos() / 1_000_000) as u16
}
fn date_time(&self) -> Option<DateTime<Utc>>;
fn unix_stamp(&self) -> UnixTime {
UnixTime::new(self.unix_secs(), self.subsec_nanos())
}
#[cfg(feature = "chrono")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "chrono")))]
fn chrono_date_time(&self) -> chrono::LocalResult<chrono::DateTime<chrono::Utc>> {
chrono::Utc.timestamp_opt(self.unix_secs(), self.subsec_nanos())
}
#[cfg(feature = "timelib")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "timelib")))]
fn timelib_date_time(&self) -> Result<time::OffsetDateTime, time::error::ComponentRange> {
Ok(time::OffsetDateTime::from_unix_timestamp(self.unix_secs())?
+ time::Duration::nanoseconds(self.subsec_nanos().into()))
}
}
/// UNIX timestamp: Elapsed seconds since 1970-01-01T00:00:00+00:00.
/// UNIX time: Elapsed non-leap seconds since 1970-01-01T00:00:00+00:00 UTC.
///
/// Also can optionally include subsecond millisecond for greater accuracy.
/// This is a commonly used time format and can therefore also be used as a generic format to
/// convert other CCSDS time formats to and from. The subsecond precision is in nanoseconds
/// similarly to other common time formats and libraries.
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct UnixTimestamp {
pub unix_seconds: i64,
subsecond_millis: u16,
pub struct UnixTime {
secs: i64,
subsec_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> {
if subsec_millis > 999 {
impl UnixTime {
/// The UNIX epoch time: 1970-01-01T00:00:00+00:00 UTC.
pub const EPOCH: Self = Self {
secs: 0,
subsec_nanos: 0,
};
/// The minimum possible `UnixTime`.
pub const MIN: Self = Self {
secs: i64::MIN,
subsec_nanos: 0,
};
/// The maximum possible `UnixTime`.
pub const MAX: Self = Self {
secs: i64::MAX,
subsec_nanos: NANOS_PER_SECOND - 1,
};
/// 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 >= NANOS_PER_SECOND {
return None;
}
Some(Self::const_new(unix_seconds, subsec_millis))
Some(Self::new(unix_seconds, subsec_nanos))
}
/// 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");
/// 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 >= 1000 {
return None;
}
Self::new_checked(unix_seconds, subsec_millis as u32 * 1_000_000)
}
/// 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 {
if subsecond_nanos >= NANOS_PER_SECOND {
panic!("invalid subsecond nanos value");
}
Self {
unix_seconds,
subsecond_millis,
secs: unix_seconds,
subsec_nanos: subsecond_nanos,
}
}
pub fn new_only_seconds(unix_seconds: i64) -> Self {
/// 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 {
if subsecond_millis >= 1000 {
panic!("invalid subsecond millisecond value");
}
Self {
unix_seconds,
subsecond_millis: 0,
secs: unix_seconds,
subsec_nanos: subsecond_millis as u32 * 1_000_000,
}
}
pub fn subsecond_millis(&self) -> u16 {
self.subsecond_millis
pub fn new_only_secs(unix_seconds: i64) -> Self {
Self {
secs: unix_seconds,
subsec_nanos: 0,
}
}
#[inline]
pub fn subsec_millis(&self) -> u16 {
(self.subsec_nanos / 1_000_000) as u16
}
pub fn subsec_nanos(&self) -> u32 {
self.subsec_nanos
}
#[cfg(feature = "std")]
@ -279,68 +345,78 @@ 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)
pub fn unix_secs_f64(&self) -> f64 {
self.secs as f64 + (self.subsec_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),
)
pub fn secs(&self) -> i64 {
self.secs
}
#[cfg(feature = "chrono")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "chrono")))]
pub fn chrono_date_time(&self) -> LocalResult<DateTime<Utc>> {
Utc.timestamp_opt(self.secs, self.subsec_nanos)
}
#[cfg(feature = "timelib")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "timelib")))]
pub fn timelib_date_time(&self) -> Result<time::OffsetDateTime, time::error::ComponentRange> {
Ok(time::OffsetDateTime::from_unix_timestamp(self.secs())?
+ time::Duration::nanoseconds(self.subsec_nanos().into()))
}
// Calculate the difference in milliseconds between two UnixTimestamps
pub fn difference_in_millis(&self, other: &UnixTimestamp) -> i64 {
let seconds_difference = self.unix_seconds - other.unix_seconds;
pub fn diff_in_millis(&self, other: &UnixTime) -> i64 {
let seconds_difference = self.secs - other.secs;
// Convert seconds difference to milliseconds
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.subsec_nanos as i64 - other.subsec_nanos as i64;
// Combine the differences
milliseconds_difference + subsecond_difference
milliseconds_difference + (subsecond_difference_nanos / 1_000_000)
}
}
impl From<DateTime<Utc>> for UnixTimestamp {
impl From<DateTime<Utc>> for UnixTime {
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())
}
}
impl PartialOrd for UnixTimestamp {
impl PartialOrd for UnixTime {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for UnixTimestamp {
impl Ord for UnixTime {
fn cmp(&self, other: &Self) -> Ordering {
if self == other {
return Ordering::Equal;
}
match self.unix_seconds.cmp(&other.unix_seconds) {
match self.secs.cmp(&other.secs) {
Ordering::Less => return Ordering::Less,
Ordering::Greater => return Ordering::Greater,
_ => (),
}
match self.subsecond_millis().cmp(&other.subsecond_millis()) {
match self.subsec_millis().cmp(&other.subsec_millis()) {
Ordering::Less => {
return if self.unix_seconds < 0 {
return if self.secs < 0 {
Ordering::Greater
} else {
Ordering::Less
}
}
Ordering::Greater => {
return if self.unix_seconds < 0 {
return if self.secs < 0 {
Ordering::Less
} else {
Ordering::Greater
@ -360,11 +436,11 @@ pub struct StampDiff {
pub duration_absolute: Duration,
}
impl Sub for UnixTimestamp {
impl Sub for UnixTime {
type Output = StampDiff;
fn sub(self, rhs: Self) -> Self::Output {
let difference = self.difference_in_millis(&rhs);
let difference = self.diff_in_millis(&rhs);
if difference < 0 {
StampDiff {
positive_duration: false,
@ -379,12 +455,9 @@ impl Sub for UnixTimestamp {
}
}
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_unix_seconds = current_stamp.unix_seconds;
fn get_new_stamp_after_addition(current_stamp: &UnixTime, duration: Duration) -> UnixTime {
let mut new_subsec_nanos = current_stamp.subsec_nanos() + duration.subsec_nanos();
let mut new_unix_seconds = current_stamp.secs;
let mut increment_seconds = |value: u32| {
if new_unix_seconds < 0 {
new_unix_seconds = new_unix_seconds
@ -396,8 +469,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(
@ -406,7 +479,7 @@ fn get_new_stamp_after_addition(
.try_into()
.expect("duration seconds exceeds u32::MAX"),
);
UnixTimestamp::const_new(new_unix_seconds, new_subsec_millis)
UnixTime::new(new_unix_seconds, new_subsec_nanos)
}
/// Please note that this operation will panic on the following conditions:
@ -414,7 +487,7 @@ fn get_new_stamp_after_addition(
/// - Unix seconds after subtraction for stamps before the unix epoch exceeds [i64::MIN].
/// - Unix seconds after addition exceeds [i64::MAX].
/// - Seconds from duration to add exceeds [u32::MAX].
impl AddAssign<Duration> for UnixTimestamp {
impl AddAssign<Duration> for UnixTime {
fn add_assign(&mut self, duration: Duration) {
*self = get_new_stamp_after_addition(self, duration);
}
@ -425,7 +498,7 @@ impl AddAssign<Duration> for UnixTimestamp {
/// - Unix seconds after subtraction for stamps before the unix epoch exceeds [i64::MIN].
/// - Unix seconds after addition exceeds [i64::MAX].
/// - Unix seconds exceeds [u32::MAX].
impl Add<Duration> for UnixTimestamp {
impl Add<Duration> for UnixTime {
type Output = Self;
fn add(self, duration: Duration) -> Self::Output {
@ -433,8 +506,8 @@ impl Add<Duration> for UnixTimestamp {
}
}
impl Add<Duration> for &UnixTimestamp {
type Output = UnixTimestamp;
impl Add<Duration> for &UnixTime {
type Output = UnixTime;
fn add(self, duration: Duration) -> Self::Output {
get_new_stamp_after_addition(self, duration)
@ -445,10 +518,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: UnixTime = UnixTime::new(5, 999_999_999);
#[allow(dead_code)]
const UNIX_STAMP_CONST_2: UnixTime = UnixTime::new_subsec_millis(5, 999);
#[test]
fn test_days_conversion() {
assert_eq!(unix_to_ccsds_days(DAYS_CCSDS_TO_UNIX.into()), 0);
@ -484,28 +562,29 @@ mod tests {
#[test]
fn basic_unix_stamp_test() {
let stamp = UnixTimestamp::new_only_seconds(-200);
assert_eq!(stamp.unix_seconds, -200);
assert_eq!(stamp.subsecond_millis(), 0);
let stamp = UnixTimestamp::new_only_seconds(250);
assert_eq!(stamp.unix_seconds, 250);
assert_eq!(stamp.subsecond_millis(), 0);
let stamp = UnixTime::new_only_secs(-200);
assert_eq!(stamp.secs, -200);
assert_eq!(stamp.subsec_millis(), 0);
let stamp = UnixTime::new_only_secs(250);
assert_eq!(stamp.secs, 250);
assert_eq!(stamp.subsec_millis(), 0);
}
#[test]
fn basic_float_unix_stamp_test() {
let stamp = UnixTimestamp::new(500, 600).unwrap();
assert_eq!(stamp.unix_seconds, 500);
let subsec_millis = stamp.subsecond_millis();
let stamp = UnixTime::new_subsec_millis_checked(500, 600).unwrap();
assert_eq!(stamp.secs, 500);
let subsec_millis = stamp.subsec_millis();
assert_eq!(subsec_millis, 600);
assert!((500.6 - stamp.unix_seconds_f64()).abs() < 0.0001);
println!("{:?}", (500.6 - stamp.unix_secs_f64()).to_string());
assert!((500.6 - stamp.unix_secs_f64()).abs() < 0.0001);
}
#[test]
fn test_ord_larger() {
let stamp0 = UnixTimestamp::new_only_seconds(5);
let stamp1 = UnixTimestamp::new(5, 500).unwrap();
let stamp2 = UnixTimestamp::new_only_seconds(6);
let stamp0 = UnixTime::new_only_secs(5);
let stamp1 = UnixTime::new_subsec_millis_checked(5, 500).unwrap();
let stamp2 = UnixTime::new_only_secs(6);
assert!(stamp1 > stamp0);
assert!(stamp2 > stamp0);
assert!(stamp2 > stamp1);
@ -513,9 +592,9 @@ mod tests {
#[test]
fn test_ord_smaller() {
let stamp0 = UnixTimestamp::new_only_seconds(5);
let stamp1 = UnixTimestamp::new(5, 500).unwrap();
let stamp2 = UnixTimestamp::new_only_seconds(6);
let stamp0 = UnixTime::new_only_secs(5);
let stamp1 = UnixTime::new_subsec_millis_checked(5, 500).unwrap();
let stamp2 = UnixTime::new_only_secs(6);
assert!(stamp0 < stamp1);
assert!(stamp0 < stamp2);
assert!(stamp1 < stamp2);
@ -523,9 +602,9 @@ mod tests {
#[test]
fn test_ord_larger_neg_numbers() {
let stamp0 = UnixTimestamp::new_only_seconds(-5);
let stamp1 = UnixTimestamp::new(-5, 500).unwrap();
let stamp2 = UnixTimestamp::new_only_seconds(-6);
let stamp0 = UnixTime::new_only_secs(-5);
let stamp1 = UnixTime::new_subsec_millis_checked(-5, 500).unwrap();
let stamp2 = UnixTime::new_only_secs(-6);
assert!(stamp0 > stamp1);
assert!(stamp0 > stamp2);
assert!(stamp1 > stamp2);
@ -535,9 +614,9 @@ mod tests {
#[test]
fn test_ord_smaller_neg_numbers() {
let stamp0 = UnixTimestamp::new_only_seconds(-5);
let stamp1 = UnixTimestamp::new(-5, 500).unwrap();
let stamp2 = UnixTimestamp::new_only_seconds(-6);
let stamp0 = UnixTime::new_only_secs(-5);
let stamp1 = UnixTime::new_subsec_millis_checked(-5, 500).unwrap();
let stamp2 = UnixTime::new_only_secs(-6);
assert!(stamp2 < stamp1);
assert!(stamp2 < stamp0);
assert!(stamp1 < stamp0);
@ -548,8 +627,8 @@ mod tests {
#[allow(clippy::nonminimal_bool)]
#[test]
fn test_eq() {
let stamp0 = UnixTimestamp::new(5, 0).unwrap();
let stamp1 = UnixTimestamp::new_only_seconds(5);
let stamp0 = UnixTime::new(5, 0);
let stamp1 = UnixTime::new_only_secs(5);
assert_eq!(stamp0, stamp1);
assert!(stamp0 <= stamp1);
assert!(stamp0 >= stamp1);
@ -559,27 +638,27 @@ mod tests {
#[test]
fn test_addition() {
let mut stamp0 = UnixTimestamp::new_only_seconds(1);
let mut stamp0 = UnixTime::new_only_secs(1);
stamp0 += Duration::from_secs(5);
assert_eq!(stamp0.unix_seconds, 6);
assert_eq!(stamp0.subsecond_millis(), 0);
assert_eq!(stamp0.secs(), 6);
assert_eq!(stamp0.subsec_millis(), 0);
let stamp1 = stamp0 + Duration::from_millis(500);
assert_eq!(stamp1.unix_seconds, 6);
assert_eq!(stamp1.subsecond_millis(), 500);
assert_eq!(stamp1.secs, 6);
assert_eq!(stamp1.subsec_millis(), 500);
}
#[test]
fn test_addition_on_ref() {
let stamp0 = &UnixTimestamp::new(20, 500).unwrap();
let stamp0 = &UnixTime::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);
assert_eq!(stamp1.secs, 23);
assert_eq!(stamp1.subsec_millis(), 0);
}
#[test]
fn test_as_dt() {
let stamp = UnixTimestamp::new_only_seconds(0);
let dt = stamp.as_date_time().unwrap();
let stamp = UnixTime::new_only_secs(0);
let dt = stamp.chrono_date_time().unwrap();
assert_eq!(dt.year(), 1970);
assert_eq!(dt.month(), 1);
assert_eq!(dt.day(), 1);
@ -590,26 +669,26 @@ mod tests {
#[test]
fn test_from_now() {
let stamp_now = UnixTimestamp::from_now().unwrap();
let dt_now = stamp_now.as_date_time().unwrap();
let stamp_now = UnixTime::from_now().unwrap();
let dt_now = stamp_now.chrono_date_time().unwrap();
assert!(dt_now.year() >= 2020);
}
#[test]
fn test_stamp_diff_positive_0() {
let stamp_later = UnixTimestamp::new(2, 0).unwrap();
let stamp_later = UnixTime::new(2, 0);
let StampDiff {
positive_duration,
duration_absolute,
} = stamp_later - UnixTimestamp::new(1, 0).unwrap();
} = stamp_later - UnixTime::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 = UnixTime::new(3, 800 * 1_000_000);
let stamp_earlier = UnixTime::new_subsec_millis_checked(1, 900).unwrap();
let StampDiff {
positive_duration,
duration_absolute,
@ -620,8 +699,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 = UnixTime::new_subsec_millis_checked(3, 800).unwrap();
let stamp_earlier = UnixTime::new_subsec_millis_checked(1, 900).unwrap();
let StampDiff {
positive_duration,
duration_absolute,
@ -632,13 +711,13 @@ mod tests {
#[test]
fn test_addition_spillover() {
let mut stamp0 = UnixTimestamp::new(1, 900).unwrap();
let mut stamp0 = UnixTime::new_subsec_millis_checked(1, 900).unwrap();
stamp0 += Duration::from_millis(100);
assert_eq!(stamp0.unix_seconds, 2);
assert_eq!(stamp0.subsecond_millis(), 0);
assert_eq!(stamp0.secs, 2);
assert_eq!(stamp0.subsec_millis(), 0);
stamp0 += Duration::from_millis(1100);
assert_eq!(stamp0.unix_seconds, 3);
assert_eq!(stamp0.subsecond_millis(), 100);
assert_eq!(stamp0.secs, 3);
assert_eq!(stamp0.subsec_millis(), 100);
}
#[test]
@ -647,4 +726,17 @@ mod tests {
let stamp_error = TimestampError::from(cuc_error);
assert_eq!(stamp_error.to_string(), format!("cuc error: {cuc_error}"));
}
#[test]
#[cfg(feature = "timelib")]
fn test_unix_stamp_as_timelib_datetime() {
let stamp_epoch = UnixTime::EPOCH;
let timelib_dt = stamp_epoch.timelib_date_time().unwrap();
assert_eq!(timelib_dt.year(), 1970);
assert_eq!(timelib_dt.month(), time::Month::January);
assert_eq!(timelib_dt.day(), 1);
assert_eq!(timelib_dt.hour(), 0);
assert_eq!(timelib_dt.minute(), 0);
assert_eq!(timelib_dt.second(), 0);
}
}