summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordfhoughton <dfhoughton@gmail.com>2019-01-27 15:49:30 -0500
committerdfhoughton <dfhoughton@gmail.com>2019-01-27 15:49:30 -0500
commit20aad02a8e255d18a8a5416212028b18708d6218 (patch)
treea1641566c4f8b05fa7ea18855143181ab9e397bf
parent0b37f5a7381430d74569e4fcb76a50ec250d7e0e (diff)
downloadtwo-timer-20aad02a8e255d18a8a5416212028b18708d6218.zip
added the month-day pattern
-rw-r--r--README.md1
-rw-r--r--src/lib.rs142
-rw-r--r--tests/tests.rs25
3 files changed, 143 insertions, 25 deletions
diff --git a/README.md b/README.md
index 1d66278..a5b98aa 100644
--- a/README.md
+++ b/README.md
@@ -24,5 +24,6 @@ Some expressions it can handle:
* next weekend
* 2000
* the nineteenth of March 1810
+* the ides of March
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 7e380af..980d03d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -191,30 +191,39 @@ lazy_static! {
pub static ref GRAMMAR: Grammar = grammar!{
(?ibBw)
- TOP -> r(r"\A") <something> r(r"\z")
+ TOP -> r(r"\A") <time_expression> r(r"\z")
- something => <universal> | <particular>
+ time_expression => <universal> | <particular>
+ // various phrases all meaning from the first measurable moment to the last
universal => [["always", "ever", "all time", "forever", "from beginning to end", "from the beginning to the end"]]
+ // anything other than universal
particular => <one_time> | <two_times>
one_time => <moment_or_period>
two_times -> ("from")? <moment_or_period> <to> <moment_or_period>
+
to => <up_to> | <through>
up_to => [["to", "until", "up to", "till"]]
through => [["up through", "through", "thru"]] | r("-+")
+
moment_or_period => <moment> | <period>
period => <named_period> | <specific_period>
specific_period => <modified_period> | <month_and_year> | <year>
month_and_year -> <a_month> <year>
named_period => <a_day> | <a_month>
modified_period -> <modifier> <modifiable_period>
- modifier => [["this", "last", "next"]]
modifiable_period => [["week", "month", "year", "pay period", "pp", "weekend"]] | <a_month> | <a_day>
moment -> <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>
- relative_day => <a_day>
- adverb => [["now", "today", "tomorrow", "yesterday"]]
+ relative_day => <a_day> | <a_day_in_month>
+
+ a_day_in_month => <ordinal_day> | <day_and_month>
+ ordinal_day -> ("the") <o_day> // the first
+ day_and_month -> <n_month> r("[./-]") <n_day> // 5-6
+ day_and_month -> <a_month> <o_n_day> // June 5, June 5th, June fifth
+ day_and_month -> ("the") <o_day> ("of") <a_month> // the 5th of June, the fifth of June
+
date_with_year => <n_date> | <a_date>
n_date -> <year> r("[./-]") <n_month> r("[./-]") <n_day>
@@ -226,6 +235,8 @@ lazy_static! {
a_date -> <day_prefix>? <n_day> <a_month> <year>
a_date -> <day_prefix>? ("the") <o_day> ("of") <a_month> <year>
+ modifier => [["this", "last", "next"]]
+ adverb => [["now", "today", "tomorrow", "yesterday"]]
day_prefix => <a_day> (",")
o_n_day => <n_day> | <o_day>
at_time -> ("at") <time>
@@ -547,11 +558,13 @@ pub fn parse(
};
} else {
return match specific_moment(first, &config) {
- Ok((d1, _)) => {
- let (d2, d3) = relative_moment(last, &config, &d1, false);
- let d2 = pick_terminus(d2, d3, is_through);
- Ok((d1, d2, true))
- }
+ Ok((d1, _)) => match relative_moment(last, &config, &d1, false) {
+ Ok((d2, d3)) => {
+ let d2 = pick_terminus(d2, d3, is_through);
+ Ok((d1, d2, true))
+ }
+ Err(s) => Err(s),
+ },
Err(s) => Err(s),
};
}
@@ -559,18 +572,28 @@ pub fn parse(
return match specific_moment(last, &config) {
Ok((d2, d3)) => {
let d2 = pick_terminus(d2, d3, is_through);
- let (d1, _) = relative_moment(first, &config, &d2, true);
- Ok((d1, d2, true))
+ match relative_moment(first, &config, &d2, true) {
+ Ok((d1, _)) => Ok((d1, d2, true)),
+ Err(s) => Err(s),
+ }
}
Err(s) => Err(s),
};
} else {
// the first moment is assumed to be before now
- let (d1, _) = relative_moment(first, &config, &config.now, true);
- // the second moment is necessarily after the first momentß
- let (d2, d3) = relative_moment(last, &config, &d1, false);
- let d2 = pick_terminus(d2, d3, is_through);
- return Ok((d1, d2, true));
+ return match relative_moment(first, &config, &config.now, true) {
+ Ok((d1, _)) => {
+ // the second moment is necessarily after the first moment
+ match relative_moment(last, &config, &d1, false) {
+ Ok((d2, d3)) => {
+ let d2 = pick_terminus(d2, d3, is_through);
+ Ok((d1, d2, true))
+ }
+ Err(s) => Err(s),
+ }
+ }
+ Err(s) => Err(s),
+ };
}
}
unreachable!();
@@ -910,7 +933,7 @@ fn handle_one_time(
} else if let Some(moment) = moment.name("specific_time") {
handle_specific_time(moment, config)
} else {
- Ok(relative_moment(moment, config, &config.now, true))
+ relative_moment(moment, config, &config.now, true)
};
match r {
Ok((d1, d2)) => Ok((d1, d2, false)),
@@ -948,7 +971,16 @@ fn relative_moment(
config: &Config,
other_time: &NaiveDateTime,
before: bool,
-) -> (NaiveDateTime, NaiveDateTime) {
+) -> Result<(NaiveDateTime, NaiveDateTime), TimeError> {
+ if let Some(a_month_and_a_day) = m.name("a_day_in_month") {
+ return match month_and_a_day(a_month_and_a_day, config, other_time, before) {
+ Ok(d) => Ok(moment_and_time(
+ &config.now(d.and_hms(0, 0, 0)).period(Period::Day),
+ m.name("time"),
+ )),
+ Err(e) => Err(e),
+ };
+ }
if let Some(day) = m.name("a_day") {
let wd = weekday(day.as_str());
let mut delta =
@@ -960,10 +992,10 @@ fn relative_moment(
if !before {
d = d + Duration::days(7);
}
- return moment_and_time(
+ return Ok(moment_and_time(
&config.now(d.and_hms(0, 0, 0)).period(Period::Day),
m.name("time"),
- );
+ ));
}
if let Some(t) = m.name("time") {
let (hour, minute, second) = time(t);
@@ -986,7 +1018,7 @@ fn relative_moment(
} else if !before && t < *other_time {
t = t + Duration::days(1);
}
- return moment_to_period(t, &period, config);
+ return Ok(moment_to_period(t, &period, config));
}
if let Some(month) = m.name("a_month") {
let month = a_month(month);
@@ -1006,15 +1038,75 @@ fn relative_moment(
let d = NaiveDate::from_ymd(year, month, 1).and_hms(0, 0, 0);
let (d1, d2) = moment_to_period(d, &Period::Month, config);
if before && d1 >= *other_time {
- return moment_to_period(d1.with_year(d1.year() - 1).unwrap(), &Period::Month, config);
+ return Ok(moment_to_period(
+ d1.with_year(d1.year() - 1).unwrap(),
+ &Period::Month,
+ config,
+ ));
} else if !before && d2 <= *other_time {
- return moment_to_period(d1.with_year(d1.year() + 1).unwrap(), &Period::Month, config);
+ return Ok(moment_to_period(
+ d1.with_year(d1.year() + 1).unwrap(),
+ &Period::Month,
+ config,
+ ));
}
- return (d1, d2);
+ return Ok((d1, d2));
}
unreachable!()
}
+// for things like "the fifth", "March fifth", "5-6"
+fn month_and_a_day(
+ m: &Match,
+ config: &Config,
+ other_time: &NaiveDateTime,
+ before: bool,
+) -> Result<NaiveDate, TimeError> {
+ if m.has("ordinal_day") {
+ let month = other_time.month();
+ return match NaiveDate::from_ymd_opt(config.now.year(), month, o_day(m, month)) {
+ Some(d) => Ok(d),
+ None => Err(TimeError::ImpossibleDate(format!(
+ "there is no day {} in the year {}",
+ m.as_str(),
+ config.now.year()
+ ))),
+ };
+ }
+ let (month, day) = if let Some(month) = m.name("n_month") {
+ let month = n_month(month);
+ let day = m.name("n_day").unwrap();
+ (month, n_day(day))
+ } else {
+ let month = a_month(m);
+ let day = if let Some(day) = m.name("n_day") {
+ n_day(day)
+ } else {
+ o_day(m, month)
+ };
+ (month, day)
+ };
+ let year = if before {
+ config.now.year()
+ } else {
+ if month < other_time.month() {
+ other_time.year() + 1
+ } else {
+ other_time.year()
+ }
+ };
+ match NaiveDate::from_ymd_opt(year, month, day) {
+ Some(d) => Ok(d),
+ None => Err(TimeError::ImpossibleDate(format!(
+ "could not construct date from {} with year {}, month {}, and day {}",
+ m.as_str(),
+ year,
+ month,
+ day
+ ))),
+ }
+}
+
fn specific_moment(
m: &Match,
config: &Config,
diff --git a/tests/tests.rs b/tests/tests.rs
index 5f9494a..3db7520 100644
--- a/tests/tests.rs
+++ b/tests/tests.rs
@@ -5,6 +5,11 @@ extern crate chrono;
use chrono::naive::NaiveDate;
use chrono::{Duration, Local};
+// a debugging method to print out the parse tree
+fn show_me(p: &str) {
+ println!("{}", two_timer::MATCHER.parse(p).unwrap());
+}
+
#[test]
fn always() {
let alpha = chrono::naive::MIN_DATE.and_hms_milli(0, 0, 0, 0);
@@ -1197,3 +1202,23 @@ fn kalends_nones_ids() {
}
}
}
+
+#[test]
+fn day_and_month() {
+ let now = NaiveDate::from_ymd(1969, 5, 10).and_hms(0, 0, 0);
+ let d1 = NaiveDate::from_ymd(1969, 5, 15).and_hms(0, 0, 0);
+ let d2 = d1 + Duration::days(1);
+ let patterns = ["the ides of May", "5-15", "the fifteenth", "May fifteenth"];
+ for p in patterns.iter() {
+ match parse(p, Some(Config::new().now(now))) {
+ Ok((start, end, _)) => {
+ assert_eq!(d1, start);
+ assert_eq!(d2, end);
+ }
+ Err(e) => {
+ println!("{:?}", e);
+ assert!(false, "didn't match");
+ }
+ }
+ }
+}