summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.md3
-rw-r--r--Cargo.lock2
-rw-r--r--Cargo.toml2
-rw-r--r--README.md4
-rw-r--r--src/lib.rs66
-rw-r--r--tests/tests.rs108
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 <dfhoughton@gmail.com>"]
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 => <moment_or_period>
- two_times -> ("from")? <moment_or_period> <to> <moment_or_period>
+ two_times -> ("from")? <moment_or_period> <to> <moment_or_period> | <since_time>
+
+ since_time -> <since> <clusivity>? <moment_or_period>
+
+ clusivity -> ("the") <terminus> ("of")
+
+ terminus => <beginning> | <end>
to => <up_to> | <through>
@@ -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::<Vec<_>>()]
h24 => [(1..=24).into_iter().flat_map(|i| vec![format!("{}", i), format!("{:02}", i)]).collect::<Vec<_>>()]
minute => (?-B) [ (0..60).into_iter().map(|i| format!("{:02}", i)).collect::<Vec<_>>() ]
- 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};
@@ -422,6 +422,16 @@ fn this_week() {
}
#[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);
let d1 = NaiveDate::from_ymd(1969, 5, 12).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)),
+ },
+ }
+ }
+}