introducing the timeparse module

This commit is contained in:
Abraham Toriz 2021-07-01 23:42:59 -05:00
parent 9112e7f3cf
commit 3dfefedb75
No known key found for this signature in database
GPG Key ID: D5B4A746DB5DD42A
7 changed files with 129 additions and 9 deletions

1
Cargo.lock generated
View File

@ -534,6 +534,7 @@ dependencies = [
"dirs",
"itertools",
"pretty_assertions",
"regex",
"rusqlite",
"serde",
"serde_yaml",

View File

@ -20,6 +20,7 @@ textwrap = "0.14"
terminal_size = "0.1"
ansi_term = "0.12"
csv = "1.1"
regex = "1.5"
[dev-dependencies]
pretty_assertions = "0.7.2"

View File

@ -12,15 +12,16 @@ use crate::database::{Database, DBVersion};
use crate::formatters::Formatter;
use crate::config::Config;
use crate::models::Entry;
use crate::timeparse::parse_time;
use super::Command;
fn local_to_utc(t: DateTime<Utc>) -> error::Result<DateTime<Utc>> {
let local_time = match Local.from_local_datetime(&t.naive_utc()) {
LocalResult::None => return Err(error::Error::NoneLocalTime(t.naive_utc())),
LocalResult::None => return Err(error::Error::NoneLocalTime(t.naive_utc().to_string())),
LocalResult::Single(t) => t,
LocalResult::Ambiguous(t1, t2) => return Err(error::Error::AmbiguousLocalTime {
orig: t.naive_utc(),
orig: t.naive_utc().to_string(),
t1, t2,
}),
};
@ -83,8 +84,8 @@ impl<'a> TryFrom<&'a ArgMatches<'a>> for Args {
fn try_from(matches: &'a ArgMatches) -> error::Result<Args> {
Ok(Args {
ids: matches.is_present("ids"),
start: matches.value_of("at").map(|s| s.parse()).transpose()?,
end: matches.value_of("at").map(|s| s.parse()).transpose()?,
start: matches.value_of("start").map(|s| parse_time(s)).transpose()?,
end: matches.value_of("end").map(|s| parse_time(s)).transpose()?,
format: matches.value_of("format").unwrap().parse()?,
grep: matches.value_of("grep").map(|s| s.into()),
sheet: matches.value_of("sheet").map(|s| s.parse()).transpose()?,

View File

@ -9,6 +9,7 @@ use crate::error;
use crate::editor;
use crate::commands::Command;
use crate::config::Config;
use crate::timeparse::parse_time;
pub struct Args {
at: Option<DateTime<Utc>>,
@ -20,7 +21,7 @@ impl<'a> TryFrom<&'a ArgMatches<'a>> for Args {
fn try_from(matches: &'a ArgMatches) -> Result<Self, Self::Error> {
Ok(Args {
at: matches.value_of("at").map(|s| s.parse()).transpose()?,
at: matches.value_of("at").map(|s| parse_time(s)).transpose()?,
note: matches.value_of("note").map(|s| s.into()),
})
}

View File

@ -27,8 +27,8 @@ pub enum Error {
#[error("Couldn't parse yaml file at: {path}\nwith error: {error}")]
YamlError{ path: PathBuf, error: serde_yaml::Error},
#[error("Could not parse datetime: {0}")]
DateTimeParseError(#[from] chrono::format::ParseError),
#[error("Could not understand '{0}' as a date format.")]
DateTimeParseError(String),
#[error("IOError: {0}")]
IOError(#[from] std::io::Error),
@ -40,11 +40,11 @@ pub enum Error {
CorruptedData(String),
#[error("Trying to parse {0} as a time in your timezone led to no results")]
NoneLocalTime(NaiveDateTime),
NoneLocalTime(String),
#[error("Trying to parse {orig} as a time in your timezone led to the ambiguous results {t1} and {t2}")]
AmbiguousLocalTime {
orig: NaiveDateTime,
orig: String,
t1: DateTime<Local>,
t2: DateTime<Local>,
}

View File

@ -5,6 +5,7 @@ pub mod editor;
pub mod formatters;
pub mod error;
pub mod models;
pub mod timeparse;
#[cfg(test)]
pub mod test_utils;

115
src/timeparse.rs Normal file
View File

@ -0,0 +1,115 @@
use chrono::{DateTime, Utc, Local, TimeZone, LocalResult};
use regex::Regex;
use crate::error::{Result, Error};
pub fn parse_time(input: &str) -> Result<DateTime<Utc>> {
// first try to parse as a full datetime with optional timezone
let re = Regex::new(r"(?xi)
(?P<year>\d{4}) # the year, mandatory
.
(?P<month>\d{2}) # the month, mandatory
.
(?P<day>\d{2}) # the day, mandatory
(. # a separator
(?P<hour>\d{2}) # the hour, optional
(. # a separator
(?P<minute>\d{2})? # the minute, optional
(. # a separator
(?P<second>\d{2}))?)?)? # the second, optional, implies minute
(?P<offset>(?P<utc>Z)|(?P<fixedoffset>(\+|-)\d{2}:\d{2}))? # the offset, optional
").unwrap();
if let Some(caps) = re.captures(input) {
if let Some(_) = caps.name("offset") {
// start with a specific offset or utc
if let Some(_) = caps.name("utc") {
// start with utc
unimplemented!()
} else {
// start with an offset
unimplemented!()
}
} else {
// start with a local time
let try_date = Local.ymd_opt(
(&caps["year"]).parse().unwrap(),
(&caps["month"]).parse().unwrap(),
(&caps["day"]).parse().unwrap(),
).and_hms_opt(
caps.name("hour").map(|m| m.as_str().parse().unwrap()).unwrap_or(0),
caps.name("minute").map(|m| m.as_str().parse().unwrap()).unwrap_or(0),
caps.name("second").map(|m| m.as_str().parse().unwrap()).unwrap_or(0),
);
return match try_date {
LocalResult::None => Err(Error::NoneLocalTime(input.into())),
LocalResult::Single(t) => Ok(Utc.from_utc_datetime(&t.naive_utc())),
LocalResult::Ambiguous(t1, t2) => Err(Error::AmbiguousLocalTime {
orig: input.into(),
t1, t2,
}),
};
}
}
Err(Error::DateTimeParseError(input.into()))
}
#[cfg(test)]
mod tests {
use chrono::TimeZone;
use super::*;
#[test]
fn parse_datetime_string() {
std::env::set_var("TZ", "America/Mexico_City");
assert_eq!(parse_time("2021-05-21 11:36").unwrap(), Utc.ymd(2021, 5, 21).and_hms(16, 36, 0));
assert_eq!(parse_time("2021-05-21 11:36:12").unwrap(), Utc.ymd(2021, 5, 21).and_hms(16, 36, 12));
assert_eq!(parse_time("2021-05-21T11:36").unwrap(), Utc.ymd(2021, 5, 21).and_hms(16, 36, 0));
assert_eq!(parse_time("2021-05-21T11:36:12").unwrap(), Utc.ymd(2021, 5, 21).and_hms(16, 36, 12));
}
#[test]
fn parse_date() {
assert_eq!(parse_time("2021-05-21").unwrap(), Utc.ymd(2021, 5, 21).and_hms(5, 0, 0));
}
#[test]
fn parse_hour() {
std::env::set_var("TZ", "America/Mexico_City");
let now = Utc::now();
assert_eq!(parse_time("11:36").unwrap(), now.date().and_hms(16, 36, 0));
assert_eq!(parse_time("11:36:35").unwrap(), now.date().and_hms(16, 36, 35));
}
#[test]
fn parse_with_specified_timezone() {
assert_eq!(parse_time("2021-05-21T11:36:12Z").unwrap(), Utc.ymd(2021, 5, 21).and_hms(11, 36, 12));
assert_eq!(parse_time("2021-05-21T11:36:12-3:00").unwrap(), Utc.ymd(2021, 5, 21).and_hms(14, 36, 12));
}
#[test]
fn parse_human_time() {
assert_eq!(parse_time("an hour ago").unwrap(), Utc.ymd(2021, 5, 21).and_hms(11, 36, 12));
assert_eq!(parse_time("two hours ago").unwrap(), Utc.ymd(2021, 5, 21).and_hms(11, 36, 12));
assert_eq!(parse_time("4 hours ago").unwrap(), Utc.ymd(2021, 5, 21).and_hms(11, 36, 12));
assert_eq!(parse_time("a minute ago").unwrap(), Utc.ymd(2021, 5, 21).and_hms(11, 36, 12));
assert_eq!(parse_time("two minutes ago").unwrap(), Utc.ymd(2021, 5, 21).and_hms(11, 36, 12));
assert_eq!(parse_time("4 minutes ago").unwrap(), Utc.ymd(2021, 5, 21).and_hms(11, 36, 12));
assert_eq!(parse_time("hace una hora").unwrap(), Utc.ymd(2021, 5, 21).and_hms(11, 36, 12));
assert_eq!(parse_time("hace dos horas").unwrap(), Utc.ymd(2021, 5, 21).and_hms(11, 36, 12));
assert_eq!(parse_time("hace cuatro horas").unwrap(), Utc.ymd(2021, 5, 21).and_hms(11, 36, 12));
assert_eq!(parse_time("hace una minuto").unwrap(), Utc.ymd(2021, 5, 21).and_hms(11, 36, 12));
assert_eq!(parse_time("hace dos minutos").unwrap(), Utc.ymd(2021, 5, 21).and_hms(11, 36, 12));
assert_eq!(parse_time("hace cuatro minutos").unwrap(), Utc.ymd(2021, 5, 21).and_hms(11, 36, 12));
}
}