Skip to content

fix: Duration.decode and Duration.times throwing on fractional values#6270

Open
chatman-media wants to merge 1 commit into
Effect-TS:mainfrom
chatman-media:fix/duration-fractional-nanos-micros
Open

fix: Duration.decode and Duration.times throwing on fractional values#6270
chatman-media wants to merge 1 commit into
Effect-TS:mainfrom
chatman-media:fix/duration-fractional-nanos-micros

Conversation

@chatman-media

Copy link
Copy Markdown

What

Two related crashes in Duration when given fractional values, both caused by passing a non-integer to BigInt():

1. Duration.decode("1.5 nanos" | "1.5 micros") throws

DURATION_REGEX explicitly permits a decimal mantissa (-?\d+(?:\.\d+)?), so the input passes validation. But the nanos/micros branches pass the raw matched string straight to BigInt:

case "nanos":  return nanos(BigInt(valueStr))   // BigInt("1.5") -> SyntaxError
case "micros": return micros(BigInt(valueStr))  // BigInt("1.5") -> SyntaxError

Every other unit (millis, seconds, …) uses Number(valueStr) and works fine with decimals (there's already a passing test for "1.5 seconds"). Only nanos/micros crash.

2. Duration.times(nanosDuration, 2.5) throws

times is typed to accept any number multiplier, but the nanos branch does:

onNanos: (nanos) => make(nanos * BigInt(times))  // BigInt(2.5) -> RangeError

So multiplying a nanosecond-backed Duration by a non-integer throws, despite the type signature allowing it.

Fix

  • decode: for nanos/micros, when the value is fractional, scale to whole nanoseconds and round (nanos is the smallest representable unit). Integer inputs keep the existing BigInt(valueStr) path, preserving exact values for large integers (e.g. "99999999999999999999 nanos").
    • "1.5 micros" -> 1500 nanos
    • "1.5 nanos" -> 2 nanos
    • "-1.5 nanos" -> Duration.zero (consistent with existing "-1.5 seconds" -> zero)
  • times: keep exact bigint multiplication for integer multipliers (no precision loss for large durations); fall back to rounded Number math only for non-integer multipliers.
    • Duration.times(Duration.nanos(2n), 2.5) -> 5 nanos

Tests

Added cases to the existing decode and times tests. Both fail on main (SyntaxError: Cannot convert 1.5 to a BigInt / RangeError: ... not an integer) and pass with the fix. Full Duration suite (48 tests), Schema Duration suite, and Schedule suite all pass; tsc -b and eslint are clean.

Part of #4349.

@github-project-automation github-project-automation Bot moved this to Discussion Ongoing in PR Backlog Jun 10, 2026
@changeset-bot

changeset-bot Bot commented Jun 10, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f2fd0b9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
effect Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

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

Labels

None yet

Projects

Status: Discussion Ongoing

Development

Successfully merging this pull request may close these issues.

1 participant