Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse relative months and years #1702

Merged
merged 3 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions gix-date/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
EliahKagan marked this conversation as resolved.
Show resolved Hide resolved
_ => return None,
_anything => Span::new().try_seconds(units),
};
Some(result.map_err(|_| Error::RelativeTimeConversion))
}
Expand Down
99 changes: 74 additions & 25 deletions gix-date/tests/time/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
Loading