summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordfhoughton <dfhoughton@gmail.com>2019-02-02 14:28:22 -0500
committerdfhoughton <dfhoughton@gmail.com>2019-02-02 14:28:22 -0500
commita2a54246b1b1e992dddfb28d3eae4554a457521d (patch)
tree8704bcd9d848cd3c7e12ae89489ee2fb1da98291
parent20aad02a8e255d18a8a5416212028b18708d6218 (diff)
downloadtwo-timer-a2a54246b1b1e992dddfb28d3eae4554a457521d.zip
added time around pattern
-rw-r--r--CHANGES.md3
-rw-r--r--README.md5
-rw-r--r--src/lib.rs147
-rw-r--r--tests/tests.rs55
4 files changed, 186 insertions, 24 deletions
diff --git a/CHANGES.md b/CHANGES.md
index 8e12b1b..53f10a0 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -13,4 +13,5 @@
## 1.0.4
* added `<year>` pattern
* added ordinals for days of the month
-* added kalends, nones, ides \ No newline at end of file
+* added kalends, nones, ides
+* added March 5th, the fifth, etc. \ No newline at end of file
diff --git a/README.md b/README.md
index a5b98aa..c90a1ef 100644
--- a/README.md
+++ b/README.md
@@ -24,6 +24,11 @@ Some expressions it can handle:
* next weekend
* 2000
* the nineteenth of March 1810
+* the 5th of November
* the ides of March
+* the first
+* two seconds before 12:00 PM
+* 1 week after May first
+* 15 minutes around 12:13:43 PM
The complete API is available at https://docs.rs/two_timer/0.1.0/two_timer/.
diff --git a/src/lib.rs b/src/lib.rs
index 980d03d..b5be34e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -212,7 +212,9 @@ lazy_static! {
named_period => <a_day> | <a_month>
modified_period -> <modifier> <modifiable_period>
modifiable_period => [["week", "month", "year", "pay period", "pp", "weekend"]] | <a_month> | <a_day>
- moment -> <at_time_on>? <some_day> <at_time>? | <specific_time> | <time>
+ moment -> <adjustment>? <point_in_time>
+ adjustment -> <amount> <direction> // two minutes before
+ point_in_time -> <at_time_on>? <some_day> <at_time>? | <specific_time> | <time>
specific_time => <first_time> | <last_time>
some_day => <specific_day> | <relative_day>
specific_day => <adverb> | <date_with_year>
@@ -235,6 +237,12 @@ lazy_static! {
a_date -> <day_prefix>? <n_day> <a_month> <year>
a_date -> <day_prefix>? ("the") <o_day> ("of") <a_month> <year>
+ amount -> <count> <unit>
+ count => r(r"[1-9][0-9]*") | <a_count>
+ a_count => [["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"]]
+ direction -> [["before", "after", "around"]]
+ unit => [["week", "day", "hour", "minute", "second"]] ("s")?
+
modifier => [["this", "last", "next"]]
adverb => [["now", "today", "tomorrow", "yesterday"]]
day_prefix => <a_day> (",")
@@ -530,7 +538,17 @@ pub fn parse(
let parse = parse.name("particular").unwrap();
let config = config.unwrap_or(Config::new());
if let Some(moment) = parse.name("one_time") {
- return handle_one_time(moment, &config);
+ return match handle_one_time(moment, &config) {
+ Err(e) => Err(e),
+ Ok((d1, d2, b)) => {
+ let (d3, d4) = adjust(d1, d2, moment);
+ if d1 == d3 {
+ Ok((d1, d2, b))
+ } else {
+ Ok((d3, d4, b))
+ }
+ }
+ };
}
if let Some(two_times) = parse.name("two_times") {
let first = &two_times.children().unwrap()[0];
@@ -539,41 +557,53 @@ pub fn parse(
if specific(first) {
if specific(last) {
return match specific_moment(first, &config) {
- Ok((d1, _)) => match specific_moment(last, &config) {
- Ok((d2, d3)) => {
- let d2 = pick_terminus(d2, d3, is_through);
- if d1 <= d2 {
- Ok((d1, d2, true))
- } else {
- Err(TimeError::Misordered(format!(
- "{} is after {}",
- first.as_str(),
- last.as_str()
- )))
+ Ok((d1, d2)) => {
+ let (d1, _) = adjust(d1, d2, first);
+ match specific_moment(last, &config) {
+ Ok((d2, d3)) => {
+ let (d2, d3) = adjust(d2, d3, last);
+ let d2 = pick_terminus(d2, d3, is_through);
+ if d1 <= d2 {
+ Ok((d1, d2, true))
+ } else {
+ Err(TimeError::Misordered(format!(
+ "{} is after {}",
+ first.as_str(),
+ last.as_str()
+ )))
+ }
}
+ Err(s) => Err(s),
}
- Err(s) => Err(s),
- },
+ }
Err(s) => Err(s),
};
} else {
return match specific_moment(first, &config) {
- Ok((d1, _)) => match relative_moment(last, &config, &d1, false) {
- Ok((d2, d3)) => {
- let d2 = pick_terminus(d2, d3, is_through);
- Ok((d1, d2, true))
+ Ok((d1, d2)) => {
+ let (d1, _) = adjust(d1, d2, first);
+ match relative_moment(last, &config, &d1, false) {
+ Ok((d2, d3)) => {
+ let (d2, d3) = adjust(d2, d3, last);
+ let d2 = pick_terminus(d2, d3, is_through);
+ Ok((d1, d2, true))
+ }
+ Err(s) => Err(s),
}
- Err(s) => Err(s),
- },
+ }
Err(s) => Err(s),
};
}
} else if specific(last) {
return match specific_moment(last, &config) {
Ok((d2, d3)) => {
+ let (d2, d3) = adjust(d2, d3, last);
let d2 = pick_terminus(d2, d3, is_through);
match relative_moment(first, &config, &d2, true) {
- Ok((d1, _)) => Ok((d1, d2, true)),
+ Ok((d1, d3)) => {
+ let (d1, _) = adjust(d1, d3, first);
+ Ok((d1, d2, true))
+ }
Err(s) => Err(s),
}
}
@@ -582,10 +612,12 @@ pub fn parse(
} else {
// the first moment is assumed to be before now
return match relative_moment(first, &config, &config.now, true) {
- Ok((d1, _)) => {
+ Ok((d1, d2)) => {
+ let (d1, _) = adjust(d1, d2, first);
// the second moment is necessarily after the first moment
match relative_moment(last, &config, &d1, false) {
Ok((d2, d3)) => {
+ let (d2, d3) = adjust(d2, d3, last);
let d2 = pick_terminus(d2, d3, is_through);
Ok((d1, d2, true))
}
@@ -1468,3 +1500,72 @@ fn weekday(s: &str) -> Weekday {
_ => unreachable!(),
}
}
+
+// adjust a period relative to another period -- e.g., "one week before June" or "five minutes around 12:00 PM"
+fn adjust(d1: NaiveDateTime, d2: NaiveDateTime, m: &Match) -> (NaiveDateTime, NaiveDateTime) {
+ if let Some(adjustment) = m.name("adjustment") {
+ let count = count(adjustment.name("count").unwrap()) as i64;
+ let unit = match adjustment
+ .name("unit")
+ .unwrap()
+ .as_str()
+ .chars()
+ .nth(0)
+ .unwrap()
+ {
+ 'w' | 'W' => Duration::weeks(count),
+ 'd' | 'D' => Duration::days(count),
+ 'h' | 'H' => Duration::hours(count),
+ 'm' | 'M' => Duration::minutes(count),
+ _ => Duration::seconds(count),
+ };
+ let direction = adjustment.name("direction").unwrap().as_str();
+ match direction.chars().nth(0).unwrap() {
+ 'b' | 'B' => {
+ let d = d1 - unit;
+ (d, d)
+ }
+ _ => match direction.chars().nth(1).unwrap() {
+ 'f' | 'F' => {
+ let d = d2 + unit;
+ (d, d)
+ }
+ _ => {
+ let d1 = d1 - Duration::milliseconds(unit.num_milliseconds() / 2);
+ let d2 = d1 + unit;
+ (d1, d2)
+ }
+ },
+ }
+ } else {
+ (d1, d2)
+ }
+}
+
+// for converting a few cardinal numbers and integer expressions
+fn count(m: &Match) -> u32 {
+ let s = m.as_str();
+ if m.has("a_count") {
+ // cardinal numbers
+ match s.chars().nth(0).expect("impossibly short") {
+ 'o' | 'O' => 1,
+ 't' | 'T' => match s.chars().nth(1).expect("impossibly short") {
+ 'w' | 'W' => 2,
+ 'h' | 'H' => 3,
+ _ => 10,
+ },
+ 'f' | 'F' => match s.chars().nth(1).expect("impossibly short") {
+ 'o' | 'O' => 4,
+ _ => 5,
+ },
+ 's' | 'S' => match s.chars().nth(1).expect("impossibly short") {
+ 'i' | 'I' => 6,
+ _ => 7,
+ },
+ 'e' | 'E' => 8,
+ _ => 9,
+ }
+ } else {
+ s.parse::<u32>().unwrap()
+ }
+}
diff --git a/tests/tests.rs b/tests/tests.rs
index 3db7520..a398fba 100644
--- a/tests/tests.rs
+++ b/tests/tests.rs
@@ -1222,3 +1222,58 @@ fn day_and_month() {
}
}
}
+
+#[test]
+fn one_week_before_may_6_1969() {
+ let d1 = NaiveDate::from_ymd(1969, 5, 6).and_hms(0, 0, 0) - Duration::days(7);
+ let patterns = ["one week before May 6, 1969", "1 week before May 6, 1969"];
+ for p in patterns.iter() {
+ match parse(p, None) {
+ Ok((start, end, _)) => {
+ assert_eq!(d1, start);
+ assert_eq!(d1, end);
+ }
+ Err(e) => {
+ println!("{:?}", e);
+ assert!(false, "didn't match");
+ }
+ }
+ }
+}
+
+#[test]
+fn one_week_after_may_6_1969() {
+ let d1 = NaiveDate::from_ymd(1969, 5, 7).and_hms(0, 0, 0) + Duration::days(7);
+ let patterns = ["one week after May 6, 1969", "1 week after May 6, 1969"];
+ for p in patterns.iter() {
+ match parse(p, None) {
+ Ok((start, end, _)) => {
+ assert_eq!(d1, start);
+ assert_eq!(d1, end);
+ }
+ Err(e) => {
+ println!("{:?}", e);
+ assert!(false, "didn't match");
+ }
+ }
+ }
+}
+
+#[test]
+fn one_week_around_may_6_1969() {
+ let d1 = NaiveDate::from_ymd(1969, 5, 6).and_hms(0, 0, 0) - Duration::milliseconds( 7 * 24 * 60 * 60 * 1000 / 2);
+ let d2 = d1 + Duration::days(7);
+ let patterns = ["one week around May 6, 1969", "1 week around May 6, 1969"];
+ for p in patterns.iter() {
+ match parse(p, None) {
+ Ok((start, end, _)) => {
+ assert_eq!(d1, start);
+ assert_eq!(d2, end);
+ }
+ Err(e) => {
+ println!("{:?}", e);
+ assert!(false, "didn't match");
+ }
+ }
+ }
+} \ No newline at end of file