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.
The table is meant to cover the same abbreviations GNU date supports, but
these four were missing, so `date -d "2024-01-15 12:00 MEZ"` failed here
while GNU accepts it. Offsets were measured against GNU coreutils: MEZ is
+1, MESZ and MEST are +2, and KST is +9.
Downstream this is what pushes uutils/coreutils to keep its own abbreviation
table in date.rs and parse the string in fragments, which is the root of
uutils/coreutils#13865.
* fix: add unix_epoch_second() with floor semantics for negative timestamps
jiff::Timestamp::as_second() uses truncation toward zero, so a timestamp
of -1.5s returns -1. GNU date's %s format uses floor (toward negative
infinity), returning -2 for the same value.
Add ParsedDateTime::unix_epoch_second() which corrects for this by
detecting a negative subsec_nanosecond and subtracting 1 from the
truncated second. Also add subsec_nanosecond() that re-normalizes to
the non-negative [0, 1_000_000_000) range matching the GNU timespec
convention.
The uutils date command can use these methods to produce correct %s
output for negative fractional epoch inputs like @-1.5.
Fixes#283
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: cover extended epoch helper paths
* test: cover direct extended epoch accessors
* test: remove panic branch to land full patch coverage
The let-else with panic! left one line reachable only on test failure,
which showed up as uncovered on codecov/patch. Use matches! + assert!
to reach the same invariant with 100% patch coverage.
* fix: support HH:MM am/pm time formats in combined date-time parsing
The combined date-time parser (e.g., "2024-06-15 12:00 PM") used
time::iso which only accepts 24-hour notation. When an am/pm suffix
followed, iso would greedily consume the time portion, leaving the
meridiem orphaned and causing a parse error.
Switch to time::parse which tries am_pm_time first (with fallback to
iso), so inputs like "2024-06-15 12:00 PM", "2024-06-15 11:30am", and
"2024-06-15 3:00 PM" are now accepted. Case insensitivity is already
handled by parse_items lowercasing the input before parsing.
Fixes#282
* perf: keep ISO fast path for combined datetime parsing
* fix(offset): accept "ut" / "UT" as a bare UTC timezone abbreviation
GNU date -d accepts "ut" and "UT" (Universal Time) as synonyms for UTC.
parse_datetime rejected them because the timezone_name_to_offset table
contained "utc" and "gmt" but was missing "ut".
Add "ut" => "+0" to the lookup table. "gmt" was already present and
continues to work unchanged.
Add regression tests:
- offset-level: timezone_name_without_offset now includes "ut"
- lib-level: test_bare_utc_timezone_abbreviations covers all four
forms (ut, UT, gmt, GMT) end-to-end
Fixes#280
* style: run rustfmt on bare UT timezone test
A fixed offset (e.g. the "UTC" keyword) was applied after relative
items, so "1970/01/01 UTC N seconds" added N seconds to a local
wall-clock that could drift across a DST boundary before being
re-anchored at the offset, yielding an instant off by the DST gap.
Anchor the offset first, then apply relative items.
Fixesuutils/coreutils#12555.