qsv_dateparser/
datetime.rs

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}
18/// Parse struct has methods implemented parsers for accepted formats.
19pub 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    /// Create a new instance of [`Parse`] with a custom parsing timezone that handles the
30    /// datetime string without time offset.
31    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    /// Create a new instance of [`Parse`] with a custom parsing timezone that handles the
45    /// datetime string without time offset, and the date parsing preference.
46    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    /// This method tries to parse the input datetime string with a list of accepted formats. See
59    /// more examples from [`Parse`], [`crate::parse()`] and [`crate::parse_with_timezone()`].
60    #[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    // unix timestamp
145    // - 0
146    // - -770172300
147    // - 1671673426.123456789
148    #[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        // convert the timestamp seconds value to nanoseconds
157        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    // rfc3339
164    // - 2021-05-01T01:17:02.604456Z
165    // - 2017-11-25T22:34:50Z
166    #[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    // rfc2822
175    // - Wed, 02 Jun 2021 06:31:39 GMT
176    #[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    // yyyy-mm-dd hh:mm:ss
185    // - 2014-04-26 05:24:37 PM
186    // - 2021-04-30 21:14
187    // - 2021-04-30 21:14:10
188    // - 2021-04-30 21:14:10.052282
189    // - 2014-04-26 17:24:37.123
190    // - 2014-04-26 17:24:37.3186369
191    // - 2012-08-03 18:31:59.257000000
192    #[inline]
193    fn ymd_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
194        let re: &Regex = regex! {
195                r"^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}(:\d{2})?(\.\d{1,9})?\s*(am|pm|AM|PM)?$"
196
197        };
198        if !re.is_match(input) {
199            return None;
200        }
201
202        self.tz
203            .datetime_from_str(input, "%Y-%m-%d %H:%M:%S")
204            .or_else(|_| self.tz.datetime_from_str(input, "%Y-%m-%d %H:%M"))
205            .or_else(|_| self.tz.datetime_from_str(input, "%Y-%m-%d %H:%M:%S%.f"))
206            .or_else(|_| self.tz.datetime_from_str(input, "%Y-%m-%d %I:%M:%S %P"))
207            .or_else(|_| self.tz.datetime_from_str(input, "%Y-%m-%d %I:%M %P"))
208            .ok()
209            .map(|parsed| parsed.with_timezone(&Utc))
210            .map(Ok)
211    }
212
213    // yyyy-mm-dd hh:mm:ss z
214    // - 2017-11-25 13:31:15 PST
215    // - 2017-11-25 13:31 PST
216    // - 2014-12-16 06:20:00 UTC
217    // - 2014-12-16 06:20:00 GMT
218    // - 2014-04-26 13:13:43 +0800
219    // - 2014-04-26 13:13:44 +09:00
220    // - 2012-08-03 18:31:59.257000000 +0000
221    // - 2015-09-30 18:48:56.35272715 UTC
222    #[inline]
223    fn ymd_hms_z(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
224        let re: &Regex = regex! {
225                r"^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}(:\d{2})?(\.\d{1,9})?(?P<tz>\s*[+-:a-zA-Z0-9]{3,6})$"
226        };
227
228        if !re.is_match(input) {
229            return None;
230        }
231        if let Some(caps) = re.captures(input) {
232            if let Some(matched_tz) = caps.name("tz") {
233                let parse_from_str = NaiveDateTime::parse_from_str;
234                return match timezone::parse(matched_tz.as_str().trim()) {
235                    Ok(offset) => parse_from_str(input, "%Y-%m-%d %H:%M:%S %Z")
236                        .or_else(|_| parse_from_str(input, "%Y-%m-%d %H:%M %Z"))
237                        .or_else(|_| parse_from_str(input, "%Y-%m-%d %H:%M:%S%.f %Z"))
238                        .ok()
239                        .and_then(|parsed| offset.from_local_datetime(&parsed).single())
240                        .map(|datetime| datetime.with_timezone(&Utc))
241                        .map(Ok),
242                    Err(err) => Some(Err(err)),
243                };
244            }
245        }
246        None
247    }
248
249    // yyyy-mm-dd
250    // - 2021-02-21
251    #[inline]
252    fn ymd(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
253        let re: &Regex = regex! {r"^\d{4}-\d{2}-\d{2}$"
254        };
255
256        if !re.is_match(input) {
257            return None;
258        }
259        let now = Utc::now()
260            .date()
261            .and_time(self.default_time)?
262            .with_timezone(self.tz);
263        NaiveDate::parse_from_str(input, "%Y-%m-%d")
264            .ok()
265            .map(|parsed| parsed.and_time(now.time()))
266            .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
267            .map(|at_tz| at_tz.with_timezone(&Utc))
268            .map(Ok)
269    }
270
271    // yyyy-mm-dd z
272    // - 2021-02-21 PST
273    // - 2021-02-21 UTC
274    // - 2020-07-20+08:00 (yyyy-mm-dd-07:00)
275    #[inline]
276    fn ymd_z(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
277        let re: &Regex = regex! {r"^\d{4}-\d{2}-\d{2}(?P<tz>\s*[+-:a-zA-Z0-9]{3,6})$"
278        };
279        if !re.is_match(input) {
280            return None;
281        }
282
283        if let Some(caps) = re.captures(input) {
284            if let Some(matched_tz) = caps.name("tz") {
285                return match timezone::parse(matched_tz.as_str().trim()) {
286                    Ok(offset) => {
287                        let now = Utc::now()
288                            .date()
289                            .and_time(self.default_time)?
290                            .with_timezone(&offset);
291                        NaiveDate::parse_from_str(input, "%Y-%m-%d %Z")
292                            .ok()
293                            .map(|parsed| parsed.and_time(now.time()))
294                            .and_then(|datetime| offset.from_local_datetime(&datetime).single())
295                            .map(|at_tz| at_tz.with_timezone(&Utc))
296                            .map(Ok)
297                    }
298                    Err(err) => Some(Err(err)),
299                };
300            }
301        }
302        None
303    }
304
305    // yyyy-mon-dd
306    // - 2021-Feb-21
307    #[inline]
308    fn month_ymd(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
309        let re: &Regex = regex! {r"^\d{4}-\w{3,9}-\d{2}$"
310        };
311        if !re.is_match(input) {
312            return None;
313        }
314
315        let now = Utc::now()
316            .date()
317            .and_time(self.default_time)?
318            .with_timezone(self.tz);
319        NaiveDate::parse_from_str(input, "%Y-%m-%d")
320            .or_else(|_| NaiveDate::parse_from_str(input, "%Y-%b-%d"))
321            .ok()
322            .map(|parsed| parsed.and_time(now.time()))
323            .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
324            .map(|at_tz| at_tz.with_timezone(&Utc))
325            .map(Ok)
326    }
327
328    // Mon dd, yyyy, hh:mm:ss
329    // - May 8, 2009 5:57:51 PM
330    // - September 17, 2012 10:09am
331    // - September 17, 2012, 10:10:09
332    #[inline]
333    fn month_mdy_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
334        let re: &Regex = regex! {
335                r"^[a-zA-Z]{3,9}\.?\s+\d{1,2},\s+\d{2,4},?\s+\d{1,2}:\d{2}(:\d{2})?\s*(am|pm|AM|PM)?$"
336        };
337        if !re.is_match(input) {
338            return None;
339        }
340
341        let dt = input.replace(", ", " ").replace(". ", " ");
342        self.tz
343            .datetime_from_str(&dt, "%B %d %Y %H:%M:%S")
344            .or_else(|_| self.tz.datetime_from_str(&dt, "%B %d %Y %H:%M"))
345            .or_else(|_| self.tz.datetime_from_str(&dt, "%B %d %Y %I:%M:%S %P"))
346            .or_else(|_| self.tz.datetime_from_str(&dt, "%B %d %Y %I:%M %P"))
347            .ok()
348            .map(|at_tz| at_tz.with_timezone(&Utc))
349            .map(Ok)
350    }
351
352    // Mon dd, yyyy hh:mm:ss z
353    // - May 02, 2021 15:51:31 UTC
354    // - May 02, 2021 15:51 UTC
355    // - May 26, 2021, 12:49 AM PDT
356    // - September 17, 2012 at 10:09am PST
357    #[inline]
358    fn month_mdy_hms_z(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
359        let re: &Regex = regex! {
360                r"^[a-zA-Z]{3,9}\s+\d{1,2},?\s+\d{4}\s*,?(at)?\s+\d{2}:\d{2}(:\d{2})?\s*(am|pm|AM|PM)?(?P<tz>\s+[+-:a-zA-Z0-9]{3,6})$",
361        };
362        if !re.is_match(input) {
363            return None;
364        }
365
366        if let Some(caps) = re.captures(input) {
367            if let Some(matched_tz) = caps.name("tz") {
368                let parse_from_str = NaiveDateTime::parse_from_str;
369                return match timezone::parse(matched_tz.as_str().trim()) {
370                    Ok(offset) => {
371                        let dt = input.replace(',', "").replace("at", "");
372                        parse_from_str(&dt, "%B %d %Y %H:%M:%S %Z")
373                            .or_else(|_| parse_from_str(&dt, "%B %d %Y %H:%M %Z"))
374                            .or_else(|_| parse_from_str(&dt, "%B %d %Y %I:%M:%S %P %Z"))
375                            .or_else(|_| parse_from_str(&dt, "%B %d %Y %I:%M %P %Z"))
376                            .ok()
377                            .and_then(|parsed| offset.from_local_datetime(&parsed).single())
378                            .map(|datetime| datetime.with_timezone(&Utc))
379                            .map(Ok)
380                    }
381                    Err(err) => Some(Err(err)),
382                };
383            }
384        }
385        None
386    }
387
388    // Mon dd, yyyy
389    // - May 25, 2021
390    // - oct 7, 1970
391    // - oct 7, 70
392    // - oct. 7, 1970
393    // - oct. 7, 70
394    // - October 7, 1970
395    #[inline]
396    fn month_mdy(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
397        let re: &Regex = regex! {r"^[a-zA-Z]{3,9}\.?\s+\d{1,2},\s+\d{2,4}$"
398        };
399        if !re.is_match(input) {
400            return None;
401        }
402
403        let now = Utc::now()
404            .date()
405            .and_time(self.default_time)?
406            .with_timezone(self.tz);
407        let dt = input.replace(", ", " ").replace(". ", " ");
408        NaiveDate::parse_from_str(&dt, "%B %d %y")
409            .or_else(|_| NaiveDate::parse_from_str(&dt, "%B %d %Y"))
410            .ok()
411            .map(|parsed| parsed.and_time(now.time()))
412            .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
413            .map(|at_tz| at_tz.with_timezone(&Utc))
414            .map(Ok)
415    }
416
417    // dd Mon yyyy hh:mm:ss
418    // - 12 Feb 2006, 19:17
419    // - 12 Feb 2006 19:17
420    // - 14 May 2019 19:11:40.164
421    #[inline]
422    fn month_dmy_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
423        let re: &Regex = regex! {
424                r"^\d{1,2}\s+[a-zA-Z]{3,9}\s+\d{2,4},?\s+\d{1,2}:[0-9]{2}(:[0-9]{2})?(\.[0-9]{1,9})?$"
425        };
426        if !re.is_match(input) {
427            return None;
428        }
429
430        let dt = input.replace(", ", " ");
431        self.tz
432            .datetime_from_str(&dt, "%d %B %Y %H:%M:%S")
433            .or_else(|_| self.tz.datetime_from_str(&dt, "%d %B %Y %H:%M"))
434            .or_else(|_| self.tz.datetime_from_str(&dt, "%d %B %Y %H:%M:%S%.f"))
435            .or_else(|_| self.tz.datetime_from_str(&dt, "%d %B %Y %I:%M:%S %P"))
436            .or_else(|_| self.tz.datetime_from_str(&dt, "%d %B %Y %I:%M %P"))
437            .ok()
438            .map(|at_tz| at_tz.with_timezone(&Utc))
439            .map(Ok)
440    }
441
442    // dd Mon yyyy
443    // - 7 oct 70
444    // - 7 oct 1970
445    // - 03 February 2013
446    // - 1 July 2013
447    #[inline]
448    fn month_dmy(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
449        let re: &Regex = regex! {r"^\d{1,2}\s+[a-zA-Z]{3,9}\s+\d{2,4}$"
450        };
451        if !re.is_match(input) {
452            return None;
453        }
454
455        let now = Utc::now()
456            .date()
457            .and_time(self.default_time)?
458            .with_timezone(self.tz);
459        NaiveDate::parse_from_str(input, "%d %B %y")
460            .or_else(|_| NaiveDate::parse_from_str(input, "%d %B %Y"))
461            .ok()
462            .map(|parsed| parsed.and_time(now.time()))
463            .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
464            .map(|at_tz| at_tz.with_timezone(&Utc))
465            .map(Ok)
466    }
467
468    // mm/dd/yyyy hh:mm:ss
469    // - 4/8/2014 22:05
470    // - 04/08/2014 22:05
471    // - 4/8/14 22:05
472    // - 04/2/2014 03:00:51
473    // - 8/8/1965 12:00:00 AM
474    // - 8/8/1965 01:00:01 PM
475    // - 8/8/1965 01:00 PM
476    // - 8/8/1965 1:00 PM
477    // - 8/8/1965 12:00 AM
478    // - 4/02/2014 03:00:51
479    // - 03/19/2012 10:11:59
480    // - 03/19/2012 10:11:59.3186369
481    #[inline]
482    fn slash_mdy_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
483        let re: &Regex = regex! {
484                r"^\d{1,2}/\d{1,2}/\d{2,4}\s+\d{1,2}:\d{2}(:\d{2})?(\.\d{1,9})?\s*(am|pm|AM|PM)?$"
485        };
486        if !re.is_match(input) {
487            return None;
488        }
489
490        self.tz
491            .datetime_from_str(input, "%m/%d/%y %H:%M:%S")
492            .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%y %H:%M"))
493            .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%y %H:%M:%S%.f"))
494            .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%y %I:%M:%S %P"))
495            .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%y %I:%M %P"))
496            .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %H:%M:%S"))
497            .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %H:%M"))
498            .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %H:%M:%S%.f"))
499            .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %I:%M:%S %P"))
500            .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %I:%M %P"))
501            .ok()
502            .map(|at_tz| at_tz.with_timezone(&Utc))
503            .map(Ok)
504    }
505
506    // dd/mm/yyyy hh:mm:ss
507    // - 8/4/2014 22:05
508    // - 08/04/2014 22:05
509    // - 8/4/14 22:05
510    // - 2/04/2014 03:00:51
511    // - 8/8/1965 12:00:00 AM
512    // - 8/8/1965 01:00:01 PM
513    // - 8/8/1965 01:00 PM
514    // - 8/8/1965 1:00 PM
515    // - 8/8/1965 12:00 AM
516    // - 02/4/2014 03:00:51
517    // - 19/03/2012 10:11:59
518    // - 19/03/2012 10:11:59.3186369
519    #[inline]
520    fn slash_dmy_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
521        let re: &Regex = regex! {
522                r"^\d{1,2}/\d{1,2}/\d{2,4}\s+\d{1,2}:\d{2}(:\d{2})?(\.\d{1,9})?\s*(am|pm|AM|PM)?$"
523        };
524        if !re.is_match(input) {
525            return None;
526        }
527
528        self.tz
529            .datetime_from_str(input, "%d/%m/%y %H:%M:%S")
530            .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%y %H:%M"))
531            .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%y %H:%M:%S%.f"))
532            .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%y %I:%M:%S %P"))
533            .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%y %I:%M %P"))
534            .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%Y %H:%M:%S"))
535            .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%Y %H:%M"))
536            .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%Y %H:%M:%S%.f"))
537            .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%Y %I:%M:%S %P"))
538            .or_else(|_| self.tz.datetime_from_str(input, "%d/%m/%Y %I:%M %P"))
539            .ok()
540            .map(|at_tz| at_tz.with_timezone(&Utc))
541            .map(Ok)
542    }
543
544    // mm/dd/yyyy
545    // - 3/31/2014
546    // - 03/31/2014
547    // - 08/21/71
548    // - 8/1/71
549    #[inline]
550    fn slash_mdy(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
551        let re: &Regex = regex! {r"^\d{1,2}/\d{1,2}/\d{2,4}$"
552        };
553        if !re.is_match(input) {
554            return None;
555        }
556
557        let now = Utc::now()
558            .date()
559            .and_time(self.default_time)?
560            .with_timezone(self.tz);
561        NaiveDate::parse_from_str(input, "%m/%d/%y")
562            .or_else(|_| NaiveDate::parse_from_str(input, "%m/%d/%Y"))
563            .ok()
564            .map(|parsed| parsed.and_time(now.time()))
565            .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
566            .map(|at_tz| at_tz.with_timezone(&Utc))
567            .map(Ok)
568    }
569
570    // dd/mm/yyyy
571    // - 31/3/2014
572    // - 31/03/2014
573    // - 21/08/71
574    // - 1/8/71
575    #[inline]
576    fn slash_dmy(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
577        let re: &Regex = regex! {r"^[0-9]{1,2}/[0-9]{1,2}/[0-9]{2,4}$"
578        };
579        if !re.is_match(input) {
580            return None;
581        }
582
583        let now = Utc::now()
584            .date()
585            .and_time(self.default_time)?
586            .with_timezone(self.tz);
587        NaiveDate::parse_from_str(input, "%d/%m/%y")
588            .or_else(|_| NaiveDate::parse_from_str(input, "%d/%m/%Y"))
589            .ok()
590            .map(|parsed| parsed.and_time(now.time()))
591            .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
592            .map(|at_tz| at_tz.with_timezone(&Utc))
593            .map(Ok)
594    }
595
596    // yyyy/mm/dd hh:mm:ss
597    // - 2014/4/8 22:05
598    // - 2014/04/08 22:05
599    // - 2014/04/2 03:00:51
600    // - 2014/4/02 03:00:51
601    // - 2012/03/19 10:11:59
602    // - 2012/03/19 10:11:59.3186369
603    #[inline]
604    fn slash_ymd_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
605        let re: &Regex = regex! {
606                r"^[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}\s+[0-9]{1,2}:[0-9]{2}(:[0-9]{2})?(\.[0-9]{1,9})?\s*(am|pm|AM|PM)?$"
607        };
608        if !re.is_match(input) {
609            return None;
610        }
611
612        self.tz
613            .datetime_from_str(input, "%Y/%m/%d %H:%M:%S")
614            .or_else(|_| self.tz.datetime_from_str(input, "%Y/%m/%d %H:%M"))
615            .or_else(|_| self.tz.datetime_from_str(input, "%Y/%m/%d %H:%M:%S%.f"))
616            .or_else(|_| self.tz.datetime_from_str(input, "%Y/%m/%d %I:%M:%S %P"))
617            .or_else(|_| self.tz.datetime_from_str(input, "%Y/%m/%d %I:%M %P"))
618            .ok()
619            .map(|at_tz| at_tz.with_timezone(&Utc))
620            .map(Ok)
621    }
622
623    // yyyy/mm/dd
624    // - 2014/3/31
625    // - 2014/03/31
626    #[inline]
627    fn slash_ymd(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
628        let re: &Regex = regex! {r"^[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}$"
629        };
630        if !re.is_match(input) {
631            return None;
632        }
633
634        let now = Utc::now()
635            .date()
636            .and_time(self.default_time)?
637            .with_timezone(self.tz);
638        NaiveDate::parse_from_str(input, "%Y/%m/%d")
639            .ok()
640            .map(|parsed| parsed.and_time(now.time()))
641            .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
642            .map(|at_tz| at_tz.with_timezone(&Utc))
643            .map(Ok)
644    }
645}
646
647#[cfg(test)]
648mod tests {
649    use super::*;
650
651    #[test]
652    fn unix_timestamp() {
653        let parse = Parse::new(&Utc, Utc::now().time());
654
655        let test_cases = vec![
656            ("0", Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)),
657            ("0000000000", Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)),
658            ("0000000000000", Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)),
659            ("0000000000000000000", Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)),
660            ("-770172300", Utc.ymd(1945, 8, 5).and_hms(23, 15, 0)),
661            (
662                "1671673426.123456789",
663                Utc.ymd(2022, 12, 22).and_hms_nano(1, 43, 46, 123456768),
664            ),
665            ("1511648546", Utc.ymd(2017, 11, 25).and_hms(22, 22, 26)),
666            (
667                "1620036248.420",
668                Utc.ymd(2021, 5, 3).and_hms_milli(10, 4, 8, 420),
669            ),
670            (
671                "1620036248.717915136",
672                Utc.ymd(2021, 5, 3).and_hms_nano(10, 4, 8, 717915136),
673            ),
674        ];
675
676        for &(input, want) in test_cases.iter() {
677            assert_eq!(
678                parse.unix_timestamp(input).unwrap().unwrap(),
679                want,
680                "unix_timestamp/{}",
681                input
682            )
683        }
684        assert!(parse.unix_timestamp("15116").is_some());
685        assert!(
686            parse
687                .unix_timestamp("16200248727179150001620024872717915000") //DevSkim: ignore DS173237
688                .is_some()
689        );
690        assert!(parse.unix_timestamp("not-a-ts").is_none());
691    }
692
693    #[test]
694    fn rfc3339() {
695        let parse = Parse::new(&Utc, Utc::now().time());
696
697        let test_cases = [
698            (
699                "2021-05-01T01:17:02.604456Z",
700                Utc.ymd(2021, 5, 1).and_hms_nano(1, 17, 2, 604456000),
701            ),
702            (
703                "2017-11-25T22:34:50Z",
704                Utc.ymd(2017, 11, 25).and_hms(22, 34, 50),
705            ),
706        ];
707
708        for &(input, want) in test_cases.iter() {
709            assert_eq!(
710                parse.rfc3339(input).unwrap().unwrap(),
711                want,
712                "rfc3339/{}",
713                input
714            )
715        }
716        assert!(parse.rfc3339("2017-11-25 22:34:50").is_none());
717        assert!(parse.rfc3339("not-date-time").is_none());
718    }
719
720    #[test]
721    fn rfc2822() {
722        let parse = Parse::new(&Utc, Utc::now().time());
723
724        let test_cases = [
725            (
726                "Wed, 02 Jun 2021 06:31:39 GMT",
727                Utc.ymd(2021, 6, 2).and_hms(6, 31, 39),
728            ),
729            (
730                "Wed, 02 Jun 2021 06:31:39 PDT",
731                Utc.ymd(2021, 6, 2).and_hms(13, 31, 39),
732            ),
733        ];
734
735        for &(input, want) in test_cases.iter() {
736            assert_eq!(
737                parse.rfc2822(input).unwrap().unwrap(),
738                want,
739                "rfc2822/{}",
740                input
741            )
742        }
743        assert!(parse.rfc2822("02 Jun 2021 06:31:39").is_none());
744        assert!(parse.rfc2822("not-date-time").is_none());
745    }
746
747    #[test]
748    fn ymd_hms() {
749        let parse = Parse::new(&Utc, Utc::now().time());
750
751        let test_cases = vec![
752            ("2021-04-30 21:14", Utc.ymd(2021, 4, 30).and_hms(21, 14, 0)),
753            (
754                "2021-04-30 21:14:10",
755                Utc.ymd(2021, 4, 30).and_hms(21, 14, 10),
756            ),
757            (
758                "2021-04-30 21:14:10.052282",
759                Utc.ymd(2021, 4, 30).and_hms_micro(21, 14, 10, 52282),
760            ),
761            (
762                "2014-04-26 05:24:37 PM",
763                Utc.ymd(2014, 4, 26).and_hms(17, 24, 37),
764            ),
765            (
766                "2014-04-26 17:24:37.123",
767                Utc.ymd(2014, 4, 26).and_hms_milli(17, 24, 37, 123),
768            ),
769            (
770                "2014-04-26 17:24:37.3186369",
771                Utc.ymd(2014, 4, 26).and_hms_nano(17, 24, 37, 318636900),
772            ),
773            (
774                "2012-08-03 18:31:59.257000000",
775                Utc.ymd(2012, 8, 3).and_hms_nano(18, 31, 59, 257000000),
776            ),
777        ];
778
779        for &(input, want) in test_cases.iter() {
780            assert_eq!(
781                parse.ymd_hms(input).unwrap().unwrap(),
782                want,
783                "ymd_hms/{}",
784                input
785            )
786        }
787        assert!(parse.ymd_hms("not-date-time").is_none());
788    }
789
790    #[test]
791    fn ymd_hms_z() {
792        let parse = Parse::new(&Utc, Utc::now().time());
793
794        let test_cases = vec![
795            (
796                "2017-11-25 13:31:15 PST",
797                Utc.ymd(2017, 11, 25).and_hms(21, 31, 15),
798            ),
799            (
800                "2017-11-25 13:31 PST",
801                Utc.ymd(2017, 11, 25).and_hms(21, 31, 0),
802            ),
803            (
804                "2014-12-16 06:20:00 UTC",
805                Utc.ymd(2014, 12, 16).and_hms(6, 20, 0),
806            ),
807            (
808                "2014-12-16 06:20:00 GMT",
809                Utc.ymd(2014, 12, 16).and_hms(6, 20, 0),
810            ),
811            (
812                "2014-04-26 13:13:43 +0800",
813                Utc.ymd(2014, 4, 26).and_hms(5, 13, 43),
814            ),
815            (
816                "2014-04-26 13:13:44 +09:00",
817                Utc.ymd(2014, 4, 26).and_hms(4, 13, 44),
818            ),
819            (
820                "2012-08-03 18:31:59.257000000 +0000",
821                Utc.ymd(2012, 8, 3).and_hms_nano(18, 31, 59, 257000000),
822            ),
823            (
824                "2015-09-30 18:48:56.35272715 UTC",
825                Utc.ymd(2015, 9, 30).and_hms_nano(18, 48, 56, 352727150),
826            ),
827        ];
828
829        for &(input, want) in test_cases.iter() {
830            assert_eq!(
831                parse.ymd_hms_z(input).unwrap().unwrap(),
832                want,
833                "ymd_hms_z/{}",
834                input
835            )
836        }
837        assert!(parse.ymd_hms_z("not-date-time").is_none());
838    }
839
840    #[test]
841    fn ymd() {
842        let parse = Parse::new(&Utc, Utc::now().time());
843
844        let test_cases = [(
845            "2021-02-21",
846            Utc.ymd(2021, 2, 21).and_time(Utc::now().time()),
847        )];
848
849        for &(input, want) in test_cases.iter() {
850            assert_eq!(
851                parse
852                    .ymd(input)
853                    .unwrap()
854                    .unwrap()
855                    .trunc_subsecs(0)
856                    .with_second(0)
857                    .unwrap(),
858                want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
859                "ymd/{}",
860                input
861            )
862        }
863        assert!(parse.ymd("not-date-time").is_none());
864    }
865
866    #[test]
867    fn ymd_z() {
868        let parse = Parse::new(&Utc, Utc::now().time());
869        let now_at_pst = Utc::now().with_timezone(&FixedOffset::west(8 * 3600));
870        let now_at_cst = Utc::now().with_timezone(&FixedOffset::east(8 * 3600));
871
872        let test_cases = [
873            (
874                "2021-02-21 PST",
875                FixedOffset::west(8 * 3600)
876                    .ymd(2021, 2, 21)
877                    .and_time(now_at_pst.time())
878                    .map(|dt| dt.with_timezone(&Utc)),
879            ),
880            (
881                "2021-02-21 UTC",
882                FixedOffset::west(0)
883                    .ymd(2021, 2, 21)
884                    .and_time(Utc::now().time())
885                    .map(|dt| dt.with_timezone(&Utc)),
886            ),
887            (
888                "2020-07-20+08:00",
889                FixedOffset::east(8 * 3600)
890                    .ymd(2020, 7, 20)
891                    .and_time(now_at_cst.time())
892                    .map(|dt| dt.with_timezone(&Utc)),
893            ),
894        ];
895
896        for &(input, want) in test_cases.iter() {
897            assert_eq!(
898                parse
899                    .ymd_z(input)
900                    .unwrap()
901                    .unwrap()
902                    .trunc_subsecs(0)
903                    .with_second(0)
904                    .unwrap(),
905                want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
906                "ymd_z/{}",
907                input
908            )
909        }
910        assert!(parse.ymd_z("not-date-time").is_none());
911    }
912
913    #[test]
914    fn month_ymd() {
915        let parse = Parse::new(&Utc, Utc::now().time());
916
917        let test_cases = [(
918            "2021-Feb-21",
919            Utc.ymd(2021, 2, 21).and_time(Utc::now().time()),
920        )];
921
922        for &(input, want) in test_cases.iter() {
923            assert_eq!(
924                parse
925                    .month_ymd(input)
926                    .unwrap()
927                    .unwrap()
928                    .trunc_subsecs(0)
929                    .with_second(0)
930                    .unwrap(),
931                want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
932                "month_ymd/{}",
933                input
934            )
935        }
936        assert!(parse.month_ymd("not-date-time").is_none());
937    }
938
939    #[test]
940    fn month_mdy_hms() {
941        let parse = Parse::new(&Utc, Utc::now().time());
942
943        let test_cases = [
944            (
945                "May 8, 2009 5:57:51 PM",
946                Utc.ymd(2009, 5, 8).and_hms(17, 57, 51),
947            ),
948            (
949                "September 17, 2012 10:09am",
950                Utc.ymd(2012, 9, 17).and_hms(10, 9, 0),
951            ),
952            (
953                "September 17, 2012, 10:10:09",
954                Utc.ymd(2012, 9, 17).and_hms(10, 10, 9),
955            ),
956        ];
957
958        for &(input, want) in test_cases.iter() {
959            assert_eq!(
960                parse.month_mdy_hms(input).unwrap().unwrap(),
961                want,
962                "month_mdy_hms/{}",
963                input
964            )
965        }
966        assert!(parse.month_mdy_hms("not-date-time").is_none());
967    }
968
969    #[test]
970    fn month_mdy_hms_z() {
971        let parse = Parse::new(&Utc, Utc::now().time());
972
973        let test_cases = [
974            (
975                "May 02, 2021 15:51:31 UTC",
976                Utc.ymd(2021, 5, 2).and_hms(15, 51, 31),
977            ),
978            (
979                "May 02, 2021 15:51 UTC",
980                Utc.ymd(2021, 5, 2).and_hms(15, 51, 0),
981            ),
982            (
983                "May 26, 2021, 12:49 AM PDT",
984                Utc.ymd(2021, 5, 26).and_hms(7, 49, 0),
985            ),
986            (
987                "September 17, 2012 at 10:09am PST",
988                Utc.ymd(2012, 9, 17).and_hms(18, 9, 0),
989            ),
990        ];
991
992        for &(input, want) in test_cases.iter() {
993            assert_eq!(
994                parse.month_mdy_hms_z(input).unwrap().unwrap(),
995                want,
996                "month_mdy_hms_z/{}",
997                input
998            )
999        }
1000        assert!(parse.month_mdy_hms_z("not-date-time").is_none());
1001    }
1002
1003    #[test]
1004    fn month_mdy() {
1005        let parse = Parse::new(&Utc, Utc::now().time());
1006
1007        let test_cases = [
1008            (
1009                "May 25, 2021",
1010                Utc.ymd(2021, 5, 25).and_time(Utc::now().time()),
1011            ),
1012            (
1013                "oct 7, 1970",
1014                Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1015            ),
1016            (
1017                "oct 7, 70",
1018                Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1019            ),
1020            (
1021                "oct. 7, 1970",
1022                Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1023            ),
1024            (
1025                "oct. 7, 70",
1026                Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1027            ),
1028            (
1029                "October 7, 1970",
1030                Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1031            ),
1032        ];
1033
1034        for &(input, want) in test_cases.iter() {
1035            assert_eq!(
1036                parse
1037                    .month_mdy(input)
1038                    .unwrap()
1039                    .unwrap()
1040                    .trunc_subsecs(0)
1041                    .with_second(0)
1042                    .unwrap(),
1043                want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1044                "month_mdy/{}",
1045                input
1046            )
1047        }
1048        assert!(parse.month_mdy("not-date-time").is_none());
1049    }
1050
1051    #[test]
1052    fn month_dmy_hms() {
1053        let parse = Parse::new(&Utc, Utc::now().time());
1054
1055        let test_cases = [
1056            (
1057                "12 Feb 2006, 19:17",
1058                Utc.ymd(2006, 2, 12).and_hms(19, 17, 0),
1059            ),
1060            ("12 Feb 2006 19:17", Utc.ymd(2006, 2, 12).and_hms(19, 17, 0)),
1061            (
1062                "14 May 2019 19:11:40.164",
1063                Utc.ymd(2019, 5, 14).and_hms_milli(19, 11, 40, 164),
1064            ),
1065        ];
1066
1067        for &(input, want) in test_cases.iter() {
1068            assert_eq!(
1069                parse.month_dmy_hms(input).unwrap().unwrap(),
1070                want,
1071                "month_dmy_hms/{}",
1072                input
1073            )
1074        }
1075        assert!(parse.month_dmy_hms("not-date-time").is_none());
1076    }
1077
1078    #[test]
1079    fn month_dmy() {
1080        let parse = Parse::new(&Utc, Utc::now().time());
1081
1082        let test_cases = [
1083            ("7 oct 70", Utc.ymd(1970, 10, 7).and_time(Utc::now().time())),
1084            (
1085                "7 oct 1970",
1086                Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
1087            ),
1088            (
1089                "03 February 2013",
1090                Utc.ymd(2013, 2, 3).and_time(Utc::now().time()),
1091            ),
1092            (
1093                "1 July 2013",
1094                Utc.ymd(2013, 7, 1).and_time(Utc::now().time()),
1095            ),
1096        ];
1097
1098        for &(input, want) in test_cases.iter() {
1099            assert_eq!(
1100                parse
1101                    .month_dmy(input)
1102                    .unwrap()
1103                    .unwrap()
1104                    .trunc_subsecs(0)
1105                    .with_second(0)
1106                    .unwrap(),
1107                want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1108                "month_dmy/{}",
1109                input
1110            )
1111        }
1112        assert!(parse.month_dmy("not-date-time").is_none());
1113    }
1114
1115    #[test]
1116    fn slash_mdy_hms() {
1117        let parse = Parse::new(&Utc, Utc::now().time());
1118
1119        let test_cases = vec![
1120            ("4/8/2014 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
1121            ("04/08/2014 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
1122            ("4/8/14 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
1123            ("04/2/2014 03:00:51", Utc.ymd(2014, 4, 2).and_hms(3, 0, 51)),
1124            ("8/8/1965 12:00:00 AM", Utc.ymd(1965, 8, 8).and_hms(0, 0, 0)),
1125            (
1126                "8/8/1965 01:00:01 PM",
1127                Utc.ymd(1965, 8, 8).and_hms(13, 0, 1),
1128            ),
1129            ("8/8/1965 01:00 PM", Utc.ymd(1965, 8, 8).and_hms(13, 0, 0)),
1130            ("8/8/1965 1:00 PM", Utc.ymd(1965, 8, 8).and_hms(13, 0, 0)),
1131            ("8/8/1965 12:00 AM", Utc.ymd(1965, 8, 8).and_hms(0, 0, 0)),
1132            ("4/02/2014 03:00:51", Utc.ymd(2014, 4, 2).and_hms(3, 0, 51)),
1133            (
1134                "03/19/2012 10:11:59",
1135                Utc.ymd(2012, 3, 19).and_hms(10, 11, 59),
1136            ),
1137            (
1138                "03/19/2012 10:11:59.3186369",
1139                Utc.ymd(2012, 3, 19).and_hms_nano(10, 11, 59, 318636900),
1140            ),
1141        ];
1142
1143        for &(input, want) in test_cases.iter() {
1144            assert_eq!(
1145                parse.slash_mdy_hms(input).unwrap().unwrap(),
1146                want,
1147                "slash_mdy_hms/{}",
1148                input
1149            )
1150        }
1151        assert!(parse.slash_mdy_hms("not-date-time").is_none());
1152    }
1153
1154    #[test]
1155    fn slash_mdy() {
1156        let parse = Parse::new(&Utc, Utc::now().time());
1157
1158        let test_cases = [
1159            (
1160                "3/31/2014",
1161                Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
1162            ),
1163            (
1164                "03/31/2014",
1165                Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
1166            ),
1167            ("08/21/71", Utc.ymd(1971, 8, 21).and_time(Utc::now().time())),
1168            ("8/1/71", Utc.ymd(1971, 8, 1).and_time(Utc::now().time())),
1169        ];
1170
1171        for &(input, want) in test_cases.iter() {
1172            assert_eq!(
1173                parse
1174                    .slash_mdy(input)
1175                    .unwrap()
1176                    .unwrap()
1177                    .trunc_subsecs(0)
1178                    .with_second(0)
1179                    .unwrap(),
1180                want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1181                "slash_mdy/{}",
1182                input
1183            )
1184        }
1185        assert!(parse.slash_mdy("not-date-time").is_none());
1186    }
1187
1188    #[test]
1189    fn slash_dmy() {
1190        let mut parse = Parse::new(&Utc, Utc::now().time());
1191
1192        let test_cases = [
1193            (
1194                "31/3/2014",
1195                Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
1196            ),
1197            (
1198                "13/11/2014",
1199                Utc.ymd(2014, 11, 13).and_time(Utc::now().time()),
1200            ),
1201            ("21/08/71", Utc.ymd(1971, 8, 21).and_time(Utc::now().time())),
1202            ("1/8/71", Utc.ymd(1971, 8, 1).and_time(Utc::now().time())),
1203        ];
1204
1205        for &(input, want) in test_cases.iter() {
1206            assert_eq!(
1207                parse
1208                    .prefer_dmy(true)
1209                    .slash_dmy(input)
1210                    .unwrap()
1211                    .unwrap()
1212                    .trunc_subsecs(0)
1213                    .with_second(0)
1214                    .unwrap(),
1215                want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1216                "slash_dmy/{}",
1217                input
1218            )
1219        }
1220        assert!(parse.slash_dmy("not-date-time").is_none());
1221    }
1222
1223    #[test]
1224    fn slash_ymd_hms() {
1225        let parse = Parse::new(&Utc, Utc::now().time());
1226
1227        let test_cases = [
1228            ("2014/4/8 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
1229            ("2014/04/08 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
1230            ("2014/04/2 03:00:51", Utc.ymd(2014, 4, 2).and_hms(3, 0, 51)),
1231            ("2014/4/02 03:00:51", Utc.ymd(2014, 4, 2).and_hms(3, 0, 51)),
1232            (
1233                "2012/03/19 10:11:59",
1234                Utc.ymd(2012, 3, 19).and_hms(10, 11, 59),
1235            ),
1236            (
1237                "2012/03/19 10:11:59.3186369",
1238                Utc.ymd(2012, 3, 19).and_hms_nano(10, 11, 59, 318636900),
1239            ),
1240        ];
1241
1242        for &(input, want) in test_cases.iter() {
1243            assert_eq!(
1244                parse.slash_ymd_hms(input).unwrap().unwrap(),
1245                want,
1246                "slash_ymd_hms/{}",
1247                input
1248            )
1249        }
1250        assert!(parse.slash_ymd_hms("not-date-time").is_none());
1251    }
1252
1253    #[test]
1254    fn slash_ymd() {
1255        let parse = Parse::new(&Utc, Utc::now().time());
1256
1257        let test_cases = [
1258            (
1259                "2014/3/31",
1260                Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
1261            ),
1262            (
1263                "2014/03/31",
1264                Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
1265            ),
1266        ];
1267
1268        for &(input, want) in test_cases.iter() {
1269            assert_eq!(
1270                parse
1271                    .slash_ymd(input)
1272                    .unwrap()
1273                    .unwrap()
1274                    .trunc_subsecs(0)
1275                    .with_second(0)
1276                    .unwrap(),
1277                want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
1278                "slash_ymd/{}",
1279                input
1280            )
1281        }
1282        assert!(parse.slash_ymd("not-date-time").is_none());
1283    }
1284}