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]
196 fn ymd_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
197 let re: &Regex = regex! {
198 r"^\d{4}-\d{2}-\d{2}[T\s]+\d{2}:\d{2}(:\d{2})?(\.\d{1,9})?\s*(am|pm|AM|PM)?$"
199
200 };
201 if !re.is_match(input) {
202 return None;
203 }
204
205 let (fmt_hms, fmt_hm, fmt_hms_f, fmt_ims_p, fmt_im_p) = if input.as_bytes()[10] == b'T' {
210 (
211 "%Y-%m-%dT%H:%M:%S",
212 "%Y-%m-%dT%H:%M",
213 "%Y-%m-%dT%H:%M:%S%.f",
214 "%Y-%m-%dT%I:%M:%S %P",
215 "%Y-%m-%dT%I:%M %P",
216 )
217 } else {
218 (
219 "%Y-%m-%d %H:%M:%S",
220 "%Y-%m-%d %H:%M",
221 "%Y-%m-%d %H:%M:%S%.f",
222 "%Y-%m-%d %I:%M:%S %P",
223 "%Y-%m-%d %I:%M %P",
224 )
225 };
226
227 self.tz
228 .datetime_from_str(input, fmt_hms)
229 .or_else(|_| self.tz.datetime_from_str(input, fmt_hm))
230 .or_else(|_| self.tz.datetime_from_str(input, fmt_hms_f))
231 .or_else(|_| self.tz.datetime_from_str(input, fmt_ims_p))
232 .or_else(|_| self.tz.datetime_from_str(input, fmt_im_p))
233 .ok()
234 .map(|parsed| parsed.with_timezone(&Utc))
235 .map(Ok)
236 }
237
238 #[inline]
248 fn ymd_hms_z(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
249 if input.len() < 17 || !input.as_bytes()[10].is_ascii_whitespace() {
251 return None;
252 }
253 let re: &Regex = regex! {
254 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})$"
255 };
256
257 if let Some(caps) = re.captures(input)
258 && let Some(matched_tz) = caps.name("tz")
259 {
260 let parse_from_str = NaiveDateTime::parse_from_str;
261 return match timezone::parse(matched_tz.as_str().trim()) {
262 Ok(offset) => parse_from_str(input, "%Y-%m-%d %H:%M:%S %Z")
263 .or_else(|_| parse_from_str(input, "%Y-%m-%d %H:%M %Z"))
264 .or_else(|_| parse_from_str(input, "%Y-%m-%d %H:%M:%S%.f %Z"))
265 .ok()
266 .and_then(|parsed| offset.from_local_datetime(&parsed).single())
267 .map(|datetime| datetime.with_timezone(&Utc))
268 .map(Ok),
269 Err(err) => Some(Err(err)),
270 };
271 }
272 None
273 }
274
275 #[inline]
278 fn ymd(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
279 let re: &Regex = regex! {r"^\d{4}-\d{2}-\d{2}$"
280 };
281
282 if !re.is_match(input) {
283 return None;
284 }
285 let now = Utc::now()
286 .date()
287 .and_time(self.default_time)?
288 .with_timezone(self.tz);
289 NaiveDate::parse_from_str(input, "%Y-%m-%d")
290 .ok()
291 .map(|parsed| parsed.and_time(now.time()))
292 .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
293 .map(|at_tz| at_tz.with_timezone(&Utc))
294 .map(Ok)
295 }
296
297 #[inline]
302 fn ymd_z(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
303 if input.len() <= 10 {
305 return None;
306 }
307 let re: &Regex = regex! {r"^\d{4}-\d{2}-\d{2}(?P<tz>\s*[+-:a-zA-Z0-9]{3,6})$"
308 };
309 if let Some(caps) = re.captures(input)
310 && let Some(matched_tz) = caps.name("tz")
311 {
312 return match timezone::parse(matched_tz.as_str().trim()) {
313 Ok(offset) => {
314 let now = Utc::now()
315 .date()
316 .and_time(self.default_time)?
317 .with_timezone(&offset);
318 NaiveDate::parse_from_str(input, "%Y-%m-%d %Z")
319 .ok()
320 .map(|parsed| parsed.and_time(now.time()))
321 .and_then(|datetime| offset.from_local_datetime(&datetime).single())
322 .map(|at_tz| at_tz.with_timezone(&Utc))
323 .map(Ok)
324 }
325 Err(err) => Some(Err(err)),
326 };
327 }
328 None
329 }
330
331 #[inline]
334 fn month_ymd(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
335 let re: &Regex = regex! {r"^\d{4}-\w{3,9}-\d{2}$"
336 };
337 if !re.is_match(input) {
338 return None;
339 }
340
341 let now = Utc::now()
342 .date()
343 .and_time(self.default_time)?
344 .with_timezone(self.tz);
345 NaiveDate::parse_from_str(input, "%Y-%m-%d")
346 .or_else(|_| NaiveDate::parse_from_str(input, "%Y-%b-%d"))
347 .ok()
348 .map(|parsed| parsed.and_time(now.time()))
349 .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
350 .map(|at_tz| at_tz.with_timezone(&Utc))
351 .map(Ok)
352 }
353
354 #[inline]
359 fn month_mdy_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
360 let re: &Regex = regex! {
361 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)?$"
362 };
363 if !re.is_match(input) {
364 return None;
365 }
366
367 let dt = input.replace([',', '.'], "");
372 self.tz
373 .datetime_from_str(&dt, "%B %d %Y %H:%M:%S")
374 .or_else(|_| self.tz.datetime_from_str(&dt, "%B %d %Y %H:%M"))
375 .or_else(|_| self.tz.datetime_from_str(&dt, "%B %d %Y %I:%M:%S %P"))
376 .or_else(|_| self.tz.datetime_from_str(&dt, "%B %d %Y %I:%M %P"))
377 .ok()
378 .map(|at_tz| at_tz.with_timezone(&Utc))
379 .map(Ok)
380 }
381
382 #[inline]
388 fn month_mdy_hms_z(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
389 if input.len() < 20 {
392 return None;
393 }
394 let bytes = input.as_bytes();
395 let has_year = (0..bytes.len().saturating_sub(3)).any(|i| {
396 bytes[i..i + 4].iter().all(|b| b.is_ascii_digit())
397 && (i == 0 || !bytes[i - 1].is_ascii_digit())
398 && bytes.get(i + 4).is_none_or(|b| !b.is_ascii_digit())
399 });
400 if !has_year {
401 return None;
402 }
403 let re: &Regex = regex! {
404 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})$",
405 };
406 if let Some(caps) = re.captures(input)
407 && let Some(matched_tz) = caps.name("tz")
408 {
409 let parse_from_str = NaiveDateTime::parse_from_str;
410 return match timezone::parse(matched_tz.as_str().trim()) {
411 Ok(offset) => {
412 let mut dt = input.replace(',', "");
413 if let Some(pos) = dt.find("at") {
414 dt.replace_range(pos..pos + 2, "");
415 }
416 parse_from_str(&dt, "%B %d %Y %H:%M:%S %Z")
417 .or_else(|_| parse_from_str(&dt, "%B %d %Y %H:%M %Z"))
418 .or_else(|_| parse_from_str(&dt, "%B %d %Y %I:%M:%S %P %Z"))
419 .or_else(|_| parse_from_str(&dt, "%B %d %Y %I:%M %P %Z"))
420 .ok()
421 .and_then(|parsed| offset.from_local_datetime(&parsed).single())
422 .map(|datetime| datetime.with_timezone(&Utc))
423 .map(Ok)
424 }
425 Err(err) => Some(Err(err)),
426 };
427 }
428 None
429 }
430
431 #[inline]
439 fn month_mdy(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
440 let re: &Regex = regex! {r"^[a-zA-Z]{3,9}\.?\s+\d{1,2},\s+\d{2,4}$"
441 };
442 if !re.is_match(input) {
443 return None;
444 }
445
446 let now = Utc::now()
447 .date()
448 .and_time(self.default_time)?
449 .with_timezone(self.tz);
450 let dt = input.replace([',', '.'], "");
454 NaiveDate::parse_from_str(&dt, "%B %d %y")
455 .or_else(|_| NaiveDate::parse_from_str(&dt, "%B %d %Y"))
456 .ok()
457 .map(|parsed| parsed.and_time(now.time()))
458 .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
459 .map(|at_tz| at_tz.with_timezone(&Utc))
460 .map(Ok)
461 }
462
463 #[inline]
468 fn month_dmy_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
469 if !input.as_bytes().contains(&b':') {
471 return None;
472 }
473 let re: &Regex = regex! {
474 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})?$"
475 };
476 if !re.is_match(input) {
477 return None;
478 }
479
480 let dt = input.replace(',', "");
481 self.tz
482 .datetime_from_str(&dt, "%d %B %Y %H:%M:%S")
483 .or_else(|_| self.tz.datetime_from_str(&dt, "%d %B %Y %H:%M"))
484 .or_else(|_| self.tz.datetime_from_str(&dt, "%d %B %Y %H:%M:%S%.f"))
485 .or_else(|_| self.tz.datetime_from_str(&dt, "%d %B %Y %I:%M:%S %P"))
486 .or_else(|_| self.tz.datetime_from_str(&dt, "%d %B %Y %I:%M %P"))
487 .ok()
488 .map(|at_tz| at_tz.with_timezone(&Utc))
489 .map(Ok)
490 }
491
492 #[inline]
498 fn month_dmy(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
499 let re: &Regex = regex! {r"^\d{1,2}\s+[a-zA-Z]{3,9}\s+\d{2,4}$"
500 };
501 if !re.is_match(input) {
502 return None;
503 }
504
505 let now = Utc::now()
506 .date()
507 .and_time(self.default_time)?
508 .with_timezone(self.tz);
509 let bytes = input.as_bytes();
512 let len = bytes.len();
513 let four_digit_year = len >= 5
514 && bytes[len - 4..].iter().all(|b| b.is_ascii_digit())
515 && bytes[len - 5].is_ascii_whitespace();
516 let parsed = if four_digit_year {
517 NaiveDate::parse_from_str(input, "%d %B %Y")
518 } else {
519 NaiveDate::parse_from_str(input, "%d %B %y")
520 .or_else(|_| NaiveDate::parse_from_str(input, "%d %B %Y"))
521 };
522 parsed
523 .ok()
524 .map(|parsed| parsed.and_time(now.time()))
525 .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
526 .map(|at_tz| at_tz.with_timezone(&Utc))
527 .map(Ok)
528 }
529
530 #[inline]
544 fn slash_mdy_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
545 let re: &Regex = regex! {
546 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)?$"
547 };
548 if !re.is_match(input) {
549 return None;
550 }
551
552 self.tz
553 .datetime_from_str(input, "%m/%d/%y %H:%M:%S")
554 .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%y %H:%M"))
555 .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%y %H:%M:%S%.f"))
556 .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%y %I:%M:%S %P"))
557 .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%y %I:%M %P"))
558 .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %H:%M:%S"))
559 .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %H:%M"))
560 .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %H:%M:%S%.f"))
561 .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %I:%M:%S %P"))
562 .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %I:%M %P"))
563 .ok()
564 .map(|at_tz| at_tz.with_timezone(&Utc))
565 .map(Ok)
566 }
567
568 #[inline]
582 fn slash_dmy_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
583 let re: &Regex = regex! {
584 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)?$"
585 };
586 if !re.is_match(input) {
587 return None;
588 }
589
590 self.tz
591 .datetime_from_str(input, "%d/%m/%y %H:%M:%S")
592 .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%y %H:%M"))
593 .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%y %H:%M:%S%.f"))
594 .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%y %I:%M:%S %P"))
595 .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%y %I:%M %P"))
596 .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%Y %H:%M:%S"))
597 .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%Y %H:%M"))
598 .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%Y %H:%M:%S%.f"))
599 .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%Y %I:%M:%S %P"))
600 .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%Y %I:%M %P"))
601 .ok()
602 .map(|at_tz| at_tz.with_timezone(&Utc))
603 .map(Ok)
604 }
605
606 #[inline]
612 fn slash_mdy(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
613 let re: &Regex = regex! {r"^\d{1,2}/\d{1,2}/\d{2,4}$"
614 };
615 if !re.is_match(input) {
616 return None;
617 }
618
619 let now = Utc::now()
620 .date()
621 .and_time(self.default_time)?
622 .with_timezone(self.tz);
623 NaiveDate::parse_from_str(input, "%m/%d/%y")
624 .or_else(|_| NaiveDate::parse_from_str(input, "%m/%d/%Y"))
625 .ok()
626 .map(|parsed| parsed.and_time(now.time()))
627 .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
628 .map(|at_tz| at_tz.with_timezone(&Utc))
629 .map(Ok)
630 }
631
632 #[inline]
638 fn slash_dmy(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
639 let re: &Regex = regex! {r"^[0-9]{1,2}/[0-9]{1,2}/[0-9]{2,4}$"
640 };
641 if !re.is_match(input) {
642 return None;
643 }
644
645 let now = Utc::now()
646 .date()
647 .and_time(self.default_time)?
648 .with_timezone(self.tz);
649 NaiveDate::parse_from_str(input, "%d/%m/%y")
650 .or_else(|_| NaiveDate::parse_from_str(input, "%d/%m/%Y"))
651 .ok()
652 .map(|parsed| parsed.and_time(now.time()))
653 .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
654 .map(|at_tz| at_tz.with_timezone(&Utc))
655 .map(Ok)
656 }
657
658 #[inline]
666 fn slash_ymd_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
667 let re: &Regex = regex! {
668 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)?$"
669 };
670 if !re.is_match(input) {
671 return None;
672 }
673
674 self.tz
675 .datetime_from_str(input, "%Y/%m/%d %H:%M:%S")
676 .or_else(|_| self.tz.datetime_from_str(input, "%Y/%m/%d %H:%M"))
677 .or_else(|_| self.tz.datetime_from_str(input, "%Y/%m/%d %H:%M:%S%.f"))
678 .or_else(|_| self.tz.datetime_from_str(input, "%Y/%m/%d %I:%M:%S %P"))
679 .or_else(|_| self.tz.datetime_from_str(input, "%Y/%m/%d %I:%M %P"))
680 .ok()
681 .map(|at_tz| at_tz.with_timezone(&Utc))
682 .map(Ok)
683 }
684
685 #[inline]
689 fn slash_ymd(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
690 let re: &Regex = regex! {r"^[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}$"
691 };
692 if !re.is_match(input) {
693 return None;
694 }
695
696 let now = Utc::now()
697 .date()
698 .and_time(self.default_time)?
699 .with_timezone(self.tz);
700 NaiveDate::parse_from_str(input, "%Y/%m/%d")
701 .ok()
702 .map(|parsed| parsed.and_time(now.time()))
703 .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
704 .map(|at_tz| at_tz.with_timezone(&Utc))
705 .map(Ok)
706 }
707}
708
709#[cfg(test)]
710mod tests {
711 use super::*;
712
713 #[test]
714 fn unix_timestamp() {
715 let parse = Parse::new(&Utc, Utc::now().time());
716
717 let test_cases = vec![
718 ("0", Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)),
719 ("0000000000", Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)),
720 ("0000000000000", Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)),
721 ("0000000000000000000", Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)),
722 ("-770172300", Utc.ymd(1945, 8, 5).and_hms(23, 15, 0)),
723 (
724 "1671673426.123456789",
725 Utc.ymd(2022, 12, 22).and_hms_nano(1, 43, 46, 123456768),
726 ),
727 ("1511648546", Utc.ymd(2017, 11, 25).and_hms(22, 22, 26)),
728 (
729 "1620036248.420",
730 Utc.ymd(2021, 5, 3).and_hms_milli(10, 4, 8, 420),
731 ),
732 (
733 "1620036248.717915136",
734 Utc.ymd(2021, 5, 3).and_hms_nano(10, 4, 8, 717915136),
735 ),
736 ];
737
738 for &(input, want) in test_cases.iter() {
739 assert_eq!(
740 parse.unix_timestamp(input).unwrap().unwrap(),
741 want,
742 "unix_timestamp/{}",
743 input
744 )
745 }
746 assert!(parse.unix_timestamp("15116").is_some());
747 assert!(
748 parse
749 .unix_timestamp("16200248727179150001620024872717915000") .is_some()
751 );
752 assert!(parse.unix_timestamp("not-a-ts").is_none());
753 }
754
755 #[test]
756 fn rfc3339() {
757 let parse = Parse::new(&Utc, Utc::now().time());
758
759 let test_cases = [
760 (
761 "2021-05-01T01:17:02.604456Z",
762 Utc.ymd(2021, 5, 1).and_hms_nano(1, 17, 2, 604456000),
763 ),
764 (
765 "2017-11-25T22:34:50Z",
766 Utc.ymd(2017, 11, 25).and_hms(22, 34, 50),
767 ),
768 ];
769
770 for &(input, want) in test_cases.iter() {
771 assert_eq!(
772 parse.rfc3339(input).unwrap().unwrap(),
773 want,
774 "rfc3339/{}",
775 input
776 )
777 }
778 assert!(parse.rfc3339("2017-11-25 22:34:50").is_none());
779 assert!(parse.rfc3339("not-date-time").is_none());
780 }
781
782 #[test]
783 fn rfc2822() {
784 let parse = Parse::new(&Utc, Utc::now().time());
785
786 let test_cases = [
787 (
788 "Wed, 02 Jun 2021 06:31:39 GMT",
789 Utc.ymd(2021, 6, 2).and_hms(6, 31, 39),
790 ),
791 (
792 "Wed, 02 Jun 2021 06:31:39 PDT",
793 Utc.ymd(2021, 6, 2).and_hms(13, 31, 39),
794 ),
795 ];
796
797 for &(input, want) in test_cases.iter() {
798 assert_eq!(
799 parse.rfc2822(input).unwrap().unwrap(),
800 want,
801 "rfc2822/{}",
802 input
803 )
804 }
805 assert!(parse.rfc2822("02 Jun 2021 06:31:39").is_none());
806 assert!(parse.rfc2822("not-date-time").is_none());
807 }
808
809 #[test]
810 fn ymd_hms() {
811 let parse = Parse::new(&Utc, Utc::now().time());
812
813 let test_cases = [
814 ("2021-04-30 21:14", Utc.ymd(2021, 4, 30).and_hms(21, 14, 0)),
815 (
816 "2021-04-30 21:14:10",
817 Utc.ymd(2021, 4, 30).and_hms(21, 14, 10),
818 ),
819 (
820 "2021-04-30 21:14:10.052282",
821 Utc.ymd(2021, 4, 30).and_hms_micro(21, 14, 10, 52282),
822 ),
823 (
824 "2014-04-26 05:24:37 PM",
825 Utc.ymd(2014, 4, 26).and_hms(17, 24, 37),
826 ),
827 (
828 "2014-04-26 17:24:37.123",
829 Utc.ymd(2014, 4, 26).and_hms_milli(17, 24, 37, 123),
830 ),
831 (
832 "2014-04-26 17:24:37.3186369",
833 Utc.ymd(2014, 4, 26).and_hms_nano(17, 24, 37, 318636900),
834 ),
835 (
836 "2012-08-03 18:31:59.257000000",
837 Utc.ymd(2012, 8, 3).and_hms_nano(18, 31, 59, 257000000),
838 ),
839 ("2020-01-15T08:00", Utc.ymd(2020, 1, 15).and_hms(8, 0, 0)),
842 ("2020-01-15T08:00:00", Utc.ymd(2020, 1, 15).and_hms(8, 0, 0)),
843 (
844 "2020-01-15T08:00:00.123",
845 Utc.ymd(2020, 1, 15).and_hms_milli(8, 0, 0, 123),
846 ),
847 (
848 "2020-01-15T08:00:00.123456",
849 Utc.ymd(2020, 1, 15).and_hms_micro(8, 0, 0, 123456),
850 ),
851 (
852 "2020-01-15T08:00:00.123456789",
853 Utc.ymd(2020, 1, 15).and_hms_nano(8, 0, 0, 123456789),
854 ),
855 ];
856
857 for &(input, want) in test_cases.iter() {
858 assert_eq!(
859 parse.ymd_hms(input).unwrap().unwrap(),
860 want,
861 "ymd_hms/{}",
862 input
863 )
864 }
865 assert!(parse.ymd_hms("not-date-time").is_none());
866
867 let t_form = parse.ymd_hms("2020-01-15T08:00:00").unwrap().unwrap();
869 let space_form = parse.ymd_hms("2020-01-15 08:00:00").unwrap().unwrap();
870 assert_eq!(t_form, space_form, "T-separator vs space disagree");
871 }
872
873 #[test]
874 fn ymd_hms_z() {
875 let parse = Parse::new(&Utc, Utc::now().time());
876
877 let test_cases = [
878 (
879 "2017-11-25 13:31:15 PST",
880 Utc.ymd(2017, 11, 25).and_hms(21, 31, 15),
881 ),
882 (
883 "2017-11-25 13:31 PST",
884 Utc.ymd(2017, 11, 25).and_hms(21, 31, 0),
885 ),
886 (
887 "2014-12-16 06:20:00 UTC",
888 Utc.ymd(2014, 12, 16).and_hms(6, 20, 0),
889 ),
890 (
891 "2014-12-16 06:20:00 GMT",
892 Utc.ymd(2014, 12, 16).and_hms(6, 20, 0),
893 ),
894 (
895 "2014-04-26 13:13:43 +0800",
896 Utc.ymd(2014, 4, 26).and_hms(5, 13, 43),
897 ),
898 (
899 "2014-04-26 13:13:44 +09:00",
900 Utc.ymd(2014, 4, 26).and_hms(4, 13, 44),
901 ),
902 (
903 "2012-08-03 18:31:59.257000000 +0000",
904 Utc.ymd(2012, 8, 3).and_hms_nano(18, 31, 59, 257000000),
905 ),
906 (
907 "2015-09-30 18:48:56.35272715 UTC",
908 Utc.ymd(2015, 9, 30).and_hms_nano(18, 48, 56, 352727150),
909 ),
910 ];
911
912 for &(input, want) in test_cases.iter() {
913 assert_eq!(
914 parse.ymd_hms_z(input).unwrap().unwrap(),
915 want,
916 "ymd_hms_z/{}",
917 input
918 )
919 }
920 assert!(parse.ymd_hms_z("not-date-time").is_none());
921 assert!(parse.ymd_hms_z("2021-04-30 21:14").is_none()); assert!(parse.ymd_hms_z("2021-04-30X21:14Z").is_none()); assert!(parse.ymd_hms_z("2021-04-30 21:1XZ").is_none()); }
928
929 #[test]
930 fn ymd() {
931 let parse = Parse::new(&Utc, Utc::now().time());
932
933 let test_cases = [(
934 "2021-02-21",
935 Utc.ymd(2021, 2, 21).and_time(Utc::now().time()),
936 )];
937
938 for &(input, want) in test_cases.iter() {
939 assert_eq!(
940 parse
941 .ymd(input)
942 .unwrap()
943 .unwrap()
944 .trunc_subsecs(0)
945 .with_second(0)
946 .unwrap(),
947 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
948 "ymd/{}",
949 input
950 )
951 }
952 assert!(parse.ymd("not-date-time").is_none());
953 }
954
955 #[test]
956 fn ymd_z() {
957 let parse = Parse::new(&Utc, Utc::now().time());
958 let now_at_pst = Utc::now().with_timezone(&FixedOffset::west(8 * 3600));
959 let now_at_cst = Utc::now().with_timezone(&FixedOffset::east(8 * 3600));
960
961 let test_cases = [
962 (
963 "2021-02-21 PST",
964 FixedOffset::west(8 * 3600)
965 .ymd(2021, 2, 21)
966 .and_time(now_at_pst.time())
967 .map(|dt| dt.with_timezone(&Utc)),
968 ),
969 (
970 "2021-02-21 UTC",
971 FixedOffset::west(0)
972 .ymd(2021, 2, 21)
973 .and_time(Utc::now().time())
974 .map(|dt| dt.with_timezone(&Utc)),
975 ),
976 (
977 "2020-07-20+08:00",
978 FixedOffset::east(8 * 3600)
979 .ymd(2020, 7, 20)
980 .and_time(now_at_cst.time())
981 .map(|dt| dt.with_timezone(&Utc)),
982 ),
983 ];
984
985 for &(input, want) in test_cases.iter() {
986 assert_eq!(
987 parse
988 .ymd_z(input)
989 .unwrap()
990 .unwrap()
991 .trunc_subsecs(0)
992 .with_second(0)
993 .unwrap(),
994 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
995 "ymd_z/{}",
996 input
997 )
998 }
999 assert!(parse.ymd_z("not-date-time").is_none());
1000 assert!(parse.ymd_z("2021-02-21").is_none()); assert!(parse.ymd_z("2021-02-21X").is_none()); }
1004
1005 #[test]
1006 fn month_ymd() {
1007 let parse = Parse::new(&Utc, Utc::now().time());
1008
1009 let test_cases = [(
1010 "2021-Feb-21",
1011 Utc.ymd(2021, 2, 21).and_time(Utc::now().time()),
1012 )];
1013
1014 for &(input, want) in test_cases.iter() {
1015 assert_eq!(
1016 parse
1017 .month_ymd(input)
1018 .unwrap()
1019 .unwrap()
1020 .trunc_subsecs(0)
1021 .with_second(0)
1022 .unwrap(),
1023 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1024 "month_ymd/{}",
1025 input
1026 )
1027 }
1028 assert!(parse.month_ymd("not-date-time").is_none());
1029 }
1030
1031 #[test]
1032 fn month_mdy_hms() {
1033 let parse = Parse::new(&Utc, Utc::now().time());
1034
1035 let test_cases = [
1036 (
1037 "May 8, 2009 5:57:51 PM",
1038 Utc.ymd(2009, 5, 8).and_hms(17, 57, 51),
1039 ),
1040 (
1041 "September 17, 2012 10:09am",
1042 Utc.ymd(2012, 9, 17).and_hms(10, 9, 0),
1043 ),
1044 (
1045 "September 17, 2012, 10:10:09",
1046 Utc.ymd(2012, 9, 17).and_hms(10, 10, 9),
1047 ),
1048 ];
1049
1050 for &(input, want) in test_cases.iter() {
1051 assert_eq!(
1052 parse.month_mdy_hms(input).unwrap().unwrap(),
1053 want,
1054 "month_mdy_hms/{}",
1055 input
1056 )
1057 }
1058 assert!(parse.month_mdy_hms("not-date-time").is_none());
1059 }
1060
1061 #[test]
1062 fn month_mdy_hms_z() {
1063 let parse = Parse::new(&Utc, Utc::now().time());
1064
1065 let test_cases = [
1066 (
1067 "May 02, 2021 15:51:31 UTC",
1068 Utc.ymd(2021, 5, 2).and_hms(15, 51, 31),
1069 ),
1070 (
1071 "May 02, 2021 15:51 UTC",
1072 Utc.ymd(2021, 5, 2).and_hms(15, 51, 0),
1073 ),
1074 (
1075 "May 26, 2021, 12:49 AM PDT",
1076 Utc.ymd(2021, 5, 26).and_hms(7, 49, 0),
1077 ),
1078 (
1079 "September 17, 2012 at 10:09am PST",
1080 Utc.ymd(2012, 9, 17).and_hms(18, 9, 0),
1081 ),
1082 ];
1083
1084 for &(input, want) in test_cases.iter() {
1085 assert_eq!(
1086 parse.month_mdy_hms_z(input).unwrap().unwrap(),
1087 want,
1088 "month_mdy_hms_z/{}",
1089 input
1090 )
1091 }
1092 assert!(parse.month_mdy_hms_z("not-date-time").is_none());
1093 assert!(parse.month_mdy_hms_z("May 27, 02:45:27 XX PST").is_none()); assert!(parse.month_mdy_hms_z("May 27 1234 something PST").is_none()); }
1098
1099 #[test]
1100 fn month_mdy() {
1101 let parse = Parse::new(&Utc, Utc::now().time());
1102
1103 let test_cases = [
1104 (
1105 "May 25, 2021",
1106 Utc.ymd(2021, 5, 25).and_time(Utc::now().time()),
1107 ),
1108 (
1109 "oct 7, 1970",
1110 Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1111 ),
1112 (
1113 "oct 7, 70",
1114 Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1115 ),
1116 (
1117 "oct. 7, 1970",
1118 Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1119 ),
1120 (
1121 "oct. 7, 70",
1122 Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1123 ),
1124 (
1125 "October 7, 1970",
1126 Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1127 ),
1128 ];
1129
1130 for &(input, want) in test_cases.iter() {
1131 assert_eq!(
1132 parse
1133 .month_mdy(input)
1134 .unwrap()
1135 .unwrap()
1136 .trunc_subsecs(0)
1137 .with_second(0)
1138 .unwrap(),
1139 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1140 "month_mdy/{}",
1141 input
1142 )
1143 }
1144 assert!(parse.month_mdy("not-date-time").is_none());
1145 }
1146
1147 #[test]
1148 fn month_dmy_hms() {
1149 let parse = Parse::new(&Utc, Utc::now().time());
1150
1151 let test_cases = [
1152 (
1153 "12 Feb 2006, 19:17",
1154 Utc.ymd(2006, 2, 12).and_hms(19, 17, 0),
1155 ),
1156 ("12 Feb 2006 19:17", Utc.ymd(2006, 2, 12).and_hms(19, 17, 0)),
1157 (
1158 "14 May 2019 19:11:40.164",
1159 Utc.ymd(2019, 5, 14).and_hms_milli(19, 11, 40, 164),
1160 ),
1161 ];
1162
1163 for &(input, want) in test_cases.iter() {
1164 assert_eq!(
1165 parse.month_dmy_hms(input).unwrap().unwrap(),
1166 want,
1167 "month_dmy_hms/{}",
1168 input
1169 )
1170 }
1171 assert!(parse.month_dmy_hms("not-date-time").is_none());
1172 }
1173
1174 #[test]
1175 fn month_dmy() {
1176 let parse = Parse::new(&Utc, Utc::now().time());
1177
1178 let test_cases = [
1179 ("7 oct 70", Utc.ymd(1970, 10, 7).and_time(Utc::now().time())),
1180 (
1181 "7 oct 1970",
1182 Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1183 ),
1184 (
1185 "03 February 2013",
1186 Utc.ymd(2013, 2, 3).and_time(Utc::now().time()),
1187 ),
1188 (
1189 "1 July 2013",
1190 Utc.ymd(2013, 7, 1).and_time(Utc::now().time()),
1191 ),
1192 ];
1193
1194 for &(input, want) in test_cases.iter() {
1195 assert_eq!(
1196 parse
1197 .month_dmy(input)
1198 .unwrap()
1199 .unwrap()
1200 .trunc_subsecs(0)
1201 .with_second(0)
1202 .unwrap(),
1203 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1204 "month_dmy/{}",
1205 input
1206 )
1207 }
1208 assert!(parse.month_dmy("not-date-time").is_none());
1209 }
1210
1211 #[test]
1214 fn month_dmy_year_fast_path() {
1215 let parse = Parse::new(&Utc, Utc::now().time());
1216
1217 let four_digit = parse.month_dmy("14 May 2019").unwrap().unwrap();
1219 assert_eq!(four_digit.year(), 2019);
1220 assert_eq!(four_digit.month(), 5);
1221 assert_eq!(four_digit.day(), 14);
1222
1223 let two_digit = parse.month_dmy("14 May 19").unwrap().unwrap();
1226 assert_eq!(two_digit.year(), 2019);
1227 assert_eq!(two_digit.month(), 5);
1228 assert_eq!(two_digit.day(), 14);
1229 }
1230
1231 #[test]
1232 fn slash_mdy_hms() {
1233 let parse = Parse::new(&Utc, Utc::now().time());
1234
1235 let test_cases = vec![
1236 ("4/8/2014 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
1237 ("04/08/2014 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
1238 ("4/8/14 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
1239 ("04/2/2014 03:00:51", Utc.ymd(2014, 4, 2).and_hms(3, 0, 51)),
1240 ("8/8/1965 12:00:00 AM", Utc.ymd(1965, 8, 8).and_hms(0, 0, 0)),
1241 (
1242 "8/8/1965 01:00:01 PM",
1243 Utc.ymd(1965, 8, 8).and_hms(13, 0, 1),
1244 ),
1245 ("8/8/1965 01:00 PM", Utc.ymd(1965, 8, 8).and_hms(13, 0, 0)),
1246 ("8/8/1965 1:00 PM", Utc.ymd(1965, 8, 8).and_hms(13, 0, 0)),
1247 ("8/8/1965 12:00 AM", Utc.ymd(1965, 8, 8).and_hms(0, 0, 0)),
1248 ("4/02/2014 03:00:51", Utc.ymd(2014, 4, 2).and_hms(3, 0, 51)),
1249 (
1250 "03/19/2012 10:11:59",
1251 Utc.ymd(2012, 3, 19).and_hms(10, 11, 59),
1252 ),
1253 (
1254 "03/19/2012 10:11:59.3186369",
1255 Utc.ymd(2012, 3, 19).and_hms_nano(10, 11, 59, 318636900),
1256 ),
1257 ];
1258
1259 for &(input, want) in test_cases.iter() {
1260 assert_eq!(
1261 parse.slash_mdy_hms(input).unwrap().unwrap(),
1262 want,
1263 "slash_mdy_hms/{}",
1264 input
1265 )
1266 }
1267 assert!(parse.slash_mdy_hms("not-date-time").is_none());
1268 }
1269
1270 #[test]
1271 fn slash_mdy() {
1272 let parse = Parse::new(&Utc, Utc::now().time());
1273
1274 let test_cases = [
1275 (
1276 "3/31/2014",
1277 Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
1278 ),
1279 (
1280 "03/31/2014",
1281 Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
1282 ),
1283 ("08/21/71", Utc.ymd(1971, 8, 21).and_time(Utc::now().time())),
1284 ("8/1/71", Utc.ymd(1971, 8, 1).and_time(Utc::now().time())),
1285 ];
1286
1287 for &(input, want) in test_cases.iter() {
1288 assert_eq!(
1289 parse
1290 .slash_mdy(input)
1291 .unwrap()
1292 .unwrap()
1293 .trunc_subsecs(0)
1294 .with_second(0)
1295 .unwrap(),
1296 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1297 "slash_mdy/{}",
1298 input
1299 )
1300 }
1301 assert!(parse.slash_mdy("not-date-time").is_none());
1302 }
1303
1304 #[test]
1305 fn slash_dmy() {
1306 let mut parse = Parse::new(&Utc, Utc::now().time());
1307
1308 let test_cases = [
1309 (
1310 "31/3/2014",
1311 Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
1312 ),
1313 (
1314 "13/11/2014",
1315 Utc.ymd(2014, 11, 13).and_time(Utc::now().time()),
1316 ),
1317 ("21/08/71", Utc.ymd(1971, 8, 21).and_time(Utc::now().time())),
1318 ("1/8/71", Utc.ymd(1971, 8, 1).and_time(Utc::now().time())),
1319 ];
1320
1321 for &(input, want) in test_cases.iter() {
1322 assert_eq!(
1323 parse
1324 .prefer_dmy(true)
1325 .slash_dmy(input)
1326 .unwrap()
1327 .unwrap()
1328 .trunc_subsecs(0)
1329 .with_second(0)
1330 .unwrap(),
1331 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1332 "slash_dmy/{}",
1333 input
1334 )
1335 }
1336 assert!(parse.slash_dmy("not-date-time").is_none());
1337 }
1338
1339 #[test]
1340 fn slash_ymd_hms() {
1341 let parse = Parse::new(&Utc, Utc::now().time());
1342
1343 let test_cases = [
1344 ("2014/4/8 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
1345 ("2014/04/08 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
1346 ("2014/04/2 03:00:51", Utc.ymd(2014, 4, 2).and_hms(3, 0, 51)),
1347 ("2014/4/02 03:00:51", Utc.ymd(2014, 4, 2).and_hms(3, 0, 51)),
1348 (
1349 "2012/03/19 10:11:59",
1350 Utc.ymd(2012, 3, 19).and_hms(10, 11, 59),
1351 ),
1352 (
1353 "2012/03/19 10:11:59.3186369",
1354 Utc.ymd(2012, 3, 19).and_hms_nano(10, 11, 59, 318636900),
1355 ),
1356 ];
1357
1358 for &(input, want) in test_cases.iter() {
1359 assert_eq!(
1360 parse.slash_ymd_hms(input).unwrap().unwrap(),
1361 want,
1362 "slash_ymd_hms/{}",
1363 input
1364 )
1365 }
1366 assert!(parse.slash_ymd_hms("not-date-time").is_none());
1367 }
1368
1369 #[test]
1370 fn slash_ymd() {
1371 let parse = Parse::new(&Utc, Utc::now().time());
1372
1373 let test_cases = [
1374 (
1375 "2014/3/31",
1376 Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
1377 ),
1378 (
1379 "2014/03/31",
1380 Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
1381 ),
1382 ];
1383
1384 for &(input, want) in test_cases.iter() {
1385 assert_eq!(
1386 parse
1387 .slash_ymd(input)
1388 .unwrap()
1389 .unwrap()
1390 .trunc_subsecs(0)
1391 .with_second(0)
1392 .unwrap(),
1393 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1394 "slash_ymd/{}",
1395 input
1396 )
1397 }
1398 assert!(parse.slash_ymd("not-date-time").is_none());
1399 }
1400}