Skip to content

fix(offset): support the military timezone letter j (local time) - #320

Merged
sylvestre merged 1 commit into
uutils:mainfrom
0xSoftBoi:fix/military-timezone-j
Aug 29, 2026
Merged

fix(offset): support the military timezone letter j (local time)#320
sylvestre merged 1 commit into
uutils:mainfrom
0xSoftBoi:fix/military-timezone-j

Conversation

@0xSoftBoi

Copy link
Copy Markdown
Contributor

GNU date accepts every letter ay as a military time zone. All of them are already in our table except one: j.

It is missing for a reason. j is the single letter that does not denote a fixed offset — it means local time, so it cannot be expressed as an Offset at all.

What GNU actually does

Verified against GNU coreutils 9.4, in TZ=America/New_York (EDT, UTC−4):

input GNU result meaning
8a 08:00 +01:00 UTC+1
8n 08:00 −01:00 UTC−1
8z 04:00 −0400 UTC
8j 08:00 −0400 local — identical to bare 8
2026-01-01 j 00:00 −0500 local, DST-aware
8foo invalid date unknown words are errors

The 2026-01-01 j row is the important one: it resolves to −05:00 in January and −04:00 in August. It follows the zone's DST rules, so no fixed offset can represent it.

The change

j is parsed as its own item and recorded on the builder as a flag rather than as an offset, so the local rule zone is used instead of a fixed one.

  • 8j, 8 j, j 8 and 8J all work.
  • It is still a time zone item, so 8j utc, 8 utc j and 8 j j are rejected as a repeated zone, matching GNU.
  • The whole alphabetic word is consumed before comparing, so 8jan still parses as a date and jj is rejected rather than matching a j prefix.
  • Unknown words stay errors — 8foo still fails, as it does in GNU.

Verification

I compared every letter az against GNU coreutils 9.4, comparing resolved Unix timestamps in a DST zone:

8a gnu=1787641200  pd=1787641200  ok
...
8j gnu=1787659200  pd=1787659200  ok
...
8z gnu=1787644800  pd=1787644800  ok
=== mismatches: 0 ===

All 26 letters now resolve to the same instant as GNU. 403 tests pass, fmt and clippy clean.

Relationship to #286

This supersedes #286, and confirms @sylvestre's review there was correct.

#286 assumed GNU silently discards unrecognized trailing alphabetic tokens after a number. It does not — date -d '8foo' is an invalid date. The only such token GNU actually accepts is j, and for a reason #286 did not model: it is a time zone, not noise to be skipped.

#286 has also drifted five months behind main and its diff would revert several later fixes, so this is a fresh branch from current main rather than an update to it.

@codecov

codecov Bot commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.34%. Comparing base (1c7fdea) to head (180746e).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #320   +/-   ##
=======================================
  Coverage   99.33%   99.34%           
=======================================
  Files          21       21           
  Lines        4075     4125   +50     
  Branches      129      136    +7     
=======================================
+ Hits         4048     4098   +50     
  Misses         26       26           
  Partials        1        1           
Flag Coverage Δ
macos_latest 99.34% <100.00%> (?)
ubuntu_latest 99.34% <100.00%> (+<0.01%) ⬆️
windows_latest 13.72% <3.70%> (-0.20%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@0xSoftBoi
0xSoftBoi force-pushed the fix/military-timezone-j branch from d2cd43c to 87a29cf Compare August 25, 2026 05:10
GNU `date` accepts every letter `a`-`y` as a military time zone, and all
of them are already in our table except `j`. It is missing because it is
the one letter that does not denote a fixed offset: `j` means *local
time*, so it cannot be expressed as an `Offset` at all.

    $ TZ=America/New_York date -d '8j'            # 08:00 -0400
    $ TZ=America/New_York date -d '2026-01-01 j'  # 00:00 -0500
    $ TZ=America/New_York date -d '8z'            # 04:00 -0400

Because it follows the base zone's DST rules rather than pinning an
offset, it is represented as its own item and recorded on the builder as
a flag rather than as an offset.

- `j` parses as its own item, so `8j`, `8 j`, `j 8` and `8J` all work.
- It is still a time zone item, so `8j utc`, `8 utc j` and `8 j j` are
  rejected as a repeated zone, matching GNU.
- The whole alphabetic word is consumed before comparing, so `8jan` still
  parses as a date and `jj` is rejected rather than matching a `j` prefix.
- Unknown words remain errors: `8foo` still fails, as it does in GNU.

Verified against GNU coreutils 9.4: all 26 letters `a`-`z` now resolve to
the same instant as GNU, in both a DST and a non-DST base zone.
@sylvestre
sylvestre force-pushed the fix/military-timezone-j branch from 87a29cf to 180746e Compare August 28, 2026 15:03
@sylvestre
sylvestre merged commit 390c747 into uutils:main Aug 29, 2026
21 checks passed
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 this pull request may close these issues.

2 participants