Description
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.