diff options
-rw-r--r-- | CHANGES.md | 6 | ||||
-rw-r--r-- | Cargo.lock | 2 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | src/lib.rs | 61 | ||||
-rw-r--r-- | tests/tests.rs | 89 |
5 files changed, 99 insertions, 61 deletions
@@ -1,5 +1,11 @@ # Change Log +## 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" +* added "month the nth" pattern -- "July the 4th", "November the Fifth" +* ***IMPORTANT*** changing the nature of daytimes -- "3 PM", "13:14" -- so their period is always 1 second; this seems +more intuitively correct to me, but it changes the semantics sufficiently that I thought it necessary to bump the major version number ## 1.3.4 * fixed panic when parsing "24" ## 1.3.3 @@ -171,7 +171,7 @@ dependencies = [ [[package]] name = "two_timer" -version = "1.3.4" +version = "2.0.0" dependencies = [ "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1,6 +1,6 @@ [package] name = "two_timer" -version = "1.3.4" +version = "2.0.0" authors = ["dfhoughton <dfhoughton@gmail.com>"] description="parser for English time expressions" homepage="https://github.com/dfhoughton/two-timer" @@ -327,7 +327,7 @@ lazy_static! { o_day => <n_ordinal> | <a_ordinal> | <roman> 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 -> <a_month> ("the")? <o_n_day> // June 5, June 5th, June fifth, June the fifth day_and_month -> ("the") <o_day> ("of") <a_month> // the 5th of June, the fifth of June o_n_day => <n_day> | <o_day> @@ -345,7 +345,7 @@ lazy_static! { displacement => [["week", "day", "hour", "minute", "second"]] ("s")? // not handling variable-width periods like months or years from_now_or_ago => [["from now", "ago"]] h12 => (?-B) [(1..=12).into_iter().collect::<Vec<_>>()] - h24 => [(1..=24).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"]] named_time => [["noon", "midnight"]] @@ -825,9 +825,7 @@ impl TimeError { // for the end time, if the span is less than a day, use the first, otherwise use the second // e.g., Monday through Friday at 3 PM should end at 3 PM, but Monday through Friday should end at the end of Friday fn pick_terminus(d1: NaiveDateTime, d2: NaiveDateTime, through: bool) -> NaiveDateTime { - if d1.day() == d2.day() && d1.month() == d2.month() && d1.year() == d2.year() { - d1 - } else if through { + if through { d2 } else { d1 @@ -1188,22 +1186,15 @@ fn handle_specific_time( Err(s) => Err(s), Ok(d) => { let (hour, minute, second, _) = time(moment); - let period = if second.is_some() { - Period::Second - } else if minute.is_some() { - Period::Minute - } else { - Period::Hour - }; let m = d .and_hms(0, 0, 0) .with_hour(hour) .unwrap() - .with_minute(minute.unwrap_or(0)) + .with_minute(minute) .unwrap() - .with_second(second.unwrap_or(0)) + .with_second(second) .unwrap(); - Ok(moment_to_period(m, &period, config)) + Ok(moment_to_period(m, &Period::Second, config)) } }; } @@ -1237,25 +1228,18 @@ fn handle_one_time( fn moment_and_time(config: &Config, daytime: Option<&Match>) -> (NaiveDateTime, NaiveDateTime) { if let Some(daytime) = daytime { let (hour, minute, second, is_midnight) = time(daytime); - let period = if second.is_some() { - Period::Second - } else if minute.is_some() { - Period::Minute - } else { - Period::Hour - }; let mut m = config .now .with_hour(hour) .unwrap() - .with_minute(minute.unwrap_or(0)) + .with_minute(minute) .unwrap() - .with_second(second.unwrap_or(0)) + .with_second(second) .unwrap(); if is_midnight { m = m + Duration::days(1); // midnight is second 0 *of the next day* } - moment_to_period(m, &period, config) + moment_to_period(m, &Period::Second, config) } else { moment_to_period(config.now, &config.period, config) } @@ -1294,19 +1278,12 @@ fn relative_moment( } if let Some(t) = m.name("time") { let (hour, minute, second, is_midnight) = time(t); - let period = if second.is_some() { - Period::Second - } else if minute.is_some() { - Period::Minute - } else { - Period::Hour - }; let mut t = other_time .with_hour(hour) .unwrap() - .with_minute(minute.unwrap_or(0)) + .with_minute(minute) .unwrap() - .with_second(second.unwrap_or(0)) + .with_second(second) .unwrap(); if is_midnight { t = t + Duration::days(1); // midnight is second 0 *of the next day* @@ -1316,7 +1293,7 @@ fn relative_moment( } else if !before && t < *other_time { t = t + Duration::days(1); } - return Ok(moment_to_period(t, &period, config)); + return Ok(moment_to_period(t, &Period::Second, config)); } if let Some(month) = m.name("a_month") { let month = a_month(month); @@ -1427,7 +1404,7 @@ fn specific_moment( m: &Match, config: &Config, ) -> Result<(NaiveDateTime, NaiveDateTime), TimeError> { - if let Some(m) = m.name("specific_day") { + if m.has("specific_day") { return handle_specific_day(m, config); } if let Some(m) = m.name("specific_period") { @@ -1462,11 +1439,11 @@ fn a_month(m: &Match) -> u32 { // extract hour, minute, and second from time match // last parameter is basically whether the value returned is for "midnight", which requires special handling -fn time(m: &Match) -> (u32, Option<u32>, Option<u32>, bool) { +fn time(m: &Match) -> (u32, u32, u32, bool) { if let Some(m) = m.name("named_time") { return match m.as_str().chars().nth(0).unwrap() { - 'n' | 'N' => (12, None, None, false), - _ => (0, None, None, true), + 'n' | 'N' => (12, 0, 0, false), + _ => (0, 0, 0, true), }; } let hour = if let Some(hour_24) = m.name("hour_24") { @@ -1498,12 +1475,12 @@ fn time(m: &Match) -> (u32, Option<u32>, Option<u32>, bool) { let minute = s_to_n(minute.as_str()); if let Some(second) = m.name("second") { let second = s_to_n(second.as_str()); - (hour, Some(minute), Some(second), false) + (hour, minute, second, false) } else { - (hour, Some(minute), None, false) + (hour, minute, 0, false) } } else { - (hour, None, None, false) + (hour, 0, 0, false) } } diff --git a/tests/tests.rs b/tests/tests.rs index a814899..ae1126e 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -88,7 +88,7 @@ fn day_5_6_69_at_3_30_pm() { { let (start, end, _) = parse(phrase, None).unwrap(); assert_eq!(then, start); - assert_eq!(then + Duration::minutes(1), end); + assert_eq!(then + Duration::seconds(1), end); } } @@ -105,7 +105,7 @@ fn day_5_6_69_at_3_pm() { { let (start, end, _) = parse(phrase, None).unwrap(); assert_eq!(then, start); - assert_eq!(then + Duration::hours(1), end); + assert_eq!(then + Duration::seconds(1), end); } } @@ -167,7 +167,7 @@ fn at_3_pm() { for phrase in ["3 PM", "3 pm", "15"].iter() { let (start, end, _) = parse(phrase, Some(Config::new().now(now))).unwrap(); assert_eq!(then, start); - assert_eq!(then + Duration::hours(1), end); + assert_eq!(then + Duration::seconds(1), end); } } @@ -178,7 +178,7 @@ fn at_3_00_pm() { for phrase in ["3:00 PM", "3:00 pm", "15:00"].iter() { let (start, end, _) = parse(phrase, Some(Config::new().now(now))).unwrap(); assert_eq!(then, start); - assert_eq!(then + Duration::minutes(1), end); + assert_eq!(then + Duration::seconds(1), end); } } @@ -200,7 +200,7 @@ fn at_3_pm_yesterday() { for phrase in ["3 PM yesterday", "3 pm yesterday", "15 yesterday"].iter() { let (start, end, _) = parse(phrase, Some(Config::new().now(now))).unwrap(); assert_eq!(then, start); - assert_eq!(then + Duration::hours(1), end); + assert_eq!(then + Duration::seconds(1), end); } } @@ -776,7 +776,7 @@ fn friday_at_3_pm() { let then = NaiveDate::from_ymd(1969, 5, 2).and_hms(15, 0, 0); let (start, end, _) = parse("Friday at 3 pm", Some(Config::new().now(now))).unwrap(); assert_eq!(then, start); - assert_eq!(then + Duration::hours(1), end); + assert_eq!(then + Duration::seconds(1), end); } #[test] @@ -785,7 +785,7 @@ fn tuesday_at_3_pm() { let then = NaiveDate::from_ymd(1969, 4, 29).and_hms(15, 0, 0); let (start, end, _) = parse("Tuesday at 3 pm", Some(Config::new().now(now))).unwrap(); assert_eq!(then, start); - assert_eq!(then + Duration::hours(1), end); + assert_eq!(then + Duration::seconds(1), end); } #[test] @@ -794,7 +794,7 @@ fn monday_at_3_pm() { let then = NaiveDate::from_ymd(1969, 5, 5).and_hms(15, 0, 0); let (start, end, _) = parse("Monday at 3 pm", Some(Config::new().now(now))).unwrap(); assert_eq!(then, start); - assert_eq!(then + Duration::hours(1), end); + assert_eq!(then + Duration::seconds(1), end); } #[test] @@ -851,7 +851,7 @@ fn tuesday_through_friday() { fn tuesday_through_3_pm_on_friday() { let now = NaiveDate::from_ymd(1969, 5, 6).and_hms(0, 0, 0); let d1 = NaiveDate::from_ymd(1969, 4, 29).and_hms(0, 0, 0); - let d2 = NaiveDate::from_ymd(1969, 5, 2).and_hms(15, 0, 0); + let d2 = NaiveDate::from_ymd(1969, 5, 2).and_hms(15, 0, 1); let (start, end, _) = parse( "Tuesday through 3 PM on Friday", Some(Config::new().now(now)), @@ -872,6 +872,56 @@ fn this_year_through_today() { } #[test] +fn noon_yesterday_through_midnight_today() { + let now = NaiveDate::from_ymd(1969, 5, 6).and_hms(0, 0, 0); + let d1 = NaiveDate::from_ymd(1969, 5, 5).and_hms(12, 0, 0); + let d2 = NaiveDate::from_ymd(1969, 5, 7).and_hms(0, 0, 1); + let (start, end, _) = parse( + "noon yesterday through midnight today", + Some(Config::new().now(now)), + ) + .unwrap(); + assert_eq!(d1, start); + assert_eq!(d2, end); +} + +#[test] +fn very_specific_through_very_specific() { + let d1 = NaiveDate::from_ymd(2014, 10, 6).and_hms(8, 57, 29); + let d2 = NaiveDate::from_ymd(2020, 3, 6).and_hms(17, 28, 34); + let (start, end, _) = parse("2014-10-06 08:57:29 - 2020-03-06 17:28:33", None).unwrap(); + assert_eq!(d1, start); + assert_eq!(d2, end); +} + +#[test] +fn very_specific_up_to_very_specific() { + let d1 = NaiveDate::from_ymd(2014, 10, 6).and_hms(8, 57, 29); + let d2 = NaiveDate::from_ymd(2020, 3, 6).and_hms(17, 28, 33); + let (start, end, _) = parse("2014-10-06 08:57:29 up to 2020-03-06 17:28:33", None).unwrap(); + assert_eq!(d1, start); + assert_eq!(d2, end); +} + +#[test] +fn somewhat_specific_through_somewhat_specific() { + let d1 = NaiveDate::from_ymd(2014, 10, 6).and_hms(8, 57, 00); + let d2 = NaiveDate::from_ymd(2020, 3, 6).and_hms(17, 28, 01); + let (start, end, _) = parse("2014-10-06 08:57 - 2020-03-06 17:28", None).unwrap(); + assert_eq!(d1, start); + assert_eq!(d2, end); +} + +#[test] +fn somewhat_specific_up_to_somewhat_specific() { + let d1 = NaiveDate::from_ymd(2014, 10, 6).and_hms(8, 57, 00); + let d2 = NaiveDate::from_ymd(2020, 3, 6).and_hms(17, 28, 00); + let (start, end, _) = parse("2014-10-06 08:57 up to 2020-03-06 17:28", None).unwrap(); + assert_eq!(d1, start); + assert_eq!(d2, end); +} + +#[test] fn april_3_25_bc() { let d1 = NaiveDate::from_ymd(-24, 4, 3).and_hms(0, 0, 0); let d2 = d1 + Duration::days(1); @@ -1084,7 +1134,7 @@ fn next_weekend_on_saturday_when_sunday_starts_week() { #[test] fn regression_12pm() { let d1 = NaiveDate::from_ymd(2018, 5, 21).and_hms(0, 0, 0); - let d2 = NaiveDate::from_ymd(2018, 5, 21).and_hms(1, 0, 0); + let d2 = d1 + Duration::seconds(1); if let Ok((start, end, _)) = parse("12 pm on May 21, 2018", None) { assert_eq!(d1, start); assert_eq!(d2, end); @@ -1241,7 +1291,14 @@ 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"]; + let patterns = [ + "the ides of May", + "5-15", + "the fifteenth", + "May fifteenth", + "May the 15th", + "May the fifteenth", + ]; for p in patterns.iter() { match parse(p, Some(Config::new().now(now))) { Ok((start, end, _)) => { @@ -1360,7 +1417,7 @@ fn number_before_test() { #[test] fn noon() { let d1 = NaiveDate::from_ymd(1969, 5, 6).and_hms(12, 0, 0); - let d2 = d1 + Duration::hours(1); + let d2 = d1 + Duration::seconds(1); match parse("noon on May 6, 1969", None) { Ok((start, end, _)) => { assert_eq!(d1, start); @@ -1376,7 +1433,7 @@ fn noon() { #[test] fn midnight() { let d1 = NaiveDate::from_ymd(1969, 5, 7).and_hms(0, 0, 0); - let d2 = d1 + Duration::hours(1); + let d2 = d1 + Duration::seconds(1); match parse("midnight on May 6, 1969", None) { Ok((start, end, _)) => { assert_eq!(d1, start); @@ -1501,7 +1558,7 @@ fn specific_time() { #[test] fn no_space_before_pm() { let d1 = NaiveDate::from_ymd(1969, 5, 6).and_hms(13, 0, 0); - let d2 = d1 + Duration::hours(1); + let d2 = d1 + Duration::seconds(1); match parse("1969-05-06 at 1PM", None) { Ok((start, end, _)) => { assert_eq!(d1, start); @@ -1512,7 +1569,6 @@ fn no_space_before_pm() { assert!(false, "didn't match"); } } - let d2 = d1 + Duration::minutes(1); match parse("1969-05-06 at 1:00PM", None) { Ok((start, end, _)) => { assert_eq!(d1, start); @@ -1523,7 +1579,6 @@ fn no_space_before_pm() { assert!(false, "didn't match"); } } - let d2 = d1 + Duration::seconds(1); match parse("1969-05-06 at 1:00:00PM", None) { Ok((start, end, _)) => { assert_eq!(d1, start); @@ -1540,4 +1595,4 @@ fn no_space_before_pm() { fn relative_time_regression() { parse("24", None).unwrap(); assert!(true, "'24' didn't cause a panic"); -}
\ No newline at end of file +} |