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

Relative time ambiguity across DST adjustments #1696

Closed
EliahKagan opened this issue Nov 22, 2024 · 8 comments · Fixed by #1702
Closed

Relative time ambiguity across DST adjustments #1696

EliahKagan opened this issue Nov 22, 2024 · 8 comments · Fixed by #1702

Comments

@EliahKagan
Copy link
Member

EliahKagan commented Nov 22, 2024

Current behavior 😯

When gix-date::date time::parse::relative::various is run in an environment that uses local time in a time zone that has daylight savings clock adjustments, it fails in the two weeks after daylight saving time changes. All other gix-date tests pass.

For example, my time zone is Eastern Time, which had a DST clock adjustment from EDT (UTC-4) to EST (UTC-5) on 3 November 2024. Since then, and until two weeks later, the test failed when I ran it locally. Here's a run from 9 November 2024:
 

        FAIL [   0.310s] gix-date::date time::parse::relative::various

--- STDOUT:              gix-date::date time::parse::relative::various ---

running 1 test
test time::parse::relative::various ... FAILED

failures:

failures:
    time::parse::relative::various

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 27 filtered out; finished in 0.31s


--- STDERR:              gix-date::date time::parse::relative::various ---
thread 'time::parse::relative::various' panicked at gix-date/tests/time/parse.rs:195:9:
assertion `left == right` failed: relative times differ
  left: 2024-10-26T20:22:51Z
 right: 2024-10-26T19:22:51Z
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

------------
     Summary [   0.325s] 29 tests run: 28 passed, 1 failed, 0 skipped
        FAIL [   0.310s] gix-date::date time::parse::relative::various
error: test run failed

Under those conditions, the time is off by one hour, due to ambiguity of phrases like 2 weeks ago. If there was a recent daylight savings adjustment to local time, then was 2 weeks ago the current time minus the usual 336 hours? Or was 2 weeks ago the time, fourteen calendar days prior, at which local clocks showed the time that local clocks show now? Often these two interpretations of relative times coincide, but they do not always.

The test failed on GNU/Linux, macOS, and Windows, and at least on GNU/Linux and macOS, it went away when TZ=UTC is set. With no change to the code, the test started passing again, on all systems, on 17 November 2024, but the bug is still present.

This bug has not been detected automatically on CI, because the environment on GitHub Actions runners uses UTC rather than local time.

(This is the bug that the mention of TZ=UTC in d74e919 (#1652) alludes to.)

Expected behavior 🤔

I am uncertain whether it is the code under test, or the test itself, that should be changed. But if we want to interpret relative times like "2 weeks ago" in the same way GNU/Linux date -d does, and in the way I think Git does (see below for details), then the code under test would have to be changed.

Either way, the bug is not specific to 2-week intervals. if the code is wrong, then the test's use of 2 weeks ago is merely a limitation in when the test is able to identify the code as wrong. If the test is wrong, then while the failure only occurs under limited conditions, the interpretation of relative time that it expresses is still broader.

This is to say that even if it is (only) the test that should change, I think making the interval shorter so that it fails less often would not be the right way to go about it.

Also, either way, we should probably make the test check multiple relative time strings representing intervals of widely varying length.

Git behavior

I don't know if I'm looking at the most relevant Git behavior, but the easiest to examine are the tests in t0006-date.sh. If I understand correctly, relative date strings represent exact numbers of seconds elapsed:

check_relative() {
	t=$(($GIT_TEST_DATE_NOW - $1))
	echo "$t -> $2" >expect
	test_expect_${3:-success} "relative date ($2)" "
	test-tool date relative $t >actual &&
	test_cmp expect actual
	"
}

check_relative 5 '5 seconds ago'
check_relative 300 '5 minutes ago'
check_relative 18000 '5 hours ago'
check_relative 432000 '5 days ago'
check_relative 1728000 '3 weeks ago'
check_relative 13000000 '5 months ago'
check_relative 37500000 '1 year, 2 months ago'
check_relative 55188000 '1 year, 9 months ago'
check_relative 630000000 '20 years ago'
check_relative 31449600 '12 months ago'
check_relative 62985600 '2 years ago'

Those tests pass when run today, on an Arch Linux system set to America/New York and using local time, including when the script is run directly. If git behaved analogously to the current behavior of gix-date, then I would expect the 3 weeks ago and 5 months ago cases, as well as some others, to have failed. I ran that test script at the tip of the master branch, with:

make -j10
cd t
./t0006-date.sh

So that would agree with what gix-date's test suite is asserting, while disagreeing with the current gix-date behavior. It seems to me that this may be a reason to consider our existing test in gix-date correct, and the current behavior of the gix-date being tested as incorrect.

Steps to reproduce 🕹

Although the tests don't fail in my time zone anymore, because 2 weeks ago is now after the DST adjustment, there are a few approaches that may reproduce it.

One is to use a tool like faketime. This carries some subtleties, though, since accesses to date and time information are not always guaranteed to go through the functions it patches, and since cursory experimentation with faketime and the date command suggests that locale-sensitive interpretation of strings like EST and EDT as time zone offsets does not always behave the way it did at the date and time faketime attempts to fake it to.

Another is to set the clock back. This will carry fewer subtleties but may be undesirable.

Instead, assuming one's time zone has DST adjustments, I suggest reproducing this by modifying the interval in the test from 2 weeks ago to an interval that refers to a date where local time used a different UTC offset than currently.

5 months ago would be nearly ideal, but cannot be used because we don't yet parse months or years, so that would give InvalidDateString { input: "5 months ago" }. (The absence of parsing of months or years might be considered a bug, but it is not this bug.)

A sufficient number of weeks will work, though. For example, starting in the top-level gitoxide directory, the interval can be changed from 2 to 20 weeks, which is approximately 5 months and should produce the bug at most (though not all) times of the year in most time zones where DST adjustments are made:

cd gix-date
sed -i.orig 's/two_weeks_ago/twenty_weeks_ago/; s/2\.weeks/20.weeks/; s/2 weeks ago/20 weeks ago/' tests/time/parse.rs
git --no-pager diff
cargo nextest run time::parse::relative::various

Edit: If #1697 is merged then the procedure here with sed -i will no longer be correct or needed, and the bug will automatically be observable at any time of year if it could be observed before at some time of year.

Running those commands on the test system produces the bug as intended:

ek in 🌐 catenary in gitoxide on  main is 📦 v0.38.0 via 🦀 v1.82.0
❯ cd gix-date

ek in 🌐 catenary in gitoxide/gix-date on  main is 📦 v0.9.1 via 🦀 v1.82.0
❯ sed -i.orig 's/two_weeks_ago/twenty_weeks_ago/; s/2\.weeks/20.weeks/; s/2 weeks ago/20 weeks ago/' tests/time/parse.rs

ek in 🌐 catenary in gitoxide/gix-date on  main [!?] is 📦 v0.9.1 via 🦀 v1.82.0
❯ git --no-pager diff
diff --git a/gix-date/tests/time/parse.rs b/gix-date/tests/time/parse.rs
index 545fbb7fc..8b0475c1d 100644
--- a/gix-date/tests/time/parse.rs
+++ b/gix-date/tests/time/parse.rs
@@ -179,9 +179,9 @@ mod relative {
     #[test]
     fn various() {
         let now = SystemTime::now();
-        let two_weeks_ago = gix_date::parse("2 weeks ago", Some(now)).unwrap();
-        assert_eq!(Sign::Plus, two_weeks_ago.sign);
-        assert_eq!(0, two_weeks_ago.offset);
+        let twenty_weeks_ago = gix_date::parse("20 weeks ago", Some(now)).unwrap();
+        assert_eq!(Sign::Plus, twenty_weeks_ago.sign);
+        assert_eq!(0, twenty_weeks_ago.offset);
         let expected = Zoned::try_from(now)
             .unwrap()
             // account for the loss of precision when creating `Time` with seconds
@@ -191,9 +191,9 @@ mod relative {
                     .mode(jiff::RoundMode::Trunc),
             )
             .unwrap()
-            .saturating_sub(2.weeks());
+            .saturating_sub(20.weeks());
         assert_eq!(
-            jiff::Timestamp::from_second(two_weeks_ago.seconds).unwrap(),
+            jiff::Timestamp::from_second(twenty_weeks_ago.seconds).unwrap(),
             expected.timestamp(),
             "relative times differ"
         );

ek in 🌐 catenary in gitoxide/gix-date on  main [!?] is 📦 v0.9.1 via 🦀 v1.82.0
❯ cargo nextest run time::parse::relative::various
   Compiling gix-date v0.9.1 (/home/ek/repos/gitoxide/gix-date)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.92s
------------
 Nextest run ID 55a072b6-40a8-4bd9-b177-58e4158ce1fe with nextest profile: default
    Starting 1 test across 2 binaries (28 tests skipped)
        FAIL [   0.014s] gix-date::date time::parse::relative::various

--- STDOUT:              gix-date::date time::parse::relative::various ---

running 1 test
test time::parse::relative::various ... FAILED

failures:

failures:
    time::parse::relative::various

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 27 filtered out; finished in 0.01s


--- STDERR:              gix-date::date time::parse::relative::various ---
thread 'time::parse::relative::various' panicked at gix-date/tests/time/parse.rs:195:9:
assertion `left == right` failed: relative times differ
  left: 2024-07-05T18:55:17Z
 right: 2024-07-05T17:55:17Z
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

  Cancelling due to test failure
------------
     Summary [   0.015s] 1 test run: 0 passed, 1 failed, 28 skipped
        FAIL [   0.014s] gix-date::date time::parse::relative::various
error: test run failed

Although less reproducible, it may be more intuitive to use a shorter interval. At the moment, 3 weeks is sufficient. A run changing 2 weeks to 3 (rather than 20) weeks can be seen here.

EliahKagan added a commit to EliahKagan/gitoxide that referenced this issue Nov 22, 2024
This tests four inputs representing relative times, instead of one,
in order to:

1. Reveal GitoxideLabs#1696 during more of the year when the tests are run on
   machines set to local time in time zones whose rules include
   daylight savings clock adjustments.

2. Illuminate the nature and limited extent of GitoxideLabs#1696 by trying both
   "weeks ago" and "minutes ago" input.

   (It looks like the "minutes ago" input always passes the test,
   while the "weeks ago" input can fail the test if the interval
   includes a DST adjustment.)

3. Cover a wider range of inputs more generally, which is probably
   a good idea even where GitoxideLabs#1696 is not involved.

Although these change intend to, and appear to succeed at,
triggering more failures due to that but on affected systems set to
local time, they are not expected to produce any new failures on
CI, since all platforms' GitHub-hosted GHA runners are set to use
UTC.

With these changes, the failure, when it occurs, looks like:

    --- STDERR:              gix-date::date time::parse::relative::various ---
    thread 'time::parse::relative::various' panicked at gix-date\tests\time\parse.rs:209:9:
    assertion `left == right` failed: relative times differ
      left: [2024-11-08T21:10:19Z, 2024-11-08T21:10:19Z, 2024-07-05T21:10:19Z, 2024-07-05T21:10:19Z]
     right: [2024-11-08T21:10:19Z, 2024-11-08T21:10:19Z, 2024-07-05T20:10:19Z, 2024-07-05T21:10:19Z]
EliahKagan added a commit to EliahKagan/gitoxide that referenced this issue Nov 22, 2024
This duration, 40 weeks, serves to demonstrate that the actual
versus expected value disagreement of 1 hour is due to DST
adjustments. Even where DST is observed, adjustments would
ordinarily have been done twice in 40 weeks. So this case should
not fail, even when the 20 weeks case does.

However, the test remains imperfect, even assuming it is run with
the time zone set to local time and in a place where DST adustments
are made, because there will sometimes never have been exactly one
DST adjustment in any of the last 2, 20, or 40 weeks.

Better temporal coverage should probably be put in place, but
something like the 40 weeks case might still make sense to keep in,
so that we are more likely to catch a possible variant of GitoxideLabs#1696
where the time zone rules had at one time had daylight savings but
were changed to no longer have it (or vice versa). It's possible
that GitoxideLabs#1696 might be fixed by a change to the tested code, or to the
test, while still leaving such a variant in place.

Failure, when it occurs, now looks like:

    --- STDERR:              gix-date::date time::parse::relative::various ---
    thread 'time::parse::relative::various' panicked at gix-date\tests\time\parse.rs:211:9:
    assertion `left == right` failed: relative times differ
      left: [2024-11-08T21:14:45Z, 2024-11-08T21:14:45Z, 2024-07-05T21:14:45Z, 2024-07-05T21:14:45Z, 2024-02-16T21:14:45Z, 2024-02-16T21:14:45Z]
     right: [2024-11-08T21:14:45Z, 2024-11-08T21:14:45Z, 2024-07-05T20:14:45Z, 2024-07-05T21:14:45Z, 2024-02-16T21:14:45Z, 2024-02-16T21:14:45Z]
EliahKagan added a commit to EliahKagan/gitoxide that referenced this issue Nov 22, 2024
This duration, 40 weeks, serves to demonstrate that the actual
versus expected value disagreement of 1 hour is due to DST
adjustments. Even where DST is observed, adjustments would
ordinarily have been done twice in 40 weeks. So this case should
not fail, even when the 20 weeks case does.

However, the test remains imperfect, even assuming it is run with
the time zone set to local time and in a place where DST adustments
are made, because there will sometimes never have been exactly one
DST adjustment in any of the last 2, 20, or 40 weeks.

Better temporal coverage should probably be put in place, but
something like the 40 weeks case might still make sense to keep in,
so that we are more likely to catch a possible variant of GitoxideLabs#1696
where the time zone rules had at one time had daylight savings but
were changed to no longer have it (or vice versa). It's possible
that GitoxideLabs#1696 might be fixed by a change to the tested code, or to the
test, while still leaving such a variant in place.

Failure, when it occurs, now looks like:

    --- STDERR:              gix-date::date time::parse::relative::various ---
    thread 'time::parse::relative::various' panicked at gix-date\tests\time\parse.rs:211:9:
    assertion `left == right` failed: relative times differ
      left: [2024-11-08T21:14:45Z, 2024-11-08T21:14:45Z, 2024-07-05T21:14:45Z, 2024-07-05T21:14:45Z, 2024-02-16T21:14:45Z, 2024-02-16T21:14:45Z]
     right: [2024-11-08T21:14:45Z, 2024-11-08T21:14:45Z, 2024-07-05T20:14:45Z, 2024-07-05T21:14:45Z, 2024-02-16T21:14:45Z, 2024-02-16T21:14:45Z]
EliahKagan added a commit to EliahKagan/gitoxide that referenced this issue Nov 23, 2024
The idea here is to make it so that:

1. If we are using local time, in a time zone with DST adjustments,
   then no matter when we run the test, at least one "X weeks ago"
   case will have exactly one DST adjustment between then and now.
   (Thus, if our local time can observe GitoxideLabs#1696, it will.)

2. It is easy to verify that this is so.

The rules are somewhat variable, and adjustments cannot be
approximated as happening every half-year:

- In multiple time zones with DST-related adjustments, an
  adjustment and its reversal can be separated by only four months
  (November to March).

- Countries may institute rules that may be hard for people not
  familiar with them to anticipate. For example, Morocco used to
  have DST-related adjustments; when it did, if the (lunar) month
  of Ramadan occurred during the usual (Gregorian-based) DST
  period, standard time (rather than DST) was in effect during
  Ramadan. Thus, in some years, there were four DST-related clock
  adjustments.

  It is hard to predict (or, at least, I do not know how to
  predict) if, or what kind of, new DST adjustments may be put in
  place somewhere.

With that said, the current test probably has more intervals than
necessary. Furthermore, if it turns out to be feasible, it may be
better to make this a true unit test by using a set time instad of
`now` to take "X weeks ago" times relative to, and by somehow
specifying or mocking out the time zone, so as to test the tricky
cases in exactly the same way no matter where, when, or in what
local configuration the test is run.

Failure, when it occurs, now looks like:

    --- STDERR:              gix-date::date time::parse::relative::various ---
    thread 'time::parse::relative::various' panicked at gix-date\tests\time\parse.rs:216:9:
    assertion failed: `(left == right)`: relative times differ

    Diff < left / right > :
     [
         2024-11-09T04:22:21Z,
    <    2024-08-17T04:22:21Z,
    <    2024-05-25T04:22:21Z,
    >    2024-08-17T03:22:21Z,
    >    2024-05-25T03:22:21Z,
         2024-03-02T04:22:21Z,
         2023-12-09T04:22:21Z,
         2024-11-09T04:22:21Z,
         2024-08-17T04:22:21Z,
         2024-05-25T04:22:21Z,
         2024-03-02T04:22:21Z,
         2023-12-09T04:22:21Z,
     ]

Equivalent cases with weeks and minutes were interleaved before,
but now all the "weeks" cases are listed before all the "minutes"
cases, because that produces more readable output when
`pretty_assertions::assert_eq!` is used.
@Byron
Copy link
Member

Byron commented Nov 23, 2024

Thanks so much for the detailed report.

I understand that Git interprets 2 weeks ago as the amount of seconds passed, whereas gix-date thanks to jiff now considers it to be 14 calendar days passed, which correctly incorporates the local time as affected by the TZ environment variable.

The dependence on the TZ environment variable was surprising to me and usually gitoxide crates make that explicit and/or overridable, but the way it's currently implemented here (or how jiff is used) makes this implicit.

Is it correct to say that in theory, if that time computation would be using UTC always without implicitly picking up the local time, it should work as expected?
If that's true, then using a jiff API that doesn't rely on the system timezone should constitute a fix.

Let me CC @BurntSushi here just so that he is informed, and might be able to share ideas on how to deal with this as well.

On another note

5 months ago would be nearly ideal, but cannot be used because we don't yet parse months or years, so that would give InvalidDateString { input: "5 months ago" }. (The absence of parsing of months or years might be considered a bug, but it is not this bug.)

I wasn't aware actually, and am surprised this is the case as it should be trivial to support. But maybe it wasn't done yet as with time, nothing truly is trivial 😅.

@EliahKagan
Copy link
Member Author

Let me CC @BurntSushi here just so that he is informed, and might be able to share ideas on how to deal with this as well.

Good idea. #1474 seems relevant, and especially this part, which I should've mentioned in the issue description, though I don't feel that I have a good handle on what ought to be done:

now.ok_or(Error::MissingCurrentTime).and_then(|now| {
let ts = Timestamp::try_from(now).map_err(|_| Error::RelativeTimeConversion)?;
// N.B. This matches the behavior of this code when it was
// written with `time`, but we might consider using the system
// time zone here. If we did, then it would implement "1 day
// ago" correctly, even when it crosses DST transitions. Since
// we're in the UTC time zone here, which has no DST, 1 day is
// in practice always 24 hours. ---AG
let zdt = ts.to_zoned(TimeZone::UTC);
zdt.checked_sub(span).map_err(|_| Error::RelativeTimeConversion)
})

This is discussed at #1474 (comment), which I hadn't noticed before.


Some other discussion in #1474 is relevant to:

5 months ago would be nearly ideal, but cannot be used because we don't yet parse months or years [...]

I wasn't aware actually, and am surprised this is the case as it should be trivial to support. But maybe it wasn't done yet as with time, nothing truly is trivial 😅.

Per #1474 (comment), it looks like parsing months and years just never ended up being added, but that it is expected to work since #1474.

@Byron
Copy link
Member

Byron commented Nov 23, 2024

Thanks for digging that out!

The code example seems to show that it already attempts to be timezone independent, but it might not be working as expected? Something there must read TZ even though it shouldn't?
It feels we are onto something here.

@BurntSushi
Copy link
Contributor

Is it correct to say that in theory, if that time computation would be using UTC always without implicitly picking up the local time, it should work as expected?

Yes. In the UTC "time zone," there are zero time zone transitions. So days are always 24 hours.

The code example seems to show that it already attempts to be timezone independent, but it might not be working as expected? Something there must read TZ even though it shouldn't?

It looks like it's the test itself?

https://github.com/BurntSushi/gitoxide.git/blob/cb3149fec63dc9e366baf0399040d20161616b22/gix-date/tests/time/parse.rs#L202-L214

That Zoned::try_from will automatically use the system time zone.

I wasn't aware actually, and am surprised this is the case as it should be trivial to support. But maybe it wasn't done yet as with time, nothing truly is trivial 😅.

When I worked on the patch to add Jiff support, I didn't do months/years because that wasn't supported before. So I didn't want to add anything more than needed. But yes, Jiff should handle it fine. Jiff should make it trivial (assuming you give it a relative date, which I believe you do), but lots of things get this wrong. For example:

https://github.com/tailhook/humantime/blob/12ce6f50894a56a410b390e5608ac9db8afe2407/src/duration.rs#L305-L307

@BurntSushi
Copy link
Contributor

If the goal is to match git's behavior here, then yeah, I think you need to interpret 2 weeks ago as a fixed amount of time that never changes due to time zone transitions. I would personally argue that git is wrong here, or at least, git's behavior is questionable.

@Byron
Copy link
Member

Byron commented Nov 23, 2024

Thanks so much for chiming in!

And thanks for the hint about the test - it seems the code itself uses UTC, so it will produce 'flat' time, but the test asserts with timezoned time. A fix should thus be in the test itself, it should use UTC, at least if it wants to match Git.

I would personally argue that git is wrong here, or at least, git's behavior is questionable.

Probably the way these relative dates are used in is so fuzzy that humans also don't have a clue if it's right or wrong, so nobody filed a bug-report for this yet.
Something I'd be afraid of is people comparing both implementations, and mismatches will then be reported here. That seems like a good reason to match Git even though one could be more correct.

When I worked on the patch to add Jiff support, I didn't do months/years because that wasn't supported before. So I didn't want to add anything more than needed.

Yes, I remember that, but don't remember who did what and why to which extend. This is to say I my note wasn't meant as criticism, just genuine surprise.

In any case, I think now we seem to know how to proceed here, which could be…

  • fix the test to not rely on the system timezone
  • (optionally) add the remaining conversions for months and years for completeness

And that should fix this issue if I am not missing something.

@BurntSushi
Copy link
Contributor

That sounds right to me!

EliahKagan added a commit to EliahKagan/gitoxide that referenced this issue Nov 25, 2024
This extends `gix_date::parse::relative::span` to recognize times
with "months" and "years", as in "3 months ago" and "2 years ago".

The actual change here is very simple, because it is directly
facilitated by GitoxideLabs#1474.

The units "seconds", "minutes", "hours", "days", and "weeks"
continue to be recognized (as before). Relative times like
"1 year, 2 months ago" remain unrecognized.

For background, see 43b6c06 (GitoxideLabs#498), c5c6bf6,
GitoxideLabs#1474 (comment),
and
GitoxideLabs#1696 (comment).

Note that this change does not fix issue GitoxideLabs#1696, which continues to
apply to the intepretation of "days" and "weeks", and now also
applies in the same way to the interpretation of "months" and
"years". (It continues not to apply to the interpretation of
"seconds", "minutes", and "hours".)

The tests are updated to cover "months" and "years", as well as to
exercise a wider range of relative times, including showing which
units (e.g. "days") are affected by GitoxideLabs#1696. A few sub-cases, of
those adjusted or added here, test strings from a related test in
Git, to allow comparison. But most are not related to cases there.

As before, the tests pass on CI, or when the time zone is otherwise
UTC, or with local time zones that never have DST adjustments.

The sub-cases are reordered to be monotone increasing in the
magnitude of the relative intervals tested (and thus monotone
decreasing in the associated absolute timestamps), and the original
input is kept zipped into the `actual` and `expected` arrays being
compared. This way, if the assertion fails (such as due to GitoxideLabs#1696),
it is clear and readable which sub-cases failed and exactly how, as
well as what all the sub-cases are and what each sub-case tests.

Reordering the sub-cases and preserving the inputs they parse in
this way avoids the disadvantages of GitoxideLabs#1697 while keeping, and
expanding on, its benefits.

Failure, where it occurs, now looks like:

    --- STDERR:              gix-date::date time::parse::relative::various ---
    thread 'time::parse::relative::various' panicked at gix-date\tests\time\parse.rs:252:9:
    assertion failed: `(left == right)`: relative times differ

    Diff < left / right > :
     [
         (
             "5 seconds ago",
             2024-11-24T23:51:49Z,
         ),
         (
             "5 minutes ago",
             2024-11-24T23:46:54Z,
         ),
         (
             "5 hours ago",
             2024-11-24T18:51:54Z,
         ),
         (
             "5 days ago",
             2024-11-19T23:51:54Z,
         ),
         (
             "3 weeks ago",
             2024-11-03T23:51:54Z,
         ),
         (
             "21 days ago",
             2024-11-03T23:51:54Z,
         ),
         (
             "504 hours ago",
             2024-11-03T23:51:54Z,
         ),
         (
             "30240 minutes ago",
             2024-11-03T23:51:54Z,
         ),
         (
             "2 months ago",
    <        2024-09-24T23:51:54Z,
    >        2024-09-24T22:51:54Z,
         ),
         (
             "1460 hours ago",
             2024-09-25T03:51:54Z,
         ),
         (
             "87600 minutes ago",
             2024-09-25T03:51:54Z,
         ),
         (
             "14 weeks ago",
    <        2024-08-18T23:51:54Z,
    >        2024-08-18T22:51:54Z,
         ),
         (
             "98 days ago",
    <        2024-08-18T23:51:54Z,
    >        2024-08-18T22:51:54Z,
         ),
         (
             "2352 hours ago",
             2024-08-18T23:51:54Z,
         ),
         (
             "141120 minutes ago",
             2024-08-18T23:51:54Z,
         ),
         (
             "5 months ago",
    <        2024-06-24T23:51:54Z,
    >        2024-06-24T22:51:54Z,
         ),
         (
             "3650 hours ago",
             2024-06-25T21:51:54Z,
         ),
         (
             "219000 minutes ago",
             2024-06-25T21:51:54Z,
         ),
         (
             "26 weeks ago",
    <        2024-05-26T23:51:54Z,
    >        2024-05-26T22:51:54Z,
         ),
         (
             "182 days ago",
    <        2024-05-26T23:51:54Z,
    >        2024-05-26T22:51:54Z,
         ),
         (
             "4368 hours ago",
             2024-05-26T23:51:54Z,
         ),
         (
             "262080 minutes ago",
             2024-05-26T23:51:54Z,
         ),
         (
             "8 months ago",
    <        2024-03-24T23:51:54Z,
    >        2024-03-24T22:51:54Z,
         ),
         (
             "5840 hours ago",
             2024-03-26T15:51:54Z,
         ),
         (
             "350400 minutes ago",
             2024-03-26T15:51:54Z,
         ),
         (
             "38 weeks ago",
             2024-03-03T23:51:54Z,
         ),
         (
             "266 days ago",
             2024-03-03T23:51:54Z,
         ),
         (
             "6384 hours ago",
             2024-03-03T23:51:54Z,
         ),
         (
             "383040 minutes ago",
             2024-03-03T23:51:54Z,
         ),
         (
             "11 months ago",
             2023-12-24T23:51:54Z,
         ),
         (
             "8030 hours ago",
             2023-12-26T09:51:54Z,
         ),
         (
             "481800 minutes ago",
             2023-12-26T09:51:54Z,
         ),
         (
             "14 months ago",
    <        2023-09-24T23:51:54Z,
    >        2023-09-24T22:51:54Z,
         ),
         (
             "21 months ago",
             2023-02-24T23:51:54Z,
         ),
         (
             "2 years ago",
             2022-11-24T23:51:54Z,
         ),
         (
             "20 years ago",
             2004-11-24T23:51:54Z,
         ),
         (
             "630720000 seconds ago",
             2004-11-29T23:51:54Z,
         ),
     ]
@EliahKagan
Copy link
Member Author

  • (optionally) add the remaining conversions for months and years for completeness

I've done just this part in #1702, since it seems like it's been wanted for some time and there's no strong reason to put it off. That PR does not fix this issue, however. It actually exacerbates it, in that the ambiguity described in this issue will apply not just to "days" and "weeks" but also to the newly recognized "months" and "years". However, the changes to the test that seem natural to do alongside recognizing those units (and that test that they are recognized) also make the contours of this issue clearer.

EliahKagan added a commit to EliahKagan/gitoxide that referenced this issue Nov 25, 2024
This extends `gix_date::parse::relative::span` to recognize times
with "months" and "years", as in "3 months ago" and "2 years ago".
Those units are supported by Git.

The actual change here to the parsing code is very simple, because
it is directly facilitated by GitoxideLabs#1474.

The units "seconds", "minutes", "hours", "days", and "weeks"
continue to be recognized (as before). Relative times like
"1 year, 2 months ago" remain unrecognized.

For background, see 43b6c06 (GitoxideLabs#498), c5c6bf6,
GitoxideLabs#1474 (comment),
and
GitoxideLabs#1696 (comment).

Note that this specific change does not itself fix issue GitoxideLabs#1696,
which applies to the intepretation of "days" and "weeks", and now
also applies in the same way to the interpretation of "months" and
"years". (It continues not to apply to the interpretation of
"seconds", "minutes", and "hours".)

The tests are updated to cover "months" and "years", as well as to
exercise a wider range of relative times, including showing which
units (e.g. "days") are affected by GitoxideLabs#1696. A few sub-cases, of
those adjusted or added here, test strings from a related test in
Git, to allow comparison. But most are not related to cases there.

As before, the tests pass on CI, or when the time zone is otherwise
UTC, or with local time zones that never have DST adjustments.

The sub-cases are reordered to be monotone increasing in the
magnitude of the relative intervals tested (and thus monotone
decreasing in the associated absolute timestamps), and the original
input is kept zipped into the `actual` and `expected` arrays being
compared. This way, if the assertion fails (such as due to GitoxideLabs#1696),
it is clear and readable which sub-cases failed and exactly how, as
well as what all the sub-cases are and what each sub-case tests.

Reordering the sub-cases and preserving the inputs they parse in
this way avoids the disadvantages of GitoxideLabs#1697 while keeping, and
expanding on, its benefits.

Failure, where it occurs, now looks like:

    --- STDERR:              gix-date::date time::parse::relative::various ---
    thread 'time::parse::relative::various' panicked at gix-date\tests\time\parse.rs:252:9:
    assertion failed: `(left == right)`: relative times differ

    Diff < left / right > :
     [
         (
             "5 seconds ago",
             2024-11-24T23:51:49Z,
         ),
         (
             "5 minutes ago",
             2024-11-24T23:46:54Z,
         ),
         (
             "5 hours ago",
             2024-11-24T18:51:54Z,
         ),
         (
             "5 days ago",
             2024-11-19T23:51:54Z,
         ),
         (
             "3 weeks ago",
             2024-11-03T23:51:54Z,
         ),
         (
             "21 days ago",
             2024-11-03T23:51:54Z,
         ),
         (
             "504 hours ago",
             2024-11-03T23:51:54Z,
         ),
         (
             "30240 minutes ago",
             2024-11-03T23:51:54Z,
         ),
         (
             "2 months ago",
    <        2024-09-24T23:51:54Z,
    >        2024-09-24T22:51:54Z,
         ),
         (
             "1460 hours ago",
             2024-09-25T03:51:54Z,
         ),
         (
             "87600 minutes ago",
             2024-09-25T03:51:54Z,
         ),
         (
             "14 weeks ago",
    <        2024-08-18T23:51:54Z,
    >        2024-08-18T22:51:54Z,
         ),
         (
             "98 days ago",
    <        2024-08-18T23:51:54Z,
    >        2024-08-18T22:51:54Z,
         ),
         (
             "2352 hours ago",
             2024-08-18T23:51:54Z,
         ),
         (
             "141120 minutes ago",
             2024-08-18T23:51:54Z,
         ),
         (
             "5 months ago",
    <        2024-06-24T23:51:54Z,
    >        2024-06-24T22:51:54Z,
         ),
         (
             "3650 hours ago",
             2024-06-25T21:51:54Z,
         ),
         (
             "219000 minutes ago",
             2024-06-25T21:51:54Z,
         ),
         (
             "26 weeks ago",
    <        2024-05-26T23:51:54Z,
    >        2024-05-26T22:51:54Z,
         ),
         (
             "182 days ago",
    <        2024-05-26T23:51:54Z,
    >        2024-05-26T22:51:54Z,
         ),
         (
             "4368 hours ago",
             2024-05-26T23:51:54Z,
         ),
         (
             "262080 minutes ago",
             2024-05-26T23:51:54Z,
         ),
         (
             "8 months ago",
    <        2024-03-24T23:51:54Z,
    >        2024-03-24T22:51:54Z,
         ),
         (
             "5840 hours ago",
             2024-03-26T15:51:54Z,
         ),
         (
             "350400 minutes ago",
             2024-03-26T15:51:54Z,
         ),
         (
             "38 weeks ago",
             2024-03-03T23:51:54Z,
         ),
         (
             "266 days ago",
             2024-03-03T23:51:54Z,
         ),
         (
             "6384 hours ago",
             2024-03-03T23:51:54Z,
         ),
         (
             "383040 minutes ago",
             2024-03-03T23:51:54Z,
         ),
         (
             "11 months ago",
             2023-12-24T23:51:54Z,
         ),
         (
             "8030 hours ago",
             2023-12-26T09:51:54Z,
         ),
         (
             "481800 minutes ago",
             2023-12-26T09:51:54Z,
         ),
         (
             "14 months ago",
    <        2023-09-24T23:51:54Z,
    >        2023-09-24T22:51:54Z,
         ),
         (
             "21 months ago",
             2023-02-24T23:51:54Z,
         ),
         (
             "2 years ago",
             2022-11-24T23:51:54Z,
         ),
         (
             "20 years ago",
             2004-11-24T23:51:54Z,
         ),
         (
             "630720000 seconds ago",
             2004-11-29T23:51:54Z,
         ),
     ]
@Byron Byron closed this as completed in 856b385 Nov 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants