-
-
Notifications
You must be signed in to change notification settings - Fork 318
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
Comments
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]
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]
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]
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.
Thanks so much for the detailed report. I understand that Git interprets The dependence on the Is it correct to say that in theory, if that time computation would be using 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
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 😅. |
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: gitoxide/gix-date/src/parse.rs Lines 134 to 144 in 438ee47
This is discussed at #1474 (comment), which I hadn't noticed before. Some other discussion in #1474 is relevant to:
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. |
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 |
Yes. In the UTC "time zone," there are zero time zone transitions. So days are always 24 hours.
It looks like it's the test itself? That
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: |
If the goal is to match git's behavior here, then yeah, I think you need to interpret |
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.
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.
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…
And that should fix this issue if I am not missing something. |
That sounds right to me! |
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, ), ]
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. |
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, ), ]
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 othergix-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:
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 was2 weeks
ago the current time minus the usual 336 hours? Or was2 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: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. Ifgit
behaved analogously to the current behavior ofgix-date
, then I would expect the3 weeks ago
and5 months ago
cases, as well as some others, to have failed. I ran that test script at the tip of themaster
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 currentgix-date
behavior. It seems to me that this may be a reason to consider our existing test ingix-date
correct, and the current behavior of thegix-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 withfaketime
and thedate
command suggests that locale-sensitive interpretation of strings likeEST
andEDT
as time zone offsets does not always behave the way it did at the date and timefaketime
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 giveInvalidDateString { 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: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:
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.
The text was updated successfully, but these errors were encountered: