use super::prelude::*;
use chrono::{Datelike, Local, TimeZone, Timelike, Utc};
use regex::Regex;
#[derive(PartialEq, Debug)]
pub enum PartialDateTime {
None,
HM { hour: u32, minute: u32 },
MD { month: u32, day: u32 },
YMDHM {
year: i32,
month: u32,
day: u32,
hour: u32,
minute: u32,
},
MDHM {
month: u32,
day: u32,
hour: u32,
minute: u32,
},
YMD { year: i32, month: u32, day: u32 },
}
impl PartialDateTime {
pub fn parse(dt: Option<String>) -> Result<Self, Error> {
if let Some(dt) = dt {
match Self::parse_date_time(dt.clone()) {
PartialDateTime::None => Err(Error::PartialDateTimeFormat(dt)),
pdt => Ok(pdt),
}
} else {
Ok(PartialDateTime::None)
}
}
pub fn parse_opt(dt: Option<String>) -> Self {
if let Some(dt) = dt {
Self::parse_date_time(dt)
} else {
Self::None
}
}
pub fn or(self, other: Self) -> Self {
match self {
Self::None => other,
_ => self,
}
}
fn merge(left: Self, right: Self) -> Self {
if let Self::HM { hour, minute } = left {
if let Self::YMD { year, month, day } = right {
return Self::YMDHM {
year,
month,
day,
hour,
minute,
};
} else if let Self::MD { month, day } = right {
return Self::MDHM {
month,
day,
hour,
minute,
};
}
} else if let Self::HM { hour, minute } = right {
if let Self::YMD { year, month, day } = left {
return Self::YMDHM {
year,
month,
day,
hour,
minute,
};
} else if let Self::MD { month, day } = left {
return Self::MDHM {
month,
day,
hour,
minute,
};
}
}
Self::None
}
fn parse_date_time(dt: String) -> Self {
let dt: Vec<&str> = dt.split(',').collect();
match dt.len() {
1 => Self::parse_dmy(dt[0]).or(Self::parse_mdy(dt[0]).or(Self::parse_ymd(dt[0])
.or(Self::parse_dm(dt[0]).or(Self::parse_md(dt[0]).or(Self::parse_hm(dt[0])))))),
2 => {
Self::merge(
Self::parse_dmy(dt[0])
.or(Self::parse_mdy(dt[0]).or(Self::parse_ymd(dt[0])
.or(Self::parse_dm(dt[0]).or(Self::parse_md(dt[0]))))),
Self::parse_hm(dt[1]),
)
.or(Self::merge(
Self::parse_dmy(dt[1])
.or(Self::parse_mdy(dt[1]).or(Self::parse_ymd(dt[1])
.or(Self::parse_dm(dt[1]).or(Self::parse_md(dt[1]))))),
Self::parse_hm(dt[0]),
))
}
_ => PartialDateTime::None,
}
}
fn parse_hm(dt: &str) -> Self {
let re = Regex::new(r"^(\d{1,2}):(\d{1,2})$").unwrap();
if let Some(cap) = re.captures_iter(dt).next() {
return Self::HM {
hour: cap[1].parse::<u32>().unwrap(),
minute: cap[2].parse::<u32>().unwrap(),
};
}
Self::None
}
fn parse_dm(dt: &str) -> Self {
let re = Regex::new(r"^(\d{1,2})\.(\d{1,2})\.$").unwrap();
if let Some(cap) = re.captures_iter(dt).next() {
return Self::MD {
month: cap[2].parse::<u32>().unwrap(),
day: cap[1].parse::<u32>().unwrap(),
};
}
Self::None
}
fn parse_md(dt: &str) -> Self {
let re = Regex::new(r"^(\d{1,2})/(\d{1,2})$").unwrap();
if let Some(cap) = re.captures_iter(dt).next() {
return Self::MD {
month: cap[1].parse::<u32>().unwrap(),
day: cap[2].parse::<u32>().unwrap(),
};
}
Self::None
}
fn parse_dmy(dt: &str) -> Self {
let re = Regex::new(r"^(\d{1,2})\.(\d{1,2})\.(\d{4})$").unwrap();
if let Some(cap) = re.captures_iter(dt).next() {
return Self::YMD {
year: cap[3].parse::<i32>().unwrap(),
month: cap[2].parse::<u32>().unwrap(),
day: cap[1].parse::<u32>().unwrap(),
};
}
Self::None
}
fn parse_ymd(dt: &str) -> Self {
let re = Regex::new(r"^(\d{4})-(\d{1,2})-(\d{1,2})$").unwrap();
if let Some(cap) = re.captures_iter(dt).next() {
return Self::YMD {
year: cap[1].parse::<i32>().unwrap(),
month: cap[2].parse::<u32>().unwrap(),
day: cap[3].parse::<u32>().unwrap(),
};
}
Self::None
}
fn parse_mdy(dt: &str) -> Self {
let re = Regex::new(r"^(\d{1,2})/(\d{1,2})/(\d{4})$").unwrap();
if let Some(cap) = re.captures_iter(dt).next() {
return Self::YMD {
year: cap[3].parse::<i32>().unwrap(),
month: cap[1].parse::<u32>().unwrap(),
day: cap[2].parse::<u32>().unwrap(),
};
}
Self::None
}
pub fn into(self, base: DateTime) -> DateTime {
let base: chrono::DateTime<Local> = base.into();
chrono::DateTime::with_timezone(
&match self {
Self::HM { hour, minute } => Local
.with_ymd_and_hms(base.year(), base.month(), base.day(), hour, minute, 0)
.unwrap(),
Self::YMDHM {
year,
month,
day,
hour,
minute,
} => Local
.with_ymd_and_hms(year, month, day, hour, minute, 0)
.unwrap(),
Self::MDHM {
month,
day,
hour,
minute,
} => Local
.with_ymd_and_hms(base.year(), month, day, hour, minute, 0)
.unwrap(),
Self::YMD { year, month, day } => {
Local.with_ymd_and_hms(year, month, day, 0, 0, 0).unwrap()
}
Self::MD { month, day } => Local
.with_ymd_and_hms(base.year(), month, day, 0, 0, 0)
.unwrap(),
Self::None => Local
.with_ymd_and_hms(
base.year(),
base.month(),
base.day(),
base.hour(),
base.minute(),
0,
)
.unwrap(),
},
&Utc,
)
.into()
}
}
#[test]
fn test_parse_date_time() {
PartialDateTime::parse_date_time("1.1.,12:00".into());
}
#[test]
fn test_parse_hm() {
assert_eq!(
PartialDateTime::parse_hm("01:00"),
PartialDateTime::HM { hour: 1, minute: 0 }
);
assert_eq!(
PartialDateTime::parse_hm("1:0"),
PartialDateTime::HM { hour: 1, minute: 0 }
);
assert_ne!(
PartialDateTime::parse_hm("1.0"),
PartialDateTime::HM { hour: 1, minute: 0 }
);
}
#[test]
fn test_parse_ymd() {
assert_eq!(
PartialDateTime::parse_ymd("2023-02-01"),
PartialDateTime::YMD {
year: 2023,
month: 2,
day: 1
}
);
assert_eq!(
PartialDateTime::parse_ymd("2023-2-1"),
PartialDateTime::YMD {
year: 2023,
month: 2,
day: 1
}
);
assert_ne!(
PartialDateTime::parse_ymd("2023.2.1"),
PartialDateTime::YMD {
year: 2023,
month: 2,
day: 1
}
);
}
#[test]
fn test_parse_mdy() {
assert_eq!(
PartialDateTime::parse_mdy("02/01/2023"),
PartialDateTime::YMD {
year: 2023,
month: 2,
day: 1
}
);
assert_eq!(
PartialDateTime::parse_mdy("2/1/2023"),
PartialDateTime::YMD {
year: 2023,
month: 2,
day: 1
}
);
assert_ne!(
PartialDateTime::parse_mdy("2.1.2023"),
PartialDateTime::YMD {
year: 2023,
month: 2,
day: 1
}
);
}
#[test]
fn test_parse_dmy() {
assert_eq!(
PartialDateTime::parse_dmy("01.02.2023"),
PartialDateTime::YMD {
year: 2023,
month: 2,
day: 1
}
);
assert_eq!(
PartialDateTime::parse_dmy("1.2.2023"),
PartialDateTime::YMD {
year: 2023,
month: 2,
day: 1
}
);
assert_ne!(
PartialDateTime::parse_dmy("1-2-2023"),
PartialDateTime::YMD {
year: 2023,
month: 2,
day: 1
}
);
}
#[test]
fn test_parse_dm() {
assert_eq!(
PartialDateTime::parse_dm("01.02."),
PartialDateTime::MD { month: 2, day: 1 }
);
assert_eq!(
PartialDateTime::parse_dm("1.2."),
PartialDateTime::MD { month: 2, day: 1 }
);
assert_ne!(
PartialDateTime::parse_dm("1.2"),
PartialDateTime::MD { month: 2, day: 1 }
);
assert_ne!(
PartialDateTime::parse_dm("1-2-"),
PartialDateTime::MD { month: 2, day: 1 }
);
}
#[test]
fn test_parse_md() {
assert_eq!(
PartialDateTime::parse_md("02/01"),
PartialDateTime::MD { month: 2, day: 1 }
);
assert_eq!(
PartialDateTime::parse_md("2/1"),
PartialDateTime::MD { month: 2, day: 1 }
);
assert_ne!(
PartialDateTime::parse_md("2-1"),
PartialDateTime::MD { month: 2, day: 1 }
);
}