Skip to content

Releases: DirazCoder/temporal-fmt

0.9.71

Choose a tag to compare

@DirazCoder DirazCoder released this 16 Sep 16:59

temporal-fmt v0.9.71

Added

  • numberingSystem / parseNumberingSystem accept 'auto' — resolves
    whichever numeral system the call's own locale actually uses, via
    Intl.NumberFormat(locale).resolvedOptions().numberingSystem, instead of
    making the caller hardcode a system code per locale. { numberingSystem: 'auto', locale: 'ar-EG' } gets Arabic-Indic digits, bn-BD gets Bengali,
    en-US stays ASCII — same option, no per-locale branching in caller code.
    Works the same on the parse side, and through createConfig(): a config's
    numberingSystem: 'auto' resolves per call against whichever locale that
    call ends up using, so one shared config can drive different digits for
    different callers instead of pinning a single system. Default's still
    'latn', so if you're not passing the option, nothing about your existing
    calls changes. When a locale's native system isn't one this library can
    transliterate — Thai, Lao, Myanmar — 'auto' falls back to 'latn' rather
    than throwing; only a malformed locale tag throws.

Fixed

  • MMMM/MMM/EEEE/EEE (and stand-alone LLLL/cccc) rendered the
    wrong month or weekday for dates before October 15, 1582. Turns out ICU's
    gregory calendar applies a Julian cutover at that date — Intl.Date- TimeFormat was silently reinterpreting anything earlier under Julian
    rules, even though Temporal itself is proleptic Gregorian throughout.
    1500-07-05 would come back labeled "June," on the wrong weekday, because
    Julian ran about 10 days behind proleptic Gregorian by then. Numeric tokens
    were never affected — they don't touch Intl at all, only the name lookups
    did. Fixed by never handing Intl the historical date in the first place:
    the month/weekday number now comes straight off Temporal's own proleptic
    fields, then gets looked up against a safe modern reference date (the same
    ones the locale vocab already builds from, so format output still parses
    straight back). Non-Gregorian-calendar objects — Hebrew, Islamic — were
    never touched by the bug and aren't touched by the fix either; ICU doesn't
    apply this cutover to them, so that path was already correct.

Docs

  • README's "Known limitations" section is gone. Every item in it had
    already grown a fuller writeup somewhere else in the doc — this was just a
    stale summary duplicating them. Two of those sections picked up a line
    this changelog entry made newly relevant: the actual fix for ambiguous
    glued tokens is zero-padding or a separator, not just the lenient flag;
    and xxx is the one offset token that can carry a sub-minute historical
    offset (Europe/London pre-1847) through verbatim, where the other five
    throw.

0.9.70

Choose a tag to compare

@DirazCoder DirazCoder released this 14 Sep 19:27

temporal-fmt v0.9.70

Added

  • ctx.registerFormatToken(token) adds a custom token into the actual
    shared table format()/parse() read from, everywhere in the process —
    createFormatter()'s own options.tokens only ever reached the one
    Formatter object it returned, never the library's top-level format().
    Last-write-wins by name, same rule as registerLocale, including against
    a built-in token name; Mod.priority is the tiebreak. Custom handlers
    still run host-side under the sandbox, same treatment createFormatter's
    tokens already got: source text crosses the boundary, gets revived with
    new Function, and is checked against the original before the mod's load
    is accepted.
  • ctx.log(level, message, meta?) and ctx.reportIssue(issue) give a
    mod a way to actually surface something to the person running the CLI.
    Sandboxed mods' own console.log goes to the worker's stderr, which
    isn't the terminal's stderr, so without these two a mod had no way to
    say "here's what I did" or "here's what I couldn't do" that anyone would
    see. Both show up in the load report; both fall back to the matching
    console method when used outside a load report entirely.
  • "minApiLevel" in mod.json lets a .tfmod declare the lowest mod API
    level it needs. A mod that needs more than the host provides now fails
    cleanly at load time, before register() runs, naming both numbers —
    previously it would run right up to the first missing ctx function and
    fail with a raw "is not a function."

Changed

  • ctx.hasPermission(capability) is now typed as the literal union of
    the four real capabilities instead of a bare string. A typo like
    'fs:reed' is a compile-time error in a TypeScript-authored mod now,
    not a capability that silently and permanently read false. No runtime
    behavior change.

Fixed

  • registerFormatToken didn't do anything. tokens.ts had the full
    mod-registration and rebuild-listener mechanism in place, but neither
    tokenize.ts's token-matching table nor format.ts's handler lookup
    ever subscribed to it — both were built once from the static built-in
    token list at module load and never touched again. A mod's registration
    was accepted and then quietly ignored by every format() call. Both
    tables now rebuild on registration, and format.ts's tokenize cache is
    cleared at the same time, since a format string already tokenized as
    literal text before a token was registered would otherwise never
    re-tokenize for the rest of the process.

0.9.61

Choose a tag to compare

@DirazCoder DirazCoder released this 12 Sep 16:48

temporal-fmt v0.9.61

Fixed

  • checkVersionRange in scripts/semverRange.mjs compared host and range
    versions by raw patch-number arithmetic, which misordered releases whose
    patch numbers don't increase monotonically with release order — e.g.
    0.9.41 (shipped Sep 7) read as newer than 0.9.5 (shipped Sep 11)
    because 41 > 5 numerically, when 0.9.5 is actually the later release.
    A mod declaring ^0.9.5 could incorrectly load against a host on 0.9.41,
    or incorrectly reject a host on 0.9.5 itself. Comparisons are now
    resolved against scripts/versions.json, a table generated from this
    changelog's actual release order via the new scripts/generateVersionTable.mjs,
    not computed from the version string.
  • scripts/versions.json was missing from the package's published files
    list. Since semverRange.mjs requires it at module load time, every
    install of 0.9.4 through 0.9.6 threw MODULE_NOT_FOUND the first time
    any mod-version check ran. Added it to files.

0.9.6

Choose a tag to compare

@DirazCoder DirazCoder released this 12 Sep 15:17

temporal-fmt v0.9.6

Security

  • Mods no longer run in the host process at all. Every mod — loose .mjs
    and .tfmod alike — now loads inside its own subprocess, started with
    Node's permission model (--permission on Node 22.13+,
    --experimental-permission on anything older), and gets zero capabilities
    by default. Previously register() ran via a plain import(), which meant
    a mod could read/write anywhere on disk or spawn whatever it wanted with
    the exact same access as the CLI process itself — there was no boundary
    between "code that formats dates" and "code that does anything Node can
    do." Filesystem reads/writes, child-process spawns, and worker threads are
    all refused by Node itself now unless the user explicitly grants them.
  • The subprocess never sees the host's environment. process.env inside a
    mod is scrubbed down to PATH/TZ/LANG plus a couple of Windows
    bootstrap variables — CI tokens, database URLs, anything else living in the
    parent's env doesn't cross over. There's no permission that hands this
    back, either: the permission model has no flag that gates environment
    variables, so the only honest option was to just not pass them in.
  • A mod that hangs or leaks memory can no longer take the whole CLI command
    down with it. register() gets killed after 10 seconds; a runtime
    override call (format()/parse() routed through a mod's overrideFormat
    etc.) gets 5; a subprocess whose RSS crosses 512MB gets killed regardless
    of what it's doing. That ceiling is watched as actual resident memory from
    the parent process, not V8's heap accounting — ArrayBuffer allocations
    count, and setting --max-old-space-size anywhere in the process tree
    can't quietly raise it. Any of these failing only takes down that one mod;
    the rest of the load pass and the command you ran keep going.

Added

  • .tfmod mods can declare a "permissions" array in mod.json —
    fs:read, fs:write, child-process, worker, each markable required or
    optional. First load prompts the user once per name@version and caches
    the answer in .temporal-fmt-permissions.json, next to mods/. A denied
    required permission fails the mod before any of its code runs, with the
    exact grant command in the error; a denied optional one just runs the mod
    with less and shows up in the report as downgraded instead of loaded.
    Loose .mjs mods have no manifest to declare any of this in, so they
    always run with nothing granted — that's the trade for not needing one.
  • ctx.hasPermission(capability), so a mod can check what it actually got
    and skip the parts of register() that need something denied, instead of
    crashing partway through.
  • scripts/managePermissions.mjs — list / grant / deny / reset
    against the same cache file the load-time prompts write to, for changing an
    answer without re-triggering a load.
  • The load report now spells out sandboxing per mod: which capabilities
    were granted vs. denied, and — for any mod holding a runtime override — the
    timeout and memory ceiling it's running under.
  • MODS.md, a standalone guide with everything that used to live in the
    README's "Mods" section plus all of the above; the README section is now a
    pointer to it.

Changed

  • overrideFormat/overrideParse (and the other override* points): a
    mod's impl is still a closure, but that closure now lives inside the mod's
    own subprocess, not the host. Every format()/parse() call that hits an
    overridden path forwards to it over a synchronous channel and blocks for
    the reply — a couple of milliseconds instead of nanoseconds, plus a bit
    more on Windows, where the host has no file descriptor for its end of a
    named pipe and the call rides a pair of scratch files instead. The loader
    tracks, per format string, whether a mod's impl is just forwarding to the
    built-in — a format string the mod never actually touches stops paying the
    round trip after the first call.
  • formatRange() batches its two endpoint calls into a single subprocess
    round trip when the active override lives in a mod, instead of paying for
    each endpoint separately.
  • createFormatter() custom token handlers are the one thing that still
    runs host-side, since a hot format() path can't afford a round trip per
    token lookup. The handler's source text ships out of the subprocess at
    register() time and gets rebuilt there with new Function, which only
    works if the handler doesn't close over anything outside its own body — the
    loader checks this by reviving the handler from source and diffing its
    output against the original's, and refuses the mod if they disagree.
  • Network access is still unrestricted — Node's permission model has no
    net capability on any currently supported version, so there was never a
    flag to gate it with. That was already true before this release; it's just
    written down explicitly in MODS.md now instead of left for someone to
    discover the hard way.

0.9.5

Choose a tag to compare

@DirazCoder DirazCoder released this 11 Sep 22:38

temporal-fmt v0.9.5

Security

  • .tfmod mod archives: mod.json's "main" field and the mod name are now
    path-validated before either one touches the filesystem. A malicious archive
    could previously declare "main": "../../../some/file.mjs" and have the
    loader import() code from OUTSIDE its extraction directory — breaking the
    "archive contents == executed code" containment the mod system documents
    (the mod name had the same traversal shape via config reads). Absolute
    paths, .. segments, and symlink escapes are all rejected before
    stat/import/read now; extraction itself was never the gap (the system tar
    binary already refuses .. members on this platform) — these checks close
    off the manifest-controlled paths tar can't see.
  • formatDistance/formatDistanceToNow reject years outside
    [-271821, 275760] (Temporal's representable range) instead of walking a
    year-per-iteration day-count loop toward them. A hostile duck-typed field
    bag ({year: 2e8} from parsed user data, say) turned that loop into a cheap
    CPU-denial primitive — about 380ms/call at 2e8, scaling roughly linearly
    past that. The day-count itself is now O(1) via the Hinnant closed form, so
    this isn't just a range check bolted on top of the slow path.
  • Out-of-range month/day in formatDistance field bags now throw the typed
    InvalidDateError. Month 0 used to hit a V8-internal Intl error with no
    useful message, and Feb 31 silently compared as "now."

Fixed

  • interval: contains() had its two half-open bound labels swapped —
    'half-open-end' excluded its start and included its end, which is exactly
    what 'half-open-start' is supposed to do, and vice versa. intersects /
    intersection / union / difference all derive endpoint ownership from
    that same logic, so they inherited the bug: two point-disjoint open
    intervals could come back "overlapping" with a non-null empty intersection.
    splitInterval now produces a true partition instead of dropping the
    original end point and double-covering interior boundaries, and
    mergeIntervals no longer aliases caller input objects.
  • round/floor/ceil/truncate: floor and ceil were literally swapped
    for every pre-1970 date. floor(1969-12-31T12:00, 'day') returned Jan 1
    1970 — the opposite of what floor means — and 'nearest' broke ties
    asymmetrically across the epoch. roundDuration floor/ceil on negative
    durations now matches Temporal semantics, and a fractional
    roundingIncrement throws the typed InvalidDurationError instead of a raw
    V8 BigInt RangeError.
  • Proleptic-Gregorian day counts: the Math.floor((y2 >= 0 ? y2 : y2-399) / 400) era form — Hinnant's truncating-division idiom, ported straight into
    JS's flooring division without adjusting for the difference — double-
    corrected for negative years, shifting every pre-year-0 date by one day
    across difference, splitInterval, and anything in the compare family.
    Replaced with the actual closed form and verified against Temporal over
    34,320 round-trip cases.
  • add()-family weekday recomputation used Date.UTC(year, month-1, day),
    which remaps years 0-99 to 1900-1999 per the ECMAScript spec. First-century
    dates — including genuine Temporal.PlainDate input — got the wrong
    dayOfWeek out of that, which meant isBusinessDay was counting Sundays as
    business days. All four call sites now use an O(1) proleptic formula
    instead of routing through Date.
  • Month arithmetic across year 0 double-borrowed the year:
    add(PlainDate('0001-01-15'), -13, 'months') returned year -2 / month 12
    instead of Temporal's -0001-12-15.
  • Recurrence, several bugs in the same area: FREQ=WEEKLY;BYDAY=… never
    matched when the start date wasn't itself on a BYDAY weekday, since whole-
    week stepping is supposed to preserve the weekday and didn't; RRULE UNTIL
    was stored as the raw string, so the first next() call threw; BYDAY
    ordinals (2MO, -1FR) were silently dropped; negative BYMONTHDAY never
    matched anything; monthly rules drifted day-of-month after the first
    short-month clamp (Jan 31 → 28, forever — RFC 5545 says skip the month
    instead); skip()'s skip phase and between() both had unbounded
    iteration; a rule interval of undefined poisoned candidates with NaN
    even though 1 is the documented default; programmatic rules now validate
    their BY* lists instead of accepting garbage silently.
  • isDST assumed northern hemisphere and was inverted year-round for
    Australia/NZ/South America/southern Africa — standard offset is now
    min(Jan, Jul) instead of always-January. getNextTransition/
    getPreviousTransition now bisect to the actual transition minute; they
    used to return the day reconstructed at the input's wall-clock time, which
    was the right day up to 10 hours late. getTransitions validates its end
    argument now too.
  • Codemod tables: date-fns S/SS (fractional seconds) and y existed in
    temporal-fmt already but were missing from the mapping table, so they
    silently translated to quoted literal text instead of the real token, or
    threw. ww locale-vs-ISO week mismatches now refuse with an explanatory
    note instead of silently printing two different numbers depending on which
    week system got picked. A large batch of real-but-unmapped tokens now fail
    loudly instead of quietly literalizing.
  • analyzeFormat token positions now track actual source indices — quoted
    spans and '' escapes were under-counted before, which mis-positioned
    every token that came after any quote in the string. previewFormat's
    default sample is now a real PlainDateTime, so locale-aware tokens
    actually render in the preview. ww/RRRR/D-family tokens validate all
    the fields they read instead of producing NaN silently, and the token
    metadata no longer claims Instant supports time tokens or
    PlainMonthDay supports day-of-year.
  • compileFormat().pieces is now served frozen. A caller mutating the
    (shared, cached) array was poisoning every subsequent format() call for
    that string, process-wide. DEFAULT_CONFIG is frozen too, for the same
    reason — one import site mutating it could change every later
    createConfig()'s defaults. registerRelativeGrammar now validates that
    matchers are actually functions and isolates ones that throw;
    parseRelative rejects contradictory "in N units ago" input instead of
    silently resolving it as future; the CLI rejects prototype-chain subcommand
    lookups (temporal-fmt toString … used to crash with a raw TypeError);
    formatISODuration emits valid ISO for negative durations (-PT30S
    instead of the unparseable PT-30S) and parseISODuration accepts that
    leading-minus form on the way back in; the holiday calendar's per-year
    cache is bounded now (1000 entries, FIFO); format(null, …) throws the
    typed error instead of a raw TypeError.
  • interval: the end-inclusivity ownership checks inside intersection()
    and union() compared in the wrong direction, which made the "other
    interval strictly extends further" branch unreachable and computed end
    bounds from the wrong interval entirely. union((1,5], [3,10)) reported an
    inclusive end even though 10 is in neither interval; the mirror bug made
    intersect([1,10), [3,5]) exclude 5 even though 5 is in both. Found this
    during a line-by-line coverage audit — an unreachable branch on a live code
    path is usually a bug report wearing a disguise.
  • timezone: isDST()'s string-offset fallback (used when the installed
    Temporal implementation doesn't expose offsetNanoseconds) was comparing
    seconds against nanoseconds, so the comparison could never come out equal.
    Every DST-observing zone read as "in DST" year-round on implementations
    that hit this fallback. Both arms are normalized to seconds now.
  • recurrence: the next-match safety cap wasn't counting iterations that
    hit a continue — empty period, candidate at or before the start — so a
    rule whose every period is empty (FREQ=MONTHLY;BYDAY=-6MO, which no month
    can satisfy) spun forever instead of terminating with "no more
    occurrences." The cap counts every iteration now.
  • recurrence: parseRRule()'s BYDAY pattern only accepted single-digit
    ordinals, but RFC 5545 ordinals run -53..53 — a valid rule like
    BYDAY=53MO threw "invalid weekday" and never even reached the range check
    that already existed for it. Two-digit ordinals parse now.
  • recurrence: formatRRule() emitted INTERVAL=undefined for
    programmatic rules that omit interval (a documented default), which
    parseRRule() itself then rejects on the way back in — a broken round
    trip. Omitted interval is now omitted from the output, matching what
    parseRRule() expects.
  • recurrence: a plain field-bag start without dayOfWeek made every
    BYDAY-dependent rule silently yield nothing — Temporal starts worked fine
    because their dayOfWeek getter seeds the chain automatically. The
    iterator now derives dayOfWeek for bag starts too, so both start shapes
    behave the same.

Tests

  • npm run test:coverage is back to 100% lines/branches/functions/
    statements — the audit above had knocked it down to 98.94%. ~60 new tests
    in test/cov-audit.test.js cover the paths the audit surfaced, and the
    handful of genuinely unreachable defensive arms (each documented at its own
    site) carry /* c8 ignore ... @preserve */ hints so they survive the
    esbuild bundle, matching the repo's existing convention.

0.8.985

Choose a tag to compare

@DirazCoder DirazCoder released this 11 Sep 22:38

temporal-fmt v0.8.985

Security

  • formatDistance/formatDistanceToNow reject years outside
    [-271821, 275760] (Temporal's representable range) instead of walking a
    year-per-iteration day-count loop toward them. A hostile duck-typed field
    bag ({year: 2e8} from app-parsed user data, say) turned that loop into a
    cheap CPU-denial primitive — around 380ms/call at year 2e8, scaling roughly
    linearly past that. Real Temporal values always clear the check without
    issue.
  • format(…, 'ww'/'RRRR') with a duck-typed field bag whose year falls
    outside that same range now throws instead of walking the ISO-week Jan-1
    weekday computation year by year — same CPU-denial shape, roughly
    360ms/call at year 2e8. This is a security fix only: the 0.9.x line's
    broader behavioral changes (O(1) closed forms, wider field validation)
    aren't backported here on purpose — this LTS line only takes fixes,
    not behavior changes.

0.9.41

Choose a tag to compare

@DirazCoder DirazCoder released this 07 Sep 15:45

temporal-fmt v0.9.41
License switched from MIT to Apache-2.0 — package.json, the
package-lock.json cache of it, root LICENSE, and the README's
license section are all updated, so there's no file left in the
repo still claiming MIT. Every file under src/ also picked up an
Apache-2.0 header (copyright line, grant, "AS IS" disclaimer),
same one-header-per-file convention as before.
Changed

  • License: MIT → Apache-2.0, across package.json, package-lock.json,
    LICENSE, and the README.
  • Apache-2.0 headers added to every file under src/.

0.8.984

Choose a tag to compare

@DirazCoder DirazCoder released this 07 Sep 15:46

temporal-fmt v0.8.984
Same relicensing as 0.9.41, backported to the 0.8.x line — MIT to
Apache-2.0 across package.json, package-lock.json, the root LICENSE
file, and the README, plus Apache-2.0 headers on every file under
src/. If you're pinned to 0.8.x, this is a license-only backport;
no code or behavior changed.
Changed

  • License: MIT → Apache-2.0, across package.json, package-lock.json,
    LICENSE, and the README.
  • Apache-2.0 headers added to every file under src/.

0.9.4

Choose a tag to compare

@DirazCoder DirazCoder released this 30 Aug 19:39

temporal-fmt v0.9.4

A mods/ folder now exists — drop a .mjs file in it and the CLI
picks it up on startup and registers it against the library, no
publishing or build step required. Modeled on the Minecraft mods-
folder convention: the host looks for files and runs them, rather
than depending on a package.

Added

  • A mod default-exports { name, register(ctx, config) }, where
    ctx is the same registration API (registerLocale,
    registerLocaleVocab, registerRelativeGrammar, createFormatter,
    createHolidayCalendar) already documented under Locales, Business
    calendars, and Extending with custom tokens.
  • .tfmod packaging for mods bigger than one file: a gzipped tar
    containing mod.json (name/version/main/requires/priority/
    temporalFmtVersion/config) plus main.mjs and an optional data/
    directory. The manifest is readable without executing any code,
    which is what makes dependency resolution happen before main.mjs
    is ever imported. Extraction shells out to the system tar binary
    rather than adding a parsing dependency.
  • requires/priority fields for controlling load order across
    mods, resolved as a real dependency graph (Kahn's algorithm) rather
    than just sorting by filename. Missing dependencies and circular
    dependencies each fail only the mods involved; everything else
    still loads.
  • Conflict reporting: if two mods register the same locale tag,
    relative-time grammar, or custom token name, the load report says
    which mod won (last-loaded wins, same as calling registerLocale
    twice already did — this just makes it visible).
  • ctx.overrideFormat / ctx.overrideParse, plus the same
    overrideXxx() shape for 79 more functions that have no internal
    call sites in this library — the full list is in the README under
    "Which functions are overridable." Unlike the additive registration
    functions, only one mod may hold a given override point; a second
    attempt fails immediately and names the mod that already owns it.
  • temporalFmtVersion in mod.json (exact version or ^ caret
    range, same semantics as npm) — checked before main.mjs is
    imported, so a mod built against a newer override surface fails
    cleanly instead of throwing from inside register().
  • Per-mod user settings: a config array in mod.json (string/
    number/boolean/enum, with optional min/max/choices) resolved
    against an optional config/<mod-name>.json file kept as a sibling
    of mods/, not inside it, so re-downloading a .tfmod never
    touches a user's settings. Unknown keys and out-of-range values are
    reported and fall back to the schema default rather than failing
    the whole mod.
  • loadMods() and formatModLoadReport() in scripts/loadMods.mjs
    (CLI-only, not published — needs fs/path) for anyone embedding
    temporal-fmt outside the CLI who wants the same loader; the
    published, Node-agnostic pieces it depends on (buildModContext(),
    isMod()) are exported from the library itself.

Changed

  • src/index.ts's exports for format, parse, and 79 other
    previously-direct re-exports now route through a small override
    registry (src/runtime.ts) instead of re-exporting straight from
    their source modules. This is what makes a mod's override reach
    every internal caller — formatRange()'s own use of format(),
    for instance — not just whoever imports format from the package
    root.
  • mergeWithConfig()'s return type no longer asserts the merged
    object back into the generic T it was called with; it now returns
    its own actual shape (T plus the five config fields, each
    optional). The old as T & {...} cast wasn't a guarantee a spread
    could honestly make for an arbitrary caller-supplied T, and tsc
    caught it once this generic got exercised through a real call site
    (the override wrapper in modApi.ts).

0.9.32

Choose a tag to compare

@DirazCoder DirazCoder released this 27 Aug 16:44

temporal-fmt v0.9.32

README quickstart was steering people into the expensive import path.
Every code sample under "Providing Temporal", "Formatting", and
"Parsing" imported format/parse from the bare temporal-fmt
entry point — which works, but the main entry re-exports everything
in the package, so a bundler pulls the whole graph in regardless of
which function you actually call. Measured with esbuild: ~27KB for
format alone via the subpath, versus ~68KB for the same single
function through the bare import. The subpath imports weren't new —
they'd existed for a while — they just weren't what the docs led
with.

Docs

  • Formatting, Parsing, and Providing Temporal code samples now
    import from temporal-fmt/format and temporal-fmt/parse instead
    of the bare temporal-fmt entry.
  • Added a "Get started" section right after Install: a four-line
    working example (one format() call, one parse() call, both via
    subpaths) before any discussion of package size or scope.
    Previously the fastest path to running code was scattered across
    three sections (Install → Providing Temporal → Formatting); now
    it's one.
  • Added the measured 27KB/68KB numbers directly to the Subpath
    imports section, so the tree-shaking claim isn't just asserted —
    sideEffects: false is set in package.json, and this documents
    what it actually gets you.
  • Added two bundlephobia badges (format and parse subpaths) next
    to the existing coverage badge, so subpath size is visible without
    digging into the docs.
  • Rewrote the top-of-file description to match what the library
    actually does now (format + parse, real validation, locale
    support) instead of the original one-liner, which only mentioned
    formatting and predated the parse API entirely.

Changed

  • package.json's description field was still the pre-parse
    wording ("Format Temporal.PlainDate/PlainDateTime/PlainTime/ ZonedDateTime objects using date-fns-style token strings.") —
    stale since parsing shipped. Updated to match the README's
    description.

NOTE: Also unrelated to this changelog — the 0.9.x lineup is probably going to last a lot longer than usual. You might expect 0.9.x to wrap up in a week like it used to, but I've slowed things down so I'm not telling people to upgrade over and over, rapidly. I'll release at a sensible pace instead. 0.9.x might run 2-3 weeks longer, and 0.8-lts will stay supported for that much longer too. When 0.10.x lands, 0.8-lts is still supported per the new policy in VERSIONS.md. Since 0.9.x is lasting much longer, 0.10.x will probably last longer too — maybe 3-4 weeks — since this library is mostly complete now and it's more about maintenance and fixes. So my rough estimate: 0.8-lts should keep getting security updates for another 1-2 months. In 0.9.4 i am expecting a big feature addition its one but its worth it before i can call it fully complete.