gix-date: accept a timezone offset after a short compact ISO8601 time - #2985
Merged
Sebastian Thiel (Byron) merged 2 commits intoSep 9, 2026
Merged
Conversation
Contributor
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Contributor
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7c5ae10483
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "Codex (@codex) review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".
parse_compact_iso8601 documents `20080214T2030` and `20080214T20` as supported "With optional timezone", but split_time_and_offset only treats a sign as the start of an offset from index 5 onwards, which assumes a time of at least `HH:MM`. parse_time_component accepts times of length 2 and 4 as well, so `20080214T2030-0400` and `20080214T20-0400` failed to parse while `20080214T20:30-04:00` succeeded. Lower the bound to 2, the length of the shortest time component this function can produce.
Sebastian Thiel (Byron)
force-pushed
the
fix-short-time-offset-split
branch
from
September 9, 2026 15:30
b3af20a to
a118bb4
Compare
Member
|
Good catch! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Disclosure
This change was prepared and verified by an AI agent (Claude, operating through Claude Code) running under my account, per
CONTRIBUTING.md"Prevent agent impersonation". Everything below - the diff, the tests, the mutation table and the differential probe - was produced and run by the agent on my machine. I will answer review questions myself.The bug
parse_compact_iso8601documents four accepted time shapes, each "With optional timezone" (gix-date/src/parse/git.rs:41-47):The last two are false today.
split_time_and_offsetonly recognises a sign as the start of an offset when it sits at index 5 or later (gix-date/src/parse/git.rs:165-170):HH:MMis five characters, so the sign lands at index 5 and passes. ButHHMMputs the sign at index 4, andHHputs it at index 2. In both cases no offset is found, the sign and the offset digits stay glued to the time,parse_time_componentcannot parse the result, andgix_date::parsereturns an error:main20080214T20:30-04:00OkOk(unchanged)20080214T2030-04:00ErrOk(1203035400, -14400)20080214T2030-0400ErrOk(1203035400, -14400)20080214T20-0400ErrOk(1203033600, -14400)Why
2The comment above the loop derives the bound from
HH:MM, but the function it feeds accepts a shorter time than that.parse_time_componentalready handles a bare two-character hour (gix-date/src/parse/git.rs:197-219):So the splitter's bound assumes a time of at least five characters while the consumer it feeds accepts times of length 2 and 4. Two characters is the shortest time this function can produce, so it is the earliest index at which an offset can legitimately begin.
The index-based bound also makes the result depend on whitespace rather than on the value. At
main:Same time, same offset; the space is what pushes the sign to index 5. With this change both return
Ok(1203035400, -14400).Who this reaches
gix::repository::identity::Personas::from_config_and_envparsesgitoxide.commit.authorDateandgitoxide.commit.committerDateand falls back on any parse error (gix/src/repository/identity.rs:153-155):So
gitoxide.commit.authorDate = 20080214T2030-0400does not produce an error - it silently records the current time on the commit instead of the requested one. The same string is also reachable throughconfig::tree::keys::Time::try_into_time(gix/src/config/tree/keys.rs:495) andconfig::tree::keys::validate::Time::validate(gix/src/config/tree/keys.rs:589), where it surfaces as a spurious validation failure.The change
Four tests were added to the existing
gix-date/tests/time/parse/compact_iso8601.rs.Verification
cargo test -p gix-date -p gix-hash -F gix-hash/sha1, with the source filetouched before every build so nothing is served from cache.src_md5is the md5 ofgix-date/src/parse/git.rsas compiled.src_md5main+ new tests (red)i >= 5f19749aa734948882d3a89c377efec83i >= 27d529b30acdc39b7c61e613419cfc149i >= 5c2d8505154ef860afb20a81ead9a1a37i >= 3eac36a8dc539ebfedb116e2cd6c7bc32hour_only_with_timezoneonly (59 / 1)i >= 135fa2ee27c56962414881ee44f159234i >= 00488b713344c809439c4b890de0b51caThe two surviving mutants deserve an explanation rather than a shrug, so I checked them against a generated corpus instead of guessing. A temporary integration test dumped
gix_date::parse(input, None)for 9,108 inputs (8,592 distinct) built from the cross product of four date prefixes (20080214T,2008-02-14T,2008-02-14,2008.02.14T), 23 time shapes (empty,2,20,203,2030,20304,203045, the colon forms, subsecond forms, and a few malformed ones), nine sign forms and eleven offset forms. The file was deleted afterwards and is not part of this PR.i >= 1produces byte-identical output to this PR on all 9,108 inputs (dd46a7b3eb1eb06e13172f3fa8c8fe52for both). It is an equivalent mutant: a sign at index 1 leaves a one-character time, which neither branch ofparse_time_componentcan parse, so both bounds returnNone. The empty failing set is expected here, not a coverage gap.i >= 0is not equivalent - it changes 45 of the 9,108 outputs. Every one of them has no time component at all (20080214T+04:00,2008-02-14 +4, and so on), whichmaincurrently reads as the time+04:00and answers04:00:00at offset 0. Turning those intoNonemay well be an improvement, but it is a separate behaviour change and I deliberately left it out of scope. That is the concrete reason the bound is2and not0.main, andi >= 3changes 63.Gates, all from a forced rebuild:
cargo +nightly fmt --all --check- exit 0cargo clippy -p gix-date --lib -- -D warnings -A unknown-lints- exit 0cargo test -p gix-date -p gix-hash -F gix-hash/sha1- exit 0, 60 + 58 + 2 + 1 + 1 passedTwo notes on what I could not check:
justis not installed here, so I ran the crate's tests directly rather than throughjust test; and a full-workspaceclippy --all-targetsfails ongix-tempfile(collapsible_if) both with and without this change, so I scoped the lint run to the crate I touched.One divergence worth knowing about
With this change
20080214T20-04:00(hour-only time, colon offset) returns(1203033600, -14400), which is the sane reading. Realgit 2.43.0returns1208617200for that string, i.e. it does not agree. The four tests I added deliberately use only forms where git and this parser agree, so nothing here pins gitoxide to the divergent case. Happy to restrict the bound further if matching git byte-for-byte on that input matters more than the documented behaviour.