diff options
-rw-r--r-- | CHANGES.md | 4 | ||||
-rw-r--r-- | Cargo.lock | 2 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | src/lib.rs | 227 | ||||
-rw-r--r-- | tests/tests.rs | 76 |
6 files changed, 301 insertions, 12 deletions
@@ -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 @@ -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)", @@ -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" @@ -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/. @@ -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"); + } + } + } + } +} |