pub mod datetime;
pub mod timezone;
use crate::datetime::Parse;
use anyhow::{Error, Result};
use chrono::prelude::*;
use std::sync::OnceLock;
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)
}
}
static MIDNIGHT: OnceLock<chrono::NaiveTime> = OnceLock::new();
#[inline]
pub fn parse(input: &str) -> Result<DateTime<Utc>> {
Parse::new(&Local, Utc::now().time()).parse(input)
}
#[inline]
pub fn parse_with_preference(input: &str, dmy_preference: bool) -> Result<DateTime<Utc>> {
let midnight = MIDNIGHT.get_or_init(|| NaiveTime::from_hms_opt(0, 0, 0).unwrap());
Parse::new_with_preference(&Utc, *midnight, dmy_preference).parse(input)
}
#[inline]
pub fn parse_with_timezone<Tz2: TimeZone>(input: &str, tz: &Tz2) -> Result<DateTime<Utc>> {
Parse::new(tz, Utc::now().time()).parse(input)
}
#[inline]
pub fn parse_with_preference_and_timezone<Tz2: TimeZone>(
input: &str,
dmy_preference: bool,
tz: &Tz2,
) -> Result<DateTime<Utc>> {
let midnight = MIDNIGHT.get_or_init(|| NaiveTime::from_hms_opt(0, 0, 0).unwrap());
Parse::new_with_preference(tz, *midnight, dmy_preference).parse(input)
}
#[inline]
pub fn parse_with<Tz2: TimeZone>(
input: &str,
tz: &Tz2,
default_time: NaiveTime,
) -> Result<DateTime<Utc>> {
Parse::new(tz, default_time).parse(input)
}
#[cfg(test)]
#[allow(deprecated)]
mod tests {
use super::*;
#[derive(Clone, Copy)]
enum Trunc {
Seconds,
None,
}
#[test]
fn parse_in_local() {
let test_cases = vec![
(
"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,
),
(
"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,
),
(
"month_ymd",
"2021-Feb-21",
Local
.ymd(2021, 2, 21)
.and_time(Local::now().time())
.unwrap()
.with_timezone(&Utc),
Trunc::Seconds,
),
(
"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,
),
];
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![
(
"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,
),
(
"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,
),
(
"month_ymd",
"2021-Feb-21",
Utc.ymd(2021, 2, 21).and_time(Utc::now().time()).unwrap(),
Trunc::Seconds,
),
(
"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,
),
];
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]
fn parse_with_preference_and_timezone_in_utc() {
let current_time = Utc::now().time();
let current_hour = current_time.hour();
let current_minute = current_time.minute();
let test_cases = vec![
(
"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,
),
(
"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_dmy_hms",
"19/03/2012 10:11:59",
Utc.ymd(2012, 3, 19).and_hms(10, 11, 59),
Trunc::None,
),
(
"slash_dmy",
"21/08/71",
Utc.ymd(1971, 8, 21).and_time(Utc::now().time()).unwrap(),
Trunc::Seconds,
),
(
"slash_dmy_hms",
"19/03/2012 10:11:59",
Utc.ymd(2012, 3, 19).and_hms(10, 11, 59),
Trunc::None,
),
(
"slash_dmy",
"31/3/2014",
Utc.ymd(2014, 3, 31).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_preference_and_timezone(input, true, &Utc).unwrap(),
want,
"parse_with_preference_and_timezone_in_utc/{}/{}",
test,
input
)
}
Trunc::Seconds => assert_eq!(
super::parse_with_preference_and_timezone(input, true, &Utc)
.unwrap()
.trunc_subsecs(0)
.with_hour(current_hour)
.unwrap()
.with_minute(current_minute)
.unwrap()
.with_second(0)
.unwrap(),
want.trunc_subsecs(0).with_second(0).unwrap(),
"parse_with_preference_and_timezone_in_utc/{}/{}",
test,
input
),
};
}
}
#[test]
fn parse_unambiguous_dmy() {
assert_eq!(
super::parse("31/3/22").unwrap().date(),
Utc.ymd(2022, 3, 31)
);
assert_eq!(
super::parse_with_preference("3/31/22", true)
.unwrap()
.date(),
Utc.ymd(2022, 3, 31)
);
assert_eq!(
super::parse_with_preference("31/07/2021", true)
.unwrap()
.date(),
Utc.ymd(2021, 7, 31)
);
}
}