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}