From 70ed11d3367f50184b9064c818c6793b1a3c07b8 Mon Sep 17 00:00:00 2001 From: dfhoughton Date: Sun, 17 May 2020 12:34:59 -0400 Subject: added 'since' expressions and also 'the' as a synonym for 'this' --- CHANGES.md | 3 ++ Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 4 ++- src/lib.rs | 66 ++++++++++++++++++++++++++++++++--- tests/tests.rs | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 177 insertions(+), 8 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9a60268..af0e7c5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,8 @@ # Change Log +## 2.1.0 +* added since expressions: "since yesterday", "since the beginning of the month", "since the end of last year", "after midnight", ... +* added "the" as a synonym of "this" as a period modifier: "the beginning of the month" = "the beginning of this month" ## 2.0.0 *2020-3-7* * fixing specific time to specific time pattern: "noon yesterday through midnight today" * allow parsing of hours with leading 0; e.g., "08:57:29" diff --git a/Cargo.lock b/Cargo.lock index f304ab2..faf1b97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -171,7 +171,7 @@ dependencies = [ [[package]] name = "two_timer" -version = "2.0.0" +version = "2.1.0" dependencies = [ "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index e167a3f..f9aa416 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "two_timer" -version = "2.0.0" +version = "2.1.0" authors = ["dfhoughton "] description="parser for English time expressions" homepage="https://github.com/dfhoughton/two-timer" diff --git a/README.md b/README.md index bc93695..3854717 100644 --- a/README.md +++ b/README.md @@ -38,5 +38,7 @@ Some expressions it can handle: * ten seconds from now * 5 minutes before and after midnight * 1969-05-06 12:03:05 +* since the start of the year +* since yesterday -The complete API is available at https://docs.rs/two_timer/0.1.0/two_timer/. +The complete API is available at https://docs.rs/two_timer/. diff --git a/src/lib.rs b/src/lib.rs index e85c886..fd88824 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -102,7 +102,7 @@ are 2. a relative expression will be interpreted as appropriate given its order -- the second expression describes a time after the first 3. if neither expression is fully-specified, the first will be interpreted relative to "now" and the -second relative ot the first +second relative to the first The rules of interpretation for relative time expressions in ranges will likely be refined further in the future. @@ -248,7 +248,13 @@ lazy_static! { one_time => - two_times -> ("from")? + two_times -> ("from")? | + + since_time -> ? + + clusivity -> ("the") ("of") + + terminus => | to => | @@ -340,17 +346,20 @@ lazy_static! { adverb => [["now", "today", "tomorrow", "yesterday"]] am_pm => (?-ib) [["am", "AM", "pm", "PM", "a.m.", "A.M.", "p.m.", "P.M."]] bce => (?-ib) [["bce", "b.c.e.", "bc", "b.c.", "BCE", "B.C.E.", "BC", "B.C."]] + beginning => [["beginning", "start"]] ce => (?-ib) [["ce", "c.e.", "ad", "a.d.", "CE", "C.E.", "AD", "A.D."]] direction -> [["before", "after", "around", "before and after"]] displacement => [["week", "day", "hour", "minute", "second"]] ("s")? // not handling variable-width periods like months or years + end => ("end") from_now_or_ago => [["from now", "ago"]] h12 => (?-B) [(1..=12).into_iter().collect::>()] h24 => [(1..=24).into_iter().flat_map(|i| vec![format!("{}", i), format!("{:02}", i)]).collect::>()] minute => (?-B) [ (0..60).into_iter().map(|i| format!("{:02}", i)).collect::>() ] - modifier => [["this", "last", "next"]] + modifier => [["the", "this", "last", "next"]] named_time => [["noon", "midnight"]] n_year => r(r"\b(?:[1-9][0-9]{0,4}|0)\b") roman => [["nones", "ides", "kalends"]] + since => [["since", "after"]] unit => [["week", "day", "hour", "minute", "second"]] ("s")? universal => [["always", "ever", "all time", "forever", "from beginning to end", "from the beginning to the end"]] up_to => [["to", "until", "up to", "till"]] @@ -548,7 +557,7 @@ lazy_static! { // various phrases all meaning from the first measurable moment to the last adverb => [["now", "today", "yesterday"]] - modifier => [["this", "last"]] + modifier => [["the", "this", "last"]] a_day => [ "Sunday Monday Tuesday Wednesday Thursday Friday Saturday Tues Weds Thurs Tues. Weds. Thurs." @@ -647,6 +656,55 @@ pub fn parse( }; } if let Some(two_times) = parse.name("two_times") { + let mut inclusive = two_times.has("beginning"); + let exclusive = !inclusive && two_times.has("end"); // note this is *explicitly* exclusive + if !(inclusive || exclusive) && (two_times.has("time") || two_times.has("precise_time")) { + // treating "since noon" as including 12:00:00 and "since 2am" as including 14:00:00 + inclusive = true; + } + if let Some(previous_time) = two_times.name("since_time") { + if specific(previous_time) { + return match specific_moment(previous_time, &config) { + Ok((d1, d2)) => { + let t = if inclusive { d1 } else { d2 }; + // if *implicitly* exclusive and we find things misordered, we become inclusive + let t = if !(inclusive || exclusive) && t > config.now { + d1 + } else { + t + }; + if t > config.now { + Err(TimeError::Misordered(format!( + "the inferred times, {} and {}, are misordered", + t, config.now + ))) + } else { + Ok((t, config.now.clone(), false)) + } + } + Err(s) => Err(s), + }; + } + return match relative_moment(previous_time, &config, &config.now, true) { + Ok((d1, d2)) => { + let t = if inclusive { d1 } else { d2 }; + let t = if !(inclusive || exclusive) && t > config.now { + d1 + } else { + t + }; + if t > config.now { + Err(TimeError::Misordered(format!( + "the inferred times, {} and {}, are misordered", + t, config.now + ))) + } else { + Ok((t, config.now.clone(), false)) + } + } + Err(s) => Err(s), + }; + } let first = &two_times.children().unwrap()[0]; let last = &two_times.children().unwrap()[2]; let is_through = two_times.has("through"); diff --git a/tests/tests.rs b/tests/tests.rs index ae1126e..c2eb23a 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,6 +1,6 @@ #![feature(test)] extern crate two_timer; -use two_timer::{parsable, parse, Config}; +use two_timer::{parsable, parse, Config, TimeError}; extern crate chrono; use chrono::naive::NaiveDate; use chrono::{Duration, Local}; @@ -421,6 +421,16 @@ fn this_week() { assert_eq!(d2, end); } +#[test] +fn the_week() { + let now = NaiveDate::from_ymd(1969, 5, 6).and_hms(0, 0, 0); + let d1 = NaiveDate::from_ymd(1969, 5, 5).and_hms(0, 0, 0); + let d2 = NaiveDate::from_ymd(1969, 5, 12).and_hms(0, 0, 0); + let (start, end, _) = parse("the week", Some(Config::new().now(now))).unwrap(); + assert_eq!(d1, start); + assert_eq!(d2, end); +} + #[test] fn next_week() { let now = NaiveDate::from_ymd(1969, 5, 6).and_hms(0, 0, 0); @@ -1428,6 +1438,17 @@ fn noon() { assert!(false, "didn't match"); } } + let now = NaiveDate::from_ymd(1969, 5, 6).and_hms(0, 0, 0); + match parse("noon on May 6, 1969", Some(Config::new().now(now))) { + Ok((start, end, _)) => { + assert_eq!(d1, start, "'noon' is the same as 'noon today'"); + assert_eq!(d2, end); + } + Err(e) => { + println!("{:?}", e); + assert!(false, "didn't match"); + } + } } #[test] @@ -1596,3 +1617,88 @@ fn relative_time_regression() { parse("24", None).unwrap(); assert!(true, "'24' didn't cause a panic"); } + +#[test] +fn since_yesterday() { + let then = NaiveDate::from_ymd(1969, 5, 10).and_hms(0, 0, 0); + let now = then + Duration::hours(5); + match parse("since yesterday", Some(Config::new().now(now))) { + Ok((start, end, two_times)) => { + assert!(!two_times, "isn't a two-time expression"); + assert!(then == start); + assert!(now == end); + } + Err(e) => { + println!("{:?}", e); + assert!(false, "didn't match"); + } + } +} + +#[test] +fn since_noon() { + let then = NaiveDate::from_ymd(1969, 5, 10).and_hms(12, 0, 0); + let now = then + Duration::hours(5); + for expr in &[ + "since noon", + "since noon today", + "since 12", + "since 12am", + "since 12am today", + "since 12:00", + "since 12:00:00", + ] { + match parse(expr, Some(Config::new().now(now))) { + Ok((start, end, two_times)) => { + assert!(!two_times, "isn't a two-time expression"); + assert!(then == start); + assert!(now == end); + } + Err(e) => { + println!("{:?}", e); + assert!(false, "didn't match"); + } + } + } +} + +#[test] +fn since_may() { + let then = NaiveDate::from_ymd(1969, 5, 1).and_hms(0, 0, 0); + let now = then + Duration::hours(5); + for expr in &[ + "since may", + "since the start of may", + "since the beginning of may", + "after may", + "after the start of may", + "after the beginning of may", + ] { + match parse(expr, Some(Config::new().now(now))) { + Ok((start, end, two_times)) => { + assert!(!two_times, "isn't a two-time expression"); + assert!(then == start); + assert!(now == end); + } + Err(e) => { + println!("{:?}", e); + assert!(false, "didn't match"); + } + } + } +} + +#[test] +fn since_the_end_of_may_misordered() { + let then = NaiveDate::from_ymd(1969, 5, 1).and_hms(0, 0, 0); + let now = then + Duration::hours(5); + for expr in &["since the end of may", "after the end of may"] { + match parse(expr, Some(Config::new().now(now))) { + Ok((..)) => assert!(false, "this should not succeed"), + Err(e) => match e { + TimeError::Misordered(_) => assert!(true, "correct error"), + _ => assert!(false, format!("unexpected error: {:?}", e)), + }, + } + } +} -- cgit v1.2.3