1use super::prelude::*;
4use chrono::{Datelike, Local, TimeZone, Timelike, Utc};
5use regex::Regex;
6
7#[derive(PartialEq, Debug)]
9pub enum PartialDateTime {
10 None,
12 HM { hour: u32, minute: u32 },
14 MD { month: u32, day: u32 },
16 YMDHM {
18 year: i32,
19 month: u32,
20 day: u32,
21 hour: u32,
22 minute: u32,
23 },
24 MDHM {
26 month: u32,
27 day: u32,
28 hour: u32,
29 minute: u32,
30 },
31 YMD { year: i32, month: u32, day: u32 },
33}
34
35impl PartialDateTime {
36 pub fn parse(dt: Option<String>) -> Result<Self, Error> {
38 if let Some(dt) = dt {
39 match Self::parse_date_time(dt.clone()) {
40 PartialDateTime::None => Err(Error::PartialDateTimeFormat(dt)),
41 pdt => Ok(pdt),
42 }
43 } else {
44 Ok(PartialDateTime::None)
45 }
46 }
47 pub fn parse_opt(dt: Option<String>) -> Self {
49 if let Some(dt) = dt {
50 Self::parse_date_time(dt)
51 } else {
52 Self::None
53 }
54 }
55 pub fn or(self, other: Self) -> Self {
57 match self {
58 Self::None => other,
59 _ => self,
60 }
61 }
62 fn merge(left: Self, right: Self) -> Self {
64 if let Self::HM { hour, minute } = left {
65 if let Self::YMD { year, month, day } = right {
66 return Self::YMDHM {
67 year,
68 month,
69 day,
70 hour,
71 minute,
72 };
73 } else if let Self::MD { month, day } = right {
74 return Self::MDHM {
75 month,
76 day,
77 hour,
78 minute,
79 };
80 }
81 } else if let Self::HM { hour, minute } = right {
82 if let Self::YMD { year, month, day } = left {
83 return Self::YMDHM {
84 year,
85 month,
86 day,
87 hour,
88 minute,
89 };
90 } else if let Self::MD { month, day } = left {
91 return Self::MDHM {
92 month,
93 day,
94 hour,
95 minute,
96 };
97 }
98 }
99 Self::None
100 }
101 fn parse_date_time(dt: String) -> Self {
103 let dt: Vec<&str> = dt.split(',').collect();
104 match dt.len() {
105 1 => Self::parse_dmy(dt[0]).or(Self::parse_mdy(dt[0]).or(Self::parse_ymd(dt[0])
106 .or(Self::parse_dm(dt[0]).or(Self::parse_md(dt[0]).or(Self::parse_hm(dt[0])))))),
107 2 => {
108 Self::merge(
109 Self::parse_dmy(dt[0])
110 .or(Self::parse_mdy(dt[0]).or(Self::parse_ymd(dt[0])
111 .or(Self::parse_dm(dt[0]).or(Self::parse_md(dt[0]))))),
112 Self::parse_hm(dt[1]),
113 )
114 .or(Self::merge(
115 Self::parse_dmy(dt[1])
116 .or(Self::parse_mdy(dt[1]).or(Self::parse_ymd(dt[1])
117 .or(Self::parse_dm(dt[1]).or(Self::parse_md(dt[1]))))),
118 Self::parse_hm(dt[0]),
119 ))
120 }
121 _ => PartialDateTime::None,
122 }
123 }
124
125 fn parse_hm(dt: &str) -> Self {
127 let re = Regex::new(r"^(\d{1,2}):(\d{1,2})$").unwrap();
128 if let Some(cap) = re.captures_iter(dt).next() {
129 return Self::HM {
130 hour: cap[1].parse::<u32>().unwrap(),
131 minute: cap[2].parse::<u32>().unwrap(),
132 };
133 }
134 Self::None
135 }
136
137 fn parse_dm(dt: &str) -> Self {
139 let re = Regex::new(r"^(\d{1,2})\.(\d{1,2})\.$").unwrap();
140 if let Some(cap) = re.captures_iter(dt).next() {
141 return Self::MD {
142 month: cap[2].parse::<u32>().unwrap(),
143 day: cap[1].parse::<u32>().unwrap(),
144 };
145 }
146 Self::None
147 }
148
149 fn parse_md(dt: &str) -> Self {
151 let re = Regex::new(r"^(\d{1,2})/(\d{1,2})$").unwrap();
152 if let Some(cap) = re.captures_iter(dt).next() {
153 return Self::MD {
154 month: cap[1].parse::<u32>().unwrap(),
155 day: cap[2].parse::<u32>().unwrap(),
156 };
157 }
158 Self::None
159 }
160 fn parse_dmy(dt: &str) -> Self {
162 let re = Regex::new(r"^(\d{1,2})\.(\d{1,2})\.(\d{4})$").unwrap();
163 if let Some(cap) = re.captures_iter(dt).next() {
164 return Self::YMD {
165 year: cap[3].parse::<i32>().unwrap(),
166 month: cap[2].parse::<u32>().unwrap(),
167 day: cap[1].parse::<u32>().unwrap(),
168 };
169 }
170 Self::None
171 }
172 fn parse_ymd(dt: &str) -> Self {
174 let re = Regex::new(r"^(\d{4})-(\d{1,2})-(\d{1,2})$").unwrap();
175 if let Some(cap) = re.captures_iter(dt).next() {
176 return Self::YMD {
177 year: cap[1].parse::<i32>().unwrap(),
178 month: cap[2].parse::<u32>().unwrap(),
179 day: cap[3].parse::<u32>().unwrap(),
180 };
181 }
182 Self::None
183 }
184 fn parse_mdy(dt: &str) -> Self {
186 let re = Regex::new(r"^(\d{1,2})/(\d{1,2})/(\d{4})$").unwrap();
187 if let Some(cap) = re.captures_iter(dt).next() {
188 return Self::YMD {
189 year: cap[3].parse::<i32>().unwrap(),
190 month: cap[1].parse::<u32>().unwrap(),
191 day: cap[2].parse::<u32>().unwrap(),
192 };
193 }
194 Self::None
195 }
196 pub fn into(self, base: DateTime) -> DateTime {
198 let base: chrono::DateTime<Local> = base.into();
199 chrono::DateTime::with_timezone(
200 &match self {
201 Self::HM { hour, minute } => Local
202 .with_ymd_and_hms(base.year(), base.month(), base.day(), hour, minute, 0)
203 .unwrap(),
204 Self::YMDHM {
205 year,
206 month,
207 day,
208 hour,
209 minute,
210 } => Local
211 .with_ymd_and_hms(year, month, day, hour, minute, 0)
212 .unwrap(),
213 Self::MDHM {
214 month,
215 day,
216 hour,
217 minute,
218 } => Local
219 .with_ymd_and_hms(base.year(), month, day, hour, minute, 0)
220 .unwrap(),
221 Self::YMD { year, month, day } => {
222 Local.with_ymd_and_hms(year, month, day, 0, 0, 0).unwrap()
223 }
224 Self::MD { month, day } => Local
225 .with_ymd_and_hms(base.year(), month, day, 0, 0, 0)
226 .unwrap(),
227 Self::None => Local
228 .with_ymd_and_hms(
229 base.year(),
230 base.month(),
231 base.day(),
232 base.hour(),
233 base.minute(),
234 0,
235 )
236 .unwrap(),
237 },
238 &Utc,
239 )
240 .into()
241 }
242}
243
244#[test]
246fn test_parse_date_time() {
247 PartialDateTime::parse_date_time("1.1.,12:00".into());
248}
249
250#[test]
252fn test_parse_hm() {
253 assert_eq!(
254 PartialDateTime::parse_hm("01:00"),
255 PartialDateTime::HM { hour: 1, minute: 0 }
256 );
257 assert_eq!(
258 PartialDateTime::parse_hm("1:0"),
259 PartialDateTime::HM { hour: 1, minute: 0 }
260 );
261 assert_ne!(
262 PartialDateTime::parse_hm("1.0"),
263 PartialDateTime::HM { hour: 1, minute: 0 }
264 );
265}
266
267#[test]
269fn test_parse_ymd() {
270 assert_eq!(
271 PartialDateTime::parse_ymd("2023-02-01"),
272 PartialDateTime::YMD {
273 year: 2023,
274 month: 2,
275 day: 1
276 }
277 );
278 assert_eq!(
279 PartialDateTime::parse_ymd("2023-2-1"),
280 PartialDateTime::YMD {
281 year: 2023,
282 month: 2,
283 day: 1
284 }
285 );
286 assert_ne!(
287 PartialDateTime::parse_ymd("2023.2.1"),
288 PartialDateTime::YMD {
289 year: 2023,
290 month: 2,
291 day: 1
292 }
293 );
294}
295
296#[test]
298fn test_parse_mdy() {
299 assert_eq!(
300 PartialDateTime::parse_mdy("02/01/2023"),
301 PartialDateTime::YMD {
302 year: 2023,
303 month: 2,
304 day: 1
305 }
306 );
307 assert_eq!(
308 PartialDateTime::parse_mdy("2/1/2023"),
309 PartialDateTime::YMD {
310 year: 2023,
311 month: 2,
312 day: 1
313 }
314 );
315 assert_ne!(
316 PartialDateTime::parse_mdy("2.1.2023"),
317 PartialDateTime::YMD {
318 year: 2023,
319 month: 2,
320 day: 1
321 }
322 );
323}
324
325#[test]
327fn test_parse_dmy() {
328 assert_eq!(
329 PartialDateTime::parse_dmy("01.02.2023"),
330 PartialDateTime::YMD {
331 year: 2023,
332 month: 2,
333 day: 1
334 }
335 );
336 assert_eq!(
337 PartialDateTime::parse_dmy("1.2.2023"),
338 PartialDateTime::YMD {
339 year: 2023,
340 month: 2,
341 day: 1
342 }
343 );
344 assert_ne!(
345 PartialDateTime::parse_dmy("1-2-2023"),
346 PartialDateTime::YMD {
347 year: 2023,
348 month: 2,
349 day: 1
350 }
351 );
352}
353
354#[test]
356fn test_parse_dm() {
357 assert_eq!(
358 PartialDateTime::parse_dm("01.02."),
359 PartialDateTime::MD { month: 2, day: 1 }
360 );
361 assert_eq!(
362 PartialDateTime::parse_dm("1.2."),
363 PartialDateTime::MD { month: 2, day: 1 }
364 );
365 assert_ne!(
366 PartialDateTime::parse_dm("1.2"),
367 PartialDateTime::MD { month: 2, day: 1 }
368 );
369 assert_ne!(
370 PartialDateTime::parse_dm("1-2-"),
371 PartialDateTime::MD { month: 2, day: 1 }
372 );
373}
374
375#[test]
377fn test_parse_md() {
378 assert_eq!(
379 PartialDateTime::parse_md("02/01"),
380 PartialDateTime::MD { month: 2, day: 1 }
381 );
382 assert_eq!(
383 PartialDateTime::parse_md("2/1"),
384 PartialDateTime::MD { month: 2, day: 1 }
385 );
386 assert_ne!(
387 PartialDateTime::parse_md("2-1"),
388 PartialDateTime::MD { month: 2, day: 1 }
389 );
390}