summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.md4
-rw-r--r--Cargo.lock2
-rw-r--r--Cargo.toml2
-rw-r--r--README.md2
-rw-r--r--src/lib.rs227
-rw-r--r--tests/tests.rs76
6 files changed, 301 insertions, 12 deletions
diff --git a/CHANGES.md b/CHANGES.md
index 8247ba7..4e5ee4d 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -9,4 +9,6 @@
## 1.0.2
* added `msg` method to `TimeError`
## 1.0.3
-* fixed "12 pm" bug \ No newline at end of file
+* fixed "12 pm" bug
+## 1.0.4
+* added `<year>` pattern \ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index a54b044..a061ae6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -108,7 +108,7 @@ dependencies = [
[[package]]
name = "two_timer"
-version = "1.0.3"
+version = "1.0.4"
dependencies = [
"chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
diff --git a/Cargo.toml b/Cargo.toml
index e796b51..1a2661c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "two_timer"
-version = "1.0.3"
+version = "1.0.4"
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 b30a454..1d66278 100644
--- a/README.md
+++ b/README.md
@@ -22,5 +22,7 @@ Some expressions it can handle:
* 1960-05-06
* 5000BCE
* next weekend
+* 2000
+* the nineteenth of March 1810
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 c540152..7e380af 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -203,7 +203,7 @@ lazy_static! {
through => [["up through", "through", "thru"]] | r("-+")
moment_or_period => <moment> | <period>
period => <named_period> | <specific_period>
- specific_period => <modified_period> | <month_and_year>
+ 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>
@@ -216,6 +216,18 @@ lazy_static! {
relative_day => <a_day>
adverb => [["now", "today", "tomorrow", "yesterday"]]
date_with_year => <n_date> | <a_date>
+
+ n_date -> <year> r("[./-]") <n_month> r("[./-]") <n_day>
+ n_date -> <year> r("[./-]") <n_day> r("[./-]") <n_month>
+ n_date -> <n_month> r("[./-]") <n_day> r("[./-]") <year>
+ n_date -> <n_day> r("[./-]") <n_month> r("[./-]") <year>
+
+ a_date -> <day_prefix>? <a_month> <o_n_day> (",") <year>
+ a_date -> <day_prefix>? <n_day> <a_month> <year>
+ a_date -> <day_prefix>? ("the") <o_day> ("of") <a_month> <year>
+
+ day_prefix => <a_day> (",")
+ o_n_day => <n_day> | <o_day>
at_time -> ("at") <time>
at_time_on -> ("at")? <time> ("on")?
time -> <hour_12> <am_pm>? | <hour_24>
@@ -230,13 +242,6 @@ lazy_static! {
am_pm => (?-i) [["am", "AM", "pm", "PM", "a.m.", "A.M.", "p.m.", "P.M."]]
h12 => [(1..=12).into_iter().collect::<Vec<_>>()]
h24 => [(1..=24).into_iter().collect::<Vec<_>>()]
- n_date -> <year> r("[./-]") <n_month> r("[./-]") <n_day>
- n_date -> <year> r("[./-]") <n_day> r("[./-]") <n_month>
- n_date -> <n_month> r("[./-]") <n_day> r("[./-]") <year>
- n_date -> <n_day> r("[./-]") <n_month> r("[./-]") <year>
- a_date -> <a_month> <n_day> (",") <year>
- a_date -> <n_day> <a_month> <year>
- a_date -> <a_day> (",") <a_month> <n_day> (",") <year>
year => <short_year> | ("-")? <n_year>
year -> <suffix_year> <year_suffix>
short_year => [
@@ -314,6 +319,74 @@ lazy_static! {
"ever after",
"the last syllable of recorded time",
]]
+ o_day => <n_ordinal> | <a_ordinal> | <roman>
+ n_ordinal => [[
+ "1st",
+ "2nd",
+ "3rd",
+ "4th",
+ "5th",
+ "6th",
+ "7th",
+ "8th",
+ "9th",
+ "10th",
+ "11th",
+ "12th",
+ "13th",
+ "14th",
+ "15th",
+ "16th",
+ "17th",
+ "18th",
+ "19th",
+ "20th",
+ "21st",
+ "22nd",
+ "23rd",
+ "24th",
+ "25th",
+ "26th",
+ "27th",
+ "28th",
+ "29th",
+ "30th",
+ "31st",
+ ]]
+ a_ordinal => [[
+ "first",
+ "second",
+ "third",
+ "fourth",
+ "fifth",
+ "sixth",
+ "seventh",
+ "eighth",
+ "ninth",
+ "tenth",
+ "eleventh",
+ "twelfth",
+ "thirteenth",
+ "fourteenth",
+ "fifteenth",
+ "sixteenth",
+ "seventeenth",
+ "eighteenth",
+ "nineteenth",
+ "twentieth",
+ "twenty-first",
+ "twenty-second",
+ "twenty-third",
+ "twenty-fourth",
+ "twenty-fifth",
+ "twenty-sixth",
+ "twenty-seventh",
+ "twenty-eighth",
+ "twenty-ninth",
+ "thirtieth",
+ "thirty-first"
+ ]]
+ roman => [["nones", "ides", "kalends"]]
};
}
lazy_static! {
@@ -607,7 +680,11 @@ fn handle_specific_day(
if let Some(date) = date.name("a_date") {
let year = year(date, &now);
let month = a_month(date);
- let day = n_day(date);
+ let day = if date.has("n_day") {
+ n_day(date)
+ } else {
+ o_day(date, month)
+ };
let d_opt = NaiveDate::from_ymd_opt(year, month, day);
return match d_opt {
None => Err(TimeError::ImpossibleDate(format!(
@@ -757,6 +834,14 @@ fn handle_specific_period(
}
};
}
+ if let Some(moment) = moment.name("year") {
+ let year = year(moment, &config.now);
+ return Ok(moment_to_period(
+ NaiveDate::from_ymd(year, 1, 1).and_hms(0, 0, 0),
+ &Period::Year,
+ config,
+ ));
+ }
unreachable!()
}
@@ -1048,6 +1133,130 @@ fn n_day(m: &Match) -> u32 {
m.name("n_day").unwrap().as_str().parse::<u32>().unwrap()
}
+fn o_day(m: &Match, month: u32) -> u32 {
+ let m = m.name("o_day").unwrap();
+ let s = m.as_str();
+ if m.has("a_ordinal") {
+ ordinal(s)
+ } else if m.has("n_ordinal") {
+ s[0..s.len() - 2].parse::<u32>().unwrap()
+ } else {
+ // roman
+ match s.chars().nth(0).expect("empty string") {
+ 'n' | 'N' => {
+ // nones
+ match month {
+ 3 | 5 | 7 | 10 => 7, // March, May, July, October
+ _ => 5,
+ }
+ }
+ 'i' | 'I' => {
+ // ides
+ match month {
+ 3 | 5 | 7 | 10 => 15, // March, May, July, October
+ _ => 13,
+ }
+ }
+ _ => 1, // kalends
+ }
+ }
+}
+
+// converts the ordinals up to thirty-first
+fn ordinal(s: &str) -> u32 {
+ match s.chars().nth(0).expect("empty string") {
+ 'f' | 'F' => {
+ match s.chars().nth(1).expect("too short") {
+ 'i' | 'I' => {
+ match s.chars().nth(2).expect("too short") {
+ 'r' | 'R' => 1, // first
+ _ => {
+ if s.len() == 5 {
+ 5 // fifth
+ } else {
+ 15 // fifteenth
+ }
+ }
+ }
+ }
+ _ => {
+ if s.len() == 6 {
+ 4 // fourth
+ } else {
+ 14 // fourteenth
+ }
+ }
+ }
+ }
+ 's' | 'S' => {
+ match s.chars().nth(1).expect("too short") {
+ 'e' | 'E' => {
+ match s.len() {
+ 6 => 2, // second
+ 7 => 7, // seventh
+ _ => 17, // seventeenth
+ }
+ }
+ _ => {
+ if s.len() == 5 {
+ 6 // sixth
+ } else {
+ 16 // sixteenth
+ }
+ }
+ }
+ }
+ 't' | 'T' => {
+ match s.chars().nth(1).expect("too short") {
+ 'h' | 'H' => {
+ match s.chars().nth(4).expect("too short") {
+ 'd' | 'D' => 3, //third
+ _ => {
+ match s.chars().nth(5).expect("too short") {
+ 'e' | 'E' => 13, // thirteenth
+ 'i' | 'I' => 30, // thirtieth
+ _ => 31, // thirty-first
+ }
+ }
+ }
+ }
+ 'e' | 'E' => 10, // tenth
+ _ => {
+ match s.chars().nth(3).expect("too short") {
+ 'l' | 'L' => 12, // twelfth
+ _ => {
+ if s.len() == 9 {
+ 20 // twentiety
+ } else {
+ 20 + ordinal(&s[7..s.len()]) // twenty-first...
+ }
+ }
+ }
+ }
+ }
+ }
+ 'e' | 'E' => {
+ match s.chars().nth(1).expect("too short") {
+ 'i' | 'I' => {
+ if s.len() == 6 {
+ 8 // eight
+ } else {
+ 18 // eighteen
+ }
+ }
+ _ => 11, // eleventh
+ }
+ }
+ _ => {
+ if s.len() == 5 {
+ 9 // ninth
+ } else {
+ 19 // nineteenth
+ }
+ }
+ }
+}
+
/// expand a moment to the period containing it
fn moment_to_period(
now: NaiveDateTime,
diff --git a/tests/tests.rs b/tests/tests.rs
index 87d348b..bd39a70 100644
--- a/tests/tests.rs
+++ b/tests/tests.rs
@@ -1054,3 +1054,79 @@ fn regression_12pm() {
assert!(false);
}
}
+
+#[test]
+fn year_2000() {
+ let d1 = NaiveDate::from_ymd(2000, 1, 1).and_hms(0, 0, 0);
+ let d2 = NaiveDate::from_ymd(2001, 1, 1).and_hms(0, 0, 0);
+ if let Ok((start, end, _)) = parse("2000", None) {
+ assert_eq!(d1, start);
+ assert_eq!(d2, end);
+ } else {
+ assert!(false);
+ }
+}
+
+#[test]
+fn ordinals() {
+ let patterns = [
+ (1, "1st", "first", "Monday"),
+ (2, "2nd", "second", "Tuesday"),
+ (3, "3rd", "third", "Wednesday"),
+ (4, "4th", "fourth", "Thursday"),
+ (5, "5th", "fifth", "Friday"),
+ (6, "6th", "sixth", "Saturday"),
+ (7, "7th", "seventh", "Sunday"),
+ (8, "8th", "eighth", "Monday"),
+ (9, "9th", "ninth", "Tuesday"),
+ (10, "10th", "tenth", "Wednesday"),
+ (11, "11th", "eleventh", "Thursday"),
+ (12, "12th", "twelfth", "Friday"),
+ (13, "13th", "thirteenth", "Saturday"),
+ (14, "14th", "fourteenth", "Sunday"),
+ (15, "15th", "fifteenth", "Monday"),
+ (16, "16th", "sixteenth", "Tuesday"),
+ (17, "17th", "seventeenth", "Wednesday"),
+ (18, "18th", "eighteenth", "Thursday"),
+ (19, "19th", "nineteenth", "Friday"),
+ (20, "20th", "twentieth", "Saturday"),
+ (21, "21st", "twenty-first", "Sunday"),
+ (22, "22nd", "twenty-second", "Monday"),
+ (23, "23rd", "twenty-third", "Tuesday"),
+ (24, "24th", "twenty-fourth", "Wednesday"),
+ (25, "25th", "twenty-fifth", "Thursday"),
+ (26, "26th", "twenty-sixth", "Friday"),
+ (27, "27th", "twenty-seventh", "Saturday"),
+ (28, "28th", "twenty-eighth", "Sunday"),
+ (29, "29th", "twenty-ninth", "Monday"),
+ (30, "30th", "thirtieth", "Tuesday"),
+ (31, "31st", "thirty-first", "Wednesday"),
+ ];
+ let base_date = NaiveDate::from_ymd(2018, 1, 1).and_hms(0, 0, 0);
+ for (cardinal, abbv, ordinal, weekday) in patterns.iter() {
+ let d1 = base_date + Duration::days(*cardinal as i64 - 1);
+ let d2 = d1 + Duration::days(1);
+ let subpatterns = [
+ format!("January {}, 2018", abbv),
+ format!("{}, January {}, 2018", weekday, abbv),
+ format!("January {}, 2018", ordinal),
+ format!("{}, January {}, 2018", weekday, ordinal),
+ format!("the {} of January 2018", abbv),
+ format!("{}, the {} of January 2018", weekday, abbv),
+ format!("the {} of January 2018", ordinal),
+ format!("{}, the {} of January 2018", weekday, ordinal),
+ ];
+ for p in subpatterns.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");
+ }
+ }
+ }
+ }
+}