reminder-bot/src/time_parser.rs

229 lines
6.7 KiB
Rust
Raw Normal View History

use std::time::{SystemTime, UNIX_EPOCH};
use std::fmt::{Display, Formatter, Result as FmtResult};
2020-09-01 14:34:50 +00:00
use crate::consts::{LOCAL_TIMEZONE, PYTHON_LOCATION};
use chrono::{DateTime, Datelike, Timelike, Utc};
use chrono_tz::Tz;
use std::convert::TryFrom;
use std::str::from_utf8;
use tokio::process::Command;
2020-09-01 14:34:50 +00:00
#[derive(Debug)]
pub enum InvalidTime {
ParseErrorDMY,
ParseErrorHMS,
ParseErrorDisplacement,
ParseErrorChrono,
}
impl Display for InvalidTime {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(f, "InvalidTime: {:?}", self)
}
}
impl std::error::Error for InvalidTime {}
enum ParseType {
Explicit,
Displacement,
}
2020-09-01 14:34:50 +00:00
pub struct TimeParser {
timezone: Tz,
inverted: bool,
time_string: String,
parse_type: ParseType,
}
impl TryFrom<&TimeParser> for i64 {
type Error = InvalidTime;
fn try_from(value: &TimeParser) -> Result<Self, Self::Error> {
value.timestamp()
}
}
impl TimeParser {
pub fn new(input: &str, timezone: Tz) -> Self {
let inverted = input.starts_with('-');
let parse_type = if input.contains('/') || input.contains(':') {
ParseType::Explicit
} else {
ParseType::Displacement
};
Self {
timezone,
inverted,
time_string: input.trim_start_matches('-').to_string(),
parse_type,
}
}
2020-09-01 14:34:50 +00:00
pub fn timestamp(&self) -> Result<i64, InvalidTime> {
match self.parse_type {
ParseType::Explicit => Ok(self.process_explicit()?),
ParseType::Displacement => {
let now = SystemTime::now();
let since_epoch = now
.duration_since(UNIX_EPOCH)
.expect("Time calculated as going backwards. Very bad");
2020-09-01 14:34:50 +00:00
Ok(since_epoch.as_secs() as i64 + self.process_displacement()?)
}
2020-09-01 14:34:50 +00:00
}
}
pub fn displacement(&self) -> Result<i64, InvalidTime> {
match self.parse_type {
ParseType::Explicit => {
let now = SystemTime::now();
let since_epoch = now
.duration_since(UNIX_EPOCH)
.expect("Time calculated as going backwards. Very bad");
Ok(self.process_explicit()? - since_epoch.as_secs() as i64)
}
2020-09-01 14:34:50 +00:00
ParseType::Displacement => Ok(self.process_displacement()?),
}
}
2020-09-01 14:34:50 +00:00
fn process_explicit(&self) -> Result<i64, InvalidTime> {
let mut time = Utc::now()
.with_timezone(&self.timezone)
.with_second(0)
.unwrap();
let mut segments = self.time_string.rsplit('-');
// this segment will always exist even if split fails
let hms = segments.next().unwrap();
let h_m_s = hms.split(':');
for (t, setter) in h_m_s.take(3).zip(&[
DateTime::with_hour,
DateTime::with_minute,
DateTime::with_second,
]) {
time = setter(&time, t.parse().map_err(|_| InvalidTime::ParseErrorHMS)?)
.map_or_else(|| Err(InvalidTime::ParseErrorHMS), |inner| Ok(inner))?;
}
2020-09-01 14:34:50 +00:00
if let Some(dmy) = segments.next() {
let mut d_m_y = dmy.split('/');
let day = d_m_y.next();
let month = d_m_y.next();
let year = d_m_y.next();
for (t, setter) in [day, month]
.iter()
.zip(&[DateTime::with_day, DateTime::with_month])
{
if let Some(t) = t {
time = setter(&time, t.parse().map_err(|_| InvalidTime::ParseErrorDMY)?)
.map_or_else(|| Err(InvalidTime::ParseErrorDMY), |inner| Ok(inner))?;
}
2020-09-01 14:34:50 +00:00
}
if let Some(year) = year {
if year.len() == 4 {
time = time
.with_year(year.parse().map_err(|_| InvalidTime::ParseErrorDMY)?)
.map_or_else(|| Err(InvalidTime::ParseErrorDMY), |inner| Ok(inner))?;
} else if year.len() == 2 {
time = time
.with_year(
format!("20{}", year)
.parse()
.map_err(|_| InvalidTime::ParseErrorDMY)?,
)
.map_or_else(|| Err(InvalidTime::ParseErrorDMY), |inner| Ok(inner))?;
} else {
Err(InvalidTime::ParseErrorDMY)?;
}
}
}
2020-09-01 14:34:50 +00:00
Ok(time.timestamp() as i64)
}
2020-09-01 14:34:50 +00:00
fn process_displacement(&self) -> Result<i64, InvalidTime> {
let mut current_buffer = "0".to_string();
2020-09-01 14:34:50 +00:00
let mut seconds = 0 as i64;
let mut minutes = 0 as i64;
let mut hours = 0 as i64;
let mut days = 0 as i64;
for character in self.time_string.chars() {
match character {
's' => {
2020-09-01 14:34:50 +00:00
seconds = current_buffer.parse::<i64>().unwrap();
current_buffer = String::from("0");
}
'm' => {
2020-09-01 14:34:50 +00:00
minutes = current_buffer.parse::<i64>().unwrap();
current_buffer = String::from("0");
}
'h' => {
2020-09-01 14:34:50 +00:00
hours = current_buffer.parse::<i64>().unwrap();
current_buffer = String::from("0");
}
'd' => {
2020-09-01 14:34:50 +00:00
days = current_buffer.parse::<i64>().unwrap();
current_buffer = String::from("0");
}
c => {
if c.is_digit(10) {
2020-08-30 20:08:08 +00:00
current_buffer += &c.to_string();
} else {
return Err(InvalidTime::ParseErrorDisplacement);
}
}
}
}
let full = (seconds
+ (minutes * 60)
+ (hours * 3600)
+ (days * 86400)
+ current_buffer.parse::<i64>().unwrap())
* if self.inverted { -1 } else { 1 };
2020-09-01 14:34:50 +00:00
Ok(full)
}
}
pub(crate) async fn natural_parser(time: &str, timezone: &str) -> Option<i64> {
Command::new(&*PYTHON_LOCATION)
.arg("-c")
.arg(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/dp.py")))
.arg(time)
.arg(timezone)
.arg(&*LOCAL_TIMEZONE)
.output()
.await
.ok()
.map(|inner| {
if inner.status.success() {
Some(from_utf8(&*inner.stdout).unwrap().parse::<i64>().unwrap())
} else {
None
}
})
.flatten()
.map(|inner| if inner < 0 { None } else { Some(inner) })
.flatten()
}