Skip to content

add Bun.ms: ms-style duration parse/format API#31613

Open
robobun wants to merge 1 commit into
farm/9555d6db/minimum-release-age-default-and-msfrom
farm/9555d6db/bun-ms
Open

add Bun.ms: ms-style duration parse/format API#31613
robobun wants to merge 1 commit into
farm/9555d6db/minimum-release-age-default-and-msfrom
farm/9555d6db/bun-ms

Conversation

@robobun

@robobun robobun commented May 30, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #30529 (merge that first; this PR's diff is shown against its branch). Reuses the parse_ms landed in #30529.

Requested in #18597; supersedes the earlier attempt #23162 (which predated the Rust rewrite by ~1200 commits).

Note: this was previously stacked on #31608 (the configVersion-gated 2-day default), which was declined because the npm registry's compact Content-Type doesn't carry the time data the age filter needs. Bun.ms doesn't depend on any of that — it's been rebased directly onto #30529.

What

Bun.ms — modeled on the npm ms package:

Bun.ms("2 days");              // 172800000   (parse string → ms)
Bun.ms("1h");                  // 3600000
Bun.ms("-1.5h");               // -5400000
Bun.ms("nonsense");            // undefined   (unparseable → undefined, like ms)
Bun.ms(60000);                 // "1m"        (format ms → string)
Bun.ms(60000, { long: true }); // "1 minute"
Bun.ms(172800000);             // "2d"
Bun.ms(1.5);                   // "1.5ms"

How

  • Parse (string → number) reuses bun_core::parse_ms from install: accept ms-style duration strings for minimumReleaseAge #30529.
  • Format (number → string) adds bun_core::format_ms, kept in f64 throughout and stringified with WTF's dtoa (JS Number.prototype.toString semantics): JavaScript Math.round tie-breaking (round half toward +∞), pluralization at ≥ 1.5× in the long form, and the same unit scale (a year is 365.25 days).

ms-package behavior matched

  • Unparseable non-empty strings return undefined (not a throw), so Bun.ms(input) ?? fallback works. Empty string / non-string-non-number inputs throw (matching ms's typeof checks — boxed new String(...) throws too).
  • Formatting emits at most days (never weeks/years — those are parse-only); sub-second keeps the raw number (Bun.ms(1.5)"1.5ms"); months are not a unit (Bun.ms("1mo")undefined).

One intentional difference: the underlying parse_ms (shared with bunfig / --minimum-release-age parsing in #30529) is slightly more lenient than ms's anchored regex — it trims surrounding whitespace and accepts a leading +. It's strictly a superset, so anything ms parses, Bun.ms parses identically.

Verification

test/js/bun/util/ms.test.ts — 13 tests pass on debug+ASAN: compact/long/case-insensitive parsing, undefined-on-unparseable, empty-string/boxed-object/wrong-type throwing, compact/long formatting, JS-round tie-breaking, raw sub-second, parse↔format round-trips, and non-finite rejection. cargo clippy clean on bun_core + bun_runtime; debug + release both build clean. Docs added to docs/runtime/utils.mdx + bun-apis.mdx.

@robobun robobun requested a review from alii as a code owner May 30, 2026 13:28
@robobun

robobun commented May 30, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 5:54 PM PT - Jun 19th, 2026

@robobun, your commit 2e4fc85337550fed14f8f0c28a8ae5799b687bbc passed in Build #63522! 🎉


🧪   To try this PR locally:

bunx bun-pr 31613

That installs a local version of the PR into your bun-31613 executable, so you can run:

bun-31613 --bun

@github-actions

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. implement Bun.ms #23162 - Earlier Zig-based implementation of the same Bun.ms() parse/format API
  2. Cleaner way to implement Bun.ms #23724 - Alternative implementation of Bun.ms()

🤖 Generated with Claude Code

@robobun

robobun commented May 30, 2026

Copy link
Copy Markdown
Collaborator Author

Yep — this intentionally supersedes #23162 (and its follow-up #23724), which implemented the same Bun.ms API in Zig before the Rust rewrite (~1200 commits ago) and never merged. This PR is the Rust port: it reuses the parse_ms already landed in this stack (#30529) and adds format_ms, matching the npm ms package exactly. Noted in the PR description; closes #18597.

Comment thread src/bun_core/fmt.rs Outdated
Comment thread src/runtime/api/BunObject.rs Outdated
Comment thread src/bun_core/fmt.rs Outdated
@robobun robobun force-pushed the farm/9555d6db/bun-ms branch from 01cc9b1 to 53897e2 Compare May 30, 2026 13:46
@robobun robobun force-pushed the farm/9555d6db/minimum-release-age-2d-default branch from 46d53b6 to e78489e Compare May 30, 2026 13:47
@robobun robobun force-pushed the farm/9555d6db/bun-ms branch from 53897e2 to 0be36c9 Compare May 30, 2026 13:48
Comment thread src/runtime/api/BunObject.rs Outdated
Comment thread src/runtime/api/BunObject.rs
Comment thread src/runtime/api/BunObject.rs Outdated
Comment thread src/runtime/api/BunObject.rs
@robobun robobun force-pushed the farm/9555d6db/bun-ms branch from 0be36c9 to 37e71ed Compare May 30, 2026 14:09
Comment thread packages/bun-types/bun.d.ts Outdated
@robobun robobun force-pushed the farm/9555d6db/bun-ms branch from 37e71ed to f18b3df Compare May 30, 2026 14:26
Comment thread test/js/bun/util/ms.test.ts Outdated
@robobun robobun force-pushed the farm/9555d6db/bun-ms branch from b9effe8 to cbb40a7 Compare May 30, 2026 14:47
Comment thread docs/runtime/utils.mdx Outdated
@robobun robobun force-pushed the farm/9555d6db/bun-ms branch from cbb40a7 to a30583f Compare May 30, 2026 14:55

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All prior feedback addressed and the implementation looks solid — deferring to a human since this ships a new public Bun.* API surface (and is stacked on two unmerged PRs), which is a product call worth a maintainer's sign-off.

Extended reasoning...

Overview

This PR adds Bun.ms(), a new public utility on the Bun global modeled on the npm ms package: string → milliseconds parsing (reusing bun_core::parse_ms from earlier in the stack) and milliseconds → string formatting (new bun_core::format_ms). Touches: ~76 lines of new Rust in fmt.rs + a ~67-line host fn in BunObject.rs, one-line wiring in the C++ LUT (BunObject.cpp / BunObject+exports.h), a re-export in lib.rs, two .d.ts overloads, two docs pages, and a 165-line test file (13 tests).

Prior review history

I left five rounds of inline comments on earlier revisions (sub-second truncation, non-number coercion, i64 saturation, undefined-vs-throw on unparseable strings, boxed-string handling, dead exception check, missing docs, a TS2769 in the test, and a broken docs example). Every one was addressed in follow-up commits (53897e2, 37e71ed, f18b3df, cbb40a7, a30583f) and all threads are resolved. The current bug-hunting pass found nothing new.

Security risks

None identified. The function is a pure string/number converter with no I/O, no filesystem/network access, and no auth/crypto/permission implications. Inputs are bounded (parse_ms operates on the byte slice directly with no regex backtracking; format_ms is constant-time per call). The one unsafe block (from_utf8_unchecked on dtoa output) is sound — WTF's dtoa only emits ASCII.

Level of scrutiny

Moderate. The implementation itself is small, well-commented, and now thoroughly tested. What pushes this above my auto-approval bar is that it introduces new public API surface on the Bun global — the overload shapes, the undefined-on-unparseable contract, and the documented intentional divergence from npm ms (lenient whitespace/+ parsing) are product decisions that become hard to change once shipped. That's a maintainer's call, not a bot's.

Other factors

  • Stacked on #31608#30529, both of which must merge first; merge order is a human concern.
  • CI shows musl LTO link failures and one Windows bun-install.test.ts failure that look unrelated/infrastructural, but a human should confirm before merge.
  • Test coverage is good: parsing (compact/long/case-insensitive/negatives), formatting (compact/long/rounding/pluralization/sub-second), strict type rejection, round-trips, and non-finite rejection.

@robobun

robobun commented May 30, 2026

Copy link
Copy Markdown
Collaborator Author

CI status

Build 60210 (rebased tip, sha d3ea742): 269+ jobs passed; all 15 failing lanes fail on the same single testtest/cli/install/bunx.test.ts → "should handle package that requires node 24", which runs live bunx --bun @angular/cli@latest against the real npm registry. The latest Angular publish exits 3 under --bun, breaking that test tree-wide on every platform: it fails identically on plain main (bf70cc79c4) with zero changes from this stack. test/js/bun/util/ms.test.ts is not in any failure set.

This needs a tree-wide fix (pin the Angular version in the test, or investigate the --bun incompatibility) — outside this PR's scope.

Bun.ms builds clean in debug + release, passes 13/13 tests locally, clippy clean. Stacked on #30529merge that first. Diff is green; ready for review/merge + maintainer sign-off on the new public API.

@robobun robobun changed the base branch from farm/9555d6db/minimum-release-age-2d-default to farm/9555d6db/minimum-release-age-default-and-ms June 1, 2026 23:30
@robobun robobun force-pushed the farm/9555d6db/bun-ms branch from aeb24c9 to 79616b0 Compare June 1, 2026 23:30
@robobun robobun force-pushed the farm/9555d6db/minimum-release-age-default-and-ms branch from 29c0f0c to fa4b4b2 Compare June 3, 2026 23:40
@robobun robobun force-pushed the farm/9555d6db/bun-ms branch from 79616b0 to d3ea742 Compare June 3, 2026 23:42

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All my earlier feedback has been addressed and the implementation looks solid; deferring for a human to sign off on adding Bun.ms to the public API surface (and on the stacked #30529 dependency).

Extended reasoning...

Overview

This PR adds Bun.ms() — an ms-package-compatible duration parse/format utility — as a new public function on the Bun global. It touches: src/bun_core/fmt.rs (new format_ms + js_math_round + write_js_number helpers), src/runtime/api/BunObject.rs (the host fn), src/jsc/bindings/BunObject.cpp / BunObject+exports.h (LUT wiring), packages/bun-types/bun.d.ts (two overloads + MsOptions), docs (bun-apis.mdx, utils.mdx), and a new 165-line test file. The PR is stacked on #30529.

Security risks

None identified. The function is pure string/number conversion with no I/O, no auth, no filesystem/network access. Inputs are strictly type-checked (primitive string or finite number only); the parser operates on a bounded byte slice with no allocation proportional to untrusted input beyond the input string itself. The one unsafe block (from_utf8_unchecked over WTF dtoa output) is sound — dtoa only emits ASCII.

Level of scrutiny

Moderate-to-high, primarily because this adds new public API surface to the Bun global. The implementation itself is small, self-contained, and well-tested (13 tests covering parse/format/edge cases/type strictness), and went through five rounds of review feedback in this thread — all of which the author addressed (sub-second truncation, i64 saturation, type coercion, undefined-on-unparseable, boxed-string handling, dead-code cleanup, docs, doc-example correctness, TS overload typecheck). The remaining decision is product/API-shape, not correctness: whether Bun.ms belongs on the global, and whether the documented one intentional divergence (slightly more lenient whitespace/+ parsing than npm ms) is acceptable.

Other factors

  • The author's own final comment explicitly requests "maintainer sign-off on the new Bun.ms public API."
  • Stacked on #30529, which must merge first.
  • CI is green on Linux/Windows; darwin failures are unrelated infra issues (missing Postgres role, OOM-killed runner).
  • No CODEOWNERS conflict apparent for these paths.

Given that new public API surface is a design decision a human maintainer should ratify, I'm deferring rather than approving.

`Bun.ms` mirrors the npm `ms` package:
- `Bun.ms("2 days")` → 172800000 (parse a duration string to milliseconds)
- `Bun.ms(60000)` → "1m" (format milliseconds to a compact string)
- `Bun.ms(60000, { long: true })` → "1 minute" (verbose form)

Parsing reuses `bun_core::parse_ms`; formatting adds `bun_core::format_ms`
with JavaScript `Math.round` tie-breaking (round half toward +∞) and the same
unit scale as the `ms` package (a year is 365.25 days; formatting emits at most
days, never weeks/years). Invalid strings and non-finite numbers throw a
TypeError.

Wires the host function into BunObject (Rust `ms` + LUT entry + exports),
adds the `Bun.ms` TypeScript overloads, and tests the parse/format/rounding
behavior against the `ms` package.

Closes #18597
@robobun robobun force-pushed the farm/9555d6db/minimum-release-age-default-and-ms branch from fa4b4b2 to 9123fb3 Compare June 20, 2026 00:06
@robobun robobun force-pushed the farm/9555d6db/bun-ms branch from d3ea742 to 2e4fc85 Compare June 20, 2026 00:07

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All earlier feedback has been addressed and the implementation looks clean and well-tested — deferring only because this adds a new public Bun.* API, which warrants a maintainer's sign-off on the API surface.

Extended reasoning...

Overview

This PR adds Bun.ms() as a new public API on the Bun global, modeled on the npm ms package: string→number duration parsing (reusing bun_core::parse_ms from #30529) and number→string formatting (new bun_core::format_ms). It touches src/bun_core/fmt.rs (the formatter + js_math_round/write_js_number helpers), src/runtime/api/BunObject.rs (the host fn), a one-line LUT entry in BunObject.cpp/BunObject+exports.h, bun.d.ts overloads, docs in utils.mdx/bun-apis.mdx, and a 165-line test file.

The PR went through several review rounds; every inline issue I raised (sub-second truncation, non-number coercion, i64 saturation, undefined-vs-throw on parse failure, boxed-string handling, dead exception check, missing docs, broken doc example, TS overload mismatch in tests) has been fixed and the threads resolved. The current bug-hunting pass found nothing new.

Security risks

None identified. The function is a pure string/number converter with no I/O, no filesystem/network access, and no privilege-bearing behavior. Inputs are strictly type-checked (primitive string or finite number only); the parser is bounded and operates on a byte slice. The one unsafe block (from_utf8_unchecked on dtoa output) is sound — WTF's dtoa emits ASCII only.

Level of scrutiny

Implementation-wise this is a small, self-contained utility with thorough test coverage (13 tests covering parse, format, rounding, edge cases, type strictness, round-trips). However, it introduces a new permanent public API on the Bun global that closes a user feature request (#18597). API-shape decisions — naming, return-undefined-vs-throw semantics, the documented leniency vs. npm ms — are product calls that should get a maintainer's explicit nod before shipping, as the author's own CI-status comment acknowledges.

Other factors

  • All prior review threads are resolved; the diff reflects every fix.
  • CI is green for this PR's own test; the only failing lane is an unrelated tree-wide bunx test hitting live npm.
  • Stacked on #30529 (merge that first).
  • No CODEOWNERS concerns surfaced for the touched paths.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant