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///
256pub fn parse_with_timezone<Tz2: TimeZone>(input: &str, tz: &Tz2) -> Result<DateTime<Utc>> {
257    Parse::new(tz, Utc::now().time()).parse(input)
258}
259
260/// Similar to [`parse()`], this function takes a datetime string and a boolean `dmy_preference`
261/// and a timezone. When timezone is not given in the input string, this function will
262/// assume and parse the datetime by the custom timezone provided in this function's arguments.
263/// When `dmy_preference` is `true`, it will parse strings using the DMY format. Otherwise, it
264/// parses them using an MDY format.
265#[inline]
266pub fn parse_with_preference_and_timezone<Tz2: TimeZone>(
267    input: &str,
268    dmy_preference: bool,
269    tz: &Tz2,
270) -> Result<DateTime<Utc>> {
271    let midnight = MIDNIGHT.get_or_init(|| NaiveTime::from_hms_opt(0, 0, 0).unwrap());
272    Parse::new_with_preference(tz, *midnight, dmy_preference).parse(input)
273}
274
275/// Similar to [`parse()`] and [`parse_with_timezone()`], this function takes a datetime string, a
276/// custom [`chrono::TimeZone`] and a default naive time. In addition to assuming timezone when
277/// it's not given in datetime string, this function also use provided default naive time in parsed
278/// [`chrono::DateTime`].
279///
280pub fn parse_with<Tz2: TimeZone>(
281    input: &str,
282    tz: &Tz2,
283    default_time: NaiveTime,
284) -> Result<DateTime<Utc>> {
285    Parse::new(tz, default_time).parse(input)
286}
287
288#[cfg(test)]
289#[allow(deprecated)]
290mod tests {
291    use super::*;
292
293    #[derive(Clone, Copy)]
294    enum Trunc {
295        Seconds,
296        None,
297    }
298
299    #[test]
300    fn parse_in_local() {
301        let test_cases = vec![
302            (
303                "rfc3339",
304                "2017-11-25T22:34:50Z",
305                Utc.ymd(2017, 11, 25).and_hms(22, 34, 50),
306                Trunc::None,
307            ),
308            (
309                "rfc2822",
310                "Wed, 02 Jun 2021 06:31:39 GMT",
311                Utc.ymd(2021, 6, 2).and_hms(6, 31, 39),
312                Trunc::None,
313            ),
314            (
315                "ymd_hms",
316                "2021-04-30 21:14:10",
317                Local
318                    .ymd(2021, 4, 30)
319                    .and_hms(21, 14, 10)
320                    .with_timezone(&Utc),
321                Trunc::None,
322            ),
323            (
324                "ymd_hms_z",
325                "2017-11-25 13:31:15 PST",
326                Utc.ymd(2017, 11, 25).and_hms(21, 31, 15),
327                Trunc::None,
328            ),
329            (
330                "ymd",
331                "2021-02-21",
332                Local
333                    .ymd(2021, 2, 21)
334                    .and_time(Local::now().time())
335                    .unwrap()
336                    .with_timezone(&Utc),
337                Trunc::Seconds,
338            ),
339            (
340                "ymd_z",
341                "2021-02-21 PST",
342                FixedOffset::west(8 * 3600)
343                    .ymd(2021, 2, 21)
344                    .and_time(
345                        Utc::now()
346                            .with_timezone(&FixedOffset::west(8 * 3600))
347                            .time(),
348                    )
349                    .unwrap()
350                    .with_timezone(&Utc),
351                Trunc::Seconds,
352            ),
353            (
354                "month_ymd",
355                "2021-Feb-21",
356                Local
357                    .ymd(2021, 2, 21)
358                    .and_time(Local::now().time())
359                    .unwrap()
360                    .with_timezone(&Utc),
361                Trunc::Seconds,
362            ),
363            (
364                "month_mdy_hms",
365                "May 8, 2009 5:57:51 PM",
366                Local
367                    .ymd(2009, 5, 8)
368                    .and_hms(17, 57, 51)
369                    .with_timezone(&Utc),
370                Trunc::None,
371            ),
372            (
373                "month_mdy_hms_z",
374                "May 02, 2021 15:51 UTC",
375                Utc.ymd(2021, 5, 2).and_hms(15, 51, 0),
376                Trunc::None,
377            ),
378            (
379                "month_mdy",
380                "May 25, 2021",
381                Local
382                    .ymd(2021, 5, 25)
383                    .and_time(Local::now().time())
384                    .unwrap()
385                    .with_timezone(&Utc),
386                Trunc::Seconds,
387            ),
388            (
389                "month_dmy_hms",
390                "14 May 2019 19:11:40.164",
391                Local
392                    .ymd(2019, 5, 14)
393                    .and_hms_milli(19, 11, 40, 164)
394                    .with_timezone(&Utc),
395                Trunc::None,
396            ),
397            (
398                "month_dmy",
399                "1 July 2013",
400                Local
401                    .ymd(2013, 7, 1)
402                    .and_time(Local::now().time())
403                    .unwrap()
404                    .with_timezone(&Utc),
405                Trunc::Seconds,
406            ),
407            (
408                "slash_mdy_hms",
409                "03/19/2012 10:11:59",
410                Local
411                    .ymd(2012, 3, 19)
412                    .and_hms(10, 11, 59)
413                    .with_timezone(&Utc),
414                Trunc::None,
415            ),
416            (
417                "slash_mdy",
418                "08/21/71",
419                Local
420                    .ymd(1971, 8, 21)
421                    .and_time(Local::now().time())
422                    .unwrap()
423                    .with_timezone(&Utc),
424                Trunc::Seconds,
425            ),
426            (
427                "slash_ymd_hms",
428                "2012/03/19 10:11:59",
429                Local
430                    .ymd(2012, 3, 19)
431                    .and_hms(10, 11, 59)
432                    .with_timezone(&Utc),
433                Trunc::None,
434            ),
435            (
436                "slash_ymd",
437                "2014/3/31",
438                Local
439                    .ymd(2014, 3, 31)
440                    .and_time(Local::now().time())
441                    .unwrap()
442                    .with_timezone(&Utc),
443                Trunc::Seconds,
444            ),
445        ];
446
447        for &(test, input, want, trunc) in test_cases.iter() {
448            match trunc {
449                Trunc::None => {
450                    assert_eq!(
451                        super::parse(input).unwrap(),
452                        want,
453                        "parse_in_local/{}/{}",
454                        test,
455                        input
456                    )
457                }
458                Trunc::Seconds => assert_eq!(
459                    super::parse(input)
460                        .unwrap()
461                        .trunc_subsecs(0)
462                        .with_second(0)
463                        .unwrap(),
464                    want.trunc_subsecs(0).with_second(0).unwrap(),
465                    "parse_in_local/{}/{}",
466                    test,
467                    input
468                ),
469            };
470        }
471    }
472
473    #[test]
474    fn parse_with_timezone_in_utc() {
475        let test_cases = vec![
476            (
477                "rfc3339",
478                "2017-11-25T22:34:50Z",
479                Utc.ymd(2017, 11, 25).and_hms(22, 34, 50),
480                Trunc::None,
481            ),
482            (
483                "rfc2822",
484                "Wed, 02 Jun 2021 06:31:39 GMT",
485                Utc.ymd(2021, 6, 2).and_hms(6, 31, 39),
486                Trunc::None,
487            ),
488            (
489                "ymd_hms",
490                "2021-04-30 21:14:10",
491                Utc.ymd(2021, 4, 30).and_hms(21, 14, 10),
492                Trunc::None,
493            ),
494            (
495                "ymd_hms_z",
496                "2017-11-25 13:31:15 PST",
497                Utc.ymd(2017, 11, 25).and_hms(21, 31, 15),
498                Trunc::None,
499            ),
500            (
501                "ymd",
502                "2021-02-21",
503                Utc.ymd(2021, 2, 21).and_time(Utc::now().time()).unwrap(),
504                Trunc::Seconds,
505            ),
506            (
507                "ymd_z",
508                "2021-02-21 PST",
509                FixedOffset::west(8 * 3600)
510                    .ymd(2021, 2, 21)
511                    .and_time(
512                        Utc::now()
513                            .with_timezone(&FixedOffset::west(8 * 3600))
514                            .time(),
515                    )
516                    .unwrap()
517                    .with_timezone(&Utc),
518                Trunc::Seconds,
519            ),
520            (
521                "month_ymd",
522                "2021-Feb-21",
523                Utc.ymd(2021, 2, 21).and_time(Utc::now().time()).unwrap(),
524                Trunc::Seconds,
525            ),
526            (
527                "month_mdy_hms",
528                "May 8, 2009 5:57:51 PM",
529                Utc.ymd(2009, 5, 8).and_hms(17, 57, 51),
530                Trunc::None,
531            ),
532            (
533                "month_mdy_hms_z",
534                "May 02, 2021 15:51 UTC",
535                Utc.ymd(2021, 5, 2).and_hms(15, 51, 0),
536                Trunc::None,
537            ),
538            (
539                "month_mdy",
540                "May 25, 2021",
541                Utc.ymd(2021, 5, 25).and_time(Utc::now().time()).unwrap(),
542                Trunc::Seconds,
543            ),
544            (
545                "month_dmy_hms",
546                "14 May 2019 19:11:40.164",
547                Utc.ymd(2019, 5, 14).and_hms_milli(19, 11, 40, 164),
548                Trunc::None,
549            ),
550            (
551                "month_dmy",
552                "1 July 2013",
553                Utc.ymd(2013, 7, 1).and_time(Utc::now().time()).unwrap(),
554                Trunc::Seconds,
555            ),
556            (
557                "slash_mdy_hms",
558                "03/19/2012 10:11:59",
559                Utc.ymd(2012, 3, 19).and_hms(10, 11, 59),
560                Trunc::None,
561            ),
562            (
563                "slash_mdy",
564                "08/21/71",
565                Utc.ymd(1971, 8, 21).and_time(Utc::now().time()).unwrap(),
566                Trunc::Seconds,
567            ),
568            (
569                "slash_ymd_hms",
570                "2012/03/19 10:11:59",
571                Utc.ymd(2012, 3, 19).and_hms(10, 11, 59),
572                Trunc::None,
573            ),
574            (
575                "slash_ymd",
576                "2014/3/31",
577                Utc.ymd(2014, 3, 31).and_time(Utc::now().time()).unwrap(),
578                Trunc::Seconds,
579            ),
580        ];
581
582        for &(test, input, want, trunc) in test_cases.iter() {
583            match trunc {
584                Trunc::None => {
585                    assert_eq!(
586                        super::parse_with_timezone(input, &Utc).unwrap(),
587                        want,
588                        "parse_with_timezone_in_utc/{}/{}",
589                        test,
590                        input
591                    )
592                }
593                Trunc::Seconds => assert_eq!(
594                    super::parse_with_timezone(input, &Utc)
595                        .unwrap()
596                        .trunc_subsecs(0)
597                        .with_second(0)
598                        .unwrap(),
599                    want.trunc_subsecs(0).with_second(0).unwrap(),
600                    "parse_with_timezone_in_utc/{}/{}",
601                    test,
602                    input
603                ),
604            };
605        }
606    }
607
608    #[test]
609    fn parse_with_preference_and_timezone_in_utc() {
610        let current_time = Utc::now().time();
611        let current_hour = current_time.hour();
612        let current_minute = current_time.minute();
613        // let current_second = current_time.second();
614        let test_cases = vec![
615            (
616                "rfc3339",
617                "2017-11-25T22:34:50Z",
618                Utc.ymd(2017, 11, 25).and_hms(22, 34, 50),
619                Trunc::None,
620            ),
621            (
622                "rfc2822",
623                "Wed, 02 Jun 2021 06:31:39 GMT",
624                Utc.ymd(2021, 6, 2).and_hms(6, 31, 39),
625                Trunc::None,
626            ),
627            // we currently do not parse dmy format using hyphens,
628            // so the following tests are commented out
629            // (
630            //     "dmy_hms",
631            //     "30-04-2021 21:14:10",
632            //     Utc.ymd(2021, 4, 30).and_hms(21, 14, 10),
633            //     Trunc::None,
634            // ),
635            // (
636            //     "dmy_hms_z",
637            //     "25-11-2017 13:31:15 PST",
638            //     Utc.ymd(2017, 11, 25).and_hms(21, 31, 15),
639            //     Trunc::None,
640            // ),
641            // (
642            //     "dmy",
643            //     "21-02-2021",
644            //     // Utc.ymd(2021, 2, 21).and_time(Utc::now().time()).unwrap(),
645            //     Utc.with_ymd_and_hms(2021, 2, 21, current_hour, current_minute, current_second)
646            //         .unwrap(),
647            //     Trunc::Seconds,
648            // ),
649            // (
650            //     "dmy_z",
651            //     "21-02-2021 PST",
652            //     FixedOffset::west(8 * 3600)
653            //         .ymd(2021, 2, 21)
654            //         .and_time(
655            //             Utc::now()
656            //                 .with_timezone(&FixedOffset::west(8 * 3600))
657            //                 .time(),
658            //         )
659            //         .unwrap()
660            //         .with_timezone(&Utc),
661            //     Trunc::Seconds,
662            // ),
663            // (
664            //     "month_dmy",
665            //     "21-Feb-2021",
666            //     Utc.ymd(2021, 2, 21).and_time(Utc::now().time()).unwrap(),
667            //     Trunc::Seconds,
668            // ),
669            (
670                "month_mdy_hms",
671                "May 8, 2009 5:57:51 PM",
672                Utc.ymd(2009, 5, 8).and_hms(17, 57, 51),
673                Trunc::None,
674            ),
675            (
676                "month_mdy_hms_z",
677                "May 02, 2021 15:51 UTC",
678                Utc.ymd(2021, 5, 2).and_hms(15, 51, 0),
679                Trunc::None,
680            ),
681            (
682                "month_mdy",
683                "May 25, 2021",
684                Utc.ymd(2021, 5, 25).and_time(Utc::now().time()).unwrap(),
685                Trunc::Seconds,
686            ),
687            (
688                "month_dmy_hms",
689                "14 May 2019 19:11:40.164",
690                Utc.ymd(2019, 5, 14).and_hms_milli(19, 11, 40, 164),
691                Trunc::None,
692            ),
693            (
694                "month_dmy",
695                "1 July 2013",
696                Utc.ymd(2013, 7, 1).and_time(Utc::now().time()).unwrap(),
697                Trunc::Seconds,
698            ),
699            (
700                "slash_dmy_hms",
701                "19/03/2012 10:11:59",
702                Utc.ymd(2012, 3, 19).and_hms(10, 11, 59),
703                Trunc::None,
704            ),
705            (
706                "slash_dmy",
707                "21/08/71",
708                Utc.ymd(1971, 8, 21).and_time(Utc::now().time()).unwrap(),
709                Trunc::Seconds,
710            ),
711            (
712                "slash_dmy_hms",
713                "19/03/2012 10:11:59",
714                Utc.ymd(2012, 3, 19).and_hms(10, 11, 59),
715                Trunc::None,
716            ),
717            (
718                "slash_dmy",
719                "31/3/2014",
720                Utc.ymd(2014, 3, 31).and_time(Utc::now().time()).unwrap(),
721                Trunc::Seconds,
722            ),
723        ];
724
725        for &(test, input, want, trunc) in test_cases.iter() {
726            match trunc {
727                Trunc::None => {
728                    assert_eq!(
729                        super::parse_with_preference_and_timezone(input, true, &Utc).unwrap(),
730                        want,
731                        "parse_with_preference_and_timezone_in_utc/{}/{}",
732                        test,
733                        input
734                    )
735                }
736                Trunc::Seconds => assert_eq!(
737                    super::parse_with_preference_and_timezone(input, true, &Utc)
738                        .unwrap()
739                        .trunc_subsecs(0)
740                        .with_hour(current_hour)
741                        .unwrap()
742                        .with_minute(current_minute)
743                        .unwrap()
744                        .with_second(0)
745                        .unwrap(),
746                    want.trunc_subsecs(0).with_second(0).unwrap(),
747                    "parse_with_preference_and_timezone_in_utc/{}/{}",
748                    test,
749                    input
750                ),
751            };
752        }
753    }
754
755    #[test]
756    fn parse_unambiguous_dmy() {
757        assert_eq!(
758            super::parse("31/3/22").unwrap().date(),
759            Utc.ymd(2022, 3, 31)
760        );
761        assert_eq!(
762            super::parse_with_preference("3/31/22", true)
763                .unwrap()
764                .date(),
765            Utc.ymd(2022, 3, 31)
766        );
767        assert_eq!(
768            super::parse_with_preference("31/07/2021", true)
769                .unwrap()
770                .date(),
771            Utc.ymd(2021, 7, 31)
772        );
773    }
774}