#![allow(deprecated)]
pub mod datetime;
pub mod timezone;
use crate::datetime::Parse;
use anyhow::{Error, Result};
use chrono::prelude::*;
#[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)
}
}
pub fn parse(input: &str) -> Result<DateTime<Utc>> {
Parse::new(&Local, None).parse(input)
}
pub fn parse_with_timezone<Tz2: TimeZone>(input: &str, tz: &Tz2) -> Result<DateTime<Utc>> {
Parse::new(tz, None).parse(input)
}
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,
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), 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.29",
Local
.ymd(2014, 3, 29)
.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()
.with_minute(0)
.unwrap(),
want.trunc_subsecs(0)
.with_second(0)
.unwrap()
.with_minute(0)
.unwrap(),
"parse_in_local/{}/{}",
test,
input
),
};
}
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), 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.29",
Utc.ymd(2014, 3, 29).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()
.with_minute(0)
.unwrap(),
want.trunc_subsecs(0)
.with_second(0)
.unwrap()
.with_minute(0)
.unwrap(),
"parse_with_timezone_in_utc/{}/{}",
test,
input
),
};
}
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), test)]
fn parse_with_edt() {
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日"),
];
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}",
)
}
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}",
)
}
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), test)]
fn parse_with_est() {
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日"),
];
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}",
)
}
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}",
)
}
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), 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日"),
];
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}",
)
}
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}",
)
}
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), 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 = [
("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日"),
];
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}",
)
}
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}",
)
}
}
}