1#![allow(deprecated)]
2use crate::timezone;
3use anyhow::{Result, anyhow};
4use chrono::prelude::*;
5use regex::Regex;
6
7macro_rules! regex {
8 ($re:literal $(,)?) => {{
9 static RE: std::sync::OnceLock<regex::Regex> = std::sync::OnceLock::new();
10 RE.get_or_init(|| unsafe {
11 regex::RegexBuilder::new($re)
12 .unicode(false)
13 .build()
14 .unwrap_unchecked()
15 })
16 }};
17}
18pub struct Parse<'z, Tz2> {
20 tz: &'z Tz2,
21 default_time: NaiveTime,
22 prefer_dmy: bool,
23}
24
25impl<'z, Tz2> Parse<'z, Tz2>
26where
27 Tz2: TimeZone,
28{
29 pub const fn new(tz: &'z Tz2, default_time: NaiveTime) -> Self {
32 Self {
33 tz,
34 default_time,
35 prefer_dmy: false,
36 }
37 }
38
39 pub const fn prefer_dmy(&mut self, yes: bool) -> &Self {
40 self.prefer_dmy = yes;
41 self
42 }
43
44 pub const fn new_with_preference(
47 tz: &'z Tz2,
48 default_time: NaiveTime,
49 prefer_dmy: bool,
50 ) -> Self {
51 Self {
52 tz,
53 default_time,
54 prefer_dmy,
55 }
56 }
57
58 #[inline]
61 pub fn parse(&self, input: &str) -> Result<DateTime<Utc>> {
62 self.rfc2822(input)
63 .or_else(|| self.unix_timestamp(input))
64 .or_else(|| self.slash_mdy_family(input))
65 .or_else(|| self.slash_ymd_family(input))
66 .or_else(|| self.ymd_family(input))
67 .or_else(|| self.month_ymd(input))
68 .or_else(|| self.month_mdy_family(input))
69 .or_else(|| self.month_dmy_family(input))
70 .unwrap_or_else(|| Err(anyhow!("{} did not match any formats.", input)))
71 }
72
73 #[inline]
74 fn ymd_family(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
75 let re: &Regex = regex! {
76 r"^\d{4}-\d{2}"
77
78 };
79
80 if !re.is_match(input) {
81 return None;
82 }
83 self.rfc3339(input)
84 .or_else(|| self.ymd_hms(input))
85 .or_else(|| self.ymd_hms_z(input))
86 .or_else(|| self.ymd(input))
87 .or_else(|| self.ymd_z(input))
88 }
89
90 #[inline]
91 fn month_mdy_family(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
92 let re: &Regex = regex! {
93 r"^[a-zA-Z]{3,9}\.?\s+\d{1,2}"
94 };
95
96 if !re.is_match(input) {
97 return None;
98 }
99 self.month_mdy_hms(input)
100 .or_else(|| self.month_mdy_hms_z(input))
101 .or_else(|| self.month_mdy(input))
102 }
103
104 #[inline]
105 fn month_dmy_family(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
106 let re: &Regex = regex! {r"^\d{1,2}\s+[a-zA-Z]{3,9}"
107 };
108
109 if !re.is_match(input) {
110 return None;
111 }
112 self.month_dmy_hms(input).or_else(|| self.month_dmy(input))
113 }
114
115 #[inline]
116 fn slash_mdy_family(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
117 let re: &Regex = regex! {r"^\d{1,2}/\d{1,2}"
118 };
119 if !re.is_match(input) {
120 return None;
121 }
122 if self.prefer_dmy {
123 self.slash_dmy_hms(input)
124 .or_else(|| self.slash_dmy(input))
125 .or_else(|| self.slash_mdy_hms(input))
126 .or_else(|| self.slash_mdy(input))
127 } else {
128 self.slash_mdy_hms(input)
129 .or_else(|| self.slash_mdy(input))
130 .or_else(|| self.slash_dmy_hms(input))
131 .or_else(|| self.slash_dmy(input))
132 }
133 }
134
135 #[inline]
136 fn slash_ymd_family(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
137 let re: &Regex = regex! {r"^[0-9]{4}/[0-9]{1,2}"};
138 if !re.is_match(input) {
139 return None;
140 }
141 self.slash_ymd_hms(input).or_else(|| self.slash_ymd(input))
142 }
143
144 #[inline]
149 fn unix_timestamp(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
150 let ts_sec_val: f64 = if let Ok(val) = fast_float2::parse(input) {
151 val
152 } else {
153 return None;
154 };
155
156 let ts_ns_val = ts_sec_val * 1_000_000_000_f64;
158
159 let result = Utc.timestamp_nanos(ts_ns_val as i64).with_timezone(&Utc);
160 Some(Ok(result))
161 }
162
163 #[inline]
167 fn rfc3339(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
168 DateTime::parse_from_rfc3339(input)
169 .ok()
170 .map(|parsed| parsed.with_timezone(&Utc))
171 .map(Ok)
172 }
173
174 #[inline]
177 fn rfc2822(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
178 DateTime::parse_from_rfc2822(input)
179 .ok()
180 .map(|parsed| parsed.with_timezone(&Utc))
181 .map(Ok)
182 }
183
184 #[inline]
193 fn ymd_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
194 let re: &Regex = regex! {
195 r"^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}(:\d{2})?(\.\d{1,9})?\s*(am|pm|AM|PM)?$"
196
197 };
198 if !re.is_match(input) {
199 return None;
200 }
201
202 self.tz
203 .datetime_from_str(input, "%Y-%m-%d %H:%M:%S")
204 .or_else(|_| self.tz.datetime_from_str(input, "%Y-%m-%d %H:%M"))
205 .or_else(|_| self.tz.datetime_from_str(input, "%Y-%m-%d %H:%M:%S%.f"))
206 .or_else(|_| self.tz.datetime_from_str(input, "%Y-%m-%d %I:%M:%S %P"))
207 .or_else(|_| self.tz.datetime_from_str(input, "%Y-%m-%d %I:%M %P"))
208 .ok()
209 .map(|parsed| parsed.with_timezone(&Utc))
210 .map(Ok)
211 }
212
213 #[inline]
223 fn ymd_hms_z(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
224 let re: &Regex = regex! {
225 r"^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}(:\d{2})?(\.\d{1,9})?(?P<tz>\s*[+-:a-zA-Z0-9]{3,6})$"
226 };
227
228 if !re.is_match(input) {
229 return None;
230 }
231 if let Some(caps) = re.captures(input) {
232 if let Some(matched_tz) = caps.name("tz") {
233 let parse_from_str = NaiveDateTime::parse_from_str;
234 return match timezone::parse(matched_tz.as_str().trim()) {
235 Ok(offset) => parse_from_str(input, "%Y-%m-%d %H:%M:%S %Z")
236 .or_else(|_| parse_from_str(input, "%Y-%m-%d %H:%M %Z"))
237 .or_else(|_| parse_from_str(input, "%Y-%m-%d %H:%M:%S%.f %Z"))
238 .ok()
239 .and_then(|parsed| offset.from_local_datetime(&parsed).single())
240 .map(|datetime| datetime.with_timezone(&Utc))
241 .map(Ok),
242 Err(err) => Some(Err(err)),
243 };
244 }
245 }
246 None
247 }
248
249 #[inline]
252 fn ymd(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
253 let re: &Regex = regex! {r"^\d{4}-\d{2}-\d{2}$"
254 };
255
256 if !re.is_match(input) {
257 return None;
258 }
259 let now = Utc::now()
260 .date()
261 .and_time(self.default_time)?
262 .with_timezone(self.tz);
263 NaiveDate::parse_from_str(input, "%Y-%m-%d")
264 .ok()
265 .map(|parsed| parsed.and_time(now.time()))
266 .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
267 .map(|at_tz| at_tz.with_timezone(&Utc))
268 .map(Ok)
269 }
270
271 #[inline]
276 fn ymd_z(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
277 let re: &Regex = regex! {r"^\d{4}-\d{2}-\d{2}(?P<tz>\s*[+-:a-zA-Z0-9]{3,6})$"
278 };
279 if !re.is_match(input) {
280 return None;
281 }
282
283 if let Some(caps) = re.captures(input) {
284 if let Some(matched_tz) = caps.name("tz") {
285 return match timezone::parse(matched_tz.as_str().trim()) {
286 Ok(offset) => {
287 let now = Utc::now()
288 .date()
289 .and_time(self.default_time)?
290 .with_timezone(&offset);
291 NaiveDate::parse_from_str(input, "%Y-%m-%d %Z")
292 .ok()
293 .map(|parsed| parsed.and_time(now.time()))
294 .and_then(|datetime| offset.from_local_datetime(&datetime).single())
295 .map(|at_tz| at_tz.with_timezone(&Utc))
296 .map(Ok)
297 }
298 Err(err) => Some(Err(err)),
299 };
300 }
301 }
302 None
303 }
304
305 #[inline]
308 fn month_ymd(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
309 let re: &Regex = regex! {r"^\d{4}-\w{3,9}-\d{2}$"
310 };
311 if !re.is_match(input) {
312 return None;
313 }
314
315 let now = Utc::now()
316 .date()
317 .and_time(self.default_time)?
318 .with_timezone(self.tz);
319 NaiveDate::parse_from_str(input, "%Y-%m-%d")
320 .or_else(|_| NaiveDate::parse_from_str(input, "%Y-%b-%d"))
321 .ok()
322 .map(|parsed| parsed.and_time(now.time()))
323 .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
324 .map(|at_tz| at_tz.with_timezone(&Utc))
325 .map(Ok)
326 }
327
328 #[inline]
333 fn month_mdy_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
334 let re: &Regex = regex! {
335 r"^[a-zA-Z]{3,9}\.?\s+\d{1,2},\s+\d{2,4},?\s+\d{1,2}:\d{2}(:\d{2})?\s*(am|pm|AM|PM)?$"
336 };
337 if !re.is_match(input) {
338 return None;
339 }
340
341 let dt = input.replace(", ", " ").replace(". ", " ");
342 self.tz
343 .datetime_from_str(&dt, "%B %d %Y %H:%M:%S")
344 .or_else(|_| self.tz.datetime_from_str(&dt, "%B %d %Y %H:%M"))
345 .or_else(|_| self.tz.datetime_from_str(&dt, "%B %d %Y %I:%M:%S %P"))
346 .or_else(|_| self.tz.datetime_from_str(&dt, "%B %d %Y %I:%M %P"))
347 .ok()
348 .map(|at_tz| at_tz.with_timezone(&Utc))
349 .map(Ok)
350 }
351
352 #[inline]
358 fn month_mdy_hms_z(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
359 let re: &Regex = regex! {
360 r"^[a-zA-Z]{3,9}\s+\d{1,2},?\s+\d{4}\s*,?(at)?\s+\d{2}:\d{2}(:\d{2})?\s*(am|pm|AM|PM)?(?P<tz>\s+[+-:a-zA-Z0-9]{3,6})$",
361 };
362 if !re.is_match(input) {
363 return None;
364 }
365
366 if let Some(caps) = re.captures(input) {
367 if let Some(matched_tz) = caps.name("tz") {
368 let parse_from_str = NaiveDateTime::parse_from_str;
369 return match timezone::parse(matched_tz.as_str().trim()) {
370 Ok(offset) => {
371 let dt = input.replace(',', "").replace("at", "");
372 parse_from_str(&dt, "%B %d %Y %H:%M:%S %Z")
373 .or_else(|_| parse_from_str(&dt, "%B %d %Y %H:%M %Z"))
374 .or_else(|_| parse_from_str(&dt, "%B %d %Y %I:%M:%S %P %Z"))
375 .or_else(|_| parse_from_str(&dt, "%B %d %Y %I:%M %P %Z"))
376 .ok()
377 .and_then(|parsed| offset.from_local_datetime(&parsed).single())
378 .map(|datetime| datetime.with_timezone(&Utc))
379 .map(Ok)
380 }
381 Err(err) => Some(Err(err)),
382 };
383 }
384 }
385 None
386 }
387
388 #[inline]
396 fn month_mdy(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
397 let re: &Regex = regex! {r"^[a-zA-Z]{3,9}\.?\s+\d{1,2},\s+\d{2,4}$"
398 };
399 if !re.is_match(input) {
400 return None;
401 }
402
403 let now = Utc::now()
404 .date()
405 .and_time(self.default_time)?
406 .with_timezone(self.tz);
407 let dt = input.replace(", ", " ").replace(". ", " ");
408 NaiveDate::parse_from_str(&dt, "%B %d %y")
409 .or_else(|_| NaiveDate::parse_from_str(&dt, "%B %d %Y"))
410 .ok()
411 .map(|parsed| parsed.and_time(now.time()))
412 .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
413 .map(|at_tz| at_tz.with_timezone(&Utc))
414 .map(Ok)
415 }
416
417 #[inline]
422 fn month_dmy_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
423 let re: &Regex = regex! {
424 r"^\d{1,2}\s+[a-zA-Z]{3,9}\s+\d{2,4},?\s+\d{1,2}:[0-9]{2}(:[0-9]{2})?(\.[0-9]{1,9})?$"
425 };
426 if !re.is_match(input) {
427 return None;
428 }
429
430 let dt = input.replace(", ", " ");
431 self.tz
432 .datetime_from_str(&dt, "%d %B %Y %H:%M:%S")
433 .or_else(|_| self.tz.datetime_from_str(&dt, "%d %B %Y %H:%M"))
434 .or_else(|_| self.tz.datetime_from_str(&dt, "%d %B %Y %H:%M:%S%.f"))
435 .or_else(|_| self.tz.datetime_from_str(&dt, "%d %B %Y %I:%M:%S %P"))
436 .or_else(|_| self.tz.datetime_from_str(&dt, "%d %B %Y %I:%M %P"))
437 .ok()
438 .map(|at_tz| at_tz.with_timezone(&Utc))
439 .map(Ok)
440 }
441
442 #[inline]
448 fn month_dmy(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
449 let re: &Regex = regex! {r"^\d{1,2}\s+[a-zA-Z]{3,9}\s+\d{2,4}$"
450 };
451 if !re.is_match(input) {
452 return None;
453 }
454
455 let now = Utc::now()
456 .date()
457 .and_time(self.default_time)?
458 .with_timezone(self.tz);
459 NaiveDate::parse_from_str(input, "%d %B %y")
460 .or_else(|_| NaiveDate::parse_from_str(input, "%d %B %Y"))
461 .ok()
462 .map(|parsed| parsed.and_time(now.time()))
463 .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
464 .map(|at_tz| at_tz.with_timezone(&Utc))
465 .map(Ok)
466 }
467
468 #[inline]
482 fn slash_mdy_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
483 let re: &Regex = regex! {
484 r"^\d{1,2}/\d{1,2}/\d{2,4}\s+\d{1,2}:\d{2}(:\d{2})?(\.\d{1,9})?\s*(am|pm|AM|PM)?$"
485 };
486 if !re.is_match(input) {
487 return None;
488 }
489
490 self.tz
491 .datetime_from_str(input, "%m/%d/%y %H:%M:%S")
492 .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%y %H:%M"))
493 .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%y %H:%M:%S%.f"))
494 .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%y %I:%M:%S %P"))
495 .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%y %I:%M %P"))
496 .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %H:%M:%S"))
497 .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %H:%M"))
498 .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %H:%M:%S%.f"))
499 .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %I:%M:%S %P"))
500 .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %I:%M %P"))
501 .ok()
502 .map(|at_tz| at_tz.with_timezone(&Utc))
503 .map(Ok)
504 }
505
506 #[inline]
520 fn slash_dmy_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
521 let re: &Regex = regex! {
522 r"^\d{1,2}/\d{1,2}/\d{2,4}\s+\d{1,2}:\d{2}(:\d{2})?(\.\d{1,9})?\s*(am|pm|AM|PM)?$"
523 };
524 if !re.is_match(input) {
525 return None;
526 }
527
528 self.tz
529 .datetime_from_str(input, "%d/%m/%y %H:%M:%S")
530 .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%y %H:%M"))
531 .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%y %H:%M:%S%.f"))
532 .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%y %I:%M:%S %P"))
533 .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%y %I:%M %P"))
534 .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%Y %H:%M:%S"))
535 .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%Y %H:%M"))
536 .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%Y %H:%M:%S%.f"))
537 .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%Y %I:%M:%S %P"))
538 .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%Y %I:%M %P"))
539 .ok()
540 .map(|at_tz| at_tz.with_timezone(&Utc))
541 .map(Ok)
542 }
543
544 #[inline]
550 fn slash_mdy(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
551 let re: &Regex = regex! {r"^\d{1,2}/\d{1,2}/\d{2,4}$"
552 };
553 if !re.is_match(input) {
554 return None;
555 }
556
557 let now = Utc::now()
558 .date()
559 .and_time(self.default_time)?
560 .with_timezone(self.tz);
561 NaiveDate::parse_from_str(input, "%m/%d/%y")
562 .or_else(|_| NaiveDate::parse_from_str(input, "%m/%d/%Y"))
563 .ok()
564 .map(|parsed| parsed.and_time(now.time()))
565 .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
566 .map(|at_tz| at_tz.with_timezone(&Utc))
567 .map(Ok)
568 }
569
570 #[inline]
576 fn slash_dmy(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
577 let re: &Regex = regex! {r"^[0-9]{1,2}/[0-9]{1,2}/[0-9]{2,4}$"
578 };
579 if !re.is_match(input) {
580 return None;
581 }
582
583 let now = Utc::now()
584 .date()
585 .and_time(self.default_time)?
586 .with_timezone(self.tz);
587 NaiveDate::parse_from_str(input, "%d/%m/%y")
588 .or_else(|_| NaiveDate::parse_from_str(input, "%d/%m/%Y"))
589 .ok()
590 .map(|parsed| parsed.and_time(now.time()))
591 .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
592 .map(|at_tz| at_tz.with_timezone(&Utc))
593 .map(Ok)
594 }
595
596 #[inline]
604 fn slash_ymd_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
605 let re: &Regex = regex! {
606 r"^[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}\s+[0-9]{1,2}:[0-9]{2}(:[0-9]{2})?(\.[0-9]{1,9})?\s*(am|pm|AM|PM)?$"
607 };
608 if !re.is_match(input) {
609 return None;
610 }
611
612 self.tz
613 .datetime_from_str(input, "%Y/%m/%d %H:%M:%S")
614 .or_else(|_| self.tz.datetime_from_str(input, "%Y/%m/%d %H:%M"))
615 .or_else(|_| self.tz.datetime_from_str(input, "%Y/%m/%d %H:%M:%S%.f"))
616 .or_else(|_| self.tz.datetime_from_str(input, "%Y/%m/%d %I:%M:%S %P"))
617 .or_else(|_| self.tz.datetime_from_str(input, "%Y/%m/%d %I:%M %P"))
618 .ok()
619 .map(|at_tz| at_tz.with_timezone(&Utc))
620 .map(Ok)
621 }
622
623 #[inline]
627 fn slash_ymd(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
628 let re: &Regex = regex! {r"^[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}$"
629 };
630 if !re.is_match(input) {
631 return None;
632 }
633
634 let now = Utc::now()
635 .date()
636 .and_time(self.default_time)?
637 .with_timezone(self.tz);
638 NaiveDate::parse_from_str(input, "%Y/%m/%d")
639 .ok()
640 .map(|parsed| parsed.and_time(now.time()))
641 .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
642 .map(|at_tz| at_tz.with_timezone(&Utc))
643 .map(Ok)
644 }
645}
646
647#[cfg(test)]
648mod tests {
649 use super::*;
650
651 #[test]
652 fn unix_timestamp() {
653 let parse = Parse::new(&Utc, Utc::now().time());
654
655 let test_cases = vec![
656 ("0", Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)),
657 ("0000000000", Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)),
658 ("0000000000000", Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)),
659 ("0000000000000000000", Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)),
660 ("-770172300", Utc.ymd(1945, 8, 5).and_hms(23, 15, 0)),
661 (
662 "1671673426.123456789",
663 Utc.ymd(2022, 12, 22).and_hms_nano(1, 43, 46, 123456768),
664 ),
665 ("1511648546", Utc.ymd(2017, 11, 25).and_hms(22, 22, 26)),
666 (
667 "1620036248.420",
668 Utc.ymd(2021, 5, 3).and_hms_milli(10, 4, 8, 420),
669 ),
670 (
671 "1620036248.717915136",
672 Utc.ymd(2021, 5, 3).and_hms_nano(10, 4, 8, 717915136),
673 ),
674 ];
675
676 for &(input, want) in test_cases.iter() {
677 assert_eq!(
678 parse.unix_timestamp(input).unwrap().unwrap(),
679 want,
680 "unix_timestamp/{}",
681 input
682 )
683 }
684 assert!(parse.unix_timestamp("15116").is_some());
685 assert!(
686 parse
687 .unix_timestamp("16200248727179150001620024872717915000") .is_some()
689 );
690 assert!(parse.unix_timestamp("not-a-ts").is_none());
691 }
692
693 #[test]
694 fn rfc3339() {
695 let parse = Parse::new(&Utc, Utc::now().time());
696
697 let test_cases = [
698 (
699 "2021-05-01T01:17:02.604456Z",
700 Utc.ymd(2021, 5, 1).and_hms_nano(1, 17, 2, 604456000),
701 ),
702 (
703 "2017-11-25T22:34:50Z",
704 Utc.ymd(2017, 11, 25).and_hms(22, 34, 50),
705 ),
706 ];
707
708 for &(input, want) in test_cases.iter() {
709 assert_eq!(
710 parse.rfc3339(input).unwrap().unwrap(),
711 want,
712 "rfc3339/{}",
713 input
714 )
715 }
716 assert!(parse.rfc3339("2017-11-25 22:34:50").is_none());
717 assert!(parse.rfc3339("not-date-time").is_none());
718 }
719
720 #[test]
721 fn rfc2822() {
722 let parse = Parse::new(&Utc, Utc::now().time());
723
724 let test_cases = [
725 (
726 "Wed, 02 Jun 2021 06:31:39 GMT",
727 Utc.ymd(2021, 6, 2).and_hms(6, 31, 39),
728 ),
729 (
730 "Wed, 02 Jun 2021 06:31:39 PDT",
731 Utc.ymd(2021, 6, 2).and_hms(13, 31, 39),
732 ),
733 ];
734
735 for &(input, want) in test_cases.iter() {
736 assert_eq!(
737 parse.rfc2822(input).unwrap().unwrap(),
738 want,
739 "rfc2822/{}",
740 input
741 )
742 }
743 assert!(parse.rfc2822("02 Jun 2021 06:31:39").is_none());
744 assert!(parse.rfc2822("not-date-time").is_none());
745 }
746
747 #[test]
748 fn ymd_hms() {
749 let parse = Parse::new(&Utc, Utc::now().time());
750
751 let test_cases = vec![
752 ("2021-04-30 21:14", Utc.ymd(2021, 4, 30).and_hms(21, 14, 0)),
753 (
754 "2021-04-30 21:14:10",
755 Utc.ymd(2021, 4, 30).and_hms(21, 14, 10),
756 ),
757 (
758 "2021-04-30 21:14:10.052282",
759 Utc.ymd(2021, 4, 30).and_hms_micro(21, 14, 10, 52282),
760 ),
761 (
762 "2014-04-26 05:24:37 PM",
763 Utc.ymd(2014, 4, 26).and_hms(17, 24, 37),
764 ),
765 (
766 "2014-04-26 17:24:37.123",
767 Utc.ymd(2014, 4, 26).and_hms_milli(17, 24, 37, 123),
768 ),
769 (
770 "2014-04-26 17:24:37.3186369",
771 Utc.ymd(2014, 4, 26).and_hms_nano(17, 24, 37, 318636900),
772 ),
773 (
774 "2012-08-03 18:31:59.257000000",
775 Utc.ymd(2012, 8, 3).and_hms_nano(18, 31, 59, 257000000),
776 ),
777 ];
778
779 for &(input, want) in test_cases.iter() {
780 assert_eq!(
781 parse.ymd_hms(input).unwrap().unwrap(),
782 want,
783 "ymd_hms/{}",
784 input
785 )
786 }
787 assert!(parse.ymd_hms("not-date-time").is_none());
788 }
789
790 #[test]
791 fn ymd_hms_z() {
792 let parse = Parse::new(&Utc, Utc::now().time());
793
794 let test_cases = vec![
795 (
796 "2017-11-25 13:31:15 PST",
797 Utc.ymd(2017, 11, 25).and_hms(21, 31, 15),
798 ),
799 (
800 "2017-11-25 13:31 PST",
801 Utc.ymd(2017, 11, 25).and_hms(21, 31, 0),
802 ),
803 (
804 "2014-12-16 06:20:00 UTC",
805 Utc.ymd(2014, 12, 16).and_hms(6, 20, 0),
806 ),
807 (
808 "2014-12-16 06:20:00 GMT",
809 Utc.ymd(2014, 12, 16).and_hms(6, 20, 0),
810 ),
811 (
812 "2014-04-26 13:13:43 +0800",
813 Utc.ymd(2014, 4, 26).and_hms(5, 13, 43),
814 ),
815 (
816 "2014-04-26 13:13:44 +09:00",
817 Utc.ymd(2014, 4, 26).and_hms(4, 13, 44),
818 ),
819 (
820 "2012-08-03 18:31:59.257000000 +0000",
821 Utc.ymd(2012, 8, 3).and_hms_nano(18, 31, 59, 257000000),
822 ),
823 (
824 "2015-09-30 18:48:56.35272715 UTC",
825 Utc.ymd(2015, 9, 30).and_hms_nano(18, 48, 56, 352727150),
826 ),
827 ];
828
829 for &(input, want) in test_cases.iter() {
830 assert_eq!(
831 parse.ymd_hms_z(input).unwrap().unwrap(),
832 want,
833 "ymd_hms_z/{}",
834 input
835 )
836 }
837 assert!(parse.ymd_hms_z("not-date-time").is_none());
838 }
839
840 #[test]
841 fn ymd() {
842 let parse = Parse::new(&Utc, Utc::now().time());
843
844 let test_cases = [(
845 "2021-02-21",
846 Utc.ymd(2021, 2, 21).and_time(Utc::now().time()),
847 )];
848
849 for &(input, want) in test_cases.iter() {
850 assert_eq!(
851 parse
852 .ymd(input)
853 .unwrap()
854 .unwrap()
855 .trunc_subsecs(0)
856 .with_second(0)
857 .unwrap(),
858 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
859 "ymd/{}",
860 input
861 )
862 }
863 assert!(parse.ymd("not-date-time").is_none());
864 }
865
866 #[test]
867 fn ymd_z() {
868 let parse = Parse::new(&Utc, Utc::now().time());
869 let now_at_pst = Utc::now().with_timezone(&FixedOffset::west(8 * 3600));
870 let now_at_cst = Utc::now().with_timezone(&FixedOffset::east(8 * 3600));
871
872 let test_cases = [
873 (
874 "2021-02-21 PST",
875 FixedOffset::west(8 * 3600)
876 .ymd(2021, 2, 21)
877 .and_time(now_at_pst.time())
878 .map(|dt| dt.with_timezone(&Utc)),
879 ),
880 (
881 "2021-02-21 UTC",
882 FixedOffset::west(0)
883 .ymd(2021, 2, 21)
884 .and_time(Utc::now().time())
885 .map(|dt| dt.with_timezone(&Utc)),
886 ),
887 (
888 "2020-07-20+08:00",
889 FixedOffset::east(8 * 3600)
890 .ymd(2020, 7, 20)
891 .and_time(now_at_cst.time())
892 .map(|dt| dt.with_timezone(&Utc)),
893 ),
894 ];
895
896 for &(input, want) in test_cases.iter() {
897 assert_eq!(
898 parse
899 .ymd_z(input)
900 .unwrap()
901 .unwrap()
902 .trunc_subsecs(0)
903 .with_second(0)
904 .unwrap(),
905 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
906 "ymd_z/{}",
907 input
908 )
909 }
910 assert!(parse.ymd_z("not-date-time").is_none());
911 }
912
913 #[test]
914 fn month_ymd() {
915 let parse = Parse::new(&Utc, Utc::now().time());
916
917 let test_cases = [(
918 "2021-Feb-21",
919 Utc.ymd(2021, 2, 21).and_time(Utc::now().time()),
920 )];
921
922 for &(input, want) in test_cases.iter() {
923 assert_eq!(
924 parse
925 .month_ymd(input)
926 .unwrap()
927 .unwrap()
928 .trunc_subsecs(0)
929 .with_second(0)
930 .unwrap(),
931 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
932 "month_ymd/{}",
933 input
934 )
935 }
936 assert!(parse.month_ymd("not-date-time").is_none());
937 }
938
939 #[test]
940 fn month_mdy_hms() {
941 let parse = Parse::new(&Utc, Utc::now().time());
942
943 let test_cases = [
944 (
945 "May 8, 2009 5:57:51 PM",
946 Utc.ymd(2009, 5, 8).and_hms(17, 57, 51),
947 ),
948 (
949 "September 17, 2012 10:09am",
950 Utc.ymd(2012, 9, 17).and_hms(10, 9, 0),
951 ),
952 (
953 "September 17, 2012, 10:10:09",
954 Utc.ymd(2012, 9, 17).and_hms(10, 10, 9),
955 ),
956 ];
957
958 for &(input, want) in test_cases.iter() {
959 assert_eq!(
960 parse.month_mdy_hms(input).unwrap().unwrap(),
961 want,
962 "month_mdy_hms/{}",
963 input
964 )
965 }
966 assert!(parse.month_mdy_hms("not-date-time").is_none());
967 }
968
969 #[test]
970 fn month_mdy_hms_z() {
971 let parse = Parse::new(&Utc, Utc::now().time());
972
973 let test_cases = [
974 (
975 "May 02, 2021 15:51:31 UTC",
976 Utc.ymd(2021, 5, 2).and_hms(15, 51, 31),
977 ),
978 (
979 "May 02, 2021 15:51 UTC",
980 Utc.ymd(2021, 5, 2).and_hms(15, 51, 0),
981 ),
982 (
983 "May 26, 2021, 12:49 AM PDT",
984 Utc.ymd(2021, 5, 26).and_hms(7, 49, 0),
985 ),
986 (
987 "September 17, 2012 at 10:09am PST",
988 Utc.ymd(2012, 9, 17).and_hms(18, 9, 0),
989 ),
990 ];
991
992 for &(input, want) in test_cases.iter() {
993 assert_eq!(
994 parse.month_mdy_hms_z(input).unwrap().unwrap(),
995 want,
996 "month_mdy_hms_z/{}",
997 input
998 )
999 }
1000 assert!(parse.month_mdy_hms_z("not-date-time").is_none());
1001 }
1002
1003 #[test]
1004 fn month_mdy() {
1005 let parse = Parse::new(&Utc, Utc::now().time());
1006
1007 let test_cases = [
1008 (
1009 "May 25, 2021",
1010 Utc.ymd(2021, 5, 25).and_time(Utc::now().time()),
1011 ),
1012 (
1013 "oct 7, 1970",
1014 Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1015 ),
1016 (
1017 "oct 7, 70",
1018 Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1019 ),
1020 (
1021 "oct. 7, 1970",
1022 Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1023 ),
1024 (
1025 "oct. 7, 70",
1026 Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1027 ),
1028 (
1029 "October 7, 1970",
1030 Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1031 ),
1032 ];
1033
1034 for &(input, want) in test_cases.iter() {
1035 assert_eq!(
1036 parse
1037 .month_mdy(input)
1038 .unwrap()
1039 .unwrap()
1040 .trunc_subsecs(0)
1041 .with_second(0)
1042 .unwrap(),
1043 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1044 "month_mdy/{}",
1045 input
1046 )
1047 }
1048 assert!(parse.month_mdy("not-date-time").is_none());
1049 }
1050
1051 #[test]
1052 fn month_dmy_hms() {
1053 let parse = Parse::new(&Utc, Utc::now().time());
1054
1055 let test_cases = [
1056 (
1057 "12 Feb 2006, 19:17",
1058 Utc.ymd(2006, 2, 12).and_hms(19, 17, 0),
1059 ),
1060 ("12 Feb 2006 19:17", Utc.ymd(2006, 2, 12).and_hms(19, 17, 0)),
1061 (
1062 "14 May 2019 19:11:40.164",
1063 Utc.ymd(2019, 5, 14).and_hms_milli(19, 11, 40, 164),
1064 ),
1065 ];
1066
1067 for &(input, want) in test_cases.iter() {
1068 assert_eq!(
1069 parse.month_dmy_hms(input).unwrap().unwrap(),
1070 want,
1071 "month_dmy_hms/{}",
1072 input
1073 )
1074 }
1075 assert!(parse.month_dmy_hms("not-date-time").is_none());
1076 }
1077
1078 #[test]
1079 fn month_dmy() {
1080 let parse = Parse::new(&Utc, Utc::now().time());
1081
1082 let test_cases = [
1083 ("7 oct 70", Utc.ymd(1970, 10, 7).and_time(Utc::now().time())),
1084 (
1085 "7 oct 1970",
1086 Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1087 ),
1088 (
1089 "03 February 2013",
1090 Utc.ymd(2013, 2, 3).and_time(Utc::now().time()),
1091 ),
1092 (
1093 "1 July 2013",
1094 Utc.ymd(2013, 7, 1).and_time(Utc::now().time()),
1095 ),
1096 ];
1097
1098 for &(input, want) in test_cases.iter() {
1099 assert_eq!(
1100 parse
1101 .month_dmy(input)
1102 .unwrap()
1103 .unwrap()
1104 .trunc_subsecs(0)
1105 .with_second(0)
1106 .unwrap(),
1107 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1108 "month_dmy/{}",
1109 input
1110 )
1111 }
1112 assert!(parse.month_dmy("not-date-time").is_none());
1113 }
1114
1115 #[test]
1116 fn slash_mdy_hms() {
1117 let parse = Parse::new(&Utc, Utc::now().time());
1118
1119 let test_cases = vec![
1120 ("4/8/2014 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
1121 ("04/08/2014 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
1122 ("4/8/14 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
1123 ("04/2/2014 03:00:51", Utc.ymd(2014, 4, 2).and_hms(3, 0, 51)),
1124 ("8/8/1965 12:00:00 AM", Utc.ymd(1965, 8, 8).and_hms(0, 0, 0)),
1125 (
1126 "8/8/1965 01:00:01 PM",
1127 Utc.ymd(1965, 8, 8).and_hms(13, 0, 1),
1128 ),
1129 ("8/8/1965 01:00 PM", Utc.ymd(1965, 8, 8).and_hms(13, 0, 0)),
1130 ("8/8/1965 1:00 PM", Utc.ymd(1965, 8, 8).and_hms(13, 0, 0)),
1131 ("8/8/1965 12:00 AM", Utc.ymd(1965, 8, 8).and_hms(0, 0, 0)),
1132 ("4/02/2014 03:00:51", Utc.ymd(2014, 4, 2).and_hms(3, 0, 51)),
1133 (
1134 "03/19/2012 10:11:59",
1135 Utc.ymd(2012, 3, 19).and_hms(10, 11, 59),
1136 ),
1137 (
1138 "03/19/2012 10:11:59.3186369",
1139 Utc.ymd(2012, 3, 19).and_hms_nano(10, 11, 59, 318636900),
1140 ),
1141 ];
1142
1143 for &(input, want) in test_cases.iter() {
1144 assert_eq!(
1145 parse.slash_mdy_hms(input).unwrap().unwrap(),
1146 want,
1147 "slash_mdy_hms/{}",
1148 input
1149 )
1150 }
1151 assert!(parse.slash_mdy_hms("not-date-time").is_none());
1152 }
1153
1154 #[test]
1155 fn slash_mdy() {
1156 let parse = Parse::new(&Utc, Utc::now().time());
1157
1158 let test_cases = [
1159 (
1160 "3/31/2014",
1161 Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
1162 ),
1163 (
1164 "03/31/2014",
1165 Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
1166 ),
1167 ("08/21/71", Utc.ymd(1971, 8, 21).and_time(Utc::now().time())),
1168 ("8/1/71", Utc.ymd(1971, 8, 1).and_time(Utc::now().time())),
1169 ];
1170
1171 for &(input, want) in test_cases.iter() {
1172 assert_eq!(
1173 parse
1174 .slash_mdy(input)
1175 .unwrap()
1176 .unwrap()
1177 .trunc_subsecs(0)
1178 .with_second(0)
1179 .unwrap(),
1180 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1181 "slash_mdy/{}",
1182 input
1183 )
1184 }
1185 assert!(parse.slash_mdy("not-date-time").is_none());
1186 }
1187
1188 #[test]
1189 fn slash_dmy() {
1190 let mut parse = Parse::new(&Utc, Utc::now().time());
1191
1192 let test_cases = [
1193 (
1194 "31/3/2014",
1195 Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
1196 ),
1197 (
1198 "13/11/2014",
1199 Utc.ymd(2014, 11, 13).and_time(Utc::now().time()),
1200 ),
1201 ("21/08/71", Utc.ymd(1971, 8, 21).and_time(Utc::now().time())),
1202 ("1/8/71", Utc.ymd(1971, 8, 1).and_time(Utc::now().time())),
1203 ];
1204
1205 for &(input, want) in test_cases.iter() {
1206 assert_eq!(
1207 parse
1208 .prefer_dmy(true)
1209 .slash_dmy(input)
1210 .unwrap()
1211 .unwrap()
1212 .trunc_subsecs(0)
1213 .with_second(0)
1214 .unwrap(),
1215 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1216 "slash_dmy/{}",
1217 input
1218 )
1219 }
1220 assert!(parse.slash_dmy("not-date-time").is_none());
1221 }
1222
1223 #[test]
1224 fn slash_ymd_hms() {
1225 let parse = Parse::new(&Utc, Utc::now().time());
1226
1227 let test_cases = [
1228 ("2014/4/8 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
1229 ("2014/04/08 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
1230 ("2014/04/2 03:00:51", Utc.ymd(2014, 4, 2).and_hms(3, 0, 51)),
1231 ("2014/4/02 03:00:51", Utc.ymd(2014, 4, 2).and_hms(3, 0, 51)),
1232 (
1233 "2012/03/19 10:11:59",
1234 Utc.ymd(2012, 3, 19).and_hms(10, 11, 59),
1235 ),
1236 (
1237 "2012/03/19 10:11:59.3186369",
1238 Utc.ymd(2012, 3, 19).and_hms_nano(10, 11, 59, 318636900),
1239 ),
1240 ];
1241
1242 for &(input, want) in test_cases.iter() {
1243 assert_eq!(
1244 parse.slash_ymd_hms(input).unwrap().unwrap(),
1245 want,
1246 "slash_ymd_hms/{}",
1247 input
1248 )
1249 }
1250 assert!(parse.slash_ymd_hms("not-date-time").is_none());
1251 }
1252
1253 #[test]
1254 fn slash_ymd() {
1255 let parse = Parse::new(&Utc, Utc::now().time());
1256
1257 let test_cases = [
1258 (
1259 "2014/3/31",
1260 Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
1261 ),
1262 (
1263 "2014/03/31",
1264 Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
1265 ),
1266 ];
1267
1268 for &(input, want) in test_cases.iter() {
1269 assert_eq!(
1270 parse
1271 .slash_ymd(input)
1272 .unwrap()
1273 .unwrap()
1274 .trunc_subsecs(0)
1275 .with_second(0)
1276 .unwrap(),
1277 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1278 "slash_ymd/{}",
1279 input
1280 )
1281 }
1282 assert!(parse.slash_ymd("not-date-time").is_none());
1283 }
1284}