introducing the timeparse module
This commit is contained in:
parent
9112e7f3cf
commit
3dfefedb75
|
@ -534,6 +534,7 @@ dependencies = [
|
|||
"dirs",
|
||||
"itertools",
|
||||
"pretty_assertions",
|
||||
"regex",
|
||||
"rusqlite",
|
||||
"serde",
|
||||
"serde_yaml",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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()?,
|
||||
|
|
|
@ -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()),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue