1use std::fmt;
2
3#[cfg(feature = "time-calculation")]
4use chrono::{DateTime, LocalResult, TimeDelta, TimeZone, Utc};
5
6#[cfg(feature = "time-calculation")]
7use crate::codetables::grib2::Table4_4;
8use crate::{Code, ForecastTime, codetables::grib2::Table1_2};
9
10#[derive(Debug, PartialEq, Eq)]
11pub struct TemporalRawInfo {
13 pub ref_time_significance: Code<Table1_2, u8>,
16 pub ref_time_unchecked: crate::def::grib2::RefTime,
18 pub forecast_time_diff: Option<ForecastTime>,
20}
21
22impl TemporalRawInfo {
23 pub(crate) fn new(
24 ref_time_significance: u8,
25 ref_time_unchecked: crate::def::grib2::RefTime,
26 forecast_time_diff: Option<ForecastTime>,
27 ) -> Self {
28 let ref_time_significance = Table1_2::try_from(ref_time_significance).into();
29 Self {
30 ref_time_significance,
31 ref_time_unchecked,
32 forecast_time_diff,
33 }
34 }
35}
36
37#[cfg(feature = "time-calculation")]
38#[cfg_attr(docsrs, doc(cfg(feature = "time-calculation")))]
39#[derive(Debug, PartialEq, Eq)]
40pub struct TemporalInfo {
42 pub ref_time: Option<DateTime<Utc>>,
44 pub forecast_time_target: Option<DateTime<Utc>>,
46}
47
48#[cfg(feature = "time-calculation")]
49impl From<&TemporalRawInfo> for TemporalInfo {
50 fn from(value: &TemporalRawInfo) -> Self {
51 let ref_time = create_date_time(
52 value.ref_time_unchecked.year.into(),
53 value.ref_time_unchecked.month.into(),
54 value.ref_time_unchecked.day.into(),
55 value.ref_time_unchecked.hour.into(),
56 value.ref_time_unchecked.minute.into(),
57 value.ref_time_unchecked.second.into(),
58 );
59 let forecast_time_delta = value
60 .forecast_time_diff
61 .as_ref()
62 .and_then(|ft| BasicTimeDelta::new(ft).and_then(|td| td.convert(ft)));
63 let forecast_time_target = ref_time
64 .zip(forecast_time_delta)
65 .map(|(time, delta)| time + delta);
66 Self {
67 ref_time,
68 forecast_time_target,
69 }
70 }
71}
72
73impl crate::def::grib2::RefTime {
74 pub fn new(year: u16, month: u8, day: u8, hour: u8, minute: u8, second: u8) -> Self {
75 Self {
76 year,
77 month,
78 day,
79 hour,
80 minute,
81 second,
82 }
83 }
84}
85
86impl fmt::Display for crate::def::grib2::RefTime {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 write!(
89 f,
90 "{:04}-{:02}-{:02} {:02}:{:02}:{:02} UTC",
91 self.year, self.month, self.day, self.hour, self.minute, self.second
92 )
93 }
94}
95
96#[cfg(feature = "time-calculation")]
97#[inline]
98fn create_date_time(
99 year: i32,
100 month: u32,
101 day: u32,
102 hour: u32,
103 minute: u32,
104 second: u32,
105) -> Option<DateTime<Utc>> {
106 let result = Utc.with_ymd_and_hms(year, month, day, hour, minute, second);
107 if let LocalResult::None = result {
108 None
109 } else {
110 Some(result.unwrap())
111 }
112}
113
114#[cfg(feature = "time-calculation")]
115struct BasicTimeDelta(fn(i64) -> Option<TimeDelta>, i64);
116
117#[cfg(feature = "time-calculation")]
118impl BasicTimeDelta {
119 fn new(ft: &ForecastTime) -> Option<Self> {
120 match ft.unit {
121 Code::Name(Table4_4::Second) => Some(Self(TimeDelta::try_seconds, 1)),
122 Code::Name(Table4_4::Minute) => Some(Self(TimeDelta::try_minutes, 1)),
123 Code::Name(Table4_4::Hour) => Some(Self(TimeDelta::try_hours, 1)),
124 Code::Name(Table4_4::ThreeHours) => Some(Self(TimeDelta::try_hours, 3)),
125 Code::Name(Table4_4::SixHours) => Some(Self(TimeDelta::try_hours, 6)),
126 Code::Name(Table4_4::TwelveHours) => Some(Self(TimeDelta::try_hours, 12)),
127 Code::Name(Table4_4::Day) => Some(Self(TimeDelta::try_days, 1)),
128 _ => None,
129 }
130 }
131
132 fn convert(&self, ft: &ForecastTime) -> Option<TimeDelta> {
133 let BasicTimeDelta(func, value) = self;
134 func(i64::try_from(ft.value).ok()? * value)
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 #[cfg(feature = "time-calculation")]
141 use super::*;
142
143 macro_rules! test_date_time_creation {
144 ($((
145 $name:ident,
146 $year:expr,
147 $month:expr,
148 $day:expr,
149 $hour:expr,
150 $minute:expr,
151 $second:expr,
152 $ok_expected:expr
153 ),)*) => ($(
154 #[cfg(feature = "time-calculation")]
155 #[test]
156 fn $name() {
157 let result = create_date_time($year, $month, $day, $hour, $minute, $second);
158 assert_eq!(result.is_some(), $ok_expected);
159 }
160 )*);
161 }
162
163 test_date_time_creation! {
164 (date_time_creation_for_valid_date_time, 2022, 1, 1, 0, 0, 0, true),
165 (date_time_creation_for_invalid_date, 2022, 11, 31, 0, 0, 0, false),
166 (date_time_creation_for_invalid_time, 2022, 1, 1, 0, 61, 0, false),
167 }
168
169 #[test]
170 fn test_utc_date_time_string() {
171 let time = crate::def::grib2::RefTime::new(2025, 1, 1, 0, 0, 0);
172 assert_eq!(format!("{time}"), "2025-01-01 00:00:00 UTC".to_owned())
173 }
174}