diff --git a/src/lib.rs b/src/lib.rs index 4d634f4..db1c8ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,7 +24,7 @@ use chrono::{ Timelike, }; -use parse_relative_time::parse_relative_time; +use parse_relative_time::parse_relative_time_at_date; use parse_timestamp::parse_timestamp; #[derive(Debug, PartialEq)] @@ -228,12 +228,8 @@ pub fn parse_datetime_at_date + Clone>( } // Parse relative time. - if let Ok(relative_time) = parse_relative_time(s.as_ref()) { - let current_time = DateTime::::from(date); - - if let Some(date_time) = current_time.checked_add_signed(relative_time) { - return Ok(date_time); - } + if let Ok(datetime) = parse_relative_time_at_date(date, s.as_ref()) { + return Ok(DateTime::::from(datetime)); } // parse time only dates diff --git a/src/parse_relative_time.rs b/src/parse_relative_time.rs index 7bc0840..afaaf34 100644 --- a/src/parse_relative_time.rs +++ b/src/parse_relative_time.rs @@ -1,16 +1,17 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. use crate::ParseDateTimeError; -use chrono::{Duration, Local, NaiveDate, Utc}; +use chrono::{DateTime, Days, Duration, Months, TimeZone}; use regex::Regex; -/// Parses a relative time string and returns a `Duration` representing the -/// relative time. -///Regex + +/// Parses a relative time string and adds the duration that it represents to the +/// given date. +/// /// # Arguments /// +/// * `date` - A `Date` instance representing the base date for the calculation /// * `s` - A string slice representing the relative time. /// -/// /// # Supported formats /// /// The function supports the following formats for relative time: @@ -38,28 +39,10 @@ use regex::Regex; /// This function will return `Err(ParseDateTimeError::InvalidInput)` if the input string /// cannot be parsed as a relative time. /// -/// ``` -pub fn parse_relative_time(s: &str) -> Result { - parse_relative_time_at_date(Utc::now().date_naive(), s) -} - -/// Parses a duration string and returns a `Duration` instance, with the duration -/// calculated from the specified date. -/// -/// # Arguments -/// -/// * `date` - A `Date` instance representing the base date for the calculation -/// * `s` - A string slice representing the relative time. -/// -/// # Errors -/// -/// This function will return `Err(ParseDateTimeError::InvalidInput)` if the input string -/// cannot be parsed as a relative time. -/// ``` -pub fn parse_relative_time_at_date( - date: NaiveDate, +pub fn parse_relative_time_at_date( + mut datetime: DateTime, s: &str, -) -> Result { +) -> Result, ParseDateTimeError> { let time_pattern: Regex = Regex::new( r"(?x) (?:(?P[-+]?\d*)\s*)? @@ -69,7 +52,6 @@ pub fn parse_relative_time_at_date( (\s*(?Pago)?)?", )?; - let mut total_duration = Duration::seconds(0); let mut is_ago = s.contains(" ago"); let mut captures_processed = 0; let mut total_length = 0; @@ -104,26 +86,28 @@ pub fn parse_relative_time_at_date( is_ago = true; } - let duration = match unit { - "years" | "year" => Duration::days(value * 365), - "months" | "month" => Duration::days(value * 30), - "fortnights" | "fortnight" => Duration::weeks(value * 2), - "weeks" | "week" => Duration::weeks(value), - "days" | "day" => Duration::days(value), - "hours" | "hour" | "h" => Duration::hours(value), - "minutes" | "minute" | "mins" | "min" | "m" => Duration::minutes(value), - "seconds" | "second" | "secs" | "sec" | "s" => Duration::seconds(value), - "yesterday" => Duration::days(-1), - "tomorrow" => Duration::days(1), - "now" | "today" => Duration::zero(), - _ => return Err(ParseDateTimeError::InvalidInput), + let new_datetime = match unit { + "years" | "year" => add_months(datetime, value * 12, is_ago), + "months" | "month" => add_months(datetime, value, is_ago), + "fortnights" | "fortnight" => add_days(datetime, value * 14, is_ago), + "weeks" | "week" => add_days(datetime, value * 7, is_ago), + "days" | "day" => add_days(datetime, value, is_ago), + "hours" | "hour" | "h" => add_duration(datetime, Duration::hours(value), is_ago), + "minutes" | "minute" | "mins" | "min" | "m" => { + add_duration(datetime, Duration::minutes(value), is_ago) + } + "seconds" | "second" | "secs" | "sec" | "s" => { + add_duration(datetime, Duration::seconds(value), is_ago) + } + "yesterday" => add_days(datetime, 1, true), + "tomorrow" => add_days(datetime, 1, false), + "now" | "today" => Some(datetime), + _ => None, + }; + datetime = match new_datetime { + Some(dt) => dt, + None => return Err(ParseDateTimeError::InvalidInput), }; - let neg_duration = -duration; - total_duration = - match total_duration.checked_add(if is_ago { &neg_duration } else { &duration }) { - Some(duration) => duration, - None => return Err(ParseDateTimeError::InvalidInput), - }; // Calculate the total length of the matched substring if let Some(m) = capture.get(0) { @@ -139,76 +123,131 @@ pub fn parse_relative_time_at_date( if captures_processed == 0 { Err(ParseDateTimeError::InvalidInput) } else { - let time_now = Local::now().date_naive(); - let date_duration = date - time_now; + Ok(datetime) + } +} + +fn add_months( + datetime: DateTime, + months: i64, + mut is_ago: bool, +) -> Option> { + let months = if months < 0 { + is_ago = !is_ago; + u32::try_from(-months).ok()? + } else { + u32::try_from(months).ok()? + }; + if is_ago { + datetime.checked_sub_months(Months::new(months)) + } else { + datetime.checked_add_months(Months::new(months)) + } +} - Ok(total_duration + date_duration) +fn add_days( + datetime: DateTime, + days: i64, + mut is_ago: bool, +) -> Option> { + let days = if days < 0 { + is_ago = !is_ago; + u64::try_from(-days).ok()? + } else { + u64::try_from(days).ok()? + }; + if is_ago { + datetime.checked_sub_days(Days::new(days)) + } else { + datetime.checked_add_days(Days::new(days)) } } +fn add_duration( + datetime: DateTime, + duration: Duration, + is_ago: bool, +) -> Option> { + let duration = if is_ago { -duration } else { duration }; + datetime.checked_add_signed(duration) +} + #[cfg(test)] mod tests { - + use super::parse_relative_time_at_date; use super::ParseDateTimeError; - use super::{parse_relative_time, parse_relative_time_at_date}; - use chrono::{Duration, Local, NaiveDate, Utc}; + use chrono::{Days, Duration, Months, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc}; + + fn parse_duration(s: &str) -> Result { + let now = Utc::now(); + let parsed = parse_relative_time_at_date(now, s)?; + Ok(parsed - now) + } #[test] fn test_years() { + let now = Utc::now(); assert_eq!( - parse_relative_time("1 year").unwrap(), - Duration::seconds(31_536_000) + parse_relative_time_at_date(now, "1 year").unwrap(), + now.checked_add_months(Months::new(12)).unwrap() ); assert_eq!( - parse_relative_time("-2 years").unwrap(), - Duration::seconds(-63_072_000) + parse_relative_time_at_date(now, "-2 years").unwrap(), + now.checked_sub_months(Months::new(24)).unwrap() ); assert_eq!( - parse_relative_time("2 years ago").unwrap(), - Duration::seconds(-63_072_000) + parse_relative_time_at_date(now, "2 years ago").unwrap(), + now.checked_sub_months(Months::new(24)).unwrap() ); assert_eq!( - parse_relative_time("year").unwrap(), - Duration::seconds(31_536_000) + parse_relative_time_at_date(now, "year").unwrap(), + now.checked_add_months(Months::new(12)).unwrap() ); } #[test] fn test_months() { + let now = Utc::now(); assert_eq!( - parse_relative_time("1 month").unwrap(), - Duration::seconds(2_592_000) + parse_relative_time_at_date(now, "1 month").unwrap(), + now.checked_add_months(Months::new(1)).unwrap() ); assert_eq!( - parse_relative_time("1 month and 2 weeks").unwrap(), - Duration::seconds(3_801_600) + parse_relative_time_at_date(now, "1 month and 2 weeks").unwrap(), + now.checked_add_months(Months::new(1)) + .unwrap() + .checked_add_days(Days::new(14)) + .unwrap() ); assert_eq!( - parse_relative_time("1 month and 2 weeks ago").unwrap(), - Duration::seconds(-3_801_600) + parse_relative_time_at_date(now, "1 month and 2 weeks ago").unwrap(), + now.checked_sub_months(Months::new(1)) + .unwrap() + .checked_sub_days(Days::new(14)) + .unwrap() ); assert_eq!( - parse_relative_time("2 months").unwrap(), - Duration::seconds(5_184_000) + parse_relative_time_at_date(now, "2 months").unwrap(), + now.checked_add_months(Months::new(2)).unwrap() ); assert_eq!( - parse_relative_time("month").unwrap(), - Duration::seconds(2_592_000) + parse_relative_time_at_date(now, "month").unwrap(), + now.checked_add_months(Months::new(1)).unwrap() ); } #[test] fn test_fortnights() { assert_eq!( - parse_relative_time("1 fortnight").unwrap(), + parse_duration("1 fortnight").unwrap(), Duration::seconds(1_209_600) ); assert_eq!( - parse_relative_time("3 fortnights").unwrap(), + parse_duration("3 fortnights").unwrap(), Duration::seconds(3_628_800) ); assert_eq!( - parse_relative_time("fortnight").unwrap(), + parse_duration("fortnight").unwrap(), Duration::seconds(1_209_600) ); } @@ -216,153 +255,124 @@ mod tests { #[test] fn test_weeks() { assert_eq!( - parse_relative_time("1 week").unwrap(), + parse_duration("1 week").unwrap(), Duration::seconds(604_800) ); assert_eq!( - parse_relative_time("1 week 3 days").unwrap(), + parse_duration("1 week 3 days").unwrap(), Duration::seconds(864_000) ); assert_eq!( - parse_relative_time("1 week 3 days ago").unwrap(), + parse_duration("1 week 3 days ago").unwrap(), Duration::seconds(-864_000) ); assert_eq!( - parse_relative_time("-2 weeks").unwrap(), + parse_duration("-2 weeks").unwrap(), Duration::seconds(-1_209_600) ); assert_eq!( - parse_relative_time("2 weeks ago").unwrap(), + parse_duration("2 weeks ago").unwrap(), Duration::seconds(-1_209_600) ); - assert_eq!( - parse_relative_time("week").unwrap(), - Duration::seconds(604_800) - ); + assert_eq!(parse_duration("week").unwrap(), Duration::seconds(604_800)); } #[test] fn test_days() { + assert_eq!(parse_duration("1 day").unwrap(), Duration::seconds(86400)); assert_eq!( - parse_relative_time("1 day").unwrap(), - Duration::seconds(86400) - ); - assert_eq!( - parse_relative_time("2 days ago").unwrap(), + parse_duration("2 days ago").unwrap(), Duration::seconds(-172_800) ); assert_eq!( - parse_relative_time("-2 days").unwrap(), + parse_duration("-2 days").unwrap(), Duration::seconds(-172_800) ); - assert_eq!( - parse_relative_time("day").unwrap(), - Duration::seconds(86400) - ); + assert_eq!(parse_duration("day").unwrap(), Duration::seconds(86400)); } #[test] fn test_hours() { + assert_eq!(parse_duration("1 hour").unwrap(), Duration::seconds(3600)); assert_eq!( - parse_relative_time("1 hour").unwrap(), - Duration::seconds(3600) - ); - assert_eq!( - parse_relative_time("1 hour ago").unwrap(), + parse_duration("1 hour ago").unwrap(), Duration::seconds(-3600) ); assert_eq!( - parse_relative_time("-2 hours").unwrap(), + parse_duration("-2 hours").unwrap(), Duration::seconds(-7200) ); - assert_eq!( - parse_relative_time("hour").unwrap(), - Duration::seconds(3600) - ); + assert_eq!(parse_duration("hour").unwrap(), Duration::seconds(3600)); } #[test] fn test_minutes() { - assert_eq!( - parse_relative_time("1 minute").unwrap(), - Duration::seconds(60) - ); - assert_eq!( - parse_relative_time("2 minutes").unwrap(), - Duration::seconds(120) - ); - assert_eq!(parse_relative_time("min").unwrap(), Duration::seconds(60)); + assert_eq!(parse_duration("1 minute").unwrap(), Duration::seconds(60)); + assert_eq!(parse_duration("2 minutes").unwrap(), Duration::seconds(120)); + assert_eq!(parse_duration("min").unwrap(), Duration::seconds(60)); } #[test] fn test_seconds() { - assert_eq!( - parse_relative_time("1 second").unwrap(), - Duration::seconds(1) - ); - assert_eq!( - parse_relative_time("2 seconds").unwrap(), - Duration::seconds(2) - ); - assert_eq!(parse_relative_time("sec").unwrap(), Duration::seconds(1)); + assert_eq!(parse_duration("1 second").unwrap(), Duration::seconds(1)); + assert_eq!(parse_duration("2 seconds").unwrap(), Duration::seconds(2)); + assert_eq!(parse_duration("sec").unwrap(), Duration::seconds(1)); } #[test] fn test_relative_days() { - assert_eq!(parse_relative_time("now").unwrap(), Duration::seconds(0)); - assert_eq!(parse_relative_time("today").unwrap(), Duration::seconds(0)); + assert_eq!(parse_duration("now").unwrap(), Duration::seconds(0)); + assert_eq!(parse_duration("today").unwrap(), Duration::seconds(0)); assert_eq!( - parse_relative_time("yesterday").unwrap(), + parse_duration("yesterday").unwrap(), Duration::seconds(-86400) ); assert_eq!( - parse_relative_time("tomorrow").unwrap(), + parse_duration("tomorrow").unwrap(), Duration::seconds(86400) ); } #[test] fn test_no_spaces() { - assert_eq!(parse_relative_time("-1hour").unwrap(), Duration::hours(-1)); - assert_eq!(parse_relative_time("+3days").unwrap(), Duration::days(3)); - assert_eq!(parse_relative_time("2weeks").unwrap(), Duration::weeks(2)); + let now = Utc::now(); + assert_eq!(parse_duration("-1hour").unwrap(), Duration::hours(-1)); + assert_eq!(parse_duration("+3days").unwrap(), Duration::days(3)); + assert_eq!(parse_duration("2weeks").unwrap(), Duration::weeks(2)); assert_eq!( - parse_relative_time("2weeks 1hour").unwrap(), + parse_duration("2weeks 1hour").unwrap(), Duration::seconds(1_213_200) ); assert_eq!( - parse_relative_time("2weeks 1hour ago").unwrap(), + parse_duration("2weeks 1hour ago").unwrap(), Duration::seconds(-1_213_200) ); assert_eq!( - parse_relative_time("+4months").unwrap(), - Duration::days(4 * 30) - ); - assert_eq!( - parse_relative_time("-2years").unwrap(), - Duration::days(-2 * 365) + parse_relative_time_at_date(now, "+4months").unwrap(), + now.checked_add_months(Months::new(4)).unwrap() ); assert_eq!( - parse_relative_time("15minutes").unwrap(), - Duration::minutes(15) + parse_relative_time_at_date(now, "-2years").unwrap(), + now.checked_sub_months(Months::new(24)).unwrap() ); + assert_eq!(parse_duration("15minutes").unwrap(), Duration::minutes(15)); assert_eq!( - parse_relative_time("-30seconds").unwrap(), + parse_duration("-30seconds").unwrap(), Duration::seconds(-30) ); assert_eq!( - parse_relative_time("30seconds ago").unwrap(), + parse_duration("30seconds ago").unwrap(), Duration::seconds(-30) ); } #[test] fn test_invalid_input() { - let result = parse_relative_time("foobar"); + let result = parse_duration("foobar"); println!("{result:?}"); assert_eq!(result, Err(ParseDateTimeError::InvalidInput)); - let result = parse_relative_time("invalid 1"); + let result = parse_duration("invalid 1"); assert_eq!(result, Err(ParseDateTimeError::InvalidInput)); // Fails for now with a panic /* let result = parse_relative_time("777777777777777771m"); @@ -374,210 +384,200 @@ mod tests { #[test] fn test_parse_relative_time_at_date() { - let date = NaiveDate::from_ymd_opt(2014, 9, 5).unwrap(); - let now = Local::now().date_naive(); - let days_diff = (date - now).num_days(); + let datetime = Utc.from_utc_datetime(&NaiveDateTime::new( + NaiveDate::from_ymd_opt(2014, 9, 5).unwrap(), + NaiveTime::from_hms_opt(0, 2, 3).unwrap(), + )); + let now = Utc::now(); + let diff = datetime - now; assert_eq!( - parse_relative_time_at_date(date, "1 day").unwrap(), - Duration::days(days_diff + 1) + parse_relative_time_at_date(datetime, "1 day").unwrap(), + now + diff + Duration::days(1) ); assert_eq!( - parse_relative_time_at_date(date, "2 hours").unwrap(), - Duration::days(days_diff) + Duration::hours(2) + parse_relative_time_at_date(datetime, "2 hours").unwrap(), + now + diff + Duration::hours(2) ); } - #[test] - fn test_invalid_input_at_date() { - let date = NaiveDate::from_ymd_opt(2014, 9, 5).unwrap(); - assert!(matches!( - parse_relative_time_at_date(date, "invalid"), - Err(ParseDateTimeError::InvalidInput) - )); - } - #[test] fn test_direction() { + let now = Utc::now(); assert_eq!( - parse_relative_time("last hour").unwrap(), + parse_duration("last hour").unwrap(), Duration::seconds(-3600) ); assert_eq!( - parse_relative_time("next year").unwrap(), - Duration::days(365) + parse_relative_time_at_date(now, "next year").unwrap(), + now.checked_add_months(Months::new(12)).unwrap() ); - assert_eq!(parse_relative_time("next week").unwrap(), Duration::days(7)); + assert_eq!(parse_duration("next week").unwrap(), Duration::days(7)); assert_eq!( - parse_relative_time("last month").unwrap(), - Duration::days(-30) + parse_relative_time_at_date(now, "last month").unwrap(), + now.checked_sub_months(Months::new(1)).unwrap() ); } #[test] fn test_duration_parsing() { + let now = Utc::now(); assert_eq!( - parse_relative_time("1 year").unwrap(), - Duration::seconds(31_536_000) + parse_relative_time_at_date(now, "1 year").unwrap(), + now.checked_add_months(Months::new(12)).unwrap() ); assert_eq!( - parse_relative_time("-2 years").unwrap(), - Duration::seconds(-63_072_000) + parse_relative_time_at_date(now, "-2 years").unwrap(), + now.checked_sub_months(Months::new(24)).unwrap() ); assert_eq!( - parse_relative_time("2 years ago").unwrap(), - Duration::seconds(-63_072_000) + parse_relative_time_at_date(now, "2 years ago").unwrap(), + now.checked_sub_months(Months::new(24)).unwrap() ); assert_eq!( - parse_relative_time("year").unwrap(), - Duration::seconds(31_536_000) + parse_relative_time_at_date(now, "year").unwrap(), + now.checked_add_months(Months::new(12)).unwrap() ); assert_eq!( - parse_relative_time("1 month").unwrap(), - Duration::seconds(2_592_000) + parse_relative_time_at_date(now, "1 month").unwrap(), + now.checked_add_months(Months::new(1)).unwrap() ); assert_eq!( - parse_relative_time("1 month and 2 weeks").unwrap(), - Duration::seconds(3_801_600) + parse_relative_time_at_date(now, "1 month and 2 weeks").unwrap(), + now.checked_add_months(Months::new(1)) + .unwrap() + .checked_add_days(Days::new(14)) + .unwrap() ); assert_eq!( - parse_relative_time("1 month, 2 weeks").unwrap(), - Duration::seconds(3_801_600) + parse_relative_time_at_date(now, "1 month, 2 weeks").unwrap(), + now.checked_add_months(Months::new(1)) + .unwrap() + .checked_add_days(Days::new(14)) + .unwrap() ); assert_eq!( - parse_relative_time("1 months 2 weeks").unwrap(), - Duration::seconds(3_801_600) + parse_relative_time_at_date(now, "1 months 2 weeks").unwrap(), + now.checked_add_months(Months::new(1)) + .unwrap() + .checked_add_days(Days::new(14)) + .unwrap() ); assert_eq!( - parse_relative_time("1 month and 2 weeks ago").unwrap(), - Duration::seconds(-3_801_600) + parse_relative_time_at_date(now, "1 month and 2 weeks ago").unwrap(), + now.checked_sub_months(Months::new(1)) + .unwrap() + .checked_sub_days(Days::new(14)) + .unwrap() ); assert_eq!( - parse_relative_time("2 months").unwrap(), - Duration::seconds(5_184_000) + parse_relative_time_at_date(now, "2 months").unwrap(), + now.checked_add_months(Months::new(2)).unwrap() ); assert_eq!( - parse_relative_time("month").unwrap(), - Duration::seconds(2_592_000) + parse_relative_time_at_date(now, "month").unwrap(), + now.checked_add_months(Months::new(1)).unwrap() ); assert_eq!( - parse_relative_time("1 fortnight").unwrap(), - Duration::seconds(1_209_600) + parse_relative_time_at_date(now, "1 fortnight").unwrap(), + now.checked_add_days(Days::new(14)).unwrap() ); assert_eq!( - parse_relative_time("3 fortnights").unwrap(), - Duration::seconds(3_628_800) + parse_relative_time_at_date(now, "3 fortnights").unwrap(), + now.checked_add_days(Days::new(3 * 14)).unwrap() ); assert_eq!( - parse_relative_time("fortnight").unwrap(), - Duration::seconds(1_209_600) + parse_relative_time_at_date(now, "fortnight").unwrap(), + now.checked_add_days(Days::new(14)).unwrap() ); assert_eq!( - parse_relative_time("1 week").unwrap(), - Duration::seconds(604_800) + parse_relative_time_at_date(now, "1 week").unwrap(), + now.checked_add_days(Days::new(7)).unwrap() ); assert_eq!( - parse_relative_time("1 week 3 days").unwrap(), - Duration::seconds(864_000) + parse_relative_time_at_date(now, "1 week 3 days").unwrap(), + now.checked_add_days(Days::new(7 + 3)).unwrap() ); assert_eq!( - parse_relative_time("1 week 3 days ago").unwrap(), - Duration::seconds(-864_000) + parse_relative_time_at_date(now, "1 week 3 days ago").unwrap(), + now.checked_sub_days(Days::new(7 + 3)).unwrap() ); assert_eq!( - parse_relative_time("-2 weeks").unwrap(), - Duration::seconds(-1_209_600) + parse_relative_time_at_date(now, "-2 weeks").unwrap(), + now.checked_sub_days(Days::new(14)).unwrap() ); assert_eq!( - parse_relative_time("2 weeks ago").unwrap(), - Duration::seconds(-1_209_600) + parse_relative_time_at_date(now, "2 weeks ago").unwrap(), + now.checked_sub_days(Days::new(14)).unwrap() ); assert_eq!( - parse_relative_time("week").unwrap(), - Duration::seconds(604_800) + parse_relative_time_at_date(now, "week").unwrap(), + now.checked_add_days(Days::new(7)).unwrap() ); + assert_eq!(parse_duration("1 day").unwrap(), Duration::seconds(86_400)); assert_eq!( - parse_relative_time("1 day").unwrap(), - Duration::seconds(86_400) - ); - assert_eq!( - parse_relative_time("2 days ago").unwrap(), + parse_duration("2 days ago").unwrap(), Duration::seconds(-172_800) ); assert_eq!( - parse_relative_time("-2 days").unwrap(), + parse_duration("-2 days").unwrap(), Duration::seconds(-172_800) ); - assert_eq!( - parse_relative_time("day").unwrap(), - Duration::seconds(86_400) - ); + assert_eq!(parse_duration("day").unwrap(), Duration::seconds(86_400)); + assert_eq!(parse_duration("1 hour").unwrap(), Duration::seconds(3_600)); + assert_eq!(parse_duration("1 h").unwrap(), Duration::seconds(3_600)); assert_eq!( - parse_relative_time("1 hour").unwrap(), - Duration::seconds(3_600) - ); - assert_eq!( - parse_relative_time("1 h").unwrap(), - Duration::seconds(3_600) - ); - assert_eq!( - parse_relative_time("1 hour ago").unwrap(), + parse_duration("1 hour ago").unwrap(), Duration::seconds(-3_600) ); assert_eq!( - parse_relative_time("-2 hours").unwrap(), + parse_duration("-2 hours").unwrap(), Duration::seconds(-7_200) ); - assert_eq!( - parse_relative_time("hour").unwrap(), - Duration::seconds(3_600) - ); - - assert_eq!( - parse_relative_time("1 minute").unwrap(), - Duration::seconds(60) - ); - assert_eq!(parse_relative_time("1 min").unwrap(), Duration::seconds(60)); - assert_eq!( - parse_relative_time("2 minutes").unwrap(), - Duration::seconds(120) - ); - assert_eq!( - parse_relative_time("2 mins").unwrap(), - Duration::seconds(120) - ); - assert_eq!(parse_relative_time("2m").unwrap(), Duration::seconds(120)); - assert_eq!(parse_relative_time("min").unwrap(), Duration::seconds(60)); - - assert_eq!( - parse_relative_time("1 second").unwrap(), - Duration::seconds(1) - ); - assert_eq!(parse_relative_time("1 s").unwrap(), Duration::seconds(1)); - assert_eq!( - parse_relative_time("2 seconds").unwrap(), - Duration::seconds(2) - ); - assert_eq!(parse_relative_time("2 secs").unwrap(), Duration::seconds(2)); - assert_eq!(parse_relative_time("2 sec").unwrap(), Duration::seconds(2)); - assert_eq!(parse_relative_time("sec").unwrap(), Duration::seconds(1)); - - assert_eq!(parse_relative_time("now").unwrap(), Duration::seconds(0)); - assert_eq!(parse_relative_time("today").unwrap(), Duration::seconds(0)); - - assert_eq!( - parse_relative_time("1 year 2 months 4 weeks 3 days and 2 seconds").unwrap(), - Duration::seconds(39_398_402) - ); - assert_eq!( - parse_relative_time("1 year 2 months 4 weeks 3 days and 2 seconds ago").unwrap(), - Duration::seconds(-39_398_402) + assert_eq!(parse_duration("hour").unwrap(), Duration::seconds(3_600)); + + assert_eq!(parse_duration("1 minute").unwrap(), Duration::seconds(60)); + assert_eq!(parse_duration("1 min").unwrap(), Duration::seconds(60)); + assert_eq!(parse_duration("2 minutes").unwrap(), Duration::seconds(120)); + assert_eq!(parse_duration("2 mins").unwrap(), Duration::seconds(120)); + assert_eq!(parse_duration("2m").unwrap(), Duration::seconds(120)); + assert_eq!(parse_duration("min").unwrap(), Duration::seconds(60)); + + assert_eq!(parse_duration("1 second").unwrap(), Duration::seconds(1)); + assert_eq!(parse_duration("1 s").unwrap(), Duration::seconds(1)); + assert_eq!(parse_duration("2 seconds").unwrap(), Duration::seconds(2)); + assert_eq!(parse_duration("2 secs").unwrap(), Duration::seconds(2)); + assert_eq!(parse_duration("2 sec").unwrap(), Duration::seconds(2)); + assert_eq!(parse_duration("sec").unwrap(), Duration::seconds(1)); + + assert_eq!(parse_duration("now").unwrap(), Duration::seconds(0)); + assert_eq!(parse_duration("today").unwrap(), Duration::seconds(0)); + + assert_eq!( + parse_relative_time_at_date(now, "1 year 2 months 4 weeks 3 days and 2 seconds") + .unwrap(), + now.checked_add_months(Months::new(12 + 2)) + .unwrap() + .checked_add_days(Days::new(4 * 7 + 3)) + .unwrap() + .checked_add_signed(Duration::seconds(2)) + .unwrap() + ); + assert_eq!( + parse_relative_time_at_date(now, "1 year 2 months 4 weeks 3 days and 2 seconds ago") + .unwrap(), + now.checked_sub_months(Months::new(12 + 2)) + .unwrap() + .checked_sub_days(Days::new(4 * 7 + 3)) + .unwrap() + .checked_sub_signed(Duration::seconds(2)) + .unwrap() ); } @@ -585,13 +585,13 @@ mod tests { #[should_panic] fn test_display_parse_duration_error_through_parse_relative_time() { let invalid_input = "9223372036854775807 seconds and 1 second"; - let _ = parse_relative_time(invalid_input).unwrap(); + let _ = parse_duration(invalid_input).unwrap(); } #[test] fn test_display_should_fail() { let invalid_input = "Thu Jan 01 12:34:00 2015"; - let error = parse_relative_time(invalid_input).unwrap_err(); + let error = parse_duration(invalid_input).unwrap_err(); assert_eq!( format!("{error}"), @@ -601,22 +601,47 @@ mod tests { #[test] fn test_parse_relative_time_at_date_day() { - let today = Utc::now().date_naive(); - let yesterday = today - Duration::days(1); + let now = Utc::now(); + let now_yesterday = now - Duration::days(1); assert_eq!( - parse_relative_time_at_date(yesterday, "2 days").unwrap(), - Duration::days(1) + parse_relative_time_at_date(now_yesterday, "2 days").unwrap(), + now + Duration::days(1) + ); + } + + #[test] + fn test_parse_relative_time_at_date_month() { + // Use January because it has 31 days rather than 30 + let now = Utc.from_utc_datetime(&NaiveDateTime::new( + NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(), + NaiveTime::from_hms_opt(0, 0, 0).unwrap(), + )); + assert_eq!( + parse_relative_time_at_date(now, "1 month").unwrap(), + now.checked_add_months(Months::new(1)).unwrap() + ); + } + + #[test] + fn test_parse_relative_time_at_date_year() { + // Use 2024 because it's a leap year and has 366 days rather than 365 + let now = Utc.from_utc_datetime(&NaiveDateTime::new( + NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(), + NaiveTime::from_hms_opt(0, 0, 0).unwrap(), + )); + assert_eq!( + parse_relative_time_at_date(now, "1 year").unwrap(), + now.checked_add_months(Months::new(12)).unwrap() ); } #[test] fn test_invalid_input_at_date_relative() { - let today = Utc::now().date_naive(); - let result = parse_relative_time_at_date(today, "foobar"); - println!("{result:?}"); + let now = Utc::now(); + let result = parse_relative_time_at_date(now, "foobar"); assert_eq!(result, Err(ParseDateTimeError::InvalidInput)); - let result = parse_relative_time_at_date(today, "invalid 1r"); + let result = parse_relative_time_at_date(now, "invalid 1r"); assert_eq!(result, Err(ParseDateTimeError::InvalidInput)); } }