Hi,
Git 2.55.0 appears to store reftable reflog timezone offsets as signed HHMM integers, whereas the specification requires signed minutes.
https://git-scm.com/docs/reftable#_log_record states:
"tz_offset is the absolute number of minutes from GMT the
committer was at the time of the update."The specification also gives GMT+0230 as an example encoded as 150.
I reproduced the discrepancy on macOS arm64 by creating a SHA-1 reftable repository and committing with this date:
2026-09-27T12:00:00+05:30
An independent Python/zlib inspection of the resulting reftable found:
Stored timezone bytes: 02 12 = 530
Expected signed minutes: 01 4a = 330Both HEAD and refs/heads/main reflog entries contained 530. Git reads its own entries back correctly as +05:30, so its writer and reader appear internally consistent, but disagree with the specification.
Here is a reproducer using only Git and Python's standard library:
(
set -eu
proof_dir=$(mktemp -d)
cd "$proof_dir"
echo "Repository retained at: $proof_dir" export GIT_CONFIG_NOSYSTEM=1
export GIT_CONFIG_GLOBAL=/dev/null
export GIT_AUTHOR_DATE='2026-09-27T12:00:00+05:30'
export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" git --version
git init --initial-branch=main --object-format=sha1 \
--ref-format=reftable example git -C example \
-c user.name=Example \
-c user.email=example@example.com \
-c commit.gpgsign=false \
-c core.logAllRefUpdates=true \
commit --allow-empty -m "Timezone example" git -C example reflog show --format='%gD' \
--date=iso-strict refs/heads/mainpython3 - <<'PY' from pathlib import Path import zlib
directory = Path("example/.git/reftable")
for name in (directory / "tables.list").read_text().splitlines():
table = (directory / name).read_bytes()
assert table[:5] == b"REFT\x01"
footer = table[-68:]
log_start = int.from_bytes(footer[48:56], "big")
if not log_start:
continue assert table[log_start:log_start + 1] == b"g"
log = zlib.decompress(table[log_start + 4:]) # Fixture-specific: email, variable-length time, then timezone.
email = b"example@example.com"
search_from = 0
while (position := log.find(email, search_from)) != -1:
position += len(email)
while log[position] & 0x80:
position += 1
position += 1 raw = log[position:position + 2]
offset = int.from_bytes(raw, "big", signed=True)
print(f"Timezone bytes: {raw.hex(' ')}; integer: {offset}")
search_from = position + 2
PY
)The relevant output is:
refs/heads/main@{2026-09-27T12:00:00+05:30}
Timezone bytes: 02 12; integer: 530
Timezone bytes: 02 12; integer: 530Is this a known discrepancy? Which representation should interoperable implementations use? If either the implementation or specification changes, how should existing tables be interpreted, given that values such as 330 are valid under both interpretations?
Given that Git consistently writes and reads HHMM values, I suspect the practical resolution is to update the specification to match existing behavior. Are there other implementations or compatibility considerations that would prevent that?
Thanks, Josh
-- Josh McKinney joshka.net