use crate::errors::Error;
use chrono::NaiveDate;
#[inline]
pub fn parse(s: &str) -> Result<NaiveDate, Error> {
match s.get(..1) {
None => Err(Error::InvalidDate),
Some(c) => {
if c.as_bytes()[0].is_ascii_digit() {
parse_unknown_alpha(s)
} else {
parse_with_alpha(s)
}
}
}
}
pub(crate) fn parse_unknown_alpha(s: &str) -> Result<NaiveDate, Error> {
parse_naive_dates(s)
}
pub(crate) fn parse_with_alpha(s: &str) -> Result<NaiveDate, Error> {
parse_naive_dates_replace(s)
}
fn parse_naive_dates(s: &str) -> Result<NaiveDate, Error> {
const PARSE_FORMATS_DASHES: &[&str] = &["%Y-%m-%d", "%Y-%b-%d"];
const PARSE_FORMATS_SLASHES: &[&str] = &["%Y/%m/%d", "%m/%d/%y", "%m/%d/%Y"];
const PARSE_FORMATS_DOT: &[&str] = &["%Y.%m.%d", "%m.%d.%y", "%m.%d.%Y"];
const PARSE_FORMATS_SPACE: &[&str] = &["%d %B %y", "%d %B %Y"];
const PARSE_FORMATS_REMAINING: &[&str] = &["%Y年%m月%d日"];
for c in s.chars() {
if !c.is_alphanumeric() {
return match c {
'-' => PARSE_FORMATS_DASHES,
'/' => PARSE_FORMATS_SLASHES,
'.' => PARSE_FORMATS_DOT,
' ' => PARSE_FORMATS_SPACE,
_ => break,
}
.iter()
.map(|fmt| NaiveDate::parse_from_str(s, fmt))
.find_map(Result::ok)
.map_or_else(|| Err(Error::InvalidDate), Ok);
}
}
PARSE_FORMATS_REMAINING
.iter()
.map(|fmt| NaiveDate::parse_from_str(s, fmt))
.find_map(Result::ok)
.map_or_else(|| Err(Error::InvalidDate), Ok)
}
fn parse_naive_dates_replace(s: &str) -> Result<NaiveDate, Error> {
const PARSE_FORMATS: &[&str] = &[
"%B %d %y",
"%B %d %Y",
"%A %B %eth %Y",
"%A %B %est %Y",
"%A %B %end %Y",
"%A %B %erd %Y",
];
let s = s.replace([',', '.'], "");
PARSE_FORMATS
.iter()
.map(|fmt| NaiveDate::parse_from_str(&s, fmt))
.find_map(Result::ok)
.map_or_else(|| Err(Error::InvalidDate), Ok)
}
#[cfg(test)]
#[allow(clippy::unreadable_literal)]
mod tests {
use super::*;
use chrono::NaiveTime;
#[test]
fn dates() -> Result<(), Box<dyn std::error::Error>> {
for (input, expected) in &[
("2021-02-21", 1613865600000000000),
("2021-Feb-21", 1613865600000000000),
("May 25, 2021", 1621900800000000000),
("oct 7, 1970", 24105600000000000),
("oct 7, 70", 24105600000000000),
("oct. 7, 1970", 24105600000000000),
("oct. 7, 70", 24105600000000000),
("October 7, 1970", 24105600000000000),
("7 oct 70", 24105600000000000),
("7 oct 1970", 24105600000000000),
("03 February 2013", 1359849600000000000),
("1 July 2013", 1372636800000000000),
("3/31/2014", 1396224000000000000),
("03/31/2014", 1396224000000000000),
("08/21/71", 51580800000000000),
("8/1/71", 49852800000000000),
("2014/3/31", 1396224000000000000),
("2014/03/31", 1396224000000000000),
("3.31.2014", 1396224000000000000),
("03.31.2014", 1396224000000000000),
("08.21.71", 51580800000000000),
("2014.03.30", 1396137600000000000),
("2014年04月08日", 1396915200000000000),
] {
assert_eq!(
*expected,
parse(input)?
.and_time(NaiveTime::from_num_seconds_from_midnight_opt(0, 0).unwrap())
.and_utc()
.timestamp_nanos_opt()
.unwrap()
);
}
Ok(())
}
}