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
84macro_rules! timestampits_conv_datetime {
92 ($t:ty) => {
93 impl From<$t> for chrono::DateTime<chrono::Utc> {
94 fn from(other: $t) -> Self {
95 const ITS_EPOCH_UNIX_MS: i64 = 1_072_915_200_000; #[allow(clippy::cast_possible_wrap, reason = "42 bits fit in i64")]
98 let its_millis = other.0 as i64 + ITS_EPOCH_UNIX_MS;
99 let utc_millis = its_millis - i64::from(its_offset_ms(its_millis.cast_unsigned()));
104
105 chrono::DateTime::from_timestamp_millis(utc_millis)
106 .expect("ITS Timestamp suddenly out of range for chrono::DateTime")
107 }
108 }
109
110 impl From<chrono::DateTime<chrono::Utc>> for $t {
111 fn from(other: chrono::DateTime<chrono::Utc>) -> $t {
112 const ITS_EPOCH_UNIX_MS: u64 = 1_072_915_200_000; #[allow(
115 clippy::cast_sign_loss,
116 reason = "expecting positive UNIX time is fine"
117 )]
118 let utc_millis = other.timestamp_millis() as u64;
119 let its_time =
120 utc_millis - ITS_EPOCH_UNIX_MS + u64::from(its_offset_ms(utc_millis));
121
122 Self(its_time)
123 }
124 }
125 };
126}
127
128fn its_offset_ms(unix_time_ms: u64) -> u16 {
129 if unix_time_ms >= 1_483_228_800_000 {
130 5000
132 } else if unix_time_ms >= 1_435_708_800_000 {
133 4000
135 } else if unix_time_ms >= 1_341_100_800_000 {
136 3000
138 } else if unix_time_ms >= 1_199_145_600_000 {
139 2000
141 } else if unix_time_ms >= 1_136_073_600_000 {
142 1000
144 } else {
145 0
146 }
147}
148
149#[cfg(any(feature = "denm_1_3_1", feature = "ivim_2_1_1"))]
151timestampits_conv_datetime!(crate::standards::cdd_1_3_1_1::its_container::TimestampIts);
152
153#[cfg(any(feature = "cpm_2_1_1", feature = "denm_2_2_1", feature = "ivim_2_2_1"))]
155timestampits_conv_datetime!(crate::standards::cdd_2_2_1::etsi_its_cdd::TimestampIts);
156
157pub struct TimestampIts(pub u64);
159timestampits_conv_datetime!(TimestampIts);
160
161#[cfg(feature = "_dsrc_2_2_1")]
163impl crate::standards::dsrc_2_2_1::etsi_its_dsrc::TimeMark {
164 #[must_use]
171 pub fn to_datetime_from_moy(
172 &self,
173 moy: &crate::standards::dsrc_2_2_1::etsi_its_dsrc::MinuteOfTheYear,
174 year: i32,
175 ) -> chrono::DateTime<chrono::Utc> {
176 let start_of_year = chrono::NaiveDate::from_ymd_opt(year, 1, 1)
178 .expect("year of ref time suddenly out of range")
179 .and_time(chrono::NaiveTime::default());
180 let ref_time = start_of_year
181 .checked_add_signed(chrono::TimeDelta::minutes(i64::from(moy.0)))
182 .expect("Resulting DateTime suddenly out of range")
183 .and_utc();
184
185 self.to_datetime_common(ref_time)
186 }
187
188 #[must_use]
195 pub fn to_datetime_from_timestamp(
196 &self,
197 ref_time: &chrono::DateTime<chrono::Utc>,
198 ) -> chrono::DateTime<chrono::Utc> {
199 use chrono::Timelike;
200
201 #[allow(
203 clippy::unwrap_used,
204 reason = "0 seconds and nanos are in the input range"
205 )]
206 let ref_time = ref_time
207 .with_second(0)
208 .and_then(|t| t.with_nanosecond(0))
209 .unwrap();
210
211 self.to_datetime_common(ref_time)
212 }
213
214 fn to_datetime_common(
218 &self,
219 ref_time: chrono::DateTime<chrono::Utc>,
220 ) -> chrono::DateTime<chrono::Utc> {
221 use chrono::Timelike;
222
223 if self.is_out_of_range() {
225 return ref_time
226 .checked_add_signed(chrono::TimeDelta::hours(1))
227 .expect("Resulting DateTime suddenly out of range");
228 }
229
230 #[allow(clippy::unwrap_used, reason = "0 minutes is a valid input")]
232 let current_full_hour = ref_time.with_minute(0).unwrap();
233 let time_mark_time = current_full_hour
234 .checked_add_signed(chrono::TimeDelta::milliseconds(self.as_millis().into()))
235 .expect("Resulting DateTime suddenly out of range");
236
237 if time_mark_time < ref_time {
241 time_mark_time
242 .checked_add_signed(chrono::TimeDelta::hours(1))
243 .expect("Resulting DateTime suddenly out of range")
244 } else {
245 time_mark_time
246 }
247 }
248}
249#[cfg(all(test, feature = "_etsi"))]
252mod tests {
253
254 #[test]
255 #[cfg(any(
256 feature = "mapem_2_2_1",
257 feature = "spatem_2_2_1",
258 feature = "srem_2_2_1",
259 feature = "ssem_2_2_1",
260 ))]
261 fn time_to_moy_and_dsecond() {
262 use crate::time_utils::moy_and_dsecond;
263
264 let date = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
266 .unwrap()
267 .and_time(chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap())
268 .and_utc();
269 let (moy, dsec) = moy_and_dsecond(date);
270
271 assert_eq!(0, moy.0);
272 assert_eq!(0, dsec.0);
273
274 let date = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
276 .unwrap()
277 .and_time(chrono::NaiveTime::from_hms_opt(0, 42, 23).unwrap())
278 .and_utc();
279 let (moy, dsec) = moy_and_dsecond(date);
280
281 assert_eq!(42, moy.0);
282 assert_eq!(23_000, dsec.0);
283
284 let date = chrono::NaiveDate::from_ymd_opt(2026, 2, 1)
286 .unwrap()
287 .and_time(chrono::NaiveTime::from_hms_opt(0, 0, 42).unwrap())
288 .and_utc();
289 let (moy, dsec) = moy_and_dsecond(date);
290
291 assert_eq!(31 * 24 * 60, moy.0);
292 assert_eq!(42_000, dsec.0);
293 }
294
295 #[test]
296 #[cfg(any(
297 feature = "mapem_2_2_1",
298 feature = "spatem_2_2_1",
299 feature = "srem_2_2_1",
300 feature = "ssem_2_2_1",
301 ))]
302 fn moy_and_dsecond_to_time() {
303 use crate::standards::dsrc_2_2_1::etsi_its_dsrc::{DSecond, MinuteOfTheYear};
304 use crate::time_utils::time_from_moy_and_dsecond;
305
306 let ref_date = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
308 .unwrap()
309 .and_time(chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap())
310 .and_utc();
311
312 let date = time_from_moy_and_dsecond(&MinuteOfTheYear(0), &DSecond(0), 2026);
313 assert_eq!(ref_date, date);
314
315 let ref_date = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
317 .unwrap()
318 .and_time(chrono::NaiveTime::from_hms_opt(0, 42, 23).unwrap())
319 .and_utc();
320
321 let date = time_from_moy_and_dsecond(&MinuteOfTheYear(42), &DSecond(23_000), 2026);
322 assert_eq!(ref_date, date);
323
324 let ref_date = chrono::NaiveDate::from_ymd_opt(2024, 2, 1)
326 .unwrap()
327 .and_time(chrono::NaiveTime::from_hms_opt(0, 0, 42).unwrap())
328 .and_utc();
329
330 let date =
331 time_from_moy_and_dsecond(&MinuteOfTheYear(31 * 24 * 60), &DSecond(42_000), 2024);
332 assert_eq!(ref_date, date);
333 }
334
335 #[test]
336 #[cfg(any(feature = "cpm_2_1_1", feature = "denm_2_2_1", feature = "ivim_2_2_1"))]
337 fn utc_to_its_timestamp() {
338 use crate::standards::cdd_2_2_1::etsi_its_cdd::TimestampIts;
339
340 let ref_date = chrono::NaiveDate::from_ymd_opt(2007, 1, 1)
342 .unwrap()
343 .and_time(chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap())
344 .and_utc();
345
346 let its: TimestampIts = ref_date.into();
347 assert_eq!(94_694_401_000, its.0);
348 }
349
350 #[test]
351 #[cfg(any(feature = "cpm_2_1_1", feature = "denm_2_2_1", feature = "ivim_2_2_1"))]
352 fn its_to_utc_timestamp() {
353 use crate::standards::cdd_2_2_1::etsi_its_cdd::TimestampIts;
354
355 let ref_date = chrono::NaiveDate::from_ymd_opt(2007, 1, 1)
357 .unwrap()
358 .and_time(chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap())
359 .and_utc();
360
361 let utc: chrono::DateTime<chrono::Utc> = TimestampIts(94_694_401_000).into();
362 assert_eq!(ref_date, utc);
363 }
364
365 #[test]
366 #[cfg(feature = "_dsrc_2_2_1")]
367 fn timemark_from_moy() {
368 use crate::standards::dsrc_2_2_1::etsi_its_dsrc::{MinuteOfTheYear, TimeMark};
369
370 let moy = MinuteOfTheYear(12 * 60 + 10);
372
373 let expected = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
375 .unwrap()
376 .and_time(chrono::NaiveTime::from_hms_opt(12, 10, 15).unwrap())
377 .and_utc();
378 let test_val_millis = (10 * 60 + 15) * 1000;
379 let time_mark = TimeMark::from_millis(test_val_millis).unwrap();
380
381 let res = time_mark.to_datetime_from_moy(&moy, 2026);
382 assert_eq!(expected, res);
383
384 let expected = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
386 .unwrap()
387 .and_time(chrono::NaiveTime::from_hms_opt(13, 9, 55).unwrap())
388 .and_utc();
389 let test_val_millis = (9 * 60 + 55) * 1000;
390 let time_mark = TimeMark::from_millis(test_val_millis).unwrap();
391
392 let res = time_mark.to_datetime_from_moy(&moy, 2026);
393 assert_eq!(expected, res);
394
395 let expected = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
397 .unwrap()
398 .and_time(chrono::NaiveTime::from_hms_opt(13, 10, 00).unwrap())
399 .and_utc();
400 let time_mark = TimeMark::out_of_range();
401
402 let res = time_mark.to_datetime_from_moy(&moy, 2026);
403 assert_eq!(expected, res);
404 }
405
406 #[test]
407 #[cfg(feature = "_dsrc_2_2_1")]
408 fn timemark_from_time() {
409 use crate::standards::dsrc_2_2_1::etsi_its_dsrc::TimeMark;
410
411 let ref_time = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
413 .unwrap()
414 .and_time(chrono::NaiveTime::from_hms_opt(12, 10, 15).unwrap())
415 .and_utc();
416
417 let expected = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
419 .unwrap()
420 .and_time(chrono::NaiveTime::from_hms_opt(12, 10, 15).unwrap())
421 .and_utc();
422 let test_val_millis = (10 * 60 + 15) * 1000;
423 let time_mark = TimeMark::from_millis(test_val_millis).unwrap();
424
425 let res = time_mark.to_datetime_from_timestamp(&ref_time);
426 assert_eq!(expected, res);
427
428 let expected = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
430 .unwrap()
431 .and_time(chrono::NaiveTime::from_hms_opt(13, 9, 55).unwrap())
432 .and_utc();
433 let test_val_millis = (9 * 60 + 55) * 1000;
434 let time_mark = TimeMark::from_millis(test_val_millis).unwrap();
435
436 let res = time_mark.to_datetime_from_timestamp(&ref_time);
437 assert_eq!(expected, res);
438
439 let expected = chrono::NaiveDate::from_ymd_opt(2026, 1, 1)
441 .unwrap()
442 .and_time(chrono::NaiveTime::from_hms_opt(12, 10, 10).unwrap())
443 .and_utc();
444 let test_val_millis = (10 * 60 + 10) * 1000;
445 let time_mark = TimeMark::from_millis(test_val_millis).unwrap();
446
447 let res = time_mark.to_datetime_from_timestamp(&ref_time);
448 assert_eq!(expected, res);
449 }
450}