Skip to main content

qsv_dateparser/
lib.rs

1//! A rust library for parsing date strings in commonly used formats. Parsed date will be returned
2//! as `chrono`'s `DateTime<Utc>`.
3//!
4//! # Quick Start
5//!
6//!
7//! Use `str`'s `parse` method:
8//!
9//! ```
10//! use chrono::prelude::*;
11//! use qsv_dateparser::DateTimeUtc;
12//! use std::error::Error;
13//!
14//! fn main() -> Result<(), Box<dyn Error>> {
15//!     assert_eq!(
16//!         "2021-05-14 18:51 PDT".parse::<DateTimeUtc>()?.0,
17//!         Utc.ymd(2021, 5, 15).and_hms(1, 51, 0),
18//!     );
19//!     Ok(())
20//! }
21//! ```
22//!
23//! ## Accepted date formats
24//!
25//! ```
26//! use qsv_dateparser::DateTimeUtc;
27//!
28//! let accepted = vec![
29//!     // unix timestamp
30//!     "1511648546",
31//!     "1620021848429",
32//!     "1620024872717915000",
33//!     "0",
34//!     "-770172300",
35//!     "1671673426.123456789",
36//!     // rfc3339
37//!     "2021-05-01T01:17:02.604456Z",
38//!     "2017-11-25T22:34:50Z",
39//!     // rfc2822
40//!     "Wed, 02 Jun 2021 06:31:39 GMT",
41//!     // yyyy-mm-dd hh:mm:ss
42//!     "2014-04-26 05:24:37 PM",
43//!     "2021-04-30 21:14",
44//!     "2021-04-30 21:14:10",
45//!     "2021-04-30 21:14:10.052282",
46//!     "2014-04-26 17:24:37.123",
47//!     "2014-04-26 17:24:37.3186369",
48//!     "2012-08-03 18:31:59.257000000",
49//!     // yyyy-mm-dd hh:mm:ss z
50//!     "2017-11-25 13:31:15 PST",
51//!     "2017-11-25 13:31 PST",
52//!     "2014-12-16 06:20:00 UTC",
53//!     "2014-12-16 06:20:00 GMT",
54//!     "2014-04-26 13:13:43 +0800",
55//!     "2014-04-26 13:13:44 +09:00",
56//!     "2012-08-03 18:31:59.257000000 +0000",
57//!     "2015-09-30 18:48:56.35272715 UTC",
58//!     // yyyy-mm-dd
59//!     "2021-02-21",
60//!     // yyyy-mm-dd z
61//!     "2021-02-21 PST",
62//!     "2021-02-21 UTC",
63//!     "2020-07-20+08:00",
64//!     // Mon dd, yyyy, hh:mm:ss
65//!     "May 8, 2009 5:57:51 PM",
66//!     "September 17, 2012 10:09am",
67//!     "September 17, 2012, 10:10:09",
68//!     // Mon dd, yyyy hh:mm:ss z
69//!     "May 02, 2021 15:51:31 UTC",
70//!     "May 02, 2021 15:51 UTC",
71//!     "May 26, 2021, 12:49 AM PDT",
72//!     "September 17, 2012 at 10:09am PST",
73//!     // yyyy-mon-dd
74//!     "2021-Feb-21",
75//!     // Mon dd, yyyy
76//!     "May 25, 2021",
77//!     "oct 7, 1970",
78//!     "oct 7, 70",
79//!     "oct. 7, 1970",
80//!     "oct. 7, 70",
81//!     "October 7, 1970",
82//!     // dd Mon yyyy hh:mm:ss
83//!     "12 Feb 2006, 19:17",
84//!     "12 Feb 2006 19:17",
85//!     "14 May 2019 19:11:40.164",
86//!     // dd Mon yyyy
87//!     "7 oct 70",
88//!     "7 oct 1970",
89//!     "03 February 2013",
90//!     "1 July 2013",
91//!     // mm/dd/yyyy hh:mm:ss
92//!     "4/8/2014 22:05",
93//!     "04/08/2014 22:05",
94//!     "4/8/14 22:05",
95//!     "04/2/2014 03:00:51",
96//!     "8/8/1965 12:00:00 AM",
97//!     "8/8/1965 01:00:01 PM",
98//!     "8/8/1965 01:00 PM",
99//!     "8/8/1965 1:00 PM",
100//!     "8/8/1965 12:00 AM",
101//!     "4/02/2014 03:00:51",
102//!     "03/19/2012 10:11:59",
103//!     "03/19/2012 10:11:59.3186369",
104//!     // mm/dd/yyyy
105//!     "3/31/2014",
106//!     "03/31/2014",
107//!     "08/21/71",
108//!     "8/1/71",
109//!     // yyyy/mm/dd hh:mm:ss
110//!     "2014/4/8 22:05",
111//!     "2014/04/08 22:05",
112//!     "2014/04/2 03:00:51",
113//!     "2014/4/02 03:00:51",
114//!     "2012/03/19 10:11:59",
115//!     "2012/03/19 10:11:59.3186369",
116//!     // yyyy/mm/dd
117//!     "2014/3/31",
118//!     "2014/03/31",
119//! ];
120//!
121//! for date_str in accepted {
122//!     let result = date_str.parse::<DateTimeUtc>();
123//!     assert!(result.is_ok())
124//! }
125//! ```
126//!
127//! ### DMY Format
128//!
129//! It also accepts dates in DMY format with `parse_with_preference`,
130//! and the `prefer_dmy` parameter set to true.
131//!
132//! ```
133//! use qsv_dateparser::parse_with_preference;
134//!
135//! let accepted = vec![
136//!     // dd/mm/yyyy
137//!     "31/12/2020",
138//!     "12/10/2019",
139//!     "03/06/2018",
140//!     "27/06/68",
141//!     // dd/mm/yyyy hh:mm:ss
142//!     "4/8/2014 22:05",
143//!     "04/08/2014 22:05",
144//!     "4/8/14 22:05",
145//!     "04/2/2014 03:00:51",
146//!     "8/8/1965 12:00:00 AM",
147//!     "8/8/1965 01:00:01 PM",
148//!     "8/8/1965 01:00 PM",
149//!     "31/12/22 15:00"
150//! ];
151//!
152//! for date_str in accepted {
153//!     let result = parse_with_preference(date_str, true);
154//!     assert!(result.is_ok());
155//! }
156//! ```
157
158/// Datetime string parser
159///
160/// ```
161/// use chrono::prelude::*;
162/// use qsv_dateparser::datetime::Parse;
163/// use std::error::Error;
164///
165/// fn main() -> Result<(), Box<dyn Error>> {
166///     let utc_now_time = Utc::now().time();
167///     let parse_with_local = Parse::new(&Local, utc_now_time);
168///     assert_eq!(
169///         parse_with_local.parse("2021-06-05 06:19 PM")?,
170///         Local.ymd(2021, 6, 5).and_hms(18, 19, 0).with_timezone(&Utc),
171///     );
172///
173///     let parse_with_utc = Parse::new(&Utc, utc_now_time);
174///     assert_eq!(
175///         parse_with_utc.parse("2021-06-05 06:19 PM")?,
176///         Utc.ymd(2021, 6, 5).and_hms(18, 19, 0),
177///     );
178///
179///     Ok(())
180/// }
181/// ```
182pub mod datetime;
183
184/// Timezone offset string parser
185///
186/// ```
187/// use chrono::prelude::*;
188/// use qsv_dateparser::timezone::parse;
189/// use std::error::Error;
190///
191/// fn main() -> Result<(), Box<dyn Error>> {
192///     assert_eq!(parse("-0800")?, FixedOffset::west(8 * 3600));
193///     assert_eq!(parse("+10:00")?, FixedOffset::east(10 * 3600));
194///     assert_eq!(parse("PST")?, FixedOffset::west(8 * 3600));
195///     assert_eq!(parse("PDT")?, FixedOffset::west(7 * 3600));
196///     assert_eq!(parse("UTC")?, FixedOffset::west(0));
197///     assert_eq!(parse("GMT")?, FixedOffset::west(0));
198///
199///     Ok(())
200/// }
201/// ```
202pub mod timezone;
203
204use crate::datetime::Parse;
205use anyhow::{Error, Result};
206use chrono::prelude::*;
207use std::sync::OnceLock;
208
209/// `DateTimeUtc` is an alias for `chrono`'s `DateTime<UTC>`. It implements `std::str::FromStr`'s
210/// `from_str` method, and it makes `str`'s `parse` method to understand the accepted date formats
211/// from this crate.
212///
213/// ```
214/// use qsv_dateparser::DateTimeUtc;
215///
216/// // parsed is DateTimeUTC and parsed.0 is chrono's DateTime<Utc>
217/// match "May 02, 2021 15:51:31 UTC".parse::<DateTimeUtc>() {
218///     Ok(parsed) => println!("PARSED into UTC datetime {:?}", parsed.0),
219///     Err(err) => println!("ERROR from parsing datetime string: {}", err)
220/// }
221/// ```
222pub struct DateTimeUtc(pub DateTime<Utc>);
223
224impl std::str::FromStr for DateTimeUtc {
225    type Err = Error;
226
227    fn from_str(s: &str) -> Result<Self> {
228        parse(s).map(DateTimeUtc)
229    }
230}
231
232static MIDNIGHT: OnceLock<chrono::NaiveTime> = OnceLock::new();
233
234/// This function tries to recognize the input datetime string with a list of accepted formats.
235/// When timezone is not provided, this function assumes it's a [`chrono::Local`] datetime. For
236/// custom timezone, use [`parse_with_timezone()`] instead.If all options are exhausted,
237/// [`parse()`] will return an error to let the caller know that no formats were matched.
238#[inline]
239pub fn parse(input: &str) -> Result<DateTime<Utc>> {
240    Parse::new(&Local, Utc::now().time()).parse(input)
241}
242
243/// Similar to [`parse()`], this function takes a datetime string and a boolean `dmy_preference`.
244/// When `dmy_preference` is `true`, it will parse strings using the DMY format. Otherwise, it
245/// parses them using an MDY format.
246#[inline]
247pub fn parse_with_preference(input: &str, dmy_preference: bool) -> Result<DateTime<Utc>> {
248    let midnight = MIDNIGHT.get_or_init(|| NaiveTime::from_hms_opt(0, 0, 0).unwrap());
249    Parse::new_with_preference(&Utc, *midnight, dmy_preference).parse(input)
250}
251
252/// Similar to [`parse()`], this function takes a datetime string and a custom [`chrono::TimeZone`],
253/// and tries to parse the datetime string. When timezone is not given in the string, this function
254/// will assume and parse the datetime by the custom timezone provided in this function's arguments.
255///
256#[inline]
257pub fn parse_with_timezone<Tz2: TimeZone>(input: &str, tz: &Tz2) -> Result<DateTime<Utc>> {
258    Parse::new(tz, Utc::now().time()).parse(input)
259}
260
261/// Similar to [`parse()`], this function takes a datetime string and a boolean `dmy_preference`
262/// and a timezone. When timezone is not given in the input string, this function will
263/// assume and parse the datetime by the custom timezone provided in this function's arguments.
264/// When `dmy_preference` is `true`, it will parse strings using the DMY format. Otherwise, it
265/// parses them using an MDY format.
266#[inline]
267pub fn parse_with_preference_and_timezone<Tz2: TimeZone>(
268    input: &str,
269    dmy_preference: bool,
270    tz: &Tz2,
271) -> Result<DateTime<Utc>> {
272    let midnight = MIDNIGHT.get_or_init(|| NaiveTime::from_hms_opt(0, 0, 0).unwrap());
273    Parse::new_with_preference(tz, *midnight, dmy_preference).parse(input)
274}
275
276/// Similar to [`parse()`] and [`parse_with_timezone()`], this function takes a datetime string, a
277/// custom [`chrono::TimeZone`] and a default naive time. In addition to assuming timezone when
278/// it's not given in datetime string, this function also use provided default naive time in parsed
279/// [`chrono::DateTime`].
280///
281#[inline]
282pub fn parse_with<Tz2: TimeZone>(
283    input: &str,
284    tz: &Tz2,
285    default_time: NaiveTime,
286) -> Result<DateTime<Utc>> {
287    Parse::new(tz, default_time).parse(input)
288}
289
290#[cfg(test)]
291#[allow(deprecated)]
292mod tests {
293    use super::*;
294
295    #[derive(Clone, Copy)]
296    enum Trunc {
297        Seconds,
298        None,
299    }
300
301    #[test]
302    fn parse_in_local() {
303        let test_cases = vec![
304            (
305                "rfc3339",
306                "2017-11-25T22:34:50Z",
307                Utc.ymd(2017, 11, 25).and_hms(22, 34, 50),
308                Trunc::None,
309            ),
310            (
311                "rfc2822",
312                "Wed, 02 Jun 2021 06:31:39 GMT",
313                Utc.ymd(2021, 6, 2).and_hms(6, 31, 39),
314                Trunc::None,
315            ),
316            (
317                "ymd_hms",
318                "2021-04-30 21:14:10",
319                Local
320                    .ymd(2021, 4, 30)
321                    .and_hms(21, 14, 10)
322                    .with_timezone(&Utc),
323                Trunc::None,
324            ),
325            (
326                "ymd_hms_z",
327                "2017-11-25 13:31:15 PST",
328                Utc.ymd(2017, 11, 25).and_hms(21, 31, 15),
329                Trunc::None,
330            ),
331            (
332                "ymd",
333                "2021-02-21",
334                Local
335                    .ymd(2021, 2, 21)
336                    .and_time(Local::now().time())
337                    .unwrap()
338                    .with_timezone(&Utc),
339                Trunc::Seconds,
340            ),
341            (
342                "ymd_z",
343                "2021-02-21 PST",
344                FixedOffset::west(8 * 3600)
345                    .ymd(2021, 2, 21)
346                    .and_time(
347                        Utc::now()
348                            .with_timezone(&FixedOffset::west(8 * 3600))
349                            .time(),
350                    )
351                    .unwrap()
352                    .with_timezone(&Utc),
353                Trunc::Seconds,
354            ),
355            (
356                "month_ymd",
357                "2021-Feb-21",
358                Local
359                    .ymd(2021, 2, 21)
360                    .and_time(Local::now().time())
361                    .unwrap()
362                    .with_timezone(&Utc),
363                Trunc::Seconds,
364            ),
365            (
366                "month_mdy_hms",
367                "May 8, 2009 5:57:51 PM",
368                Local
369                    .ymd(2009, 5, 8)
370                    .and_hms(17, 57, 51)
371                    .with_timezone(&Utc),
372                Trunc::None,
373            ),
374            (
375                "month_mdy_hms_z",
376                "May 02, 2021 15:51 UTC",
377                Utc.ymd(2021, 5, 2).and_hms(15, 51, 0),
378                Trunc::None,
379            ),
380            (
381                "month_mdy",
382                "May 25, 2021",
383                Local
384                    .ymd(2021, 5, 25)
385                    .and_time(Local::now().time())
386                    .unwrap()
387                    .with_timezone(&Utc),
388                Trunc::Seconds,
389            ),
390            (
391                "month_dmy_hms",
392                "14 May 2019 19:11:40.164",
393                Local
394                    .ymd(2019, 5, 14)
395                    .and_hms_milli(19, 11, 40, 164)
396                    .with_timezone(&Utc),
397                Trunc::None,
398            ),
399            (
400                "month_dmy",
401                "1 July 2013",
402                Local
403                    .ymd(2013, 7, 1)
404                    .and_time(Local::now().time())
405                    .unwrap()
406                    .with_timezone(&Utc),
407                Trunc::Seconds,
408            ),
409            (
410                "slash_mdy_hms",
411                "03/19/2012 10:11:59",
412                Local
413                    .ymd(2012, 3, 19)
414                    .and_hms(10, 11, 59)
415                    .with_timezone(&Utc),
416                Trunc::None,
417            ),
418            (
419                "slash_mdy",
420                "08/21/71",
421                Local
422                    .ymd(1971, 8, 21)
423                    .and_time(Local::now().time())
424                    .unwrap()
425                    .with_timezone(&Utc),
426                Trunc::Seconds,
427            ),
428            (
429                "slash_ymd_hms",
430                "2012/03/19 10:11:59",
431                Local
432                    .ymd(2012, 3, 19)
433                    .and_hms(10, 11, 59)
434                    .with_timezone(&Utc),
435                Trunc::None,
436            ),
437            (
438                "slash_ymd",
439                "2014/3/31",
440                Local
441                    .ymd(2014, 3, 31)
442                    .and_time(Local::now().time())
443                    .unwrap()
444                    .with_timezone(&Utc),
445                Trunc::Seconds,
446            ),
447        ];
448
449        for &(test, input, want, trunc) in test_cases.iter() {
450            match trunc {
451                Trunc::None => {
452                    assert_eq!(
453                        super::parse(input).unwrap(),
454                        want,
455                        "parse_in_local/{}/{}",
456                        test,
457                        input
458                    )
459                }
460                Trunc::Seconds => assert_eq!(
461                    super::parse(input)
462                        .unwrap()
463                        .trunc_subsecs(0)
464                        .with_second(0)
465                        .unwrap(),
466                    want.trunc_subsecs(0).with_second(0).unwrap(),
467                    "parse_in_local/{}/{}",
468                    test,
469                    input
470                ),
471            };
472        }
473    }
474
475    #[test]
476    fn parse_with_timezone_in_utc() {
477        let test_cases = vec![
478            (
479                "rfc3339",
480                "2017-11-25T22:34:50Z",
481                Utc.ymd(2017, 11, 25).and_hms(22, 34, 50),
482                Trunc::None,
483            ),
484            (
485                "rfc2822",
486                "Wed, 02 Jun 2021 06:31:39 GMT",
487                Utc.ymd(2021, 6, 2).and_hms(6, 31, 39),
488                Trunc::None,
489            ),
490            (
491                "ymd_hms",
492                "2021-04-30 21:14:10",
493                Utc.ymd(2021, 4, 30).and_hms(21, 14, 10),
494                Trunc::None,
495            ),
496            (
497                "ymd_hms_z",
498                "2017-11-25 13:31:15 PST",
499                Utc.ymd(2017, 11, 25).and_hms(21, 31, 15),
500                Trunc::None,
501            ),
502            (
503                "ymd",
504                "2021-02-21",
505                Utc.ymd(2021, 2, 21).and_time(Utc::now().time()).unwrap(),
506                Trunc::Seconds,
507            ),
508            (
509                "ymd_z",
510                "2021-02-21 PST",
511                FixedOffset::west(8 * 3600)
512                    .ymd(2021, 2, 21)
513                    .and_time(
514                        Utc::now()
515                            .with_timezone(&FixedOffset::west(8 * 3600))
516                            .time(),
517                    )
518                    .unwrap()
519                    .with_timezone(&Utc),
520                Trunc::Seconds,
521            ),
522            (
523                "month_ymd",
524                "2021-Feb-21",
525                Utc.ymd(2021, 2, 21).and_time(Utc::now().time()).unwrap(),
526                Trunc::Seconds,
527            ),
528            (
529                "month_mdy_hms",
530                "May 8, 2009 5:57:51 PM",
531                Utc.ymd(2009, 5, 8).and_hms(17, 57, 51),
532                Trunc::None,
533            ),
534            (
535                "month_mdy_hms_z",
536                "May 02, 2021 15:51 UTC",
537                Utc.ymd(2021, 5, 2).and_hms(15, 51, 0),
538                Trunc::None,
539            ),
540            (
541                "month_mdy",
542                "May 25, 2021",
543                Utc.ymd(2021, 5, 25).and_time(Utc::now().time()).unwrap(),
544                Trunc::Seconds,
545            ),
546            (
547                "month_dmy_hms",
548                "14 May 2019 19:11:40.164",
549                Utc.ymd(2019, 5, 14).and_hms_milli(19, 11, 40, 164),
550                Trunc::None,
551            ),
552            (
553                "month_dmy",
554                "1 July 2013",
555                Utc.ymd(2013, 7, 1).and_time(Utc::now().time()).unwrap(),
556                Trunc::Seconds,
557            ),
558            (
559                "slash_mdy_hms",
560                "03/19/2012 10:11:59",
561                Utc.ymd(2012, 3, 19).and_hms(10, 11, 59),
562                Trunc::None,
563            ),
564            (
565                "slash_mdy",
566                "08/21/71",
567                Utc.ymd(1971, 8, 21).and_time(Utc::now().time()).unwrap(),
568                Trunc::Seconds,
569            ),
570            (
571                "slash_ymd_hms",
572                "2012/03/19 10:11:59",
573                Utc.ymd(2012, 3, 19).and_hms(10, 11, 59),
574                Trunc::None,
575            ),
576            (
577                "slash_ymd",
578                "2014/3/31",
579                Utc.ymd(2014, 3, 31).and_time(Utc::now().time()).unwrap(),
580                Trunc::Seconds,
581            ),
582        ];
583
584        for &(test, input, want, trunc) in test_cases.iter() {
585            match trunc {
586                Trunc::None => {
587                    assert_eq!(
588                        super::parse_with_timezone(input, &Utc).unwrap(),
589                        want,
590                        "parse_with_timezone_in_utc/{}/{}",
591                        test,
592                        input
593                    )
594                }
595                Trunc::Seconds => assert_eq!(
596                    super::parse_with_timezone(input, &Utc)
597                        .unwrap()
598                        .trunc_subsecs(0)
599                        .with_second(0)
600                        .unwrap(),
601                    want.trunc_subsecs(0).with_second(0).unwrap(),
602                    "parse_with_timezone_in_utc/{}/{}",
603                    test,
604                    input
605                ),
606            };
607        }
608    }
609
610    #[test]
611    fn parse_with_preference_and_timezone_in_utc() {
612        let current_time = Utc::now().time();
613        let current_hour = current_time.hour();
614        let current_minute = current_time.minute();
615        // let current_second = current_time.second();
616        let test_cases = vec![
617            (
618                "rfc3339",
619                "2017-11-25T22:34:50Z",
620                Utc.ymd(2017, 11, 25).and_hms(22, 34, 50),
621                Trunc::None,
622            ),
623            (
624                "rfc2822",
625                "Wed, 02 Jun 2021 06:31:39 GMT",
626                Utc.ymd(2021, 6, 2).and_hms(6, 31, 39),
627                Trunc::None,
628            ),
629            // we currently do not parse dmy format using hyphens,
630            // so the following tests are commented out
631            // (
632            //     "dmy_hms",
633            //     "30-04-2021 21:14:10",
634            //     Utc.ymd(2021, 4, 30).and_hms(21, 14, 10),
635            //     Trunc::None,
636            // ),
637            // (
638            //     "dmy_hms_z",
639            //     "25-11-2017 13:31:15 PST",
640            //     Utc.ymd(2017, 11, 25).and_hms(21, 31, 15),
641            //     Trunc::None,
642            // ),
643            // (
644            //     "dmy",
645            //     "21-02-2021",
646            //     // Utc.ymd(2021, 2, 21).and_time(Utc::now().time()).unwrap(),
647            //     Utc.with_ymd_and_hms(2021, 2, 21, current_hour, current_minute, current_second)
648            //         .unwrap(),
649            //     Trunc::Seconds,
650            // ),
651            // (
652            //     "dmy_z",
653            //     "21-02-2021 PST",
654            //     FixedOffset::west(8 * 3600)
655            //         .ymd(2021, 2, 21)
656            //         .and_time(
657            //             Utc::now()
658            //                 .with_timezone(&FixedOffset::west(8 * 3600))
659            //                 .time(),
660            //         )
661            //         .unwrap()
662            //         .with_timezone(&Utc),
663            //     Trunc::Seconds,
664            // ),
665            // (
666            //     "month_dmy",
667            //     "21-Feb-2021",
668            //     Utc.ymd(2021, 2, 21).and_time(Utc::now().time()).unwrap(),
669            //     Trunc::Seconds,
670            // ),
671            (
672                "month_mdy_hms",
673                "May 8, 2009 5:57:51 PM",
674                Utc.ymd(2009, 5, 8).and_hms(17, 57, 51),
675                Trunc::None,
676            ),
677            (
678                "month_mdy_hms_z",
679                "May 02, 2021 15:51 UTC",
680                Utc.ymd(2021, 5, 2).and_hms(15, 51, 0),
681                Trunc::None,
682            ),
683            (
684                "month_mdy",
685                "May 25, 2021",
686                Utc.ymd(2021, 5, 25).and_time(Utc::now().time()).unwrap(),
687                Trunc::Seconds,
688            ),
689            (
690                "month_dmy_hms",
691                "14 May 2019 19:11:40.164",
692                Utc.ymd(2019, 5, 14).and_hms_milli(19, 11, 40, 164),
693                Trunc::None,
694            ),
695            (
696                "month_dmy",
697                "1 July 2013",
698                Utc.ymd(2013, 7, 1).and_time(Utc::now().time()).unwrap(),
699                Trunc::Seconds,
700            ),
701            (
702                "slash_dmy_hms",
703                "19/03/2012 10:11:59",
704                Utc.ymd(2012, 3, 19).and_hms(10, 11, 59),
705                Trunc::None,
706            ),
707            (
708                "slash_dmy",
709                "21/08/71",
710                Utc.ymd(1971, 8, 21).and_time(Utc::now().time()).unwrap(),
711                Trunc::Seconds,
712            ),
713            (
714                "slash_dmy_hms",
715                "19/03/2012 10:11:59",
716                Utc.ymd(2012, 3, 19).and_hms(10, 11, 59),
717                Trunc::None,
718            ),
719            (
720                "slash_dmy",
721                "31/3/2014",
722                Utc.ymd(2014, 3, 31).and_time(Utc::now().time()).unwrap(),
723                Trunc::Seconds,
724            ),
725        ];
726
727        for &(test, input, want, trunc) in test_cases.iter() {
728            match trunc {
729                Trunc::None => {
730                    assert_eq!(
731                        super::parse_with_preference_and_timezone(input, true, &Utc).unwrap(),
732                        want,
733                        "parse_with_preference_and_timezone_in_utc/{}/{}",
734                        test,
735                        input
736                    )
737                }
738                Trunc::Seconds => assert_eq!(
739                    super::parse_with_preference_and_timezone(input, true, &Utc)
740                        .unwrap()
741                        .trunc_subsecs(0)
742                        .with_hour(current_hour)
743                        .unwrap()
744                        .with_minute(current_minute)
745                        .unwrap()
746                        .with_second(0)
747                        .unwrap(),
748                    want.trunc_subsecs(0).with_second(0).unwrap(),
749                    "parse_with_preference_and_timezone_in_utc/{}/{}",
750                    test,
751                    input
752                ),
753            };
754        }
755    }
756
757    #[test]
758    fn parse_unambiguous_dmy() {
759        assert_eq!(
760            super::parse("31/3/22").unwrap().date(),
761            Utc.ymd(2022, 3, 31)
762        );
763        assert_eq!(
764            super::parse_with_preference("3/31/22", true)
765                .unwrap()
766                .date(),
767            Utc.ymd(2022, 3, 31)
768        );
769        assert_eq!(
770            super::parse_with_preference("31/07/2021", true)
771                .unwrap()
772                .date(),
773            Utc.ymd(2021, 7, 31)
774        );
775    }
776}