5.x → 6.0.0, entry by entry
Installable As of 2026-08-21 — npm view @ultimat3/core version answers 7.0.0, so 6.0.0 is behind latest and every entry below is a step you take on the way to it. Run that command anyway rather than trusting this line; a version written into a page goes stale on the next tag.
Seven breaking entries, and the first is a runtime refusal with no compile error in front of it.
Start here — the one edit
Every single-label timezone name except UTC is refused. isValidTimeZone answers false, canonicalTimeZone answers undefined, assertTimeZone throws X_TIMEZONE_INVALID — and every @ultimat3/time formatter is downstream of that one call. 43 names change answer, tabulated once under the 6.0.0 section of CHANGELOG.md; that table is the source and is deliberately not copied here.
- formatDate(at, { locale, zone: 'CET' })
+ formatDate(at, { locale, zone: 'Europe/Paris' })Which class the name is in decides whether the swap is mechanical
| Class | Names | Replacement |
|---|---|---|
| geographic link — 24 of the 43 | Japan, GB, Hongkong, NZ, … | the Area/Location spelling: Asia/Tokyo, Europe/London, Asia/Hong_Kong, Pacific/Auckland. Textual — identical wall clock, identical offset |
| UTC alias | UCT, Universal, Zulu | UTC |
the GMT family | GMT, GMT0, GMT+0, GMT-0, Greenwich | Etc/GMT, which still renders the label GMT. Not UTC, which renders UTC — same instant, different text on any surface that prints the zone name |
| abbreviation | CET, EET, MET, WET, EST, MST, HST, EST5EDT, CST6CDT, MST7MDT, PST8PDT | none, and that is the defect. An abbreviation names no jurisdiction and carries no DST rule, so only the author knows which city's clock was meant: Europe/Paris for CET, America/New_York for EST5EDT, America/Phoenix for MST |
Etc/GMT+2 is unaffected — only a leading sign is a bare offset, and that + sits inside a real zone name. US/Eastern and Asia/Calcutta are unaffected too: a deprecated two-label alias is still Area/Location.
Where the names hide, and why no build error finds them
TimeZone is string in @ultimat3/time, so 'CET' compiles. Nothing fails until the call runs.
| Site | Spelling | At 6.0.0 |
|---|---|---|
| a formatter, or zone arithmetic | zone: on formatDate, formatDateTime, formatRange, zonePartsAt, … | throws X_TIMEZONE_INVALID on the first call |
| a scheduled task | tz: on task() | refused where the task is declared — task() validates through isValidTimeZone, so this one is caught at boot |
app.config.ts | defaultTimeZone | refused at boot — defineConfig validates through core's own statement of the structural rule, so a stale key is X_CONFIG_INVALID naming the field, with the swap in its fix: |
a client's x-timezone header | any of the 43 | no error — resolveTimeZone falls through to the configured default, so a hand-written client sending CET silently renders in your default zone. Browsers are unaffected: Intl.DateTimeFormat().resolvedOptions().timeZone is always Area/Location |
Find every candidate:
grep -rnE "(zone|tz|defaultTimeZone): *'[^/']+'" --include='*.ts' --include='*.tsx' .Run it from the app root. Every hit is a single-label zone; 'UTC' is the only one already correct.
Why it changed
Intl answers "can I format this", never "is this an IANA zone", and at ICU 78 the two stopped agreeing: Bun 1.4 resolves CET, EST, GMT and MST where ICU 75 threw. A runtime upgrade alone therefore reopened the "no date without an explicit IANA zone" rule — silently, and in the direction that fails dangerous, because an abbreviation carries no DST rule. The judgement is now structural instead of delegated: an identifier is Area/Location, and UTC is the one legal exception. That refuses the single-label backward links along with the abbreviations, and is meant to — no structural rule keeps CET out while letting Japan in, both being one label, and the alternative is a denylist that grows with every tzdata release. #251, and Timezones and dates for the rule it restores.
Fixed, and neither costs an edit
| Fix | What changes for you |
|---|---|
island JSX compiles through babel-preset-solid | client-side Solid reactivity inside an island works at all. An island containing JSX compiled to React.createElement and threw ReferenceError: React is not defined on first interaction, with the gate green. Two build-time dependencies join @ultimat3/cli; zero bytes reach your client bundle (#243) |
@ultimat3/core loads in a browser bundle | core's three module-scope AsyncLocalStorage constructions — the request context, the active span, the impersonation reason — moved onto one lazy seam, so @ultimat3/ui no longer throws TypeError: undefined is not a constructor at module evaluation (#244). Six more constructions outside core were untouched at 6.0.0 and carry the same defect — @ultimat3/db, @ultimat3/entity, @ultimat3/ai; they are [Unreleased], along with the guard that makes the rule a build error (#255) |
Rebuild to pick either up.