#[cfg(any(
feature = "mapem_2_2_1",
feature = "spatem_2_2_1",
feature = "srem_2_2_1",
feature = "ssem_2_2_1",
))]
#[allow(
clippy::missing_panics_doc,
reason = "unwrap is safe b/c of preconditions"
)]
#[must_use]
pub fn moy_and_dsecond(
time: chrono::DateTime<chrono::Utc>,
) -> (
crate::standards::dsrc_2_2_1::etsi_its_dsrc::MinuteOfTheYear,
crate::standards::dsrc_2_2_1::etsi_its_dsrc::DSecond,
) {
use chrono::{Datelike, Timelike};
let naive_time = time.naive_utc();
let start_of_year = chrono::NaiveDate::from_ymd_opt(naive_time.year(), 1, 1)
.expect("year of ref time suddenly out of range")
.and_time(chrono::NaiveTime::default());
let diff = time.naive_utc() - start_of_year;
#[allow(clippy::cast_possible_truncation, reason = "max of 527040 fits in u32")]
#[allow(clippy::cast_sign_loss, reason = "precondition assures positive value")]
let minutes = diff.num_minutes() as u32;
#[allow(clippy::cast_possible_truncation, reason = "max of 60000 fits in u16")]
let millis = (naive_time.second() * 1000 + naive_time.nanosecond() / 1_000_000) as u16;
let moy = crate::standards::dsrc_2_2_1::etsi_its_dsrc::MinuteOfTheYear(minutes);
let dsec = crate::standards::dsrc_2_2_1::etsi_its_dsrc::DSecond::from_millis(millis)
.expect("DSecond suddenly out of range");
(moy, dsec)
}
#[cfg(any(
feature = "mapem_2_2_1",
feature = "spatem_2_2_1",
feature = "srem_2_2_1",
feature = "ssem_2_2_1",
))]
#[allow(
clippy::missing_panics_doc,
reason = "unwrap is safe b/c of preconditions"
)]
#[must_use]
pub fn time_from_moy_and_dsecond(
moy: &crate::standards::dsrc_2_2_1::etsi_its_dsrc::MinuteOfTheYear,
second: &crate::standards::dsrc_2_2_1::etsi_its_dsrc::DSecond,
year: i32,
) -> chrono::DateTime<chrono::Utc> {
let start_of_year = chrono::NaiveDate::from_ymd_opt(year, 1, 1)
.expect("year of ref time suddenly out of range")
.and_time(chrono::NaiveTime::default());
let time = start_of_year
.checked_add_signed(chrono::TimeDelta::minutes(i64::from(moy.0)))
.expect("Resulting DateTime suddenly out of range")
.checked_add_signed(chrono::TimeDelta::milliseconds(i64::from(second.0)))
.expect("Resulting DateTime suddenly out of range");
time.and_utc()
}
#[cfg(feature = "_etsi")]
macro_rules! timestampits_conv_datetime {
($t:ty) => {
impl From<$t> for chrono::DateTime<chrono::Utc> {
fn from(other: $t) -> Self {
const ITS_EPOCH_UNIX_MS: i64 = 1_072_915_200_000;
#[allow(clippy::cast_possible_wrap, reason = "42 bits fit in i64")]
let its_millis = other.0 as i64 + ITS_EPOCH_UNIX_MS;
let utc_millis = its_millis - i64::from(its_offset_ms(its_millis.cast_unsigned()));
chrono::DateTime::from_timestamp_millis(utc_millis)
.expect("ITS Timestamp suddenly out of range for chrono::DateTime")
}
}
impl From<chrono::DateTime<chrono::Utc>> for $t {
fn from(other: chrono::DateTime<chrono::Utc>) -> $t {
const ITS_EPOCH_UNIX_MS: u64 = 1_072_915_200_000;
#[allow(
clippy::cast_sign_loss,
reason = "expecting positive UNIX time is fine"
)]
let utc_millis = other.timestamp_millis() as u64;
let its_time =
utc_millis - ITS_EPOCH_UNIX_MS + u64::from(its_offset_ms(utc_millis));
Self(its_time)
}
}
};
}
#[cfg(feature = "_etsi")]
fn its_offset_ms(unix_time_ms: u64) -> u16 {
if unix_time_ms >= 1_483_228_800_000 {
5000
} else if unix_time_ms >= 1_435_708_800_000 {
4000
} else if unix_time_ms >= 1_341_100_800_000 {
3000
} else if unix_time_ms >= 1_199_145_600_000 {
2000
} else if unix_time_ms >= 1_136_073_600_000 {
1000
} else {
0
}
}
#[cfg(any(feature = "denm_1_3_1", feature = "ivim_2_1_1"))]
timestampits_conv_datetime!(crate::standards::cdd_1_3_1_1::its_container::TimestampIts);
#[cfg(any(feature = "cpm_2_1_1", feature = "denm_2_2_1", feature = "ivim_2_2_1"))]
timestampits_conv_datetime!(crate::standards::cdd_2_2_1::etsi_its_cdd::TimestampIts);
#[cfg(feature = "_dsrc_2_2_1")]
impl crate::standards::dsrc_2_2_1::etsi_its_dsrc::TimeMark {
#[must_use]
pub fn to_datetime_from_moy(
&self,
moy: &crate::standards::dsrc_2_2_1::etsi_its_dsrc::MinuteOfTheYear,
year: i32,
) -> chrono::DateTime<chrono::Utc> {
let start_of_year = chrono::NaiveDate::from_ymd_opt(year, 1, 1)
.expect("year of ref time suddenly out of range")
.and_time(chrono::NaiveTime::default());
let ref_time = start_of_year
.checked_add_signed(chrono::TimeDelta::minutes(i64::from(moy.0)))
.expect("Resulting DateTime suddenly out of range")
.and_utc();
self.to_datetime_common(ref_time)
}
#[must_use]
pub fn to_datetime_from_timestamp(
&self,
ref_time: &chrono::DateTime<chrono::Utc>,
) -> chrono::DateTime<chrono::Utc> {
use chrono::Timelike;
#[allow(
clippy::unwrap_used,
reason = "0 seconds and nanos are in the input range"
)]
let ref_time = ref_time
.with_second(0)
.and_then(|t| t.with_nanosecond(0))
.unwrap();
self.to_datetime_common(ref_time)
}
fn to_datetime_common(
&self,
ref_time: chrono::DateTime<chrono::Utc>,
) -> chrono::DateTime<chrono::Utc> {
use chrono::Timelike;
if self.is_out_of_range() {
return ref_time
.checked_add_signed(chrono::TimeDelta::hours(1))
.expect("Resulting DateTime suddenly out of range");
}
#[allow(clippy::unwrap_used, reason = "0 minutes is a valid input")]
let current_full_hour = ref_time.with_minute(0).unwrap();
let time_mark_time = current_full_hour
.checked_add_signed(chrono::TimeDelta::milliseconds(self.as_millis().into()))
.expect("Resulting DateTime suddenly out of range");
if time_mark_time < ref_time {
time_mark_time
.checked_add_signed(chrono::TimeDelta::hours(1))
.expect("Resulting DateTime suddenly out of range")
} else {
time_mark_time
}
}
}
#[cfg(all(test, feature = "_etsi"))]
mod tests {
#[test]
#[cfg(any(
feature = "mapem_2_2_1",
feature = "spatem_2_2_1",
feature = "srem_2_2_1",
feature = "ssem_2_2_1",
))]
fn time_to_moy_and_dsecond() {
use crate::time_utils::moy_and_dsecond;
let date = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
.unwrap()
.and_time(chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap())
.and_utc();
let (moy, dsec) = moy_and_dsecond(date);
assert_eq!(0, moy.0);
assert_eq!(0, dsec.0);
let date = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
.unwrap()
.and_time(chrono::NaiveTime::from_hms_opt(0, 42, 23).unwrap())
.and_utc();
let (moy, dsec) = moy_and_dsecond(date);
assert_eq!(42, moy.0);
assert_eq!(23_000, dsec.0);
let date = chrono::NaiveDate::from_ymd_opt(2026, 2, 1)
.unwrap()
.and_time(chrono::NaiveTime::from_hms_opt(0, 0, 42).unwrap())
.and_utc();
let (moy, dsec) = moy_and_dsecond(date);
assert_eq!(31 * 24 * 60, moy.0);
assert_eq!(42_000, dsec.0);
}
#[test]
#[cfg(any(
feature = "mapem_2_2_1",
feature = "spatem_2_2_1",
feature = "srem_2_2_1",
feature = "ssem_2_2_1",
))]
fn moy_and_dsecond_to_time() {
use crate::standards::dsrc_2_2_1::etsi_its_dsrc::{DSecond, MinuteOfTheYear};
use crate::time_utils::time_from_moy_and_dsecond;
let ref_date = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
.unwrap()
.and_time(chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap())
.and_utc();
let date = time_from_moy_and_dsecond(&MinuteOfTheYear(0), &DSecond(0), 2026);
assert_eq!(ref_date, date);
let ref_date = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
.unwrap()
.and_time(chrono::NaiveTime::from_hms_opt(0, 42, 23).unwrap())
.and_utc();
let date = time_from_moy_and_dsecond(&MinuteOfTheYear(42), &DSecond(23_000), 2026);
assert_eq!(ref_date, date);
let ref_date = chrono::NaiveDate::from_ymd_opt(2024, 2, 1)
.unwrap()
.and_time(chrono::NaiveTime::from_hms_opt(0, 0, 42).unwrap())
.and_utc();
let date =
time_from_moy_and_dsecond(&MinuteOfTheYear(31 * 24 * 60), &DSecond(42_000), 2024);
assert_eq!(ref_date, date);
}
#[test]
#[cfg(any(feature = "cpm_2_1_1", feature = "denm_2_2_1", feature = "ivim_2_2_1"))]
fn utc_to_its_timestamp() {
use crate::standards::cdd_2_2_1::etsi_its_cdd::TimestampIts;
let ref_date = chrono::NaiveDate::from_ymd_opt(2007, 1, 1)
.unwrap()
.and_time(chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap())
.and_utc();
let its: TimestampIts = ref_date.into();
assert_eq!(94_694_401_000, its.0);
}
#[test]
#[cfg(any(feature = "cpm_2_1_1", feature = "denm_2_2_1", feature = "ivim_2_2_1"))]
fn its_to_utc_timestamp() {
use crate::standards::cdd_2_2_1::etsi_its_cdd::TimestampIts;
let ref_date = chrono::NaiveDate::from_ymd_opt(2007, 1, 1)
.unwrap()
.and_time(chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap())
.and_utc();
let utc: chrono::DateTime<chrono::Utc> = TimestampIts(94_694_401_000).into();
assert_eq!(ref_date, utc);
}
#[test]
#[cfg(feature = "_dsrc_2_2_1")]
fn timemark_from_moy() {
use crate::standards::dsrc_2_2_1::etsi_its_dsrc::{MinuteOfTheYear, TimeMark};
let moy = MinuteOfTheYear(12 * 60 + 10);
let expected = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
.unwrap()
.and_time(chrono::NaiveTime::from_hms_opt(12, 10, 15).unwrap())
.and_utc();
let test_val_millis = (10 * 60 + 15) * 1000;
let time_mark = TimeMark::from_millis(test_val_millis).unwrap();
let res = time_mark.to_datetime_from_moy(&moy, 2026);
assert_eq!(expected, res);
let expected = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
.unwrap()
.and_time(chrono::NaiveTime::from_hms_opt(13, 9, 55).unwrap())
.and_utc();
let test_val_millis = (9 * 60 + 55) * 1000;
let time_mark = TimeMark::from_millis(test_val_millis).unwrap();
let res = time_mark.to_datetime_from_moy(&moy, 2026);
assert_eq!(expected, res);
let expected = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
.unwrap()
.and_time(chrono::NaiveTime::from_hms_opt(13, 10, 00).unwrap())
.and_utc();
let time_mark = TimeMark::out_of_range();
let res = time_mark.to_datetime_from_moy(&moy, 2026);
assert_eq!(expected, res);
}
#[test]
#[cfg(feature = "_dsrc_2_2_1")]
fn timemark_from_time() {
use crate::standards::dsrc_2_2_1::etsi_its_dsrc::TimeMark;
let ref_time = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
.unwrap()
.and_time(chrono::NaiveTime::from_hms_opt(12, 10, 15).unwrap())
.and_utc();
let expected = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
.unwrap()
.and_time(chrono::NaiveTime::from_hms_opt(12, 10, 15).unwrap())
.and_utc();
let test_val_millis = (10 * 60 + 15) * 1000;
let time_mark = TimeMark::from_millis(test_val_millis).unwrap();
let res = time_mark.to_datetime_from_timestamp(&ref_time);
assert_eq!(expected, res);
let expected = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
.unwrap()
.and_time(chrono::NaiveTime::from_hms_opt(13, 9, 55).unwrap())
.and_utc();
let test_val_millis = (9 * 60 + 55) * 1000;
let time_mark = TimeMark::from_millis(test_val_millis).unwrap();
let res = time_mark.to_datetime_from_timestamp(&ref_time);
assert_eq!(expected, res);
let expected = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
.unwrap()
.and_time(chrono::NaiveTime::from_hms_opt(12, 10, 10).unwrap())
.and_utc();
let test_val_millis = (10 * 60 + 10) * 1000;
let time_mark = TimeMark::from_millis(test_val_millis).unwrap();
let res = time_mark.to_datetime_from_timestamp(&ref_time);
assert_eq!(expected, res);
}
}