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}