blob: 6c704114391f24ad4f89bb3038c3d46c548da665 [file] [log] [blame] [edit]
#![allow(deprecated)]
//! A rust library for parsing date strings in commonly used formats. Parsed date will be returned
//! as `chrono`'s `DateTime<Utc>`.
//!
//! # Quick Start
//!
//! ```
//! use chrono::prelude::*;
//! use dateparser::parse;
//! use std::error::Error;
//!
//! fn main() -> Result<(), Box<dyn Error>> {
//! assert_eq!(
//! parse("6:15pm UTC")?,
//! Utc::now().date().and_time(
//! NaiveTime::from_hms(18, 15, 0),
//! ).unwrap(),
//! );
//! Ok(())
//! }
//! ```
//!
//! Use `str`'s `parse` method:
//!
//! ```
//! use chrono::prelude::*;
//! use dateparser::DateTimeUtc;
//! use std::error::Error;
//!
//! fn main() -> Result<(), Box<dyn Error>> {
//! assert_eq!(
//! "2021-05-14 18:51 PDT".parse::<DateTimeUtc>()?.0,
//! Utc.ymd(2021, 5, 15).and_hms(1, 51, 0),
//! );
//! Ok(())
//! }
//! ```
//!
//! Parse using a custom timezone offset for a datetime string that doesn't come with a specific
//! timezone:
//!
//! ```
//! use dateparser::parse_with_timezone;
//! use chrono::offset::Utc;
//! use std::error::Error;
//!
//! fn main() -> Result<(), Box<dyn Error>> {
//! let parsed_in_utc = parse_with_timezone("6:15pm", &Utc)?;
//! assert_eq!(
//! parsed_in_utc,
//! Utc::now().date().and_hms(18, 15, 0),
//! );
//! Ok(())
//! }
//! ```
//!
//! ## Accepted date formats
//!
//! ```
//! use dateparser::DateTimeUtc;
//!
//! let accepted = vec![
//! // unix timestamp
//! "1511648546",
//! "1620021848429",
//! "1620024872717915000",
//! // rfc3339
//! "2021-05-01T01:17:02.604456Z",
//! "2017-11-25T22:34:50Z",
//! // rfc2822
//! "Wed, 02 Jun 2021 06:31:39 GMT",
//! // postgres timestamp yyyy-mm-dd hh:mm:ss z
//! "2019-11-29 08:08-08",
//! "2019-11-29 08:08:05-08",
//! "2021-05-02 23:31:36.0741-07",
//! "2021-05-02 23:31:39.12689-07",
//! "2019-11-29 08:15:47.624504-08",
//! "2017-07-19 03:21:51+00:00",
//! // yyyy-mm-dd hh:mm:ss
//! "2014-04-26 05:24:37 PM",
//! "2021-04-30 21:14",
//! "2021-04-30 21:14:10",
//! "2021-04-30 21:14:10.052282",
//! "2014-04-26 17:24:37.123",
//! "2014-04-26 17:24:37.3186369",
//! "2012-08-03 18:31:59.257000000",
//! // yyyy-mm-dd hh:mm:ss z
//! "2017-11-25 13:31:15 PST",
//! "2017-11-25 13:31 PST",
//! "2014-12-16 06:20:00 UTC",
//! "2014-12-16 06:20:00 GMT",
//! "2014-04-26 13:13:43 +0800",
//! "2014-04-26 13:13:44 +09:00",
//! "2012-08-03 18:31:59.257000000 +0000",
//! "2015-09-30 18:48:56.35272715 UTC",
//! // yyyy-mm-dd
//! "2021-02-21",
//! // yyyy-mm-dd z
//! "2021-02-21 PST",
//! "2021-02-21 UTC",
//! "2020-07-20+08:00",
//! // hh:mm:ss
//! "01:06:06",
//! "4:00pm",
//! "6:00 AM",
//! // hh:mm:ss z
//! "01:06:06 PST",
//! "4:00pm PST",
//! "6:00 AM PST",
//! "6:00pm UTC",
//! // Mon dd hh:mm:ss
//! "May 6 at 9:24 PM",
//! "May 27 02:45:27",
//! // Mon dd, yyyy, hh:mm:ss
//! "May 8, 2009 5:57:51 PM",
//! "September 17, 2012 10:09am",
//! "September 17, 2012, 10:10:09",
//! // Mon dd, yyyy hh:mm:ss z
//! "May 02, 2021 15:51:31 UTC",
//! "May 02, 2021 15:51 UTC",
//! "May 26, 2021, 12:49 AM PDT",
//! "September 17, 2012 at 10:09am PST",
//! // yyyy-mon-dd
//! "2021-Feb-21",
//! // Mon dd, yyyy
//! "May 25, 2021",
//! "oct 7, 1970",
//! "oct 7, 70",
//! "oct. 7, 1970",
//! "oct. 7, 70",
//! "October 7, 1970",
//! // dd Mon yyyy hh:mm:ss
//! "12 Feb 2006, 19:17",
//! "12 Feb 2006 19:17",
//! "14 May 2019 19:11:40.164",
//! // dd Mon yyyy
//! "7 oct 70",
//! "7 oct 1970",
//! "03 February 2013",
//! "1 July 2013",
//! // mm/dd/yyyy hh:mm:ss
//! "4/8/2014 22:05",
//! "04/08/2014 22:05",
//! "4/8/14 22:05",
//! "04/2/2014 03:00:51",
//! "8/8/1965 12:00:00 AM",
//! "8/8/1965 01:00:01 PM",
//! "8/8/1965 01:00 PM",
//! "8/8/1965 1:00 PM",
//! "8/8/1965 12:00 AM",
//! "4/02/2014 03:00:51",
//! "03/19/2012 10:11:59",
//! "03/19/2012 10:11:59.3186369",
//! // mm/dd/yyyy
//! "3/31/2014",
//! "03/31/2014",
//! "08/21/71",
//! "8/1/71",
//! // yyyy/mm/dd hh:mm:ss
//! "2014/4/8 22:05",
//! "2014/04/08 22:05",
//! "2014/04/2 03:00:51",
//! "2014/4/02 03:00:51",
//! "2012/03/19 10:11:59",
//! "2012/03/19 10:11:59.3186369",
//! // yyyy/mm/dd
//! "2014/3/31",
//! "2014/03/31",
//! // mm.dd.yyyy
//! "3.31.2014",
//! "03.31.2014",
//! "08.21.71",
//! // yyyy.mm.dd
//! "2014.03.30",
//! "2014.03",
//! // yymmdd hh:mm:ss mysql log
//! "171113 14:14:20",
//! // chinese yyyy mm dd hh mm ss
//! "2014年04月08日11时25分18秒",
//! // chinese yyyy mm dd
//! "2014年04月08日",
//! ];
//!
//! for date_str in accepted {
//! let result = date_str.parse::<DateTimeUtc>();
//! assert!(result.is_ok())
//! }
//! ```
/// Datetime string parser
///
/// ```
/// use chrono::prelude::*;
/// use dateparser::datetime::Parse;
/// use std::error::Error;
///
/// fn main() -> Result<(), Box<dyn Error>> {
/// let parse_with_local = Parse::new(&Local, None);
/// assert_eq!(
/// parse_with_local.parse("2021-06-05 06:19 PM")?,
/// Local.ymd(2021, 6, 5).and_hms(18, 19, 0).with_timezone(&Utc),
/// );
///
/// let parse_with_utc = Parse::new(&Utc, None);
/// assert_eq!(
/// parse_with_utc.parse("2021-06-05 06:19 PM")?,
/// Utc.ymd(2021, 6, 5).and_hms(18, 19, 0),
/// );
///
/// Ok(())
/// }
/// ```
pub mod datetime;
/// Timezone offset string parser
///
/// ```
/// use chrono::prelude::*;
/// use dateparser::timezone::parse;
/// use std::error::Error;
///
/// fn main() -> Result<(), Box<dyn Error>> {
/// assert_eq!(parse("-0800")?, FixedOffset::west(8 * 3600));
/// assert_eq!(parse("+10:00")?, FixedOffset::east(10 * 3600));
/// assert_eq!(parse("PST")?, FixedOffset::west(8 * 3600));
/// assert_eq!(parse("PDT")?, FixedOffset::west(7 * 3600));
/// assert_eq!(parse("UTC")?, FixedOffset::west(0));
/// assert_eq!(parse("GMT")?, FixedOffset::west(0));
///
/// Ok(())
/// }
/// ```
pub mod timezone;
use crate::datetime::Parse;
use anyhow::{Error, Result};
use chrono::prelude::*;
/// DateTimeUtc is an alias for `chrono`'s `DateTime<UTC>`. It implements `std::str::FromStr`'s
/// `from_str` method, and it makes `str`'s `parse` method to understand the accepted date formats
/// from this crate.
///
/// ```
/// use dateparser::DateTimeUtc;
///
/// // parsed is DateTimeUTC and parsed.0 is chrono's DateTime<Utc>
/// match "May 02, 2021 15:51:31 UTC".parse::<DateTimeUtc>() {
/// Ok(parsed) => println!("PARSED into UTC datetime {:?}", parsed.0),
/// Err(err) => println!("ERROR from parsing datetime string: {}", err)
/// }
/// ```
#[derive(Clone, Debug)]
pub struct DateTimeUtc(pub DateTime<Utc>);
impl std::str::FromStr for DateTimeUtc {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
parse(s).map(DateTimeUtc)
}
}
/// This function tries to recognize the input datetime string with a list of accepted formats.
/// When timezone is not provided, this function assumes it's a [`chrono::Local`] datetime. For
/// custom timezone, use [`parse_with_timezone()`] instead.If all options are exhausted,
/// [`parse()`] will return an error to let the caller know that no formats were matched.
///
/// ```
/// use dateparser::parse;
/// use chrono::offset::{Local, Utc};
/// use chrono_tz::US::Pacific;
///
/// let parsed = parse("6:15pm").unwrap();
///
/// assert_eq!(
/// parsed,
/// Local::now().date().and_hms(18, 15, 0).with_timezone(&Utc),
/// );
///
/// assert_eq!(
/// parsed.with_timezone(&Pacific),
/// Local::now().date().and_hms(18, 15, 0).with_timezone(&Utc).with_timezone(&Pacific),
/// );
/// ```
pub fn parse(input: &str) -> Result<DateTime<Utc>> {
Parse::new(&Local, None).parse(input)
}
/// Similar to [`parse()`], this function takes a datetime string and a custom [`chrono::TimeZone`],
/// and tries to parse the datetime string. When timezone is not given in the string, this function
/// will assume and parse the datetime by the custom timezone provided in this function's arguments.
///
/// ```
/// use dateparser::parse_with_timezone;
/// use chrono::offset::{Local, Utc};
/// use chrono_tz::US::Pacific;
///
/// let parsed_in_local = parse_with_timezone("6:15pm", &Local).unwrap();
/// assert_eq!(
/// parsed_in_local,
/// Local::now().date().and_hms(18, 15, 0).with_timezone(&Utc),
/// );
///
/// let parsed_in_utc = parse_with_timezone("6:15pm", &Utc).unwrap();
/// assert_eq!(
/// parsed_in_utc,
/// Utc::now().date().and_hms(18, 15, 0),
/// );
///
/// let parsed_in_pacific = parse_with_timezone("6:15pm", &Pacific).unwrap();
/// assert_eq!(
/// parsed_in_pacific,
/// Utc::now().with_timezone(&Pacific).date().and_hms(18, 15, 0).with_timezone(&Utc),
/// );
/// ```
pub fn parse_with_timezone<Tz2: TimeZone>(input: &str, tz: &Tz2) -> Result<DateTime<Utc>> {
Parse::new(tz, None).parse(input)
}
/// Similar to [`parse()`] and [`parse_with_timezone()`], this function takes a datetime string, a
/// custom [`chrono::TimeZone`] and a default naive time. In addition to assuming timezone when
/// it's not given in datetime string, this function also use provided default naive time in parsed
/// [`chrono::DateTime`].
///
/// ```
/// use dateparser::parse_with;
/// use chrono::prelude::*;
///
/// let utc_now = Utc::now().time().trunc_subsecs(0);
/// let local_now = Local::now().time().trunc_subsecs(0);
/// let midnight_naive = NaiveTime::from_hms_opt(0, 0, 0).unwrap();
/// let before_midnight_naive = NaiveTime::from_hms_opt(23, 59, 59).unwrap();
///
/// let parsed_with_local_now = parse_with("2021-10-09", &Local, local_now);
/// let parsed_with_local_midnight = parse_with("2021-10-09", &Local, midnight_naive);
/// let parsed_with_local_before_midnight = parse_with("2021-10-09", &Local, before_midnight_naive);
/// let parsed_with_utc_now = parse_with("2021-10-09", &Utc, utc_now);
/// let parsed_with_utc_midnight = parse_with("2021-10-09", &Utc, midnight_naive);
///
/// assert_eq!(
/// parsed_with_local_now.unwrap(),
/// Local.ymd(2021, 10, 9).and_time(local_now).unwrap().with_timezone(&Utc),
/// "parsed_with_local_now"
/// );
/// assert_eq!(
/// parsed_with_local_midnight.unwrap(),
/// Local.ymd(2021, 10, 9).and_time(midnight_naive).unwrap().with_timezone(&Utc),
/// "parsed_with_local_midnight"
/// );
/// assert_eq!(
/// parsed_with_local_before_midnight.unwrap(),
/// Local.ymd(2021, 10, 9).and_time(before_midnight_naive).unwrap().with_timezone(&Utc),
/// "parsed_with_local_before_midnight"
/// );
/// assert_eq!(
/// parsed_with_utc_now.unwrap(),
/// Utc.ymd(2021, 10, 9).and_time(utc_now).unwrap(),
/// "parsed_with_utc_now"
/// );
/// assert_eq!(
/// parsed_with_utc_midnight.unwrap(),
/// Utc.ymd(2021, 10, 9).and_hms(0, 0, 0),
/// "parsed_with_utc_midnight"
/// );
/// ```
pub fn parse_with<Tz2: TimeZone>(
input: &str,
tz: &Tz2,
default_time: NaiveTime,
) -> Result<DateTime<Utc>> {
Parse::new(tz, Some(default_time)).parse(input)
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone, Copy)]
enum Trunc {
Seconds,
None,
}
#[test]
fn parse_in_local() {
let test_cases = vec![
(
"unix_timestamp",
"1511648546",
Utc.ymd(2017, 11, 25).and_hms(22, 22, 26),
Trunc::None,
),
(
"rfc3339",
"2017-11-25T22:34:50Z",
Utc.ymd(2017, 11, 25).and_hms(22, 34, 50),
Trunc::None,
),
(
"rfc2822",
"Wed, 02 Jun 2021 06:31:39 GMT",
Utc.ymd(2021, 6, 2).and_hms(6, 31, 39),
Trunc::None,
),
(
"postgres_timestamp",
"2019-11-29 08:08:05-08",
Utc.ymd(2019, 11, 29).and_hms(16, 8, 5),
Trunc::None,
),
(
"ymd_hms",
"2021-04-30 21:14:10",
Local
.ymd(2021, 4, 30)
.and_hms(21, 14, 10)
.with_timezone(&Utc),
Trunc::None,
),
(
"ymd_hms_z",
"2017-11-25 13:31:15 PST",
Utc.ymd(2017, 11, 25).and_hms(21, 31, 15),
Trunc::None,
),
(
"ymd",
"2021-02-21",
Local
.ymd(2021, 2, 21)
.and_time(Local::now().time())
.unwrap()
.with_timezone(&Utc),
Trunc::Seconds,
),
(
"ymd_z",
"2021-02-21 PST",
FixedOffset::west(8 * 3600)
.ymd(2021, 2, 21)
.and_time(
Utc::now()
.with_timezone(&FixedOffset::west(8 * 3600))
.time(),
)
.unwrap()
.with_timezone(&Utc),
Trunc::Seconds,
),
(
"hms",
"4:00pm",
Local::now()
.date()
.and_time(NaiveTime::from_hms(16, 0, 0))
.unwrap()
.with_timezone(&Utc),
Trunc::None,
),
(
"hms_z",
"6:00 AM PST",
Utc::now()
.with_timezone(&FixedOffset::west(8 * 3600))
.date()
.and_time(NaiveTime::from_hms(6, 0, 0))
.unwrap()
.with_timezone(&Utc),
Trunc::None,
),
(
"month_ymd",
"2021-Feb-21",
Local
.ymd(2021, 2, 21)
.and_time(Local::now().time())
.unwrap()
.with_timezone(&Utc),
Trunc::Seconds,
),
(
"month_md_hms",
"May 27 02:45:27",
Local
.ymd(Local::now().year(), 5, 27)
.and_hms(2, 45, 27)
.with_timezone(&Utc),
Trunc::None,
),
(
"month_mdy_hms",
"May 8, 2009 5:57:51 PM",
Local
.ymd(2009, 5, 8)
.and_hms(17, 57, 51)
.with_timezone(&Utc),
Trunc::None,
),
(
"month_mdy_hms_z",
"May 02, 2021 15:51 UTC",
Utc.ymd(2021, 5, 2).and_hms(15, 51, 0),
Trunc::None,
),
(
"month_mdy",
"May 25, 2021",
Local
.ymd(2021, 5, 25)
.and_time(Local::now().time())
.unwrap()
.with_timezone(&Utc),
Trunc::Seconds,
),
(
"month_dmy_hms",
"14 May 2019 19:11:40.164",
Local
.ymd(2019, 5, 14)
.and_hms_milli(19, 11, 40, 164)
.with_timezone(&Utc),
Trunc::None,
),
(
"month_dmy",
"1 July 2013",
Local
.ymd(2013, 7, 1)
.and_time(Local::now().time())
.unwrap()
.with_timezone(&Utc),
Trunc::Seconds,
),
(
"slash_mdy_hms",
"03/19/2012 10:11:59",
Local
.ymd(2012, 3, 19)
.and_hms(10, 11, 59)
.with_timezone(&Utc),
Trunc::None,
),
(
"slash_mdy",
"08/21/71",
Local
.ymd(1971, 8, 21)
.and_time(Local::now().time())
.unwrap()
.with_timezone(&Utc),
Trunc::Seconds,
),
(
"slash_ymd_hms",
"2012/03/19 10:11:59",
Local
.ymd(2012, 3, 19)
.and_hms(10, 11, 59)
.with_timezone(&Utc),
Trunc::None,
),
(
"slash_ymd",
"2014/3/31",
Local
.ymd(2014, 3, 31)
.and_time(Local::now().time())
.unwrap()
.with_timezone(&Utc),
Trunc::Seconds,
),
(
"dot_mdy_or_ymd",
"2014.03.30",
Local
.ymd(2014, 3, 30)
.and_time(Local::now().time())
.unwrap()
.with_timezone(&Utc),
Trunc::Seconds,
),
(
"mysql_log_timestamp",
"171113 14:14:20",
Local
.ymd(2017, 11, 13)
.and_hms(14, 14, 20)
.with_timezone(&Utc),
Trunc::None,
),
(
"chinese_ymd_hms",
"2014年04月08日11时25分18秒",
Local
.ymd(2014, 4, 8)
.and_hms(11, 25, 18)
.with_timezone(&Utc),
Trunc::None,
),
(
"chinese_ymd",
"2014年04月08日",
Local
.ymd(2014, 4, 8)
.and_time(Local::now().time())
.unwrap()
.with_timezone(&Utc),
Trunc::Seconds,
),
];
for &(test, input, want, trunc) in test_cases.iter() {
match trunc {
Trunc::None => {
assert_eq!(
super::parse(input).unwrap(),
want,
"parse_in_local/{}/{}",
test,
input
)
}
Trunc::Seconds => assert_eq!(
super::parse(input)
.unwrap()
.trunc_subsecs(0)
.with_second(0)
.unwrap(),
want.trunc_subsecs(0).with_second(0).unwrap(),
"parse_in_local/{}/{}",
test,
input
),
};
}
}
#[test]
fn parse_with_timezone_in_utc() {
let test_cases = vec![
(
"unix_timestamp",
"1511648546",
Utc.ymd(2017, 11, 25).and_hms(22, 22, 26),
Trunc::None,
),
(
"rfc3339",
"2017-11-25T22:34:50Z",
Utc.ymd(2017, 11, 25).and_hms(22, 34, 50),
Trunc::None,
),
(
"rfc2822",
"Wed, 02 Jun 2021 06:31:39 GMT",
Utc.ymd(2021, 6, 2).and_hms(6, 31, 39),
Trunc::None,
),
(
"postgres_timestamp",
"2019-11-29 08:08:05-08",
Utc.ymd(2019, 11, 29).and_hms(16, 8, 5),
Trunc::None,
),
(
"ymd_hms",
"2021-04-30 21:14:10",
Utc.ymd(2021, 4, 30).and_hms(21, 14, 10),
Trunc::None,
),
(
"ymd_hms_z",
"2017-11-25 13:31:15 PST",
Utc.ymd(2017, 11, 25).and_hms(21, 31, 15),
Trunc::None,
),
(
"ymd",
"2021-02-21",
Utc.ymd(2021, 2, 21).and_time(Utc::now().time()).unwrap(),
Trunc::Seconds,
),
(
"ymd_z",
"2021-02-21 PST",
FixedOffset::west(8 * 3600)
.ymd(2021, 2, 21)
.and_time(
Utc::now()
.with_timezone(&FixedOffset::west(8 * 3600))
.time(),
)
.unwrap()
.with_timezone(&Utc),
Trunc::Seconds,
),
(
"hms",
"4:00pm",
Utc::now()
.date()
.and_time(NaiveTime::from_hms(16, 0, 0))
.unwrap(),
Trunc::None,
),
(
"hms_z",
"6:00 AM PST",
FixedOffset::west(8 * 3600)
.from_local_date(
&Utc::now()
.with_timezone(&FixedOffset::west(8 * 3600))
.date()
.naive_local(),
)
.and_time(NaiveTime::from_hms(6, 0, 0))
.unwrap()
.with_timezone(&Utc),
Trunc::None,
),
(
"month_ymd",
"2021-Feb-21",
Utc.ymd(2021, 2, 21).and_time(Utc::now().time()).unwrap(),
Trunc::Seconds,
),
(
"month_md_hms",
"May 27 02:45:27",
Utc.ymd(Utc::now().year(), 5, 27).and_hms(2, 45, 27),
Trunc::None,
),
(
"month_mdy_hms",
"May 8, 2009 5:57:51 PM",
Utc.ymd(2009, 5, 8).and_hms(17, 57, 51),
Trunc::None,
),
(
"month_mdy_hms_z",
"May 02, 2021 15:51 UTC",
Utc.ymd(2021, 5, 2).and_hms(15, 51, 0),
Trunc::None,
),
(
"month_mdy",
"May 25, 2021",
Utc.ymd(2021, 5, 25).and_time(Utc::now().time()).unwrap(),
Trunc::Seconds,
),
(
"month_dmy_hms",
"14 May 2019 19:11:40.164",
Utc.ymd(2019, 5, 14).and_hms_milli(19, 11, 40, 164),
Trunc::None,
),
(
"month_dmy",
"1 July 2013",
Utc.ymd(2013, 7, 1).and_time(Utc::now().time()).unwrap(),
Trunc::Seconds,
),
(
"slash_mdy_hms",
"03/19/2012 10:11:59",
Utc.ymd(2012, 3, 19).and_hms(10, 11, 59),
Trunc::None,
),
(
"slash_mdy",
"08/21/71",
Utc.ymd(1971, 8, 21).and_time(Utc::now().time()).unwrap(),
Trunc::Seconds,
),
(
"slash_ymd_hms",
"2012/03/19 10:11:59",
Utc.ymd(2012, 3, 19).and_hms(10, 11, 59),
Trunc::None,
),
(
"slash_ymd",
"2014/3/31",
Utc.ymd(2014, 3, 31).and_time(Utc::now().time()).unwrap(),
Trunc::Seconds,
),
(
"dot_mdy_or_ymd",
"2014.03.30",
Utc.ymd(2014, 3, 30).and_time(Utc::now().time()).unwrap(),
Trunc::Seconds,
),
(
"mysql_log_timestamp",
"171113 14:14:20",
Utc.ymd(2017, 11, 13).and_hms(14, 14, 20),
Trunc::None,
),
(
"chinese_ymd_hms",
"2014年04月08日11时25分18秒",
Utc.ymd(2014, 4, 8).and_hms(11, 25, 18),
Trunc::None,
),
(
"chinese_ymd",
"2014年04月08日",
Utc.ymd(2014, 4, 8).and_time(Utc::now().time()).unwrap(),
Trunc::Seconds,
),
];
for &(test, input, want, trunc) in test_cases.iter() {
match trunc {
Trunc::None => {
assert_eq!(
super::parse_with_timezone(input, &Utc).unwrap(),
want,
"parse_with_timezone_in_utc/{}/{}",
test,
input
)
}
Trunc::Seconds => assert_eq!(
super::parse_with_timezone(input, &Utc)
.unwrap()
.trunc_subsecs(0)
.with_second(0)
.unwrap(),
want.trunc_subsecs(0).with_second(0).unwrap(),
"parse_with_timezone_in_utc/{}/{}",
test,
input
),
};
}
}
// test parse_with() with various timezones and times
#[test]
fn parse_with_edt() {
// Eastern Daylight Time (EDT) is from (as of 2023) 2nd Sun in Mar to 1st Sun in Nov
// It is UTC -4
let midnight_naive = NaiveTime::from_hms_opt(0, 0, 0).unwrap();
let before_midnight_naive = NaiveTime::from_hms_opt(23, 59, 59).unwrap();
let us_edt = &FixedOffset::west_opt(4 * 3600).unwrap();
let edt_test_cases = vec![
("ymd", "2023-04-21"),
("ymd_z", "2023-04-21 EDT"),
("month_ymd", "2023-Apr-21"),
("month_mdy", "April 21, 2023"),
("month_dmy", "21 April 2023"),
("slash_mdy", "04/21/23"),
("slash_ymd", "2023/4/21"),
("dot_mdy_or_ymd", "2023.04.21"),
("chinese_ymd", "2023年04月21日"),
];
// test us_edt at midnight
let us_edt_midnight_as_utc = Utc.ymd(2023, 4, 21).and_hms(4, 0, 0);
for &(test, input) in edt_test_cases.iter() {
assert_eq!(
super::parse_with(input, us_edt, midnight_naive).unwrap(),
us_edt_midnight_as_utc,
"parse_with/{test}/{input}",
)
}
// test us_edt at 23:59:59 - UTC will be one day ahead
let us_edt_before_midnight_as_utc = Utc.ymd(2023, 4, 22).and_hms(3, 59, 59);
for &(test, input) in edt_test_cases.iter() {
assert_eq!(
super::parse_with(input, us_edt, before_midnight_naive).unwrap(),
us_edt_before_midnight_as_utc,
"parse_with/{test}/{input}",
)
}
}
#[test]
fn parse_with_est() {
// Eastern Standard Time (EST) is from (as of 2023) 1st Sun in Nov to 2nd Sun in Mar
// It is UTC -5
let midnight_naive = NaiveTime::from_hms_opt(0, 0, 0).unwrap();
let before_midnight_naive = NaiveTime::from_hms_opt(23, 59, 59).unwrap();
let us_est = &FixedOffset::west(5 * 3600);
let est_test_cases = vec![
("ymd", "2023-12-21"),
("ymd_z", "2023-12-21 EST"),
("month_ymd", "2023-Dec-21"),
("month_mdy", "December 21, 2023"),
("month_dmy", "21 December 2023"),
("slash_mdy", "12/21/23"),
("slash_ymd", "2023/12/21"),
("dot_mdy_or_ymd", "2023.12.21"),
("chinese_ymd", "2023年12月21日"),
];
// test us_est at midnight
let us_est_midnight_as_utc = Utc.ymd(2023, 12, 21).and_hms(5, 0, 0);
for &(test, input) in est_test_cases.iter() {
assert_eq!(
super::parse_with(input, us_est, midnight_naive).unwrap(),
us_est_midnight_as_utc,
"parse_with/{test}/{input}",
)
}
// test us_est at 23:59:59 - UTC will be one day ahead
let us_est_before_midnight_as_utc = Utc.ymd(2023, 12, 22).and_hms(4, 59, 59);
for &(test, input) in est_test_cases.iter() {
assert_eq!(
super::parse_with(input, us_est, before_midnight_naive).unwrap(),
us_est_before_midnight_as_utc,
"parse_with/{test}/{input}",
)
}
}
#[test]
fn parse_with_utc() {
let midnight_naive = NaiveTime::from_hms_opt(0, 0, 0).unwrap();
let before_midnight_naive = NaiveTime::from_hms_opt(23, 59, 59).unwrap();
let utc_test_cases = vec![
("ymd", "2023-12-21"),
("ymd_z", "2023-12-21 UTC"),
("month_ymd", "2023-Dec-21"),
("month_mdy", "December 21, 2023"),
("month_dmy", "21 December 2023"),
("slash_mdy", "12/21/23"),
("slash_ymd", "2023/12/21"),
("dot_mdy_or_ymd", "2023.12.21"),
("chinese_ymd", "2023年12月21日"),
];
// test utc at midnight
let utc_midnight = Utc.ymd(2023, 12, 21).and_hms(0, 0, 0);
for &(test, input) in utc_test_cases.iter() {
assert_eq!(
super::parse_with(input, &Utc, midnight_naive).unwrap(),
utc_midnight,
"parse_with/{test}/{input}",
)
}
// test utc at 23:59:59
let utc_before_midnight = Utc.ymd(2023, 12, 21).and_hms(23, 59, 59);
for &(test, input) in utc_test_cases.iter() {
assert_eq!(
super::parse_with(input, &Utc, before_midnight_naive).unwrap(),
utc_before_midnight,
"parse_with/{test}/{input}",
)
}
}
#[test]
fn parse_with_local() {
let midnight_naive = NaiveTime::from_hms_opt(0, 0, 0).unwrap();
let before_midnight_naive = NaiveTime::from_hms_opt(23, 59, 59).unwrap();
let local_test_cases = vec![
("ymd", "2023-12-21"),
("month_ymd", "2023-Dec-21"),
("month_mdy", "December 21, 2023"),
("month_dmy", "21 December 2023"),
("slash_mdy", "12/21/23"),
("slash_ymd", "2023/12/21"),
("dot_mdy_or_ymd", "2023.12.21"),
("chinese_ymd", "2023年12月21日"),
];
// test local at midnight
let local_midnight_as_utc = Local.ymd(2023, 12, 21).and_hms(0, 0, 0).with_timezone(&Utc);
for &(test, input) in local_test_cases.iter() {
assert_eq!(
super::parse_with(input, &Local, midnight_naive).unwrap(),
local_midnight_as_utc,
"parse_with/{test}/{input}",
)
}
// test local at 23:59:59
let local_before_midnight_as_utc = Local
.ymd(2023, 12, 21)
.and_hms(23, 59, 59)
.with_timezone(&Utc);
for &(test, input) in local_test_cases.iter() {
assert_eq!(
super::parse_with(input, &Local, before_midnight_naive).unwrap(),
local_before_midnight_as_utc,
"parse_with/{test}/{input}",
)
}
}
}