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}
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]
45fn cannot_be_date(input: &str) -> bool {
46 input.bytes().any(|b| !DATE_BYTE[b as usize])
47}
48
49pub struct Parse<'z, Tz2> {
51 tz: &'z Tz2,
52 default_time: NaiveTime,
53 prefer_dmy: bool,
54}
55
56impl<'z, Tz2> Parse<'z, Tz2>
57where
58 Tz2: TimeZone,
59{
60 pub const fn new(tz: &'z Tz2, default_time: NaiveTime) -> Self {
63 Self {
64 tz,
65 default_time,
66 prefer_dmy: false,
67 }
68 }
69
70 pub const fn prefer_dmy(&mut self, yes: bool) -> &Self {
71 self.prefer_dmy = yes;
72 self
73 }
74
75 pub const fn new_with_preference(
78 tz: &'z Tz2,
79 default_time: NaiveTime,
80 prefer_dmy: bool,
81 ) -> Self {
82 Self {
83 tz,
84 default_time,
85 prefer_dmy,
86 }
87 }
88
89 #[inline]
109 pub fn parse(&self, input: &str) -> Result<DateTime<Utc>> {
110 if cannot_be_date(input) {
111 return Err(anyhow!("{} did not match any formats.", input));
112 }
113 self.slash_mdy_family(input)
114 .or_else(|| self.slash_ymd_family(input))
115 .or_else(|| self.ymd_family(input))
116 .or_else(|| self.month_ymd(input))
117 .or_else(|| self.month_mdy_family(input))
118 .or_else(|| self.month_dmy_family(input))
119 .or_else(|| self.unix_timestamp(input))
120 .or_else(|| self.rfc2822(input))
121 .unwrap_or_else(|| Err(anyhow!("{} did not match any formats.", input)))
122 }
123
124 #[inline]
125 fn ymd_family(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
126 let re: &Regex = regex! {
127 r"^\d{4}-\d{2}"
128
129 };
130
131 if !re.is_match(input) {
132 return None;
133 }
134 self.rfc3339(input)
135 .or_else(|| self.ymd_hms(input))
136 .or_else(|| self.ymd_hms_z(input))
137 .or_else(|| self.ymd(input))
138 .or_else(|| self.ymd_z(input))
139 }
140
141 #[inline]
142 fn month_mdy_family(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
143 let re: &Regex = regex! {
144 r"^[a-zA-Z]{3,9}\.?\s+\d{1,2}"
145 };
146
147 if !re.is_match(input) {
148 return None;
149 }
150 self.month_mdy_hms(input)
151 .or_else(|| self.month_mdy_hms_z(input))
152 .or_else(|| self.month_mdy(input))
153 }
154
155 #[inline]
156 fn month_dmy_family(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
157 let re: &Regex = regex! {r"^\d{1,2}\s+[a-zA-Z]{3,9}"
158 };
159
160 if !re.is_match(input) {
161 return None;
162 }
163 self.month_dmy_hms(input).or_else(|| self.month_dmy(input))
164 }
165
166 #[inline]
167 fn slash_mdy_family(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
168 let re: &Regex = regex! {r"^\d{1,2}/\d{1,2}"
169 };
170 if !re.is_match(input) {
171 return None;
172 }
173 if self.prefer_dmy {
174 self.slash_dmy_hms(input)
175 .or_else(|| self.slash_dmy(input))
176 .or_else(|| self.slash_mdy_hms(input))
177 .or_else(|| self.slash_mdy(input))
178 } else {
179 self.slash_mdy_hms(input)
180 .or_else(|| self.slash_mdy(input))
181 .or_else(|| self.slash_dmy_hms(input))
182 .or_else(|| self.slash_dmy(input))
183 }
184 }
185
186 #[inline]
187 fn slash_ymd_family(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
188 let re: &Regex = regex! {r"^[0-9]{4}/[0-9]{1,2}"};
189 if !re.is_match(input) {
190 return None;
191 }
192 self.slash_ymd_hms(input).or_else(|| self.slash_ymd(input))
193 }
194
195 #[inline]
200 fn unix_timestamp(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
201 let &b0 = input.as_bytes().first()?;
206 if !(b0.is_ascii_digit() || matches!(b0, b'+' | b'-' | b'.' | b'i' | b'I' | b'n' | b'N')) {
207 return None;
208 }
209
210 let ts_sec_val: f64 = if let Ok(val) = fast_float2::parse(input) {
211 val
212 } else {
213 return None;
214 };
215
216 let ts_ns_val = ts_sec_val * 1_000_000_000_f64;
218
219 let result = Utc.timestamp_nanos(ts_ns_val as i64).with_timezone(&Utc);
220 Some(Ok(result))
221 }
222
223 #[inline]
227 fn rfc3339(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
228 DateTime::parse_from_rfc3339(input)
229 .ok()
230 .map(|parsed| parsed.with_timezone(&Utc))
231 .map(Ok)
232 }
233
234 #[inline]
237 fn rfc2822(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
238 if !input.as_bytes().contains(&b':') {
243 return None;
244 }
245 DateTime::parse_from_rfc2822(input)
246 .ok()
247 .map(|parsed| parsed.with_timezone(&Utc))
248 .map(Ok)
249 }
250
251 #[inline]
263 fn ymd_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
264 let re: &Regex = regex! {
265 r"^\d{4}-\d{2}-\d{2}[T\s]+\d{2}:\d{2}(:\d{2})?(\.\d{1,9})?\s*(am|pm|AM|PM)?$"
266
267 };
268 if !re.is_match(input) {
269 return None;
270 }
271
272 let (fmt_hms, fmt_hm, fmt_hms_f, fmt_ims_p, fmt_im_p) = if input.as_bytes()[10] == b'T' {
277 (
278 "%Y-%m-%dT%H:%M:%S",
279 "%Y-%m-%dT%H:%M",
280 "%Y-%m-%dT%H:%M:%S%.f",
281 "%Y-%m-%dT%I:%M:%S %P",
282 "%Y-%m-%dT%I:%M %P",
283 )
284 } else {
285 (
286 "%Y-%m-%d %H:%M:%S",
287 "%Y-%m-%d %H:%M",
288 "%Y-%m-%d %H:%M:%S%.f",
289 "%Y-%m-%d %I:%M:%S %P",
290 "%Y-%m-%d %I:%M %P",
291 )
292 };
293
294 self.tz
295 .datetime_from_str(input, fmt_hms)
296 .or_else(|_| self.tz.datetime_from_str(input, fmt_hm))
297 .or_else(|_| self.tz.datetime_from_str(input, fmt_hms_f))
298 .or_else(|_| self.tz.datetime_from_str(input, fmt_ims_p))
299 .or_else(|_| self.tz.datetime_from_str(input, fmt_im_p))
300 .ok()
301 .map(|parsed| parsed.with_timezone(&Utc))
302 .map(Ok)
303 }
304
305 #[inline]
315 fn ymd_hms_z(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
316 if input.len() < 17 || !input.as_bytes()[10].is_ascii_whitespace() {
318 return None;
319 }
320 let re: &Regex = regex! {
321 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})$"
322 };
323
324 if let Some(caps) = re.captures(input)
325 && let Some(matched_tz) = caps.name("tz")
326 {
327 let parse_from_str = NaiveDateTime::parse_from_str;
328 return match timezone::parse(matched_tz.as_str().trim()) {
329 Ok(offset) => parse_from_str(input, "%Y-%m-%d %H:%M:%S %Z")
330 .or_else(|_| parse_from_str(input, "%Y-%m-%d %H:%M %Z"))
331 .or_else(|_| parse_from_str(input, "%Y-%m-%d %H:%M:%S%.f %Z"))
332 .ok()
333 .and_then(|parsed| offset.from_local_datetime(&parsed).single())
334 .map(|datetime| datetime.with_timezone(&Utc))
335 .map(Ok),
336 Err(err) => Some(Err(err)),
337 };
338 }
339 None
340 }
341
342 #[inline]
345 fn ymd(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
346 let re: &Regex = regex! {r"^\d{4}-\d{2}-\d{2}$"
347 };
348
349 if !re.is_match(input) {
350 return None;
351 }
352 let now = Utc::now()
353 .date()
354 .and_time(self.default_time)?
355 .with_timezone(self.tz);
356 NaiveDate::parse_from_str(input, "%Y-%m-%d")
357 .ok()
358 .map(|parsed| parsed.and_time(now.time()))
359 .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
360 .map(|at_tz| at_tz.with_timezone(&Utc))
361 .map(Ok)
362 }
363
364 #[inline]
369 fn ymd_z(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
370 if input.len() <= 10 {
372 return None;
373 }
374 let re: &Regex = regex! {r"^\d{4}-\d{2}-\d{2}(?P<tz>\s*[+-:a-zA-Z0-9]{3,6})$"
375 };
376 if let Some(caps) = re.captures(input)
377 && let Some(matched_tz) = caps.name("tz")
378 {
379 return match timezone::parse(matched_tz.as_str().trim()) {
380 Ok(offset) => {
381 let now = Utc::now()
382 .date()
383 .and_time(self.default_time)?
384 .with_timezone(&offset);
385 NaiveDate::parse_from_str(input, "%Y-%m-%d %Z")
386 .ok()
387 .map(|parsed| parsed.and_time(now.time()))
388 .and_then(|datetime| offset.from_local_datetime(&datetime).single())
389 .map(|at_tz| at_tz.with_timezone(&Utc))
390 .map(Ok)
391 }
392 Err(err) => Some(Err(err)),
393 };
394 }
395 None
396 }
397
398 #[inline]
401 fn month_ymd(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
402 let re: &Regex = regex! {r"^\d{4}-\w{3,9}-\d{2}$"
403 };
404 if !re.is_match(input) {
405 return None;
406 }
407
408 let now = Utc::now()
409 .date()
410 .and_time(self.default_time)?
411 .with_timezone(self.tz);
412 NaiveDate::parse_from_str(input, "%Y-%m-%d")
413 .or_else(|_| NaiveDate::parse_from_str(input, "%Y-%b-%d"))
414 .ok()
415 .map(|parsed| parsed.and_time(now.time()))
416 .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
417 .map(|at_tz| at_tz.with_timezone(&Utc))
418 .map(Ok)
419 }
420
421 #[inline]
426 fn month_mdy_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
427 let re: &Regex = regex! {
428 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)?$"
429 };
430 if !re.is_match(input) {
431 return None;
432 }
433
434 let dt = input.replace([',', '.'], "");
439 self.tz
440 .datetime_from_str(&dt, "%B %d %Y %H:%M:%S")
441 .or_else(|_| self.tz.datetime_from_str(&dt, "%B %d %Y %H:%M"))
442 .or_else(|_| self.tz.datetime_from_str(&dt, "%B %d %Y %I:%M:%S %P"))
443 .or_else(|_| self.tz.datetime_from_str(&dt, "%B %d %Y %I:%M %P"))
444 .ok()
445 .map(|at_tz| at_tz.with_timezone(&Utc))
446 .map(Ok)
447 }
448
449 #[inline]
455 fn month_mdy_hms_z(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
456 if input.len() < 20 {
459 return None;
460 }
461 let bytes = input.as_bytes();
462 let has_year = (0..bytes.len().saturating_sub(3)).any(|i| {
463 bytes[i..i + 4].iter().all(|b| b.is_ascii_digit())
464 && (i == 0 || !bytes[i - 1].is_ascii_digit())
465 && bytes.get(i + 4).is_none_or(|b| !b.is_ascii_digit())
466 });
467 if !has_year {
468 return None;
469 }
470 let re: &Regex = regex! {
471 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})$",
472 };
473 if let Some(caps) = re.captures(input)
474 && let Some(matched_tz) = caps.name("tz")
475 {
476 let parse_from_str = NaiveDateTime::parse_from_str;
477 return match timezone::parse(matched_tz.as_str().trim()) {
478 Ok(offset) => {
479 let mut dt = input.replace(',', "");
480 if let Some(pos) = dt.find("at") {
481 dt.replace_range(pos..pos + 2, "");
482 }
483 parse_from_str(&dt, "%B %d %Y %H:%M:%S %Z")
484 .or_else(|_| parse_from_str(&dt, "%B %d %Y %H:%M %Z"))
485 .or_else(|_| parse_from_str(&dt, "%B %d %Y %I:%M:%S %P %Z"))
486 .or_else(|_| parse_from_str(&dt, "%B %d %Y %I:%M %P %Z"))
487 .ok()
488 .and_then(|parsed| offset.from_local_datetime(&parsed).single())
489 .map(|datetime| datetime.with_timezone(&Utc))
490 .map(Ok)
491 }
492 Err(err) => Some(Err(err)),
493 };
494 }
495 None
496 }
497
498 #[inline]
506 fn month_mdy(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
507 let re: &Regex = regex! {r"^[a-zA-Z]{3,9}\.?\s+\d{1,2},\s+\d{2,4}$"
508 };
509 if !re.is_match(input) {
510 return None;
511 }
512
513 let now = Utc::now()
514 .date()
515 .and_time(self.default_time)?
516 .with_timezone(self.tz);
517 let dt = input.replace([',', '.'], "");
521 NaiveDate::parse_from_str(&dt, "%B %d %y")
522 .or_else(|_| NaiveDate::parse_from_str(&dt, "%B %d %Y"))
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]
535 fn month_dmy_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
536 if !input.as_bytes().contains(&b':') {
538 return None;
539 }
540 let re: &Regex = regex! {
541 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})?$"
542 };
543 if !re.is_match(input) {
544 return None;
545 }
546
547 let dt = input.replace(',', "");
548 self.tz
549 .datetime_from_str(&dt, "%d %B %Y %H:%M:%S")
550 .or_else(|_| self.tz.datetime_from_str(&dt, "%d %B %Y %H:%M"))
551 .or_else(|_| self.tz.datetime_from_str(&dt, "%d %B %Y %H:%M:%S%.f"))
552 .or_else(|_| self.tz.datetime_from_str(&dt, "%d %B %Y %I:%M:%S %P"))
553 .or_else(|_| self.tz.datetime_from_str(&dt, "%d %B %Y %I:%M %P"))
554 .ok()
555 .map(|at_tz| at_tz.with_timezone(&Utc))
556 .map(Ok)
557 }
558
559 #[inline]
565 fn month_dmy(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
566 let re: &Regex = regex! {r"^\d{1,2}\s+[a-zA-Z]{3,9}\s+\d{2,4}$"
567 };
568 if !re.is_match(input) {
569 return None;
570 }
571
572 let now = Utc::now()
573 .date()
574 .and_time(self.default_time)?
575 .with_timezone(self.tz);
576 let bytes = input.as_bytes();
579 let len = bytes.len();
580 let four_digit_year = len >= 5
581 && bytes[len - 4..].iter().all(|b| b.is_ascii_digit())
582 && bytes[len - 5].is_ascii_whitespace();
583 let parsed = if four_digit_year {
584 NaiveDate::parse_from_str(input, "%d %B %Y")
585 } else {
586 NaiveDate::parse_from_str(input, "%d %B %y")
587 .or_else(|_| NaiveDate::parse_from_str(input, "%d %B %Y"))
588 };
589 parsed
590 .ok()
591 .map(|parsed| parsed.and_time(now.time()))
592 .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
593 .map(|at_tz| at_tz.with_timezone(&Utc))
594 .map(Ok)
595 }
596
597 #[inline]
611 fn slash_mdy_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
612 let re: &Regex = regex! {
613 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)?$"
614 };
615 if !re.is_match(input) {
616 return None;
617 }
618
619 self.tz
620 .datetime_from_str(input, "%m/%d/%y %H:%M:%S")
621 .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%y %H:%M"))
622 .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%y %H:%M:%S%.f"))
623 .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%y %I:%M:%S %P"))
624 .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%y %I:%M %P"))
625 .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %H:%M:%S"))
626 .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %H:%M"))
627 .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %H:%M:%S%.f"))
628 .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %I:%M:%S %P"))
629 .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %I:%M %P"))
630 .ok()
631 .map(|at_tz| at_tz.with_timezone(&Utc))
632 .map(Ok)
633 }
634
635 #[inline]
649 fn slash_dmy_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
650 let re: &Regex = regex! {
651 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)?$"
652 };
653 if !re.is_match(input) {
654 return None;
655 }
656
657 self.tz
658 .datetime_from_str(input, "%d/%m/%y %H:%M:%S")
659 .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%y %H:%M"))
660 .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%y %H:%M:%S%.f"))
661 .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%y %I:%M:%S %P"))
662 .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%y %I:%M %P"))
663 .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%Y %H:%M:%S"))
664 .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%Y %H:%M"))
665 .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%Y %H:%M:%S%.f"))
666 .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%Y %I:%M:%S %P"))
667 .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%Y %I:%M %P"))
668 .ok()
669 .map(|at_tz| at_tz.with_timezone(&Utc))
670 .map(Ok)
671 }
672
673 #[inline]
679 fn slash_mdy(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
680 let re: &Regex = regex! {r"^\d{1,2}/\d{1,2}/\d{2,4}$"
681 };
682 if !re.is_match(input) {
683 return None;
684 }
685
686 let now = Utc::now()
687 .date()
688 .and_time(self.default_time)?
689 .with_timezone(self.tz);
690 NaiveDate::parse_from_str(input, "%m/%d/%y")
691 .or_else(|_| NaiveDate::parse_from_str(input, "%m/%d/%Y"))
692 .ok()
693 .map(|parsed| parsed.and_time(now.time()))
694 .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
695 .map(|at_tz| at_tz.with_timezone(&Utc))
696 .map(Ok)
697 }
698
699 #[inline]
705 fn slash_dmy(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
706 let re: &Regex = regex! {r"^[0-9]{1,2}/[0-9]{1,2}/[0-9]{2,4}$"
707 };
708 if !re.is_match(input) {
709 return None;
710 }
711
712 let now = Utc::now()
713 .date()
714 .and_time(self.default_time)?
715 .with_timezone(self.tz);
716 NaiveDate::parse_from_str(input, "%d/%m/%y")
717 .or_else(|_| NaiveDate::parse_from_str(input, "%d/%m/%Y"))
718 .ok()
719 .map(|parsed| parsed.and_time(now.time()))
720 .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
721 .map(|at_tz| at_tz.with_timezone(&Utc))
722 .map(Ok)
723 }
724
725 #[inline]
733 fn slash_ymd_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
734 let re: &Regex = regex! {
735 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)?$"
736 };
737 if !re.is_match(input) {
738 return None;
739 }
740
741 self.tz
742 .datetime_from_str(input, "%Y/%m/%d %H:%M:%S")
743 .or_else(|_| self.tz.datetime_from_str(input, "%Y/%m/%d %H:%M"))
744 .or_else(|_| self.tz.datetime_from_str(input, "%Y/%m/%d %H:%M:%S%.f"))
745 .or_else(|_| self.tz.datetime_from_str(input, "%Y/%m/%d %I:%M:%S %P"))
746 .or_else(|_| self.tz.datetime_from_str(input, "%Y/%m/%d %I:%M %P"))
747 .ok()
748 .map(|at_tz| at_tz.with_timezone(&Utc))
749 .map(Ok)
750 }
751
752 #[inline]
756 fn slash_ymd(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
757 let re: &Regex = regex! {r"^[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}$"
758 };
759 if !re.is_match(input) {
760 return None;
761 }
762
763 let now = Utc::now()
764 .date()
765 .and_time(self.default_time)?
766 .with_timezone(self.tz);
767 NaiveDate::parse_from_str(input, "%Y/%m/%d")
768 .ok()
769 .map(|parsed| parsed.and_time(now.time()))
770 .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
771 .map(|at_tz| at_tz.with_timezone(&Utc))
772 .map(Ok)
773 }
774}
775
776#[cfg(test)]
777mod tests {
778 use super::*;
779
780 #[test]
781 fn unix_timestamp() {
782 let parse = Parse::new(&Utc, Utc::now().time());
783
784 let test_cases = vec![
785 ("0", Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)),
786 ("0000000000", Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)),
787 ("0000000000000", Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)),
788 ("0000000000000000000", Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)),
789 ("-770172300", Utc.ymd(1945, 8, 5).and_hms(23, 15, 0)),
790 (
791 "1671673426.123456789",
792 Utc.ymd(2022, 12, 22).and_hms_nano(1, 43, 46, 123456768),
793 ),
794 ("1511648546", Utc.ymd(2017, 11, 25).and_hms(22, 22, 26)),
795 (
796 "1620036248.420",
797 Utc.ymd(2021, 5, 3).and_hms_milli(10, 4, 8, 420),
798 ),
799 (
800 "1620036248.717915136",
801 Utc.ymd(2021, 5, 3).and_hms_nano(10, 4, 8, 717915136),
802 ),
803 ];
804
805 for &(input, want) in test_cases.iter() {
806 assert_eq!(
807 parse.unix_timestamp(input).unwrap().unwrap(),
808 want,
809 "unix_timestamp/{}",
810 input
811 )
812 }
813 assert!(parse.unix_timestamp("15116").is_some());
814 assert!(
815 parse
816 .unix_timestamp("16200248727179150001620024872717915000") .is_some()
818 );
819 assert!(parse.unix_timestamp("not-a-ts").is_none());
820 }
821
822 #[test]
823 fn rfc3339() {
824 let parse = Parse::new(&Utc, Utc::now().time());
825
826 let test_cases = [
827 (
828 "2021-05-01T01:17:02.604456Z",
829 Utc.ymd(2021, 5, 1).and_hms_nano(1, 17, 2, 604456000),
830 ),
831 (
832 "2017-11-25T22:34:50Z",
833 Utc.ymd(2017, 11, 25).and_hms(22, 34, 50),
834 ),
835 ];
836
837 for &(input, want) in test_cases.iter() {
838 assert_eq!(
839 parse.rfc3339(input).unwrap().unwrap(),
840 want,
841 "rfc3339/{}",
842 input
843 )
844 }
845 assert!(parse.rfc3339("2017-11-25 22:34:50").is_none());
846 assert!(parse.rfc3339("not-date-time").is_none());
847 }
848
849 #[test]
850 fn rfc2822() {
851 let parse = Parse::new(&Utc, Utc::now().time());
852
853 let test_cases = [
854 (
855 "Wed, 02 Jun 2021 06:31:39 GMT",
856 Utc.ymd(2021, 6, 2).and_hms(6, 31, 39),
857 ),
858 (
859 "Wed, 02 Jun 2021 06:31:39 PDT",
860 Utc.ymd(2021, 6, 2).and_hms(13, 31, 39),
861 ),
862 ];
863
864 for &(input, want) in test_cases.iter() {
865 assert_eq!(
866 parse.rfc2822(input).unwrap().unwrap(),
867 want,
868 "rfc2822/{}",
869 input
870 )
871 }
872 assert!(parse.rfc2822("02 Jun 2021 06:31:39").is_none());
873 assert!(parse.rfc2822("not-date-time").is_none());
874 }
875
876 #[test]
877 fn ymd_hms() {
878 let parse = Parse::new(&Utc, Utc::now().time());
879
880 let test_cases = [
881 ("2021-04-30 21:14", Utc.ymd(2021, 4, 30).and_hms(21, 14, 0)),
882 (
883 "2021-04-30 21:14:10",
884 Utc.ymd(2021, 4, 30).and_hms(21, 14, 10),
885 ),
886 (
887 "2021-04-30 21:14:10.052282",
888 Utc.ymd(2021, 4, 30).and_hms_micro(21, 14, 10, 52282),
889 ),
890 (
891 "2014-04-26 05:24:37 PM",
892 Utc.ymd(2014, 4, 26).and_hms(17, 24, 37),
893 ),
894 (
895 "2014-04-26 17:24:37.123",
896 Utc.ymd(2014, 4, 26).and_hms_milli(17, 24, 37, 123),
897 ),
898 (
899 "2014-04-26 17:24:37.3186369",
900 Utc.ymd(2014, 4, 26).and_hms_nano(17, 24, 37, 318636900),
901 ),
902 (
903 "2012-08-03 18:31:59.257000000",
904 Utc.ymd(2012, 8, 3).and_hms_nano(18, 31, 59, 257000000),
905 ),
906 ("2020-01-15T08:00", Utc.ymd(2020, 1, 15).and_hms(8, 0, 0)),
909 ("2020-01-15T08:00:00", Utc.ymd(2020, 1, 15).and_hms(8, 0, 0)),
910 (
911 "2020-01-15T08:00:00.123",
912 Utc.ymd(2020, 1, 15).and_hms_milli(8, 0, 0, 123),
913 ),
914 (
915 "2020-01-15T08:00:00.123456",
916 Utc.ymd(2020, 1, 15).and_hms_micro(8, 0, 0, 123456),
917 ),
918 (
919 "2020-01-15T08:00:00.123456789",
920 Utc.ymd(2020, 1, 15).and_hms_nano(8, 0, 0, 123456789),
921 ),
922 ];
923
924 for &(input, want) in test_cases.iter() {
925 assert_eq!(
926 parse.ymd_hms(input).unwrap().unwrap(),
927 want,
928 "ymd_hms/{}",
929 input
930 )
931 }
932 assert!(parse.ymd_hms("not-date-time").is_none());
933
934 let t_form = parse.ymd_hms("2020-01-15T08:00:00").unwrap().unwrap();
936 let space_form = parse.ymd_hms("2020-01-15 08:00:00").unwrap().unwrap();
937 assert_eq!(t_form, space_form, "T-separator vs space disagree");
938 }
939
940 #[test]
941 fn ymd_hms_z() {
942 let parse = Parse::new(&Utc, Utc::now().time());
943
944 let test_cases = [
945 (
946 "2017-11-25 13:31:15 PST",
947 Utc.ymd(2017, 11, 25).and_hms(21, 31, 15),
948 ),
949 (
950 "2017-11-25 13:31 PST",
951 Utc.ymd(2017, 11, 25).and_hms(21, 31, 0),
952 ),
953 (
954 "2014-12-16 06:20:00 UTC",
955 Utc.ymd(2014, 12, 16).and_hms(6, 20, 0),
956 ),
957 (
958 "2014-12-16 06:20:00 GMT",
959 Utc.ymd(2014, 12, 16).and_hms(6, 20, 0),
960 ),
961 (
962 "2014-04-26 13:13:43 +0800",
963 Utc.ymd(2014, 4, 26).and_hms(5, 13, 43),
964 ),
965 (
966 "2014-04-26 13:13:44 +09:00",
967 Utc.ymd(2014, 4, 26).and_hms(4, 13, 44),
968 ),
969 (
970 "2012-08-03 18:31:59.257000000 +0000",
971 Utc.ymd(2012, 8, 3).and_hms_nano(18, 31, 59, 257000000),
972 ),
973 (
974 "2015-09-30 18:48:56.35272715 UTC",
975 Utc.ymd(2015, 9, 30).and_hms_nano(18, 48, 56, 352727150),
976 ),
977 ];
978
979 for &(input, want) in test_cases.iter() {
980 assert_eq!(
981 parse.ymd_hms_z(input).unwrap().unwrap(),
982 want,
983 "ymd_hms_z/{}",
984 input
985 )
986 }
987 assert!(parse.ymd_hms_z("not-date-time").is_none());
988 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()); }
995
996 #[test]
997 fn ymd() {
998 let parse = Parse::new(&Utc, Utc::now().time());
999
1000 let test_cases = [(
1001 "2021-02-21",
1002 Utc.ymd(2021, 2, 21).and_time(Utc::now().time()),
1003 )];
1004
1005 for &(input, want) in test_cases.iter() {
1006 assert_eq!(
1007 parse
1008 .ymd(input)
1009 .unwrap()
1010 .unwrap()
1011 .trunc_subsecs(0)
1012 .with_second(0)
1013 .unwrap(),
1014 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1015 "ymd/{}",
1016 input
1017 )
1018 }
1019 assert!(parse.ymd("not-date-time").is_none());
1020 }
1021
1022 #[test]
1023 fn ymd_z() {
1024 let parse = Parse::new(&Utc, Utc::now().time());
1025 let now_at_pst = Utc::now().with_timezone(&FixedOffset::west(8 * 3600));
1026 let now_at_cst = Utc::now().with_timezone(&FixedOffset::east(8 * 3600));
1027
1028 let test_cases = [
1029 (
1030 "2021-02-21 PST",
1031 FixedOffset::west(8 * 3600)
1032 .ymd(2021, 2, 21)
1033 .and_time(now_at_pst.time())
1034 .map(|dt| dt.with_timezone(&Utc)),
1035 ),
1036 (
1037 "2021-02-21 UTC",
1038 FixedOffset::west(0)
1039 .ymd(2021, 2, 21)
1040 .and_time(Utc::now().time())
1041 .map(|dt| dt.with_timezone(&Utc)),
1042 ),
1043 (
1044 "2020-07-20+08:00",
1045 FixedOffset::east(8 * 3600)
1046 .ymd(2020, 7, 20)
1047 .and_time(now_at_cst.time())
1048 .map(|dt| dt.with_timezone(&Utc)),
1049 ),
1050 ];
1051
1052 for &(input, want) in test_cases.iter() {
1053 assert_eq!(
1054 parse
1055 .ymd_z(input)
1056 .unwrap()
1057 .unwrap()
1058 .trunc_subsecs(0)
1059 .with_second(0)
1060 .unwrap(),
1061 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1062 "ymd_z/{}",
1063 input
1064 )
1065 }
1066 assert!(parse.ymd_z("not-date-time").is_none());
1067 assert!(parse.ymd_z("2021-02-21").is_none()); assert!(parse.ymd_z("2021-02-21X").is_none()); }
1071
1072 #[test]
1073 fn month_ymd() {
1074 let parse = Parse::new(&Utc, Utc::now().time());
1075
1076 let test_cases = [(
1077 "2021-Feb-21",
1078 Utc.ymd(2021, 2, 21).and_time(Utc::now().time()),
1079 )];
1080
1081 for &(input, want) in test_cases.iter() {
1082 assert_eq!(
1083 parse
1084 .month_ymd(input)
1085 .unwrap()
1086 .unwrap()
1087 .trunc_subsecs(0)
1088 .with_second(0)
1089 .unwrap(),
1090 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1091 "month_ymd/{}",
1092 input
1093 )
1094 }
1095 assert!(parse.month_ymd("not-date-time").is_none());
1096 }
1097
1098 #[test]
1099 fn month_mdy_hms() {
1100 let parse = Parse::new(&Utc, Utc::now().time());
1101
1102 let test_cases = [
1103 (
1104 "May 8, 2009 5:57:51 PM",
1105 Utc.ymd(2009, 5, 8).and_hms(17, 57, 51),
1106 ),
1107 (
1108 "September 17, 2012 10:09am",
1109 Utc.ymd(2012, 9, 17).and_hms(10, 9, 0),
1110 ),
1111 (
1112 "September 17, 2012, 10:10:09",
1113 Utc.ymd(2012, 9, 17).and_hms(10, 10, 9),
1114 ),
1115 ];
1116
1117 for &(input, want) in test_cases.iter() {
1118 assert_eq!(
1119 parse.month_mdy_hms(input).unwrap().unwrap(),
1120 want,
1121 "month_mdy_hms/{}",
1122 input
1123 )
1124 }
1125 assert!(parse.month_mdy_hms("not-date-time").is_none());
1126 }
1127
1128 #[test]
1129 fn month_mdy_hms_z() {
1130 let parse = Parse::new(&Utc, Utc::now().time());
1131
1132 let test_cases = [
1133 (
1134 "May 02, 2021 15:51:31 UTC",
1135 Utc.ymd(2021, 5, 2).and_hms(15, 51, 31),
1136 ),
1137 (
1138 "May 02, 2021 15:51 UTC",
1139 Utc.ymd(2021, 5, 2).and_hms(15, 51, 0),
1140 ),
1141 (
1142 "May 26, 2021, 12:49 AM PDT",
1143 Utc.ymd(2021, 5, 26).and_hms(7, 49, 0),
1144 ),
1145 (
1146 "September 17, 2012 at 10:09am PST",
1147 Utc.ymd(2012, 9, 17).and_hms(18, 9, 0),
1148 ),
1149 ];
1150
1151 for &(input, want) in test_cases.iter() {
1152 assert_eq!(
1153 parse.month_mdy_hms_z(input).unwrap().unwrap(),
1154 want,
1155 "month_mdy_hms_z/{}",
1156 input
1157 )
1158 }
1159 assert!(parse.month_mdy_hms_z("not-date-time").is_none());
1160 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()); }
1165
1166 #[test]
1167 fn month_mdy() {
1168 let parse = Parse::new(&Utc, Utc::now().time());
1169
1170 let test_cases = [
1171 (
1172 "May 25, 2021",
1173 Utc.ymd(2021, 5, 25).and_time(Utc::now().time()),
1174 ),
1175 (
1176 "oct 7, 1970",
1177 Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1178 ),
1179 (
1180 "oct 7, 70",
1181 Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1182 ),
1183 (
1184 "oct. 7, 1970",
1185 Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1186 ),
1187 (
1188 "oct. 7, 70",
1189 Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1190 ),
1191 (
1192 "October 7, 1970",
1193 Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1194 ),
1195 ];
1196
1197 for &(input, want) in test_cases.iter() {
1198 assert_eq!(
1199 parse
1200 .month_mdy(input)
1201 .unwrap()
1202 .unwrap()
1203 .trunc_subsecs(0)
1204 .with_second(0)
1205 .unwrap(),
1206 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1207 "month_mdy/{}",
1208 input
1209 )
1210 }
1211 assert!(parse.month_mdy("not-date-time").is_none());
1212 }
1213
1214 #[test]
1215 fn month_dmy_hms() {
1216 let parse = Parse::new(&Utc, Utc::now().time());
1217
1218 let test_cases = [
1219 (
1220 "12 Feb 2006, 19:17",
1221 Utc.ymd(2006, 2, 12).and_hms(19, 17, 0),
1222 ),
1223 ("12 Feb 2006 19:17", Utc.ymd(2006, 2, 12).and_hms(19, 17, 0)),
1224 (
1225 "14 May 2019 19:11:40.164",
1226 Utc.ymd(2019, 5, 14).and_hms_milli(19, 11, 40, 164),
1227 ),
1228 ];
1229
1230 for &(input, want) in test_cases.iter() {
1231 assert_eq!(
1232 parse.month_dmy_hms(input).unwrap().unwrap(),
1233 want,
1234 "month_dmy_hms/{}",
1235 input
1236 )
1237 }
1238 assert!(parse.month_dmy_hms("not-date-time").is_none());
1239 }
1240
1241 #[test]
1242 fn month_dmy() {
1243 let parse = Parse::new(&Utc, Utc::now().time());
1244
1245 let test_cases = [
1246 ("7 oct 70", Utc.ymd(1970, 10, 7).and_time(Utc::now().time())),
1247 (
1248 "7 oct 1970",
1249 Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1250 ),
1251 (
1252 "03 February 2013",
1253 Utc.ymd(2013, 2, 3).and_time(Utc::now().time()),
1254 ),
1255 (
1256 "1 July 2013",
1257 Utc.ymd(2013, 7, 1).and_time(Utc::now().time()),
1258 ),
1259 ];
1260
1261 for &(input, want) in test_cases.iter() {
1262 assert_eq!(
1263 parse
1264 .month_dmy(input)
1265 .unwrap()
1266 .unwrap()
1267 .trunc_subsecs(0)
1268 .with_second(0)
1269 .unwrap(),
1270 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1271 "month_dmy/{}",
1272 input
1273 )
1274 }
1275 assert!(parse.month_dmy("not-date-time").is_none());
1276 }
1277
1278 #[test]
1281 fn month_dmy_year_fast_path() {
1282 let parse = Parse::new(&Utc, Utc::now().time());
1283
1284 let four_digit = parse.month_dmy("14 May 2019").unwrap().unwrap();
1286 assert_eq!(four_digit.year(), 2019);
1287 assert_eq!(four_digit.month(), 5);
1288 assert_eq!(four_digit.day(), 14);
1289
1290 let two_digit = parse.month_dmy("14 May 19").unwrap().unwrap();
1293 assert_eq!(two_digit.year(), 2019);
1294 assert_eq!(two_digit.month(), 5);
1295 assert_eq!(two_digit.day(), 14);
1296 }
1297
1298 #[test]
1299 fn slash_mdy_hms() {
1300 let parse = Parse::new(&Utc, Utc::now().time());
1301
1302 let test_cases = vec![
1303 ("4/8/2014 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
1304 ("04/08/2014 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
1305 ("4/8/14 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
1306 ("04/2/2014 03:00:51", Utc.ymd(2014, 4, 2).and_hms(3, 0, 51)),
1307 ("8/8/1965 12:00:00 AM", Utc.ymd(1965, 8, 8).and_hms(0, 0, 0)),
1308 (
1309 "8/8/1965 01:00:01 PM",
1310 Utc.ymd(1965, 8, 8).and_hms(13, 0, 1),
1311 ),
1312 ("8/8/1965 01:00 PM", Utc.ymd(1965, 8, 8).and_hms(13, 0, 0)),
1313 ("8/8/1965 1:00 PM", Utc.ymd(1965, 8, 8).and_hms(13, 0, 0)),
1314 ("8/8/1965 12:00 AM", Utc.ymd(1965, 8, 8).and_hms(0, 0, 0)),
1315 ("4/02/2014 03:00:51", Utc.ymd(2014, 4, 2).and_hms(3, 0, 51)),
1316 (
1317 "03/19/2012 10:11:59",
1318 Utc.ymd(2012, 3, 19).and_hms(10, 11, 59),
1319 ),
1320 (
1321 "03/19/2012 10:11:59.3186369",
1322 Utc.ymd(2012, 3, 19).and_hms_nano(10, 11, 59, 318636900),
1323 ),
1324 ];
1325
1326 for &(input, want) in test_cases.iter() {
1327 assert_eq!(
1328 parse.slash_mdy_hms(input).unwrap().unwrap(),
1329 want,
1330 "slash_mdy_hms/{}",
1331 input
1332 )
1333 }
1334 assert!(parse.slash_mdy_hms("not-date-time").is_none());
1335 }
1336
1337 #[test]
1338 fn slash_mdy() {
1339 let parse = Parse::new(&Utc, Utc::now().time());
1340
1341 let test_cases = [
1342 (
1343 "3/31/2014",
1344 Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
1345 ),
1346 (
1347 "03/31/2014",
1348 Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
1349 ),
1350 ("08/21/71", Utc.ymd(1971, 8, 21).and_time(Utc::now().time())),
1351 ("8/1/71", Utc.ymd(1971, 8, 1).and_time(Utc::now().time())),
1352 ];
1353
1354 for &(input, want) in test_cases.iter() {
1355 assert_eq!(
1356 parse
1357 .slash_mdy(input)
1358 .unwrap()
1359 .unwrap()
1360 .trunc_subsecs(0)
1361 .with_second(0)
1362 .unwrap(),
1363 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1364 "slash_mdy/{}",
1365 input
1366 )
1367 }
1368 assert!(parse.slash_mdy("not-date-time").is_none());
1369 }
1370
1371 #[test]
1372 fn slash_dmy() {
1373 let mut parse = Parse::new(&Utc, Utc::now().time());
1374
1375 let test_cases = [
1376 (
1377 "31/3/2014",
1378 Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
1379 ),
1380 (
1381 "13/11/2014",
1382 Utc.ymd(2014, 11, 13).and_time(Utc::now().time()),
1383 ),
1384 ("21/08/71", Utc.ymd(1971, 8, 21).and_time(Utc::now().time())),
1385 ("1/8/71", Utc.ymd(1971, 8, 1).and_time(Utc::now().time())),
1386 ];
1387
1388 for &(input, want) in test_cases.iter() {
1389 assert_eq!(
1390 parse
1391 .prefer_dmy(true)
1392 .slash_dmy(input)
1393 .unwrap()
1394 .unwrap()
1395 .trunc_subsecs(0)
1396 .with_second(0)
1397 .unwrap(),
1398 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1399 "slash_dmy/{}",
1400 input
1401 )
1402 }
1403 assert!(parse.slash_dmy("not-date-time").is_none());
1404 }
1405
1406 #[test]
1407 fn slash_ymd_hms() {
1408 let parse = Parse::new(&Utc, Utc::now().time());
1409
1410 let test_cases = [
1411 ("2014/4/8 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
1412 ("2014/04/08 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
1413 ("2014/04/2 03:00:51", Utc.ymd(2014, 4, 2).and_hms(3, 0, 51)),
1414 ("2014/4/02 03:00:51", Utc.ymd(2014, 4, 2).and_hms(3, 0, 51)),
1415 (
1416 "2012/03/19 10:11:59",
1417 Utc.ymd(2012, 3, 19).and_hms(10, 11, 59),
1418 ),
1419 (
1420 "2012/03/19 10:11:59.3186369",
1421 Utc.ymd(2012, 3, 19).and_hms_nano(10, 11, 59, 318636900),
1422 ),
1423 ];
1424
1425 for &(input, want) in test_cases.iter() {
1426 assert_eq!(
1427 parse.slash_ymd_hms(input).unwrap().unwrap(),
1428 want,
1429 "slash_ymd_hms/{}",
1430 input
1431 )
1432 }
1433 assert!(parse.slash_ymd_hms("not-date-time").is_none());
1434 }
1435
1436 #[test]
1437 fn slash_ymd() {
1438 let parse = Parse::new(&Utc, Utc::now().time());
1439
1440 let test_cases = [
1441 (
1442 "2014/3/31",
1443 Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
1444 ),
1445 (
1446 "2014/03/31",
1447 Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
1448 ),
1449 ];
1450
1451 for &(input, want) in test_cases.iter() {
1452 assert_eq!(
1453 parse
1454 .slash_ymd(input)
1455 .unwrap()
1456 .unwrap()
1457 .trunc_subsecs(0)
1458 .with_second(0)
1459 .unwrap(),
1460 want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1461 "slash_ymd/{}",
1462 input
1463 )
1464 }
1465 assert!(parse.slash_ymd("not-date-time").is_none());
1466 }
1467}