1use crate::{calendars::Calendar, duration::CFDuration};
5
6#[derive(Debug, PartialEq)]
7pub enum Unit {
8 Year,
9 Month,
10 Day,
11 Hour,
12 Minute,
13 Second,
14 Millisecond,
15 Microsecond,
16 Nanosecond,
17}
18
19impl Unit {
20 pub fn to_duration(&self, calendar: Calendar) -> CFDuration {
21 match self {
22 Unit::Year => CFDuration::from_years(1, calendar),
23 Unit::Month => CFDuration::from_months(1, calendar),
24 Unit::Day => CFDuration::from_days(1, calendar),
25 Unit::Hour => CFDuration::from_hours(1, calendar),
26 Unit::Minute => CFDuration::from_minutes(1, calendar),
27 Unit::Second => CFDuration::from_seconds(1, calendar),
28 Unit::Millisecond => CFDuration::from_milliseconds(1, calendar),
29 Unit::Microsecond => CFDuration::from_microseconds(1, calendar),
30 Unit::Nanosecond => CFDuration::from_nanoseconds(1, calendar),
31 }
32 }
33}
34#[derive(Debug)]
35pub struct ParsedDatetime {
36 pub ymd: (i64, u8, u8),
37 pub hms: Option<(u8, u8, f32)>,
38 pub tz: Option<(i8, u8)>,
39 pub nanosecond: Option<i64>,
40}
41#[derive(Debug)]
42pub struct ParsedCFTime {
43 pub unit: Unit,
44 pub datetime: ParsedDatetime,
45}
46pub fn parse_cf_time(unit: &str) -> Result<ParsedCFTime, crate::errors::Error> {
47 let mut matches: Vec<&str> = unit.split(' ').collect();
48 matches.retain(|&s| !s.trim().is_empty());
50 if matches.len() < 3 {
51 return Err(crate::errors::Error::UnitParserError(unit.to_string()));
52 }
53
54 let duration_unit = match matches[0] {
55 "common_years" | "common_year" => Unit::Year,
56 "months" | "month" => Unit::Month,
57 "days" | "day" | "d" => Unit::Day,
58 "hours" | "hour" | "hrs" | "hr" | "h" => Unit::Hour,
59 "minutes" | "minute" | "mins" | "min" => Unit::Minute,
60 "seconds" | "second" | "secs" | "sec" | "s" => Unit::Second,
61 "milliseconds" | "millisecond" | "millisecs" | "millisec" | "msecs" | "msec" | "ms" => {
62 Unit::Millisecond
63 }
64 "microseconds" | "microsecond" | "microsecs" | "microsec" => Unit::Microsecond,
65 _ => {
66 return Err(crate::errors::Error::UnitParserError(
67 format!("Invalid duration unit '{}' in '{unit}'", matches[0]).to_string(),
68 ))
69 }
70 };
71
72 if matches[1] != "since" {
73 return Err(crate::errors::Error::UnitParserError(
74 format!("Expected 'since' found : '{}'", matches[1]).to_string(),
75 ));
76 }
77
78 let date: Vec<&str> = matches[2].split('-').collect();
79 if date.len() != 3 {
80 return Err(crate::errors::Error::UnitParserError(
81 format!("Invalid date: {unit}").to_string(),
82 ));
83 }
84 let year = date[0].parse::<i64>()?;
85 let month = date[1].parse::<u8>()?;
86 let day = date[2].parse::<u8>()?;
87
88 if matches.len() <= 3 {
89 return Ok(ParsedCFTime {
90 unit: duration_unit,
91 datetime: ParsedDatetime {
92 ymd: (year, month, day),
93 hms: None,
94 tz: None,
95 nanosecond: None,
96 },
97 });
98 }
99
100 let time: Vec<&str> = matches[3].split(':').collect();
101 if time.len() != 3 {
102 return Err(crate::errors::Error::UnitParserError(
103 format!("Invalid time '{}' in '{unit}'", matches[3]).to_string(),
104 ));
105 }
106 let hour = time[0].parse::<u8>()?;
107 let minute = time[1].parse::<u8>()?;
108 let second = time[2].parse::<f32>()?;
109
110 if matches.len() <= 4 {
111 return Ok(ParsedCFTime {
112 unit: duration_unit,
113 datetime: ParsedDatetime {
114 ymd: (year, month, day),
115 hms: Some((hour, minute, second)),
116 tz: None,
117 nanosecond: None,
118 },
119 });
120 }
121
122 let tz: Vec<&str> = matches[4].split(':').collect();
123 if tz.len() > 2 || tz.len() <= 0 {
124 return Err(crate::errors::Error::UnitParserError(
125 format!("Invalid time '{}' in '{unit}'", matches[4]).to_string(),
126 ));
127 }
128 let mut tzhour = 0;
129 let mut tzminute = 0;
130 if tz.len() == 1 {
131 tzhour = tz[0].parse::<i8>()?;
132 tzminute = 0;
133 } else if tz.len() == 2 {
134 tzhour = tz[0].parse::<i8>()?;
135 tzminute = tz[1].parse::<u8>()?;
136 }
137 Ok(ParsedCFTime {
138 unit: duration_unit,
139 datetime: ParsedDatetime {
140 ymd: (year, month, day),
141 hms: Some((hour, minute, second)),
142 tz: Some((tzhour, tzminute)),
143 nanosecond: None,
144 },
145 })
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn test_valid_duration_units() {
154 let units = vec![
156 ("common_years since 2023-01-01", Unit::Year),
157 ("months since 2023-01-01", Unit::Month),
158 ("day since 2023-01-01", Unit::Day),
159 ];
161
162 for (input, expected_unit) in units {
163 let result = parse_cf_time(input).unwrap();
164 assert!(result.unit == expected_unit);
165 assert_eq!(result.datetime.ymd, (2023, 1, 1));
166 assert_eq!(result.datetime.hms, None);
167 assert_eq!(result.datetime.tz, None);
168 assert_eq!(result.datetime.nanosecond, None);
169 }
170 }
171
172 #[test]
173 fn test_valid_date_time_units() {
174 let units = vec![
176 (
178 "seconds since 1992-10-8 15:15:42.5 -6:00",
179 ParsedCFTime {
180 unit: Unit::Second,
181 datetime: ParsedDatetime {
182 ymd: (1992, 10, 8),
183 hms: Some((15, 15, 42.5)),
184 tz: Some((-6, 0)),
185 nanosecond: None,
186 },
187 },
188 ),
189 (
191 "seconds since 1992-10-08",
192 ParsedCFTime {
193 unit: Unit::Second,
194 datetime: ParsedDatetime {
195 ymd: (1992, 10, 8),
196 hms: None,
197 tz: None,
198 nanosecond: None,
199 },
200 },
201 ),
202 (
203 "minutes since 2000-01-01",
204 ParsedCFTime {
205 unit: Unit::Minute,
206 datetime: ParsedDatetime {
207 ymd: (2000, 1, 1),
208 hms: None,
209 tz: None,
210 nanosecond: None,
211 },
212 },
213 ),
214 (
215 "hour since 1985-12-31",
216 ParsedCFTime {
217 unit: Unit::Hour,
218 datetime: ParsedDatetime {
219 ymd: (1985, 12, 31),
220 hms: None,
221 tz: None,
222 nanosecond: None,
223 },
224 },
225 ),
226 (
228 "seconds since 2022-11-30 10:15:20",
229 ParsedCFTime {
230 unit: Unit::Second,
231 datetime: ParsedDatetime {
232 ymd: (2022, 11, 30),
233 hms: Some((10, 15, 20.0)),
234 tz: None,
235 nanosecond: None,
236 },
237 },
238 ),
239 (
240 "minutes since 2010-05-15 05:30:00",
241 ParsedCFTime {
242 unit: Unit::Minute,
243 datetime: ParsedDatetime {
244 ymd: (2010, 5, 15),
245 hms: Some((5, 30, 0.0)),
246 tz: None,
247 nanosecond: None,
248 },
249 },
250 ),
251 (
252 "hour since 1999-03-20 12:00:01",
253 ParsedCFTime {
254 unit: Unit::Hour,
255 datetime: ParsedDatetime {
256 ymd: (1999, 3, 20),
257 hms: Some((12, 0, 1.0)),
258 tz: None,
259 nanosecond: None,
260 },
261 },
262 ),
263 (
265 "seconds since 2015-07-04 16:45:30 +02:30",
266 ParsedCFTime {
267 unit: Unit::Second,
268 datetime: ParsedDatetime {
269 ymd: (2015, 7, 4),
270 hms: Some((16, 45, 30.0)),
271 tz: Some((2, 30)),
272 nanosecond: None,
273 },
274 },
275 ),
276 (
277 "minutes since 2023-12-25 08:00:00 -05:00",
278 ParsedCFTime {
279 unit: Unit::Minute,
280 datetime: ParsedDatetime {
281 ymd: (2023, 12, 25),
282 hms: Some((8, 0, 0.0)),
283 tz: Some((-5, 0)),
284 nanosecond: None,
285 },
286 },
287 ),
288 (
289 "hour since 2018-09-10 00:00:00 -03:30",
290 ParsedCFTime {
291 unit: Unit::Hour,
292 datetime: ParsedDatetime {
293 ymd: (2018, 9, 10),
294 hms: Some((0, 0, 0.0)),
295 tz: Some((-3, 30)),
296 nanosecond: None,
297 },
298 },
299 ),
300 ];
301
302 for (input, expected_unit) in units {
303 let result = parse_cf_time(input).unwrap();
304 assert!(result.unit == expected_unit.unit);
305 assert_eq!(result.datetime.ymd, expected_unit.datetime.ymd);
306 assert_eq!(result.datetime.hms, expected_unit.datetime.hms);
307 assert_eq!(result.datetime.tz, expected_unit.datetime.tz);
308 assert_eq!(
309 result.datetime.nanosecond,
310 expected_unit.datetime.nanosecond
311 );
312 }
313 }
314 #[test]
315 fn test_not_valid_date_time_units() {
316 let units = vec![
318 "seconds since 2019-06-15 -07:00",
319 "nanoseconds since 2020-01-01 9876543210", "invalid_unit since 2023-01-01", "hou since 2023-01-01", "minutes 2023-01-01", ];
324
325 for input in units {
326 let result = parse_cf_time(input);
327 assert!(matches!(
328 result.err().unwrap(),
329 crate::errors::Error::UnitParserError(_)
330 ))
331 }
332 }
333 }