diff --git a/gix-date/src/parse.rs b/gix-date/src/parse.rs index 376bdc0ba36..f33081eaf30 100644 --- a/gix-date/src/parse.rs +++ b/gix-date/src/parse.rs @@ -153,9 +153,10 @@ mod relative { "hour" => Span::new().try_hours(units), "day" => Span::new().try_days(units), "week" => Span::new().try_weeks(units), - // TODO months & years? YES + "month" => Span::new().try_months(units), + "year" => Span::new().try_years(units), // Ignore values you don't know, assume seconds then (so does git) - _ => return None, + _anything => Span::new().try_seconds(units), }; Some(result.map_err(|_| Error::RelativeTimeConversion)) } diff --git a/gix-date/tests/time/parse.rs b/gix-date/tests/time/parse.rs index bee1ac4c856..44222752e8c 100644 --- a/gix-date/tests/time/parse.rs +++ b/gix-date/tests/time/parse.rs @@ -181,39 +181,88 @@ mod relative { fn various() { let now = SystemTime::now(); + // For comparison, a few are the same as in: https://github.com/git/git/blob/master/t/t0006-date.sh let cases = [ - ("2 weeks ago", 2.weeks()), + ("5 seconds ago", 5.seconds()), + ("12345 florx ago", 12_345.seconds()), // Anything parses as seconds + ("5 minutes ago", 5.minutes()), + ("5 hours ago", 5.hours()), + ("5 days ago", 5.days()), + ("3 weeks ago", 3.weeks()), + ("21 days ago", 21.days()), // 3 weeks + ("504 hours ago", 504.hours()), // 3 weeks + ("30240 minutes ago", 30_240.minutes()), // 3 weeks + ("2 months ago", 2.months()), + ("1460 hours ago", 1460.hours()), // 2 months + ("87600 minutes ago", 87_600.minutes()), // 2 months ("14 weeks ago", 14.weeks()), - ("26 weeks ago", 26.weeks()), - ("38 weeks ago", 38.weeks()), - ("50 weeks ago", 50.weeks()), - ("20160 minutes ago", 20_160.minutes()), // 2 weeks + ("98 days ago", 98.days()), // 14 weeks + ("2352 hours ago", 2352.hours()), // 14 weeks ("141120 minutes ago", 141_120.minutes()), // 14 weeks + ("5 months ago", 5.months()), + ("3650 hours ago", 3650.hours()), // 5 months + ("219000 minutes ago", 219_000.minutes()), // 5 months + ("26 weeks ago", 26.weeks()), + ("182 days ago", 182.days()), // 26 weeks + ("4368 hours ago", 4368.hours()), // 26 weeks ("262080 minutes ago", 262_080.minutes()), // 26 weeks + ("8 months ago", 8.months()), + ("5840 hours ago", 5840.hours()), // 8 months + ("350400 minutes ago", 350_400.minutes()), // 8 months + ("38 weeks ago", 38.weeks()), + ("266 days ago", 266.days()), // 38 weeks + ("6384 hours ago", 6384.hours()), // 38 weeks ("383040 minutes ago", 383_040.minutes()), // 38 weeks - ("504000 minutes ago", 504_000.minutes()), // 50 weeks + ("11 months ago", 11.months()), + ("8030 hours ago", 8030.hours()), // 11 months + ("481800 minutes ago", 481_800.minutes()), // 11 months + ("14 months ago", 14.months()), // "1 year, 2 months ago" not yet supported. + ("21 months ago", 21.months()), // "1 year, 9 months ago" not yet supported. + ("2 years ago", 2.years()), + ("20 years ago", 20.years()), + ("630720000 seconds ago", 630_720_000.seconds()), // 20 years ]; - let times = cases.map(|(input, _)| gix_date::parse(input, Some(now)).unwrap()); - - assert_eq!(times.map(|_| Sign::Plus), times.map(|time| time.sign)); - assert_eq!(times.map(|_| 0), times.map(|time| time.offset)); - - let expected = cases.map(|(_, span)| { - Zoned::try_from(now) - .unwrap() - // account for the loss of precision when creating `Time` with seconds - .round( - jiff::ZonedRound::new() - .smallest(jiff::Unit::Second) - .mode(jiff::RoundMode::Trunc), - ) - .unwrap() - .saturating_sub(span) - .timestamp() + let cases_with_times = cases.map(|(input, _)| { + let time = gix_date::parse(input, Some(now)).expect("relative time string should parse to a Time"); + (input, time) + }); + assert_eq!( + cases_with_times.map(|(_, time)| time.sign), + cases_with_times.map(|_| Sign::Plus), + "Despite being in the past, the dates produced are positive, as they are still post-epoch" + ); + assert_eq!( + cases_with_times.map(|(_, time)| time.offset), + cases_with_times.map(|_| 0), + "They don't pick up local time" + ); + + let expected = cases.map(|(input, span)| { + let expected = Zoned::new( + now.try_into().expect("system time is representable"), + // As relative dates are always UTC in Git, we do the same, and must + // compare to UTC as well or else time might be off due to daylight savings, etc. + jiff::tz::TimeZone::UTC, + ) + // account for the loss of precision when creating `Time` with seconds + .round( + jiff::ZonedRound::new() + .smallest(jiff::Unit::Second) + .mode(jiff::RoundMode::Trunc), + ) + .expect("test needs to truncate current timestamp to seconds") + .saturating_sub(span) + .timestamp(); + + (input, expected) + }); + let actual = cases_with_times.map(|(input, time)| { + let actual = jiff::Timestamp::from_second(time.seconds) + .expect("seconds obtained from a Time should convert to Timestamp"); + (input, actual) }); - let actual = times.map(|time| jiff::Timestamp::from_second(time.seconds).unwrap()); - assert_eq!(actual, expected, "relative times differ"); + assert_eq!(actual, expected); } }