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(|| {
11 regex::RegexBuilder::new($re)
12 .unicode(false)
13 .build()
14 .expect("invalid regex literal")
15 })
16 }};
17}
18const fn build_date_byte_table() -> [bool; 256] {
22 let mut table = [false; 256];
23 let mut i = 0usize;
24 while i < 256 {
25 let b = i as u8;
26 table[i] = b.is_ascii_alphanumeric()
27 || matches!(b, b' ' | 0x09..=0x0D)
28 || matches!(b, b'-' | b'+' | b'/' | b':' | b'.' | b',');
29 i += 1;
30 }
31 table
32}
33
34static DATE_BYTE: [bool; 256] = build_date_byte_table();
35
36#[inline]
44fn cannot_be_date(input: &str) -> bool {
45 input.bytes().any(|b| !DATE_BYTE[b as usize])
46}
47
48#[inline]
58fn slash_year_is_two_digits(bytes: &[u8]) -> bool {
59 let mut slashes = 0u8;
60 let mut year_len = 0usize;
61 for &b in bytes {
62 if b == b'/' {
63 slashes += 1;
64 } else if slashes == 2 {
65 if b.is_ascii_digit() {
66 year_len += 1;
67 } else {
68 break;
69 }
70 }
71 }
72 year_len == 2
73}
74
75pub struct Parse<'z, Tz2> {
77 tz: &'z Tz2,
78 default_time: NaiveTime,
79 prefer_dmy: bool,
80}
81
82impl<'z, Tz2> Parse<'z, Tz2>
83where
84 Tz2: TimeZone,
85{
86 pub const fn new(tz: &'z Tz2, default_time: NaiveTime) -> Self {
89 Self {
90 tz,
91 default_time,
92 prefer_dmy: false,
93 }
94 }
95
96 pub const fn prefer_dmy(&mut self, yes: bool) -> &Self {
97 self.prefer_dmy = yes;
98 self
99 }
100
101 pub const fn new_with_preference(
104 tz: &'z Tz2,
105 default_time: NaiveTime,
106 prefer_dmy: bool,
107 ) -> Self {
108 Self {
109 tz,
110 default_time,
111 prefer_dmy,
112 }
113 }
114
115 #[inline]
136 pub fn parse(&self, input: &str) -> Result<DateTime<Utc>> {
137 if cannot_be_date(input) {
138 return Err(anyhow!("{} did not match any formats.", input));
139 }
140 self.slash_mdy_family(input)
141 .or_else(|| self.slash_ymd_family(input))
142 .or_else(|| self.ymd_family(input))
143 .or_else(|| self.month_ymd(input))
144 .or_else(|| self.month_mdy_family(input))
145 .or_else(|| self.month_dmy_family(input))
146 .or_else(|| self.unix_timestamp(input))
147 .or_else(|| self.rfc2822(input))
148 .unwrap_or_else(|| Err(anyhow!("{} did not match any formats.", input)))
149 }
150
151 #[inline]
152 fn ymd_family(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
153 let re: &Regex = regex! {
154 r"^\d{4}-\d{2}"
155
156 };
157
158 if !re.is_match(input) {
159 return None;
160 }
161 self.rfc3339(input)
162 .or_else(|| self.ymd_hms(input))
163 .or_else(|| self.ymd_hms_z(input))
164 .or_else(|| self.ymd(input))
165 .or_else(|| self.ymd_z(input))
166 }
167
168 #[inline]
169 fn month_mdy_family(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
170 let re: &Regex = regex! {
171 r"^[a-zA-Z]{3,9}\.?\s+\d{1,2}"
172 };
173
174 if !re.is_match(input) {
175 return None;
176 }
177 self.month_mdy_hms(input)
178 .or_else(|| self.month_mdy_hms_z(input))
179 .or_else(|| self.month_mdy(input))
180 }
181
182 #[inline]
183 fn month_dmy_family(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
184 let re: &Regex = regex! {r"^\d{1,2}\s+[a-zA-Z]{3,9}"
185 };
186
187 if !re.is_match(input) {
188 return None;
189 }
190 self.month_dmy_hms(input).or_else(|| self.month_dmy(input))
191 }
192
193 #[inline]
194 fn slash_mdy_family(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
195 let re: &Regex = regex! {r"^\d{1,2}/\d{1,2}"
196 };
197 if !re.is_match(input) {
198 return None;
199 }
200 if self.prefer_dmy {
201 self.slash_dmy_hms(input)
202 .or_else(|| self.slash_dmy(input))
203 .or_else(|| self.slash_mdy_hms(input))
204 .or_else(|| self.slash_mdy(input))
205 } else {
206 self.slash_mdy_hms(input)
207 .or_else(|| self.slash_mdy(input))
208 .or_else(|| self.slash_dmy_hms(input))
209 .or_else(|| self.slash_dmy(input))
210 }
211 }
212
213 #[inline]
214 fn slash_ymd_family(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
215 let re: &Regex = regex! {r"^[0-9]{4}/[0-9]{1,2}"};
216 if !re.is_match(input) {
217 return None;
218 }
219 self.slash_ymd_hms(input).or_else(|| self.slash_ymd(input))
220 }
221
222 #[inline]
227 fn unix_timestamp(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
228 let &b0 = input.as_bytes().first()?;
235 if !(b0.is_ascii_digit() || matches!(b0, b'+' | b'-' | b'.')) {
236 return None;
237 }
238
239 let ts_sec_val: f64 = if let Ok(val) = fast_float2::parse(input) {
240 val
241 } else {
242 return None;
243 };
244
245 if !ts_sec_val.is_finite() {
250 return None;
251 }
252
253 let ts_ns_val = ts_sec_val * 1_000_000_000_f64;
255
256 let result = Utc.timestamp_nanos(ts_ns_val as i64).with_timezone(&Utc);
257 Some(Ok(result))
258 }
259
260 #[inline]
264 fn rfc3339(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
265 DateTime::parse_from_rfc3339(input)
266 .ok()
267 .map(|parsed| parsed.with_timezone(&Utc))
268 .map(Ok)
269 }
270
271 #[inline]
274 fn rfc2822(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
275 if !input.as_bytes().contains(&b':') {
280 return None;
281 }
282 DateTime::parse_from_rfc2822(input)
283 .ok()
284 .map(|parsed| parsed.with_timezone(&Utc))
285 .map(Ok)
286 }
287
288 #[inline]
300 fn ymd_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
301 let re: &Regex = regex! {
302 r"^\d{4}-\d{2}-\d{2}[T\s]+\d{2}:\d{2}(?::\d{2})?(?:\.\d{1,9})?\s*(?:am|pm|AM|PM)?$"
303
304 };
305 if !re.is_match(input) {
306 return None;
307 }
308
309 let (fmt_hms, fmt_hm, fmt_hms_f, fmt_ims_p, fmt_im_p) = if input.as_bytes()[10] == b'T' {
314 (
315 "%Y-%m-%dT%H:%M:%S",
316 "%Y-%m-%dT%H:%M",
317 "%Y-%m-%dT%H:%M:%S%.f",
318 "%Y-%m-%dT%I:%M:%S %P",
319 "%Y-%m-%dT%I:%M %P",
320 )
321 } else {
322 (
323 "%Y-%m-%d %H:%M:%S",
324 "%Y-%m-%d %H:%M",
325 "%Y-%m-%d %H:%M:%S%.f",
326 "%Y-%m-%d %I:%M:%S %P",
327 "%Y-%m-%d %I:%M %P",
328 )
329 };
330
331 self.tz
332 .datetime_from_str(input, fmt_hms)
333 .or_else(|_| self.tz.datetime_from_str(input, fmt_hm))
334 .or_else(|_| self.tz.datetime_from_str(input, fmt_hms_f))
335 .or_else(|_| self.tz.datetime_from_str(input, fmt_ims_p))
336 .or_else(|_| self.tz.datetime_from_str(input, fmt_im_p))
337 .ok()
338 .map(|parsed| parsed.with_timezone(&Utc))
339 .map(Ok)
340 }
341
342 #[inline]
352 fn ymd_hms_z(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
353 if input.len() < 17 || !input.as_bytes()[10].is_ascii_whitespace() {
355 return None;
356 }
357 let re: &Regex = regex! {
358 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})$"
359 };
360
361 if let Some(caps) = re.captures(input)
362 && let Some(matched_tz) = caps.name("tz")
363 {
364 let parse_from_str = NaiveDateTime::parse_from_str;
365 return match timezone::parse(matched_tz.as_str().trim()) {
366 Ok(offset) => parse_from_str(input, "%Y-%m-%d %H:%M:%S %Z")
367 .or_else(|_| parse_from_str(input, "%Y-%m-%d %H:%M %Z"))
368 .or_else(|_| parse_from_str(input, "%Y-%m-%d %H:%M:%S%.f %Z"))
369 .ok()
370 .and_then(|parsed| offset.from_local_datetime(&parsed).single())
371 .map(|datetime| datetime.with_timezone(&Utc))
372 .map(Ok),
373 Err(err) => Some(Err(err)),
374 };
375 }
376 None
377 }
378
379 #[inline]
382 fn ymd(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
383 let re: &Regex = regex! {r"^\d{4}-\d{2}-\d{2}$"
384 };
385
386 if !re.is_match(input) {
387 return None;
388 }
389 let now = Utc::now()
390 .date()
391 .and_time(self.default_time)?
392 .with_timezone(self.tz);
393 NaiveDate::parse_from_str(input, "%Y-%m-%d")
394 .ok()
395 .map(|parsed| parsed.and_time(now.time()))
396 .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
397 .map(|at_tz| at_tz.with_timezone(&Utc))
398 .map(Ok)
399 }
400
401 #[inline]
406 fn ymd_z(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
407 if input.len() <= 10 {
409 return None;
410 }
411 let re: &Regex = regex! {r"^\d{4}-\d{2}-\d{2}(?P<tz>\s*[+-:a-zA-Z0-9]{3,6})$"
412 };
413 if let Some(caps) = re.captures(input)
414 && let Some(matched_tz) = caps.name("tz")
415 {
416 return match timezone::parse(matched_tz.as_str().trim()) {
417 Ok(offset) => {
418 let now = Utc::now()
419 .date()
420 .and_time(self.default_time)?
421 .with_timezone(&offset);
422 NaiveDate::parse_from_str(input, "%Y-%m-%d %Z")
423 .ok()
424 .map(|parsed| parsed.and_time(now.time()))
425 .and_then(|datetime| offset.from_local_datetime(&datetime).single())
426 .map(|at_tz| at_tz.with_timezone(&Utc))
427 .map(Ok)
428 }
429 Err(err) => Some(Err(err)),
430 };
431 }
432 None
433 }
434
435 #[inline]
438 fn month_ymd(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
439 let re: &Regex = regex! {r"^\d{4}-\w{3,9}-\d{2}$"
440 };
441 if !re.is_match(input) {
442 return None;
443 }
444
445 let now = Utc::now()
446 .date()
447 .and_time(self.default_time)?
448 .with_timezone(self.tz);
449 NaiveDate::parse_from_str(input, "%Y-%m-%d")
450 .or_else(|_| NaiveDate::parse_from_str(input, "%Y-%b-%d"))
451 .ok()
452 .map(|parsed| parsed.and_time(now.time()))
453 .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
454 .map(|at_tz| at_tz.with_timezone(&Utc))
455 .map(Ok)
456 }
457
458 #[inline]
463 fn month_mdy_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
464 let re: &Regex = regex! {
465 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)?$"
466 };
467 if !re.is_match(input) {
468 return None;
469 }
470
471 let dt = input.replace([',', '.'], "");
476 self.tz
477 .datetime_from_str(&dt, "%B %d %Y %H:%M:%S")
478 .or_else(|_| self.tz.datetime_from_str(&dt, "%B %d %Y %H:%M"))
479 .or_else(|_| self.tz.datetime_from_str(&dt, "%B %d %Y %I:%M:%S %P"))
480 .or_else(|_| self.tz.datetime_from_str(&dt, "%B %d %Y %I:%M %P"))
481 .ok()
482 .map(|at_tz| at_tz.with_timezone(&Utc))
483 .map(Ok)
484 }
485
486 #[inline]
492 fn month_mdy_hms_z(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
493 if input.len() < 20 {
496 return None;
497 }
498 let bytes = input.as_bytes();
499 let has_year = (0..bytes.len().saturating_sub(3)).any(|i| {
500 bytes[i..i + 4].iter().all(|b| b.is_ascii_digit())
501 && (i == 0 || !bytes[i - 1].is_ascii_digit())
502 && bytes.get(i + 4).is_none_or(|b| !b.is_ascii_digit())
503 });
504 if !has_year {
505 return None;
506 }
507 let re: &Regex = regex! {
508 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})$",
509 };
510 if let Some(caps) = re.captures(input)
511 && let Some(matched_tz) = caps.name("tz")
512 {
513 let parse_from_str = NaiveDateTime::parse_from_str;
514 return match timezone::parse(matched_tz.as_str().trim()) {
515 Ok(offset) => {
516 let mut dt = input.replace(',', "");
517 if let Some(pos) = dt.find("at") {
518 dt.replace_range(pos..pos + 2, "");
519 }
520 parse_from_str(&dt, "%B %d %Y %H:%M:%S %Z")
521 .or_else(|_| parse_from_str(&dt, "%B %d %Y %H:%M %Z"))
522 .or_else(|_| parse_from_str(&dt, "%B %d %Y %I:%M:%S %P %Z"))
523 .or_else(|_| parse_from_str(&dt, "%B %d %Y %I:%M %P %Z"))
524 .ok()
525 .and_then(|parsed| offset.from_local_datetime(&parsed).single())
526 .map(|datetime| datetime.with_timezone(&Utc))
527 .map(Ok)
528 }
529 Err(err) => Some(Err(err)),
530 };
531 }
532 None
533 }
534
535 #[inline]
543 fn month_mdy(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
544 let re: &Regex = regex! {r"^[a-zA-Z]{3,9}\.?\s+\d{1,2},\s+\d{2,4}$"
545 };
546 if !re.is_match(input) {
547 return None;
548 }
549
550 let now = Utc::now()
551 .date()
552 .and_time(self.default_time)?
553 .with_timezone(self.tz);
554 let dt = input.replace([',', '.'], "");
558 NaiveDate::parse_from_str(&dt, "%B %d %y")
559 .or_else(|_| NaiveDate::parse_from_str(&dt, "%B %d %Y"))
560 .ok()
561 .map(|parsed| parsed.and_time(now.time()))
562 .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
563 .map(|at_tz| at_tz.with_timezone(&Utc))
564 .map(Ok)
565 }
566
567 #[inline]
572 fn month_dmy_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
573 if !input.as_bytes().contains(&b':') {
575 return None;
576 }
577 let re: &Regex = regex! {
578 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})?$"
579 };
580 if !re.is_match(input) {
581 return None;
582 }
583
584 let dt = input.replace(',', "");
585 self.tz
586 .datetime_from_str(&dt, "%d %B %Y %H:%M:%S")
587 .or_else(|_| self.tz.datetime_from_str(&dt, "%d %B %Y %H:%M"))
588 .or_else(|_| self.tz.datetime_from_str(&dt, "%d %B %Y %H:%M:%S%.f"))
589 .or_else(|_| self.tz.datetime_from_str(&dt, "%d %B %Y %I:%M:%S %P"))
590 .or_else(|_| self.tz.datetime_from_str(&dt, "%d %B %Y %I:%M %P"))
591 .ok()
592 .map(|at_tz| at_tz.with_timezone(&Utc))
593 .map(Ok)
594 }
595
596 #[inline]
602 fn month_dmy(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
603 let re: &Regex = regex! {r"^\d{1,2}\s+[a-zA-Z]{3,9}\s+\d{2,4}$"
604 };
605 if !re.is_match(input) {
606 return None;
607 }
608
609 let now = Utc::now()
610 .date()
611 .and_time(self.default_time)?
612 .with_timezone(self.tz);
613 let bytes = input.as_bytes();
616 let len = bytes.len();
617 let four_digit_year = len >= 5
618 && bytes[len - 4..].iter().all(|b| b.is_ascii_digit())
619 && bytes[len - 5].is_ascii_whitespace();
620 let parsed = if four_digit_year {
621 NaiveDate::parse_from_str(input, "%d %B %Y")
622 } else {
623 NaiveDate::parse_from_str(input, "%d %B %y")
624 .or_else(|_| NaiveDate::parse_from_str(input, "%d %B %Y"))
625 };
626 parsed
627 .ok()
628 .map(|parsed| parsed.and_time(now.time()))
629 .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
630 .map(|at_tz| at_tz.with_timezone(&Utc))
631 .map(Ok)
632 }
633
634 #[inline]
648 fn slash_mdy_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
649 let re: &Regex = regex! {
650 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)?$"
651 };
652 if !re.is_match(input) {
653 return None;
654 }
655
656 let (fmt_hms, fmt_hm, fmt_hms_f, fmt_ims_p, fmt_im_p) =
660 if slash_year_is_two_digits(input.as_bytes()) {
661 (
662 "%m/%d/%y %H:%M:%S",
663 "%m/%d/%y %H:%M",
664 "%m/%d/%y %H:%M:%S%.f",
665 "%m/%d/%y %I:%M:%S %P",
666 "%m/%d/%y %I:%M %P",
667 )
668 } else {
669 (
670 "%m/%d/%Y %H:%M:%S",
671 "%m/%d/%Y %H:%M",
672 "%m/%d/%Y %H:%M:%S%.f",
673 "%m/%d/%Y %I:%M:%S %P",
674 "%m/%d/%Y %I:%M %P",
675 )
676 };
677 self.tz
678 .datetime_from_str(input, fmt_hms)
679 .or_else(|_| self.tz.datetime_from_str(input, fmt_hm))
680 .or_else(|_| self.tz.datetime_from_str(input, fmt_hms_f))
681 .or_else(|_| self.tz.datetime_from_str(input, fmt_ims_p))
682 .or_else(|_| self.tz.datetime_from_str(input, fmt_im_p))
683 .ok()
684 .map(|at_tz| at_tz.with_timezone(&Utc))
685 .map(Ok)
686 }
687
688 #[inline]
702 fn slash_dmy_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
703 let re: &Regex = regex! {
704 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)?$"
705 };
706 if !re.is_match(input) {
707 return None;
708 }
709
710 let (fmt_hms, fmt_hm, fmt_hms_f, fmt_ims_p, fmt_im_p) =
712 if slash_year_is_two_digits(input.as_bytes()) {
713 (
714 "%d/%m/%y %H:%M:%S",
715 "%d/%m/%y %H:%M",
716 "%d/%m/%y %H:%M:%S%.f",
717 "%d/%m/%y %I:%M:%S %P",
718 "%d/%m/%y %I:%M %P",
719 )
720 } else {
721 (
722 "%d/%m/%Y %H:%M:%S",
723 "%d/%m/%Y %H:%M",
724 "%d/%m/%Y %H:%M:%S%.f",
725 "%d/%m/%Y %I:%M:%S %P",
726 "%d/%m/%Y %I:%M %P",
727 )
728 };
729 self.tz
730 .datetime_from_str(input, fmt_hms)
731 .or_else(|_| self.tz.datetime_from_str(input, fmt_hm))
732 .or_else(|_| self.tz.datetime_from_str(input, fmt_hms_f))
733 .or_else(|_| self.tz.datetime_from_str(input, fmt_ims_p))
734 .or_else(|_| self.tz.datetime_from_str(input, fmt_im_p))
735 .ok()
736 .map(|at_tz| at_tz.with_timezone(&Utc))
737 .map(Ok)
738 }
739
740 #[inline]
746 fn slash_mdy(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
747 let re: &Regex = regex! {r"^\d{1,2}/\d{1,2}/\d{2,4}$"
748 };
749 if !re.is_match(input) {
750 return None;
751 }
752
753 let now = Utc::now()
754 .date()
755 .and_time(self.default_time)?
756 .with_timezone(self.tz);
757 let fmt = if slash_year_is_two_digits(input.as_bytes()) {
758 "%m/%d/%y"
759 } else {
760 "%m/%d/%Y"
761 };
762 NaiveDate::parse_from_str(input, fmt)
763 .ok()
764 .map(|parsed| parsed.and_time(now.time()))
765 .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
766 .map(|at_tz| at_tz.with_timezone(&Utc))
767 .map(Ok)
768 }
769
770 #[inline]
776 fn slash_dmy(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
777 let re: &Regex = regex! {r"^[0-9]{1,2}/[0-9]{1,2}/[0-9]{2,4}$"
778 };
779 if !re.is_match(input) {
780 return None;
781 }
782
783 let now = Utc::now()
784 .date()
785 .and_time(self.default_time)?
786 .with_timezone(self.tz);
787 let fmt = if slash_year_is_two_digits(input.as_bytes()) {
788 "%d/%m/%y"
789 } else {
790 "%d/%m/%Y"
791 };
792 NaiveDate::parse_from_str(input, fmt)
793 .ok()
794 .map(|parsed| parsed.and_time(now.time()))
795 .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
796 .map(|at_tz| at_tz.with_timezone(&Utc))
797 .map(Ok)
798 }
799
800 #[inline]
808 fn slash_ymd_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
809 let re: &Regex = regex! {
810 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)?$"
811 };
812 if !re.is_match(input) {
813 return None;
814 }
815
816 self.tz
817 .datetime_from_str(input, "%Y/%m/%d %H:%M:%S")
818 .or_else(|_| self.tz.datetime_from_str(input, "%Y/%m/%d %H:%M"))
819 .or_else(|_| self.tz.datetime_from_str(input, "%Y/%m/%d %H:%M:%S%.f"))
820 .or_else(|_| self.tz.datetime_from_str(input, "%Y/%m/%d %I:%M:%S %P"))
821 .or_else(|_| self.tz.datetime_from_str(input, "%Y/%m/%d %I:%M %P"))
822 .ok()
823 .map(|at_tz| at_tz.with_timezone(&Utc))
824 .map(Ok)
825 }
826
827 #[inline]
831 fn slash_ymd(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
832 let re: &Regex = regex! {r"^[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}$"
833 };
834 if !re.is_match(input) {
835 return None;
836 }
837
838 let now = Utc::now()
839 .date()
840 .and_time(self.default_time)?
841 .with_timezone(self.tz);
842 NaiveDate::parse_from_str(input, "%Y/%m/%d")
843 .ok()
844 .map(|parsed| parsed.and_time(now.time()))
845 .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
846 .map(|at_tz| at_tz.with_timezone(&Utc))
847 .map(Ok)
848 }
849}
850
851#[cfg(test)]
852mod tests {
853 use super::*;
854
855 #[test]
856 fn unix_timestamp() {
857 let parse = Parse::new(&Utc, Utc::now().time());
858
859 let test_cases = vec![
860 ("0", Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)),
861 ("0000000000", Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)),
862 ("0000000000000", Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)),
863 ("0000000000000000000", Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)),
864 ("-770172300", Utc.ymd(1945, 8, 5).and_hms(23, 15, 0)),
865 (
866 "1671673426.123456789",
867 Utc.ymd(2022, 12, 22).and_hms_nano(1, 43, 46, 123456768),
868 ),
869 ("1511648546", Utc.ymd(2017, 11, 25).and_hms(22, 22, 26)),
870 (
871 "1620036248.420",
872 Utc.ymd(2021, 5, 3).and_hms_milli(10, 4, 8, 420),
873 ),
874 (
875 "1620036248.717915136",
876 Utc.ymd(2021, 5, 3).and_hms_nano(10, 4, 8, 717915136),
877 ),
878 ];
879
880 for &(input, want) in test_cases.iter() {
881 assert_eq!(
882 parse.unix_timestamp(input).unwrap().unwrap(),
883 want,
884 "unix_timestamp/{}",
885 input
886 )
887 }
888 assert!(parse.unix_timestamp("15116").is_some());
889 assert!(
890 parse
891 .unix_timestamp("16200248727179150001620024872717915000") .is_some()
893 );
894 assert!(parse.unix_timestamp("not-a-ts").is_none());
895 for input in [
898 "inf", "nan", "INF", "NaN", "infinity", "+inf", "-inf", "-nan",
899 ] {
900 assert!(
901 parse.unix_timestamp(input).is_none(),
902 "unix_timestamp must reject non-finite {input}"
903 );
904 }
905 }
906
907 #[test]
908 fn rfc3339() {
909 let parse = Parse::new(&Utc, Utc::now().time());
910
911 let test_cases = [
912 (
913 "2021-05-01T01:17:02.604456Z",
914 Utc.ymd(2021, 5, 1).and_hms_nano(1, 17, 2, 604456000),
915 ),
916 (
917 "2017-11-25T22:34:50Z",
918 Utc.ymd(2017, 11, 25).and_hms(22, 34, 50),
919 ),
920 ];
921
922 for &(input, want) in test_cases.iter() {
923 assert_eq!(
924 parse.rfc3339(input).unwrap().unwrap(),
925 want,
926 "rfc3339/{}",
927 input
928 )
929 }
930 assert!(parse.rfc3339("2017-11-25 22:34:50").is_none());
931 assert!(parse.rfc3339("not-date-time").is_none());
932 }
933
934 #[test]
935 fn rfc2822() {
936 let parse = Parse::new(&Utc, Utc::now().time());
937
938 let test_cases = [
939 (
940 "Wed, 02 Jun 2021 06:31:39 GMT",
941 Utc.ymd(2021, 6, 2).and_hms(6, 31, 39),
942 ),
943 (
944 "Wed, 02 Jun 2021 06:31:39 PDT",
945 Utc.ymd(2021, 6, 2).and_hms(13, 31, 39),
946 ),
947 ];
948
949 for &(input, want) in test_cases.iter() {
950 assert_eq!(
951 parse.rfc2822(input).unwrap().unwrap(),
952 want,
953 "rfc2822/{}",
954 input
955 )
956 }
957 assert!(parse.rfc2822("02 Jun 2021 06:31:39").is_none());
958 assert!(parse.rfc2822("not-date-time").is_none());
959 }
960
961 #[test]
962 fn ymd_hms() {
963 let parse = Parse::new(&Utc, Utc::now().time());
964
965 let test_cases = [
966 ("2021-04-30 21:14", Utc.ymd(2021, 4, 30).and_hms(21, 14, 0)),
967 (
968 "2021-04-30 21:14:10",
969 Utc.ymd(2021, 4, 30).and_hms(21, 14, 10),
970 ),
971 (
972 "2021-04-30 21:14:10.052282",
973 Utc.ymd(2021, 4, 30).and_hms_micro(21, 14, 10, 52282),
974 ),
975 (
976 "2014-04-26 05:24:37 PM",
977 Utc.ymd(2014, 4, 26).and_hms(17, 24, 37),
978 ),
979 (
980 "2014-04-26 17:24:37.123",
981 Utc.ymd(2014, 4, 26).and_hms_milli(17, 24, 37, 123),
982 ),
983 (
984 "2014-04-26 17:24:37.3186369",
985 Utc.ymd(2014, 4, 26).and_hms_nano(17, 24, 37, 318636900),
986 ),
987 (
988 "2012-08-03 18:31:59.257000000",
989 Utc.ymd(2012, 8, 3).and_hms_nano(18, 31, 59, 257000000),
990 ),
991 ("2020-01-15T08:00", Utc.ymd(2020, 1, 15).and_hms(8, 0, 0)),
994 ("2020-01-15T08:00:00", Utc.ymd(2020, 1, 15).and_hms(8, 0, 0)),
995 (
996 "2020-01-15T08:00:00.123",
997 Utc.ymd(2020, 1, 15).and_hms_milli(8, 0, 0, 123),
998 ),
999 (
1000 "2020-01-15T08:00:00.123456",
1001 Utc.ymd(2020, 1, 15).and_hms_micro(8, 0, 0, 123456),
1002 ),
1003 (
1004 "2020-01-15T08:00:00.123456789",
1005 Utc.ymd(2020, 1, 15).and_hms_nano(8, 0, 0, 123456789),
1006 ),
1007 ];
1008
1009 for &(input, want) in test_cases.iter() {
1010 assert_eq!(
1011 parse.ymd_hms(input).unwrap().unwrap(),
1012 want,
1013 "ymd_hms/{}",
1014 input
1015 )
1016 }
1017 assert!(parse.ymd_hms("not-date-time").is_none());
1018
1019 let t_form = parse.ymd_hms("2020-01-15T08:00:00").unwrap().unwrap();
1021 let space_form = parse.ymd_hms("2020-01-15 08:00:00").unwrap().unwrap();
1022 assert_eq!(t_form, space_form, "T-separator vs space disagree");
1023 }
1024
1025 #[test]
1026 fn ymd_hms_z() {
1027 let parse = Parse::new(&Utc, Utc::now().time());
1028
1029 let test_cases = [
1030 (
1031 "2017-11-25 13:31:15 PST",
1032 Utc.ymd(2017, 11, 25).and_hms(21, 31, 15),
1033 ),
1034 (
1035 "2017-11-25 13:31 PST",
1036 Utc.ymd(2017, 11, 25).and_hms(21, 31, 0),
1037 ),
1038 (
1039 "2014-12-16 06:20:00 UTC",
1040 Utc.ymd(2014, 12, 16).and_hms(6, 20, 0),
1041 ),
1042 (
1043 "2014-12-16 06:20:00 GMT",
1044 Utc.ymd(2014, 12, 16).and_hms(6, 20, 0),
1045 ),
1046 (
1047 "2014-04-26 13:13:43 +0800",
1048 Utc.ymd(2014, 4, 26).and_hms(5, 13, 43),
1049 ),
1050 (
1051 "2014-04-26 13:13:44 +09:00",
1052 Utc.ymd(2014, 4, 26).and_hms(4, 13, 44),
1053 ),
1054 (
1055 "2012-08-03 18:31:59.257000000 +0000",
1056 Utc.ymd(2012, 8, 3).and_hms_nano(18, 31, 59, 257000000),
1057 ),
1058 (
1059 "2015-09-30 18:48:56.35272715 UTC",
1060 Utc.ymd(2015, 9, 30).and_hms_nano(18, 48, 56, 352727150),
1061 ),
1062 ];
1063
1064 for &(input, want) in test_cases.iter() {
1065 assert_eq!(
1066 parse.ymd_hms_z(input).unwrap().unwrap(),
1067 want,
1068 "ymd_hms_z/{}",
1069 input
1070 )
1071 }
1072 assert!(parse.ymd_hms_z("not-date-time").is_none());
1073 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()); }
1080
1081 #[test]
1082 fn ymd() {
1083 let parse = Parse::new(&Utc, Utc::now().time());
1084
1085 let test_cases = [(
1086 "2021-02-21",
1087 Utc.ymd(2021, 2, 21).and_time(Utc::now().time()),
1088 )];
1089
1090 for &(input, want) in test_cases.iter() {
1091 assert_eq!(
1092 parse
1093 .ymd(input)
1094 .unwrap()
1095 .unwrap()
1096 .trunc_subsecs(0)
1097 .with_second(0)
1098 .unwrap(),
1099 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1100 "ymd/{}",
1101 input
1102 )
1103 }
1104 assert!(parse.ymd("not-date-time").is_none());
1105 }
1106
1107 #[test]
1108 fn ymd_z() {
1109 let parse = Parse::new(&Utc, Utc::now().time());
1110 let now_at_pst = Utc::now().with_timezone(&FixedOffset::west(8 * 3600));
1111 let now_at_cst = Utc::now().with_timezone(&FixedOffset::east(8 * 3600));
1112
1113 let test_cases = [
1114 (
1115 "2021-02-21 PST",
1116 FixedOffset::west(8 * 3600)
1117 .ymd(2021, 2, 21)
1118 .and_time(now_at_pst.time())
1119 .map(|dt| dt.with_timezone(&Utc)),
1120 ),
1121 (
1122 "2021-02-21 UTC",
1123 FixedOffset::west(0)
1124 .ymd(2021, 2, 21)
1125 .and_time(Utc::now().time())
1126 .map(|dt| dt.with_timezone(&Utc)),
1127 ),
1128 (
1129 "2020-07-20+08:00",
1130 FixedOffset::east(8 * 3600)
1131 .ymd(2020, 7, 20)
1132 .and_time(now_at_cst.time())
1133 .map(|dt| dt.with_timezone(&Utc)),
1134 ),
1135 ];
1136
1137 for &(input, want) in test_cases.iter() {
1138 assert_eq!(
1139 parse
1140 .ymd_z(input)
1141 .unwrap()
1142 .unwrap()
1143 .trunc_subsecs(0)
1144 .with_second(0)
1145 .unwrap(),
1146 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1147 "ymd_z/{}",
1148 input
1149 )
1150 }
1151 assert!(parse.ymd_z("not-date-time").is_none());
1152 assert!(parse.ymd_z("2021-02-21").is_none()); assert!(parse.ymd_z("2021-02-21X").is_none()); }
1156
1157 #[test]
1158 fn month_ymd() {
1159 let parse = Parse::new(&Utc, Utc::now().time());
1160
1161 let test_cases = [(
1162 "2021-Feb-21",
1163 Utc.ymd(2021, 2, 21).and_time(Utc::now().time()),
1164 )];
1165
1166 for &(input, want) in test_cases.iter() {
1167 assert_eq!(
1168 parse
1169 .month_ymd(input)
1170 .unwrap()
1171 .unwrap()
1172 .trunc_subsecs(0)
1173 .with_second(0)
1174 .unwrap(),
1175 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1176 "month_ymd/{}",
1177 input
1178 )
1179 }
1180 assert!(parse.month_ymd("not-date-time").is_none());
1181 }
1182
1183 #[test]
1184 fn month_mdy_hms() {
1185 let parse = Parse::new(&Utc, Utc::now().time());
1186
1187 let test_cases = [
1188 (
1189 "May 8, 2009 5:57:51 PM",
1190 Utc.ymd(2009, 5, 8).and_hms(17, 57, 51),
1191 ),
1192 (
1193 "September 17, 2012 10:09am",
1194 Utc.ymd(2012, 9, 17).and_hms(10, 9, 0),
1195 ),
1196 (
1197 "September 17, 2012, 10:10:09",
1198 Utc.ymd(2012, 9, 17).and_hms(10, 10, 9),
1199 ),
1200 ];
1201
1202 for &(input, want) in test_cases.iter() {
1203 assert_eq!(
1204 parse.month_mdy_hms(input).unwrap().unwrap(),
1205 want,
1206 "month_mdy_hms/{}",
1207 input
1208 )
1209 }
1210 assert!(parse.month_mdy_hms("not-date-time").is_none());
1211 }
1212
1213 #[test]
1214 fn month_mdy_hms_z() {
1215 let parse = Parse::new(&Utc, Utc::now().time());
1216
1217 let test_cases = [
1218 (
1219 "May 02, 2021 15:51:31 UTC",
1220 Utc.ymd(2021, 5, 2).and_hms(15, 51, 31),
1221 ),
1222 (
1223 "May 02, 2021 15:51 UTC",
1224 Utc.ymd(2021, 5, 2).and_hms(15, 51, 0),
1225 ),
1226 (
1227 "May 26, 2021, 12:49 AM PDT",
1228 Utc.ymd(2021, 5, 26).and_hms(7, 49, 0),
1229 ),
1230 (
1231 "September 17, 2012 at 10:09am PST",
1232 Utc.ymd(2012, 9, 17).and_hms(18, 9, 0),
1233 ),
1234 ];
1235
1236 for &(input, want) in test_cases.iter() {
1237 assert_eq!(
1238 parse.month_mdy_hms_z(input).unwrap().unwrap(),
1239 want,
1240 "month_mdy_hms_z/{}",
1241 input
1242 )
1243 }
1244 assert!(parse.month_mdy_hms_z("not-date-time").is_none());
1245 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()); }
1250
1251 #[test]
1252 fn month_mdy() {
1253 let parse = Parse::new(&Utc, Utc::now().time());
1254
1255 let test_cases = [
1256 (
1257 "May 25, 2021",
1258 Utc.ymd(2021, 5, 25).and_time(Utc::now().time()),
1259 ),
1260 (
1261 "oct 7, 1970",
1262 Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1263 ),
1264 (
1265 "oct 7, 70",
1266 Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1267 ),
1268 (
1269 "oct. 7, 1970",
1270 Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1271 ),
1272 (
1273 "oct. 7, 70",
1274 Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1275 ),
1276 (
1277 "October 7, 1970",
1278 Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1279 ),
1280 ];
1281
1282 for &(input, want) in test_cases.iter() {
1283 assert_eq!(
1284 parse
1285 .month_mdy(input)
1286 .unwrap()
1287 .unwrap()
1288 .trunc_subsecs(0)
1289 .with_second(0)
1290 .unwrap(),
1291 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1292 "month_mdy/{}",
1293 input
1294 )
1295 }
1296 assert!(parse.month_mdy("not-date-time").is_none());
1297 }
1298
1299 #[test]
1300 fn month_dmy_hms() {
1301 let parse = Parse::new(&Utc, Utc::now().time());
1302
1303 let test_cases = [
1304 (
1305 "12 Feb 2006, 19:17",
1306 Utc.ymd(2006, 2, 12).and_hms(19, 17, 0),
1307 ),
1308 ("12 Feb 2006 19:17", Utc.ymd(2006, 2, 12).and_hms(19, 17, 0)),
1309 (
1310 "14 May 2019 19:11:40.164",
1311 Utc.ymd(2019, 5, 14).and_hms_milli(19, 11, 40, 164),
1312 ),
1313 ];
1314
1315 for &(input, want) in test_cases.iter() {
1316 assert_eq!(
1317 parse.month_dmy_hms(input).unwrap().unwrap(),
1318 want,
1319 "month_dmy_hms/{}",
1320 input
1321 )
1322 }
1323 assert!(parse.month_dmy_hms("not-date-time").is_none());
1324 }
1325
1326 #[test]
1327 fn month_dmy() {
1328 let parse = Parse::new(&Utc, Utc::now().time());
1329
1330 let test_cases = [
1331 ("7 oct 70", Utc.ymd(1970, 10, 7).and_time(Utc::now().time())),
1332 (
1333 "7 oct 1970",
1334 Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1335 ),
1336 (
1337 "03 February 2013",
1338 Utc.ymd(2013, 2, 3).and_time(Utc::now().time()),
1339 ),
1340 (
1341 "1 July 2013",
1342 Utc.ymd(2013, 7, 1).and_time(Utc::now().time()),
1343 ),
1344 ];
1345
1346 for &(input, want) in test_cases.iter() {
1347 assert_eq!(
1348 parse
1349 .month_dmy(input)
1350 .unwrap()
1351 .unwrap()
1352 .trunc_subsecs(0)
1353 .with_second(0)
1354 .unwrap(),
1355 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1356 "month_dmy/{}",
1357 input
1358 )
1359 }
1360 assert!(parse.month_dmy("not-date-time").is_none());
1361 }
1362
1363 #[test]
1366 fn month_dmy_year_fast_path() {
1367 let parse = Parse::new(&Utc, Utc::now().time());
1368
1369 let four_digit = parse.month_dmy("14 May 2019").unwrap().unwrap();
1371 assert_eq!(four_digit.year(), 2019);
1372 assert_eq!(four_digit.month(), 5);
1373 assert_eq!(four_digit.day(), 14);
1374
1375 let two_digit = parse.month_dmy("14 May 19").unwrap().unwrap();
1378 assert_eq!(two_digit.year(), 2019);
1379 assert_eq!(two_digit.month(), 5);
1380 assert_eq!(two_digit.day(), 14);
1381 }
1382
1383 #[test]
1384 fn slash_mdy_hms() {
1385 let parse = Parse::new(&Utc, Utc::now().time());
1386
1387 let test_cases = vec![
1388 ("4/8/2014 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
1389 ("04/08/2014 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
1390 ("4/8/14 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
1391 ("04/2/2014 03:00:51", Utc.ymd(2014, 4, 2).and_hms(3, 0, 51)),
1392 ("8/8/1965 12:00:00 AM", Utc.ymd(1965, 8, 8).and_hms(0, 0, 0)),
1393 (
1394 "8/8/1965 01:00:01 PM",
1395 Utc.ymd(1965, 8, 8).and_hms(13, 0, 1),
1396 ),
1397 ("8/8/1965 01:00 PM", Utc.ymd(1965, 8, 8).and_hms(13, 0, 0)),
1398 ("8/8/1965 1:00 PM", Utc.ymd(1965, 8, 8).and_hms(13, 0, 0)),
1399 ("8/8/1965 12:00 AM", Utc.ymd(1965, 8, 8).and_hms(0, 0, 0)),
1400 ("4/02/2014 03:00:51", Utc.ymd(2014, 4, 2).and_hms(3, 0, 51)),
1401 (
1402 "03/19/2012 10:11:59",
1403 Utc.ymd(2012, 3, 19).and_hms(10, 11, 59),
1404 ),
1405 (
1406 "03/19/2012 10:11:59.3186369",
1407 Utc.ymd(2012, 3, 19).and_hms_nano(10, 11, 59, 318636900),
1408 ),
1409 ];
1410
1411 for &(input, want) in test_cases.iter() {
1412 assert_eq!(
1413 parse.slash_mdy_hms(input).unwrap().unwrap(),
1414 want,
1415 "slash_mdy_hms/{}",
1416 input
1417 )
1418 }
1419 assert!(parse.slash_mdy_hms("not-date-time").is_none());
1420 }
1421
1422 #[test]
1423 fn slash_mdy() {
1424 let parse = Parse::new(&Utc, Utc::now().time());
1425
1426 let test_cases = [
1427 (
1428 "3/31/2014",
1429 Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
1430 ),
1431 (
1432 "03/31/2014",
1433 Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
1434 ),
1435 ("08/21/71", Utc.ymd(1971, 8, 21).and_time(Utc::now().time())),
1436 ("8/1/71", Utc.ymd(1971, 8, 1).and_time(Utc::now().time())),
1437 ];
1438
1439 for &(input, want) in test_cases.iter() {
1440 assert_eq!(
1441 parse
1442 .slash_mdy(input)
1443 .unwrap()
1444 .unwrap()
1445 .trunc_subsecs(0)
1446 .with_second(0)
1447 .unwrap(),
1448 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1449 "slash_mdy/{}",
1450 input
1451 )
1452 }
1453 assert!(parse.slash_mdy("not-date-time").is_none());
1454 }
1455
1456 #[test]
1457 fn slash_dmy() {
1458 let mut parse = Parse::new(&Utc, Utc::now().time());
1459
1460 let test_cases = [
1461 (
1462 "31/3/2014",
1463 Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
1464 ),
1465 (
1466 "13/11/2014",
1467 Utc.ymd(2014, 11, 13).and_time(Utc::now().time()),
1468 ),
1469 ("21/08/71", Utc.ymd(1971, 8, 21).and_time(Utc::now().time())),
1470 ("1/8/71", Utc.ymd(1971, 8, 1).and_time(Utc::now().time())),
1471 ];
1472
1473 for &(input, want) in test_cases.iter() {
1474 assert_eq!(
1475 parse
1476 .prefer_dmy(true)
1477 .slash_dmy(input)
1478 .unwrap()
1479 .unwrap()
1480 .trunc_subsecs(0)
1481 .with_second(0)
1482 .unwrap(),
1483 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1484 "slash_dmy/{}",
1485 input
1486 )
1487 }
1488 assert!(parse.slash_dmy("not-date-time").is_none());
1489 }
1490
1491 #[test]
1492 fn slash_ymd_hms() {
1493 let parse = Parse::new(&Utc, Utc::now().time());
1494
1495 let test_cases = [
1496 ("2014/4/8 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
1497 ("2014/04/08 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
1498 ("2014/04/2 03:00:51", Utc.ymd(2014, 4, 2).and_hms(3, 0, 51)),
1499 ("2014/4/02 03:00:51", Utc.ymd(2014, 4, 2).and_hms(3, 0, 51)),
1500 (
1501 "2012/03/19 10:11:59",
1502 Utc.ymd(2012, 3, 19).and_hms(10, 11, 59),
1503 ),
1504 (
1505 "2012/03/19 10:11:59.3186369",
1506 Utc.ymd(2012, 3, 19).and_hms_nano(10, 11, 59, 318636900),
1507 ),
1508 ];
1509
1510 for &(input, want) in test_cases.iter() {
1511 assert_eq!(
1512 parse.slash_ymd_hms(input).unwrap().unwrap(),
1513 want,
1514 "slash_ymd_hms/{}",
1515 input
1516 )
1517 }
1518 assert!(parse.slash_ymd_hms("not-date-time").is_none());
1519 }
1520
1521 #[test]
1522 fn slash_ymd() {
1523 let parse = Parse::new(&Utc, Utc::now().time());
1524
1525 let test_cases = [
1526 (
1527 "2014/3/31",
1528 Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
1529 ),
1530 (
1531 "2014/03/31",
1532 Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
1533 ),
1534 ];
1535
1536 for &(input, want) in test_cases.iter() {
1537 assert_eq!(
1538 parse
1539 .slash_ymd(input)
1540 .unwrap()
1541 .unwrap()
1542 .trunc_subsecs(0)
1543 .with_second(0)
1544 .unwrap(),
1545 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1546 "slash_ymd/{}",
1547 input
1548 )
1549 }
1550 assert!(parse.slash_ymd("not-date-time").is_none());
1551 }
1552}