1#[cfg(any(
10 feature = "mapem_2_2_1",
11 feature = "spatem_2_2_1",
12 feature = "srem_2_2_1",
13 feature = "ssem_2_2_1",
14))]
15#[allow(
16 clippy::missing_panics_doc,
17 reason = "unwrap is safe b/c of preconditions"
18)]
19#[must_use]
22pub fn moy_and_dsecond(
23 time: chrono::DateTime<chrono::Utc>,
24) -> (
25 crate::standards::dsrc_2_2_1::etsi_its_dsrc::MinuteOfTheYear,
26 crate::standards::dsrc_2_2_1::etsi_its_dsrc::DSecond,
27) {
28 use chrono::{Datelike, Timelike};
29
30 let naive_time = time.naive_utc();
32 let start_of_year = chrono::NaiveDate::from_ymd_opt(naive_time.year(), 1, 1)
33 .expect("year of ref time suddenly out of range")
34 .and_time(chrono::NaiveTime::default());
35
36 let diff = time.naive_utc() - start_of_year;
38 #[allow(clippy::cast_possible_truncation, reason = "max of 527040 fits in u32")]
39 #[allow(clippy::cast_sign_loss, reason = "precondition assures positive value")]
40 let minutes = diff.num_minutes() as u32;
41
42 #[allow(clippy::cast_possible_truncation, reason = "max of 60000 fits in u16")]
43 let millis = (naive_time.second() * 1000 + naive_time.nanosecond() / 1_000_000) as u16;
44
45 let moy = crate::standards::dsrc_2_2_1::etsi_its_dsrc::MinuteOfTheYear(minutes);
46 let dsec = crate::standards::dsrc_2_2_1::etsi_its_dsrc::DSecond::from_millis(millis)
47 .expect("DSecond suddenly out of range");
48 (moy, dsec)
49}
50
51#[cfg(any(
52 feature = "mapem_2_2_1",
53 feature = "spatem_2_2_1",
54 feature = "srem_2_2_1",
55 feature = "ssem_2_2_1",
56))]
57#[allow(
58 clippy::missing_panics_doc,
59 reason = "unwrap is safe b/c of preconditions"
60)]
61#[must_use]
64pub fn time_from_moy_and_dsecond(
65 moy: &crate::standards::dsrc_2_2_1::etsi_its_dsrc::MinuteOfTheYear,
66 second: &crate::standards::dsrc_2_2_1::etsi_its_dsrc::DSecond,
67 year: i32,
68) -> chrono::DateTime<chrono::Utc> {
69 let start_of_year = chrono::NaiveDate::from_ymd_opt(year, 1, 1)
71 .expect("year of ref time suddenly out of range")
72 .and_time(chrono::NaiveTime::default());
73
74 let time = start_of_year
76 .checked_add_signed(chrono::TimeDelta::minutes(i64::from(moy.0)))
77 .expect("Resulting DateTime suddenly out of range")
78 .checked_add_signed(chrono::TimeDelta::milliseconds(i64::from(second.0)))
79 .expect("Resulting DateTime suddenly out of range");
80
81 time.and_utc()
82}
83
84#[cfg(feature = "_etsi")]
92macro_rules! timestampits_conv_datetime {
93 ($t:ty) => {
94 impl From<$t> for chrono::DateTime<chrono::Utc> {
95 fn from(other: $t) -> Self {
96 const ITS_EPOCH_UNIX_MS: i64 = 1_072_915_200_000; #[allow(clippy::cast_possible_wrap, reason = "42 bits fit in i64")]
99 let its_millis = other.0 as i64 + ITS_EPOCH_UNIX_MS;
100 let utc_millis = its_millis - i64::from(its_offset_ms(its_millis.cast_unsigned()));
105
106 chrono::DateTime::from_timestamp_millis(utc_millis)
107 .expect("ITS Timestamp suddenly out of range for chrono::DateTime")
108 }
109 }
110
111 impl From<chrono::DateTime<chrono::Utc>> for $t {
112 fn from(other: chrono::DateTime<chrono::Utc>) -> $t {
113 const ITS_EPOCH_UNIX_MS: u64 = 1_072_915_200_000; #[allow(
116 clippy::cast_sign_loss,
117 reason = "expecting positive UNIX time is fine"
118 )]
119 let utc_millis = other.timestamp_millis() as u64;
120 let its_time =
121 utc_millis - ITS_EPOCH_UNIX_MS + u64::from(its_offset_ms(utc_millis));
122
123 Self(its_time)
124 }
125 }
126 };
127}
128
129#[cfg(feature = "_etsi")]
130fn its_offset_ms(unix_time_ms: u64) -> u16 {
131 if unix_time_ms >= 1_483_228_800_000 {
132 5000
134 } else if unix_time_ms >= 1_435_708_800_000 {
135 4000
137 } else if unix_time_ms >= 1_341_100_800_000 {
138 3000
140 } else if unix_time_ms >= 1_199_145_600_000 {
141 2000
143 } else if unix_time_ms >= 1_136_073_600_000 {
144 1000
146 } else {
147 0
148 }
149}
150
151#[cfg(any(feature = "denm_1_3_1", feature = "ivim_2_1_1"))]
153timestampits_conv_datetime!(crate::standards::cdd_1_3_1_1::its_container::TimestampIts);
154
155#[cfg(any(feature = "cpm_2_1_1", feature = "denm_2_2_1", feature = "ivim_2_2_1"))]
157timestampits_conv_datetime!(crate::standards::cdd_2_2_1::etsi_its_cdd::TimestampIts);
158
159#[cfg(feature = "_dsrc_2_2_1")]
161impl crate::standards::dsrc_2_2_1::etsi_its_dsrc::TimeMark {
162 #[must_use]
169 pub fn to_datetime_from_moy(
170 &self,
171 moy: &crate::standards::dsrc_2_2_1::etsi_its_dsrc::MinuteOfTheYear,
172 year: i32,
173 ) -> chrono::DateTime<chrono::Utc> {
174 let start_of_year = chrono::NaiveDate::from_ymd_opt(year, 1, 1)
176 .expect("year of ref time suddenly out of range")
177 .and_time(chrono::NaiveTime::default());
178 let ref_time = start_of_year
179 .checked_add_signed(chrono::TimeDelta::minutes(i64::from(moy.0)))
180 .expect("Resulting DateTime suddenly out of range")
181 .and_utc();
182
183 self.to_datetime_common(ref_time)
184 }
185
186 #[must_use]
193 pub fn to_datetime_from_timestamp(
194 &self,
195 ref_time: &chrono::DateTime<chrono::Utc>,
196 ) -> chrono::DateTime<chrono::Utc> {
197 use chrono::Timelike;
198
199 #[allow(
201 clippy::unwrap_used,
202 reason = "0 seconds and nanos are in the input range"
203 )]
204 let ref_time = ref_time
205 .with_second(0)
206 .and_then(|t| t.with_nanosecond(0))
207 .unwrap();
208
209 self.to_datetime_common(ref_time)
210 }
211
212 fn to_datetime_common(
216 &self,
217 ref_time: chrono::DateTime<chrono::Utc>,
218 ) -> chrono::DateTime<chrono::Utc> {
219 use chrono::Timelike;
220
221 if self.is_out_of_range() {
223 return ref_time
224 .checked_add_signed(chrono::TimeDelta::hours(1))
225 .expect("Resulting DateTime suddenly out of range");
226 }
227
228 #[allow(clippy::unwrap_used, reason = "0 minutes is a valid input")]
230 let current_full_hour = ref_time.with_minute(0).unwrap();
231 let time_mark_time = current_full_hour
232 .checked_add_signed(chrono::TimeDelta::milliseconds(self.as_millis().into()))
233 .expect("Resulting DateTime suddenly out of range");
234
235 if time_mark_time < ref_time {
239 time_mark_time
240 .checked_add_signed(chrono::TimeDelta::hours(1))
241 .expect("Resulting DateTime suddenly out of range")
242 } else {
243 time_mark_time
244 }
245 }
246}
247#[cfg(all(test, feature = "_etsi"))]
250mod tests {
251
252 #[test]
253 #[cfg(any(
254 feature = "mapem_2_2_1",
255 feature = "spatem_2_2_1",
256 feature = "srem_2_2_1",
257 feature = "ssem_2_2_1",
258 ))]
259 fn time_to_moy_and_dsecond() {
260 use crate::time_utils::moy_and_dsecond;
261
262 let date = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
264 .unwrap()
265 .and_time(chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap())
266 .and_utc();
267 let (moy, dsec) = moy_and_dsecond(date);
268
269 assert_eq!(0, moy.0);
270 assert_eq!(0, dsec.0);
271
272 let date = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
274 .unwrap()
275 .and_time(chrono::NaiveTime::from_hms_opt(0, 42, 23).unwrap())
276 .and_utc();
277 let (moy, dsec) = moy_and_dsecond(date);
278
279 assert_eq!(42, moy.0);
280 assert_eq!(23_000, dsec.0);
281
282 let date = chrono::NaiveDate::from_ymd_opt(2026, 2, 1)
284 .unwrap()
285 .and_time(chrono::NaiveTime::from_hms_opt(0, 0, 42).unwrap())
286 .and_utc();
287 let (moy, dsec) = moy_and_dsecond(date);
288
289 assert_eq!(31 * 24 * 60, moy.0);
290 assert_eq!(42_000, dsec.0);
291 }
292
293 #[test]
294 #[cfg(any(
295 feature = "mapem_2_2_1",
296 feature = "spatem_2_2_1",
297 feature = "srem_2_2_1",
298 feature = "ssem_2_2_1",
299 ))]
300 fn moy_and_dsecond_to_time() {
301 use crate::standards::dsrc_2_2_1::etsi_its_dsrc::{DSecond, MinuteOfTheYear};
302 use crate::time_utils::time_from_moy_and_dsecond;
303
304 let ref_date = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
306 .unwrap()
307 .and_time(chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap())
308 .and_utc();
309
310 let date = time_from_moy_and_dsecond(&MinuteOfTheYear(0), &DSecond(0), 2026);
311 assert_eq!(ref_date, date);
312
313 let ref_date = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
315 .unwrap()
316 .and_time(chrono::NaiveTime::from_hms_opt(0, 42, 23).unwrap())
317 .and_utc();
318
319 let date = time_from_moy_and_dsecond(&MinuteOfTheYear(42), &DSecond(23_000), 2026);
320 assert_eq!(ref_date, date);
321
322 let ref_date = chrono::NaiveDate::from_ymd_opt(2024, 2, 1)
324 .unwrap()
325 .and_time(chrono::NaiveTime::from_hms_opt(0, 0, 42).unwrap())
326 .and_utc();
327
328 let date =
329 time_from_moy_and_dsecond(&MinuteOfTheYear(31 * 24 * 60), &DSecond(42_000), 2024);
330 assert_eq!(ref_date, date);
331 }
332
333 #[test]
334 #[cfg(any(feature = "cpm_2_1_1", feature = "denm_2_2_1", feature = "ivim_2_2_1"))]
335 fn utc_to_its_timestamp() {
336 use crate::standards::cdd_2_2_1::etsi_its_cdd::TimestampIts;
337
338 let ref_date = chrono::NaiveDate::from_ymd_opt(2007, 1, 1)
340 .unwrap()
341 .and_time(chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap())
342 .and_utc();
343
344 let its: TimestampIts = ref_date.into();
345 assert_eq!(94_694_401_000, its.0);
346 }
347
348 #[test]
349 #[cfg(any(feature = "cpm_2_1_1", feature = "denm_2_2_1", feature = "ivim_2_2_1"))]
350 fn its_to_utc_timestamp() {
351 use crate::standards::cdd_2_2_1::etsi_its_cdd::TimestampIts;
352
353 let ref_date = chrono::NaiveDate::from_ymd_opt(2007, 1, 1)
355 .unwrap()
356 .and_time(chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap())
357 .and_utc();
358
359 let utc: chrono::DateTime<chrono::Utc> = TimestampIts(94_694_401_000).into();
360 assert_eq!(ref_date, utc);
361 }
362
363 #[test]
364 #[cfg(feature = "_dsrc_2_2_1")]
365 fn timemark_from_moy() {
366 use crate::standards::dsrc_2_2_1::etsi_its_dsrc::{MinuteOfTheYear, TimeMark};
367
368 let moy = MinuteOfTheYear(12 * 60 + 10);
370
371 let expected = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
373 .unwrap()
374 .and_time(chrono::NaiveTime::from_hms_opt(12, 10, 15).unwrap())
375 .and_utc();
376 let test_val_millis = (10 * 60 + 15) * 1000;
377 let time_mark = TimeMark::from_millis(test_val_millis).unwrap();
378
379 let res = time_mark.to_datetime_from_moy(&moy, 2026);
380 assert_eq!(expected, res);
381
382 let expected = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
384 .unwrap()
385 .and_time(chrono::NaiveTime::from_hms_opt(13, 9, 55).unwrap())
386 .and_utc();
387 let test_val_millis = (9 * 60 + 55) * 1000;
388 let time_mark = TimeMark::from_millis(test_val_millis).unwrap();
389
390 let res = time_mark.to_datetime_from_moy(&moy, 2026);
391 assert_eq!(expected, res);
392
393 let expected = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
395 .unwrap()
396 .and_time(chrono::NaiveTime::from_hms_opt(13, 10, 00).unwrap())
397 .and_utc();
398 let time_mark = TimeMark::out_of_range();
399
400 let res = time_mark.to_datetime_from_moy(&moy, 2026);
401 assert_eq!(expected, res);
402 }
403
404 #[test]
405 #[cfg(feature = "_dsrc_2_2_1")]
406 fn timemark_from_time() {
407 use crate::standards::dsrc_2_2_1::etsi_its_dsrc::TimeMark;
408
409 let ref_time = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
411 .unwrap()
412 .and_time(chrono::NaiveTime::from_hms_opt(12, 10, 15).unwrap())
413 .and_utc();
414
415 let expected = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
417 .unwrap()
418 .and_time(chrono::NaiveTime::from_hms_opt(12, 10, 15).unwrap())
419 .and_utc();
420 let test_val_millis = (10 * 60 + 15) * 1000;
421 let time_mark = TimeMark::from_millis(test_val_millis).unwrap();
422
423 let res = time_mark.to_datetime_from_timestamp(&ref_time);
424 assert_eq!(expected, res);
425
426 let expected = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
428 .unwrap()
429 .and_time(chrono::NaiveTime::from_hms_opt(13, 9, 55).unwrap())
430 .and_utc();
431 let test_val_millis = (9 * 60 + 55) * 1000;
432 let time_mark = TimeMark::from_millis(test_val_millis).unwrap();
433
434 let res = time_mark.to_datetime_from_timestamp(&ref_time);
435 assert_eq!(expected, res);
436
437 let expected = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
439 .unwrap()
440 .and_time(chrono::NaiveTime::from_hms_opt(12, 10, 10).unwrap())
441 .and_utc();
442 let test_val_millis = (10 * 60 + 10) * 1000;
443 let time_mark = TimeMark::from_millis(test_val_millis).unwrap();
444
445 let res = time_mark.to_datetime_from_timestamp(&ref_time);
446 assert_eq!(expected, res);
447 }
448}