diff --git a/README.md b/README.md index 4152487..6305c93 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ The `parse_datetime` and `parse_datetime_at_date` functions support absolute dat - "now" or "today" - "yesterday" - "tomorrow" -- use "ago" for the past +- use "ago" for the past or "hence" for the future - use "next" or "last" with `unit` (e.g., "next week", "last year") - unix timestamps (for example "@0" "@1344000") diff --git a/src/items/mod.rs b/src/items/mod.rs index 92e1d9a..928ca42 100644 --- a/src/items/mod.rs +++ b/src/items/mod.rs @@ -159,7 +159,7 @@ pub(crate) fn parse_at_local + Clone>(input: S) -> Result ModalResult { opt(alt((s('+').value(1), s('-').value(-1)))), s(sec_and_nsec), s(alpha1).verify(|s: &str| matches!(s, "seconds" | "second" | "sec" | "secs")), - ago, + direction, ) .verify_map(|(sign, (sec, nsec), _, ago)| { let sec = i64::try_from(sec).ok()?; @@ -102,7 +102,7 @@ fn seconds(input: &mut &str) -> ModalResult { } fn displacement(input: &mut &str) -> ModalResult { - (opt(ordinal), s(alpha1), ago) + (opt(ordinal), s(alpha1), direction) .verify_map(|(n, unit, ago): (Option, &str, bool)| { let multiplier = n.unwrap_or(1) * if ago { -1 } else { 1 }; Some(match unit.strip_suffix('s').unwrap_or(unit) { @@ -120,8 +120,10 @@ fn displacement(input: &mut &str) -> ModalResult { .parse_next(input) } -fn ago(input: &mut &str) -> ModalResult { - opt(s("ago")).map(|o| o.is_some()).parse_next(input) +fn direction(input: &mut &str) -> ModalResult { + opt(alt((s("ago").value(true), s("hence").value(false)))) + .map(|backward| backward.unwrap_or(false)) + .parse_next(input) } #[cfg(test)] diff --git a/src/lib.rs b/src/lib.rs index 10fe36a..128baa9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -784,6 +784,30 @@ mod tests { mod test_relative { use crate::parse_datetime; + #[test] + fn test_hence_suffix() { + let now: jiff::Zoned = "2026-01-15T12:00:00Z[UTC]".parse().unwrap(); + for relative in [ + "2 months", + "year", + "-2 days", + "next week", + "0 hours", + "1.5 seconds", + ] { + let expected = crate::parse_datetime_at_date(now.clone(), relative).unwrap(); + for suffix in ["hence", "HENCE"] { + assert_eq!( + crate::parse_datetime_at_date(now.clone(), format!("{relative} {suffix}")) + .unwrap(), + expected, + ); + } + } + assert!(crate::parse_datetime_at_date(now.clone(), "hence").is_err()); + assert!(crate::parse_datetime_at_date(now, "2 days henceforth").is_err()); + } + #[test] fn test_month() { assert_eq!(