Skip to content

Fix POSIX TZ edge cases: midnight fall-back, sub-hour offsets, quoted names, abolished DST - #5

Merged
isaric merged 1 commit into
mainfrom
fix/posix-edge-cases
Sep 16, 2026
Merged

isaric merged 1 commit into
mainfrom
fix/posix-edge-cases

Conversation

@isaric

@isaric isaric commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

What

FormatTimeZone produced wrong strings for several classes of zone. This rewrites the transition logic and adds table-driven tests checked against the POSIX strings in the IANA tzdata footers.

Zone Before After
Asia/Beirut (falls back at 00:00) EET-2EEST,M3.5.0/0,M10.4.6/24 EET-2EEST,M3.5.0/0,M10.5.0/0
Asia/Kolkata IST-5 IST-5:30
Europe/Istanbul (DST abolished) +03-3+03,M1.3.2/6:14:07,M1.1.1/0 <+03>-3
Europe/Zagreb CET-1CEST,M3.5.0/2,M10.5.0/3 CET-1CEST,M3.5.0,M10.5.0/3

How

  • Each transition is expressed in the wall clock in force just before it (POSIX semantics) instead of taking the post-transition local hour and adjusting by ±1. That removes the h1-- / h2++ hacks and fixes fall-backs that cross midnight.
  • The hour is omitted only when it is 02:00 (the POSIX default). Previously it was omitted whenever both rules happened to share an hour, which is wrong for e.g. /0,/0.
  • Week ordinal 5 ("last") is used only when no later same weekday exists in the month; days 22–24 are now 4.
  • Offsets keep minutes and seconds; numeric abbreviations are quoted.
  • Abolished-DST zones are detected (Go returns a far-future bound there, not a zero one).

Output change

Rule 1 no longer carries an explicit /2. Both forms are valid POSIX and parsed identically by every TZ parser I know of, but if a consumer string-compares, it will notice. GetPosixOffset is unchanged and marked deprecated because it truncates minutes.

Tests

go test -race ./... on Go 1.19 and stable via the updated workflow: 13 zones incl. Sydney (southern hemisphere), London (01:00 transition), Beirut, Kolkata, Tokyo, Istanbul, São Paulo, UTC.

🤖 Generated with Claude Code

…s, numeric zone names and abolished DST

- express each transition in the pre-transition wall clock instead of
  adjusting hours by +/-1; fixes zones such as Asia/Beirut (M10.5.0/0)
- omit the hour only when it is 02:00, the POSIX default; the old code
  omitted it whenever both rules shared an hour
- render offsets with minutes/seconds (IST-5:30) instead of truncating
- quote non-alphabetic abbreviations (<+03>-3) as POSIX requires
- zones that abolished DST no longer get invented rules
- week ordinal 5 ("last") only when no later same weekday exists
- table-driven tests against tzdata strings, incl. southern hemisphere
- CI: gofmt, vet, race tests on Go 1.19 and stable; publish on release

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@isaric
isaric merged commit fc0a956 into main Sep 16, 2026
3 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.

1 participant