Compare commits
21 Commits
v0.10.0
...
85a8eb3f4a
Author | SHA1 | Date | |
---|---|---|---|
85a8eb3f4a
|
|||
3faffd52fc | |||
7476fc8096
|
|||
59c7ece126
|
|||
6f5254bdbd | |||
bd1927c5c2
|
|||
77862868d5
|
|||
ea05a547ac | |||
0ab69b3ddc
|
|||
240f0bc267 | |||
00744a22fc
|
|||
c5aeeec19f
|
|||
d13cd28962 | |||
5641d9007e
|
|||
f39ea2f793 | |||
e4730d4b8f
|
|||
64ea7e609d
|
|||
ebaa6210a4
|
|||
d14f532f62 | |||
6ea18d3715 | |||
6056342334
|
51
CHANGELOG.md
51
CHANGELOG.md
@ -8,6 +8,57 @@ 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`.
|
||||
- Error handling for ECSS and time module is more granular now, with a new
|
||||
`DateBeforeCcsdsEpochError` error and a `DateBeforeCcsdsEpoch` enum variant for both
|
||||
`CdsError` and `CucError`.
|
||||
|
||||
# [v0.11.0-rc.0] 2024-03-04
|
||||
|
||||
## Added
|
||||
|
||||
- `From<$TY>` for the `EcssEnum$TY` ECSS enum type definitions.
|
||||
- `Sub` implementation for `UnixTimestamp` to calculate the duration between two timestamps.
|
||||
|
||||
## Changed
|
||||
|
||||
- `CcsdsTimeProvider` `subsecond_millis` function now returns `u16` instead of `Option<u16>`.
|
||||
- `UnixTimestamp` `subsecond_millis` function now returns `u16` instead of `Option<u16>`.
|
||||
|
||||
# [v0.10.0] 2024-02-17
|
||||
|
||||
## Added
|
||||
|
15
Cargo.toml
15
Cargo.toml
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "spacepackets"
|
||||
version = "0.10.0"
|
||||
version = "0.11.0-rc.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.61"
|
||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -205,13 +205,12 @@ pub trait PusPacket: CcsdsPacket {
|
||||
fn crc16(&self) -> Option<u16>;
|
||||
}
|
||||
|
||||
pub(crate) fn crc_from_raw_data(raw_data: &[u8]) -> Result<u16, PusError> {
|
||||
pub(crate) fn crc_from_raw_data(raw_data: &[u8]) -> Result<u16, ByteConversionError> {
|
||||
if raw_data.len() < 2 {
|
||||
return Err(ByteConversionError::FromSliceTooSmall {
|
||||
found: raw_data.len(),
|
||||
expected: 2,
|
||||
}
|
||||
.into());
|
||||
});
|
||||
}
|
||||
Ok(u16::from_be_bytes(
|
||||
raw_data[raw_data.len() - 2..raw_data.len()]
|
||||
@ -248,13 +247,12 @@ pub(crate) fn user_data_from_raw(
|
||||
current_idx: usize,
|
||||
total_len: usize,
|
||||
slice: &[u8],
|
||||
) -> Result<&[u8], PusError> {
|
||||
) -> Result<&[u8], ByteConversionError> {
|
||||
match current_idx {
|
||||
_ if current_idx > total_len - 2 => Err(ByteConversionError::FromSliceTooSmall {
|
||||
found: total_len - 2,
|
||||
expected: current_idx,
|
||||
}
|
||||
.into()),
|
||||
}),
|
||||
_ => Ok(&slice[current_idx..total_len - 2]),
|
||||
}
|
||||
}
|
||||
@ -360,10 +358,34 @@ impl<TYPE: Debug + Copy + Clone + PartialEq + Eq + ToBeBytes + Into<u64>> EcssEn
|
||||
{
|
||||
}
|
||||
|
||||
pub type EcssEnumU8 = GenericEcssEnumWrapper<u8>;
|
||||
pub type EcssEnumU16 = GenericEcssEnumWrapper<u16>;
|
||||
pub type EcssEnumU32 = GenericEcssEnumWrapper<u32>;
|
||||
pub type EcssEnumU64 = GenericEcssEnumWrapper<u64>;
|
||||
impl<T: Copy + Into<u64>> From<T> for GenericEcssEnumWrapper<T> {
|
||||
fn from(value: T) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! generic_ecss_enum_typedefs_and_from_impls {
|
||||
($($ty:ty => $Enum:ident),*) => {
|
||||
$(
|
||||
pub type $Enum = GenericEcssEnumWrapper<$ty>;
|
||||
|
||||
impl From<$Enum> for $ty {
|
||||
fn from(value: $Enum) -> Self {
|
||||
value.value_typed()
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
// Generates EcssEnum<$TY> type definitions as well as a From<$TY> for EcssEnum<$TY>
|
||||
// implementation.
|
||||
generic_ecss_enum_typedefs_and_from_impls! {
|
||||
u8 => EcssEnumU8,
|
||||
u16 => EcssEnumU16,
|
||||
u32 => EcssEnumU32,
|
||||
u64 => EcssEnumU64
|
||||
}
|
||||
|
||||
/// Generic trait for PUS packet abstractions which can written to a raw slice as their raw
|
||||
/// byte representation. This is especially useful for generic abstractions which depend only
|
||||
@ -406,6 +428,8 @@ mod tests {
|
||||
assert_eq!(buf[1], 1);
|
||||
assert_eq!(my_enum.value(), 1);
|
||||
assert_eq!(my_enum.value_typed(), 1);
|
||||
let enum_as_u8: u8 = my_enum.into();
|
||||
assert_eq!(enum_as_u8, 1);
|
||||
let vec = my_enum.to_vec();
|
||||
assert_eq!(vec, buf[1..2]);
|
||||
}
|
||||
@ -423,6 +447,8 @@ mod tests {
|
||||
assert_eq!(buf[2], 0x2f);
|
||||
assert_eq!(my_enum.value(), 0x1f2f);
|
||||
assert_eq!(my_enum.value_typed(), 0x1f2f);
|
||||
let enum_as_raw: u16 = my_enum.into();
|
||||
assert_eq!(enum_as_raw, 0x1f2f);
|
||||
let vec = my_enum.to_vec();
|
||||
assert_eq!(vec, buf[1..3]);
|
||||
}
|
||||
@ -458,6 +484,8 @@ mod tests {
|
||||
assert_eq!(buf[4], 0x4f);
|
||||
assert_eq!(my_enum.value(), 0x1f2f3f4f);
|
||||
assert_eq!(my_enum.value_typed(), 0x1f2f3f4f);
|
||||
let enum_as_raw: u32 = my_enum.into();
|
||||
assert_eq!(enum_as_raw, 0x1f2f3f4f);
|
||||
let vec = my_enum.to_vec();
|
||||
assert_eq!(vec, buf[1..5]);
|
||||
}
|
||||
@ -494,6 +522,8 @@ mod tests {
|
||||
assert_eq!(buf[7], 0x5f);
|
||||
assert_eq!(my_enum.value(), 0x1f2f3f4f5f);
|
||||
assert_eq!(my_enum.value_typed(), 0x1f2f3f4f5f);
|
||||
let enum_as_raw: u64 = my_enum.into();
|
||||
assert_eq!(enum_as_raw, 0x1f2f3f4f5f);
|
||||
assert_eq!(u64::from_be_bytes(buf), 0x1f2f3f4f5f);
|
||||
let vec = my_enum.to_vec();
|
||||
assert_eq!(vec, buf);
|
||||
|
@ -654,8 +654,7 @@ impl<'raw_data> PusTcCreator<'raw_data> {
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
|
||||
pub fn append_to_vec(&self, vec: &mut Vec<u8>) -> Result<usize, PusError> {
|
||||
pub fn append_to_vec(&self, vec: &mut Vec<u8>) -> usize {
|
||||
let sph_zc = crate::zc::SpHeader::from(self.sp_header);
|
||||
let mut appended_len = PUS_TC_MIN_LEN_WITHOUT_APP_DATA;
|
||||
appended_len += self.app_data.len();
|
||||
@ -668,7 +667,7 @@ impl<'raw_data> PusTcCreator<'raw_data> {
|
||||
let mut digest = CRC_CCITT_FALSE.digest();
|
||||
digest.update(&vec[start_idx..start_idx + appended_len - 2]);
|
||||
vec.extend_from_slice(&digest.finalize().to_be_bytes());
|
||||
Ok(appended_len)
|
||||
appended_len
|
||||
}
|
||||
}
|
||||
|
||||
@ -763,7 +762,8 @@ pub struct PusTcReader<'raw_data> {
|
||||
|
||||
impl<'raw_data> PusTcReader<'raw_data> {
|
||||
/// Create a [PusTcReader] instance from a raw slice. On success, it returns a tuple containing
|
||||
/// the instance and the found byte length of the packet.
|
||||
/// the instance and the found byte length of the packet. This function also performs a CRC
|
||||
/// check and will return an appropriate [PusError] if the check fails.
|
||||
pub fn new(slice: &'raw_data [u8]) -> Result<(Self, usize), PusError> {
|
||||
let raw_data_len = slice.len();
|
||||
if raw_data_len < PUS_TC_MIN_LEN_WITHOUT_APP_DATA {
|
||||
@ -1010,9 +1010,7 @@ mod tests {
|
||||
fn test_vec_ser_deser() {
|
||||
let pus_tc = base_ping_tc_simple_ctor();
|
||||
let mut test_vec = Vec::new();
|
||||
let size = pus_tc
|
||||
.append_to_vec(&mut test_vec)
|
||||
.expect("Error writing TC to vector");
|
||||
let size = pus_tc.append_to_vec(&mut test_vec);
|
||||
assert_eq!(size, 13);
|
||||
verify_test_tc_raw(&test_vec.as_slice());
|
||||
verify_crc_no_app_data(&test_vec.as_slice());
|
||||
@ -1058,9 +1056,7 @@ mod tests {
|
||||
let pus_tc = base_ping_tc_simple_ctor_with_app_data(&[1, 2, 3]);
|
||||
verify_test_tc(&pus_tc, true, 16);
|
||||
let mut test_vec = Vec::new();
|
||||
let size = pus_tc
|
||||
.append_to_vec(&mut test_vec)
|
||||
.expect("Error writing TC to vector");
|
||||
let size = pus_tc.append_to_vec(&mut test_vec);
|
||||
assert_eq!(test_vec[11], 1);
|
||||
assert_eq!(test_vec[12], 2);
|
||||
assert_eq!(test_vec[13], 3);
|
||||
|
@ -660,6 +660,37 @@ impl<'raw_data> PusTmCreator<'raw_data> {
|
||||
self.update_ccsds_data_len();
|
||||
}
|
||||
|
||||
/// Write the raw PUS byte representation to a provided buffer.
|
||||
pub fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, ByteConversionError> {
|
||||
let mut curr_idx = 0;
|
||||
let total_size = self.len_written();
|
||||
if total_size > slice.len() {
|
||||
return Err(ByteConversionError::ToSliceTooSmall {
|
||||
found: slice.len(),
|
||||
expected: total_size,
|
||||
});
|
||||
}
|
||||
self.sp_header
|
||||
.write_to_be_bytes(&mut slice[0..CCSDS_HEADER_LEN])?;
|
||||
curr_idx += CCSDS_HEADER_LEN;
|
||||
let sec_header_len = size_of::<zc::PusTmSecHeaderWithoutTimestamp>();
|
||||
let sec_header = zc::PusTmSecHeaderWithoutTimestamp::try_from(self.sec_header).unwrap();
|
||||
sec_header
|
||||
.write_to_bytes(&mut slice[curr_idx..curr_idx + sec_header_len])
|
||||
.ok_or(ByteConversionError::ZeroCopyToError)?;
|
||||
curr_idx += sec_header_len;
|
||||
slice[curr_idx..curr_idx + self.sec_header.timestamp.len()]
|
||||
.copy_from_slice(self.sec_header.timestamp);
|
||||
curr_idx += self.sec_header.timestamp.len();
|
||||
slice[curr_idx..curr_idx + self.source_data.len()].copy_from_slice(self.source_data);
|
||||
curr_idx += self.source_data.len();
|
||||
let mut digest = CRC_CCITT_FALSE.digest();
|
||||
digest.update(&slice[0..curr_idx]);
|
||||
slice[curr_idx..curr_idx + 2].copy_from_slice(&digest.finalize().to_be_bytes());
|
||||
curr_idx += 2;
|
||||
Ok(curr_idx)
|
||||
}
|
||||
|
||||
/// Append the raw PUS byte representation to a provided [alloc::vec::Vec]
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
|
||||
@ -689,34 +720,7 @@ impl WritablePusPacket for PusTmCreator<'_> {
|
||||
}
|
||||
/// Write the raw PUS byte representation to a provided buffer.
|
||||
fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError> {
|
||||
let mut curr_idx = 0;
|
||||
let total_size = self.len_written();
|
||||
if total_size > slice.len() {
|
||||
return Err(ByteConversionError::ToSliceTooSmall {
|
||||
found: slice.len(),
|
||||
expected: total_size,
|
||||
}
|
||||
.into());
|
||||
}
|
||||
self.sp_header
|
||||
.write_to_be_bytes(&mut slice[0..CCSDS_HEADER_LEN])?;
|
||||
curr_idx += CCSDS_HEADER_LEN;
|
||||
let sec_header_len = size_of::<zc::PusTmSecHeaderWithoutTimestamp>();
|
||||
let sec_header = zc::PusTmSecHeaderWithoutTimestamp::try_from(self.sec_header).unwrap();
|
||||
sec_header
|
||||
.write_to_bytes(&mut slice[curr_idx..curr_idx + sec_header_len])
|
||||
.ok_or(ByteConversionError::ZeroCopyToError)?;
|
||||
curr_idx += sec_header_len;
|
||||
slice[curr_idx..curr_idx + self.sec_header.timestamp.len()]
|
||||
.copy_from_slice(self.sec_header.timestamp);
|
||||
curr_idx += self.sec_header.timestamp.len();
|
||||
slice[curr_idx..curr_idx + self.source_data.len()].copy_from_slice(self.source_data);
|
||||
curr_idx += self.source_data.len();
|
||||
let mut digest = CRC_CCITT_FALSE.digest();
|
||||
digest.update(&slice[0..curr_idx]);
|
||||
slice[curr_idx..curr_idx + 2].copy_from_slice(&digest.finalize().to_be_bytes());
|
||||
curr_idx += 2;
|
||||
Ok(curr_idx)
|
||||
Ok(Self::write_to_bytes(self, slice)?)
|
||||
}
|
||||
}
|
||||
|
||||
@ -788,6 +792,9 @@ impl<'raw_data> PusTmReader<'raw_data> {
|
||||
/// Create a [PusTmReader] instance from a raw slice. On success, it returns a tuple containing
|
||||
/// the instance and the found byte length of the packet. The timestamp length needs to be
|
||||
/// known beforehand.
|
||||
///
|
||||
/// This function will check the CRC-16 of the PUS packet and will return an appropriate
|
||||
/// [PusError] if the check fails.
|
||||
pub fn new(slice: &'raw_data [u8], timestamp_len: usize) -> Result<(Self, usize), PusError> {
|
||||
let raw_data_len = slice.len();
|
||||
if raw_data_len < PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA {
|
||||
@ -1097,7 +1104,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 +1143,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)
|
||||
@ -1260,18 +1267,11 @@ mod tests {
|
||||
let res = pus_tm.write_to_bytes(&mut buf);
|
||||
assert!(res.is_err());
|
||||
let error = res.unwrap_err();
|
||||
assert!(matches!(error, PusError::ByteConversion { .. }));
|
||||
match error {
|
||||
PusError::ByteConversion(err) => match err {
|
||||
ByteConversionError::ToSliceTooSmall { found, expected } => {
|
||||
assert_eq!(expected, 22);
|
||||
assert_eq!(found, 16);
|
||||
}
|
||||
_ => panic!("Invalid PUS error {:?}", err),
|
||||
},
|
||||
_ => {
|
||||
panic!("Invalid error {:?}", error);
|
||||
}
|
||||
if let ByteConversionError::ToSliceTooSmall { found, expected } = error {
|
||||
assert_eq!(expected, 22);
|
||||
assert_eq!(found, 16);
|
||||
} else {
|
||||
panic!("Invalid error {:?}", error);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
@ -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(),
|
||||
|
732
src/time/cds.rs
732
src/time/cds.rs
File diff suppressed because it is too large
Load Diff
905
src/time/cuc.rs
905
src/time/cuc.rs
File diff suppressed because it is too large
Load Diff
459
src/time/mod.rs
459
src/time/mod.rs
@ -1,9 +1,10 @@
|
||||
//! CCSDS Time Code Formats according to [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
|
||||
use crate::ByteConversionError;
|
||||
use chrono::{DateTime, LocalResult, TimeZone, Utc};
|
||||
#[cfg(feature = "chrono")]
|
||||
use chrono::{TimeZone, Utc};
|
||||
use core::cmp::Ordering;
|
||||
use core::fmt::{Display, Formatter};
|
||||
use core::ops::{Add, AddAssign};
|
||||
use core::ops::{Add, AddAssign, Sub};
|
||||
use core::time::Duration;
|
||||
use core::u8;
|
||||
|
||||
@ -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,32 @@ 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))]
|
||||
pub struct DateBeforeCcsdsEpochError(UnixTime);
|
||||
|
||||
impl Display for DateBeforeCcsdsEpochError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "date before ccsds epoch: {:?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl Error for DateBeforeCcsdsEpochError {}
|
||||
|
||||
#[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>),
|
||||
CustomEpochNotSupported,
|
||||
}
|
||||
|
||||
@ -90,9 +105,6 @@ impl Display for TimestampError {
|
||||
TimestampError::ByteConversion(e) => {
|
||||
write!(f, "time stamp: {e}")
|
||||
}
|
||||
TimestampError::DateBeforeCcsdsEpoch(e) => {
|
||||
write!(f, "datetime with date before ccsds epoch: {e}")
|
||||
}
|
||||
TimestampError::CustomEpochNotSupported => {
|
||||
write!(f, "custom epochs are not supported")
|
||||
}
|
||||
@ -111,6 +123,7 @@ impl Error for TimestampError {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cds::CdsError> for TimestampError {
|
||||
fn from(e: cds::CdsError) -> Self {
|
||||
TimestampError::Cds(e)
|
||||
@ -196,6 +209,7 @@ pub trait TimeWriter {
|
||||
fn write_to_bytes(&self, bytes: &mut [u8]) -> Result<usize, TimestampError>;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
|
||||
fn to_vec(&self) -> Result<alloc::vec::Vec<u8>, TimestampError> {
|
||||
let mut vec = alloc::vec![0; self.len_written()];
|
||||
self.write_to_bytes(&mut vec)?;
|
||||
@ -203,16 +217,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;
|
||||
|
||||
@ -221,66 +234,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) -> Option<u16>;
|
||||
fn unix_stamp(&self) -> UnixTimestamp {
|
||||
if self.subsecond_millis().is_none() {
|
||||
return UnixTimestamp::new_only_seconds(self.unix_seconds());
|
||||
}
|
||||
UnixTimestamp::const_new(self.unix_seconds(), self.subsecond_millis().unwrap())
|
||||
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_time(&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. Please note that a
|
||||
/// subsecond millisecond value of 0 gets converted to [None].
|
||||
/// 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: Option<u16>,
|
||||
pub struct UnixTime {
|
||||
secs: i64,
|
||||
subsec_nanos: u32,
|
||||
}
|
||||
|
||||
impl UnixTimestamp {
|
||||
/// Returns none if the subsecond millisecond value is larger than 999. 0 is converted to
|
||||
/// a [None] value.
|
||||
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, subsec_millis: u16) -> Self {
|
||||
if subsec_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 [Self::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");
|
||||
}
|
||||
let subsecond_millis = if subsec_millis == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(subsec_millis)
|
||||
};
|
||||
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 [Self::new_subsec_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: None,
|
||||
secs: unix_seconds,
|
||||
subsec_nanos: subsecond_millis as u32 * 1_000_000,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subsecond_millis(&self) -> Option<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")]
|
||||
@ -288,63 +355,88 @@ 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 {
|
||||
let mut secs = self.unix_seconds as f64;
|
||||
if let Some(subsec_millis) = self.subsecond_millis {
|
||||
secs += subsec_millis as f64 / 1000.0;
|
||||
}
|
||||
secs
|
||||
pub fn 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.unwrap_or(0) 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) -> chrono::LocalResult<chrono::DateTime<chrono::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 diff_in_millis(&self, other: &UnixTime) -> Option<i64> {
|
||||
let seconds_difference = self.secs.checked_sub(other.secs)?;
|
||||
// Convert seconds difference to milliseconds
|
||||
let milliseconds_difference = seconds_difference.checked_mul(1000)?;
|
||||
|
||||
// Calculate the difference in subsecond milliseconds directly
|
||||
let subsecond_difference_nanos = self.subsec_nanos as i64 - other.subsec_nanos as i64;
|
||||
|
||||
// Combine the differences
|
||||
Some(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)
|
||||
#[cfg(feature = "chrono")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "chrono")))]
|
||||
impl From<chrono::DateTime<chrono::Utc>> for UnixTime {
|
||||
fn from(value: chrono::DateTime<chrono::Utc>) -> Self {
|
||||
Self::new(value.timestamp(), value.timestamp_subsec_nanos())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for UnixTimestamp {
|
||||
#[cfg(feature = "timelib")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "timelib")))]
|
||||
impl From<time::OffsetDateTime> for UnixTime {
|
||||
fn from(value: time::OffsetDateTime) -> Self {
|
||||
Self::new(value.unix_timestamp(), value.nanosecond())
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
.unwrap_or(0)
|
||||
.cmp(&other.subsecond_millis().unwrap_or(0))
|
||||
{
|
||||
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
|
||||
@ -356,13 +448,36 @@ impl Ord for UnixTimestamp {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_new_stamp_after_addition(
|
||||
current_stamp: &UnixTimestamp,
|
||||
duration: Duration,
|
||||
) -> UnixTimestamp {
|
||||
let mut new_subsec_millis =
|
||||
current_stamp.subsecond_millis().unwrap_or(0) + duration.subsec_millis() as u16;
|
||||
let mut new_unix_seconds = current_stamp.unix_seconds;
|
||||
/// Difference between two UNIX timestamps. The [Duration] type can not contain negative durations,
|
||||
/// so the sign information is supplied separately.
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub struct StampDiff {
|
||||
pub positive_duration: bool,
|
||||
pub duration_absolute: Duration,
|
||||
}
|
||||
|
||||
impl Sub for UnixTime {
|
||||
type Output = Option<StampDiff>;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
let difference = self.diff_in_millis(&rhs)?;
|
||||
Some(if difference < 0 {
|
||||
StampDiff {
|
||||
positive_duration: false,
|
||||
duration_absolute: Duration::from_millis(-difference as u64),
|
||||
}
|
||||
} else {
|
||||
StampDiff {
|
||||
positive_duration: true,
|
||||
duration_absolute: Duration::from_millis(difference as u64),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@ -374,8 +489,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(
|
||||
@ -384,7 +499,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:
|
||||
@ -392,7 +507,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);
|
||||
}
|
||||
@ -403,7 +518,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 {
|
||||
@ -411,8 +526,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)
|
||||
@ -423,10 +538,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);
|
||||
@ -462,29 +582,29 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn basic_unix_stamp_test() {
|
||||
let stamp = UnixTimestamp::new_only_seconds(-200);
|
||||
assert_eq!(stamp.unix_seconds, -200);
|
||||
assert!(stamp.subsecond_millis().is_none());
|
||||
let stamp = UnixTimestamp::new_only_seconds(250);
|
||||
assert_eq!(stamp.unix_seconds, 250);
|
||||
assert!(stamp.subsecond_millis().is_none());
|
||||
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!(stamp.subsecond_millis.is_some());
|
||||
assert_eq!(stamp.unix_seconds, 500);
|
||||
let subsec_millis = stamp.subsecond_millis().unwrap();
|
||||
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);
|
||||
@ -492,9 +612,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);
|
||||
@ -502,9 +622,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);
|
||||
@ -514,9 +634,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);
|
||||
@ -524,10 +644,11 @@ mod tests {
|
||||
assert!(stamp2 <= stamp1);
|
||||
}
|
||||
|
||||
#[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);
|
||||
@ -537,28 +658,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!(stamp0.subsecond_millis().is_none());
|
||||
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!(stamp1.subsecond_millis().is_some());
|
||||
assert_eq!(stamp1.subsecond_millis().unwrap(), 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!(stamp1.subsecond_millis().is_none());
|
||||
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);
|
||||
@ -569,20 +689,55 @@ 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 = UnixTime::new(2, 0);
|
||||
let StampDiff {
|
||||
positive_duration,
|
||||
duration_absolute,
|
||||
} = (stamp_later - UnixTime::new(1, 0)).expect("stamp diff error");
|
||||
assert!(positive_duration);
|
||||
assert_eq!(duration_absolute, Duration::from_secs(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stamp_diff_positive_1() {
|
||||
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,
|
||||
} = (stamp_later - stamp_earlier).expect("stamp diff error");
|
||||
assert!(positive_duration);
|
||||
assert_eq!(duration_absolute, Duration::from_millis(1900));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stamp_diff_negative() {
|
||||
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,
|
||||
} = (stamp_earlier - stamp_later).expect("stamp diff error");
|
||||
assert!(!positive_duration);
|
||||
assert_eq!(duration_absolute, Duration::from_millis(1900));
|
||||
}
|
||||
|
||||
#[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!(stamp0.subsecond_millis().is_none());
|
||||
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().unwrap(), 100);
|
||||
assert_eq!(stamp0.secs, 3);
|
||||
assert_eq!(stamp0.subsec_millis(), 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -591,4 +746,26 @@ 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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "timelib")]
|
||||
fn test_unix_stamp_from_timelib_datetime() {
|
||||
let timelib_dt = time::OffsetDateTime::UNIX_EPOCH;
|
||||
let unix_time = UnixTime::from(timelib_dt);
|
||||
let timelib_converted_back = unix_time.timelib_date_time().unwrap();
|
||||
assert_eq!(timelib_dt, timelib_converted_back);
|
||||
}
|
||||
}
|
||||
|
@ -77,6 +77,7 @@ pub trait UnsignedEnum {
|
||||
fn value(&self) -> u64;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
|
||||
fn to_vec(&self) -> alloc::vec::Vec<u8> {
|
||||
let mut buf = alloc::vec![0; self.size()];
|
||||
self.write_to_be_bytes(&mut buf).unwrap();
|
||||
|
Reference in New Issue
Block a user