Skip to content

Commit

Permalink
Fix handling of hrtime in Duration.decode (#4341)
Browse files Browse the repository at this point in the history
Co-authored-by: Giulio Canti <[email protected]>
  • Loading branch information
fubhy and gcanti authored Jan 30, 2025
1 parent fe0e4e8 commit 766113c
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 6 deletions.
8 changes: 8 additions & 0 deletions .changeset/good-pillows-leave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"effect": patch
---

Improve `Duration.decode` Handling of High-Resolution Time

- **Ensured Immutability**: Added the `readonly` modifier to `[seconds: number, nanos: number]` in `DurationInput` to prevent accidental modifications.
- **Better Edge Case Handling**: Now correctly processes special values like `-Infinity` and `NaN` when they appear in the tuple representation of duration.
14 changes: 10 additions & 4 deletions packages/effect/src/Duration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export type DurationInput =
| Duration
| number // millis
| bigint // nanos
| [seconds: number, nanos: number]
| readonly [seconds: number, nanos: number]
| `${number} ${Unit}`

const DURATION_REGEX = /^(-?\d+(?:\.\d+)?)\s+(nanos?|micros?|millis?|seconds?|minutes?|hours?|days?|weeks?)$/
Expand All @@ -98,10 +98,16 @@ export const decode = (input: DurationInput): Duration => {
return millis(input)
} else if (isBigInt(input)) {
return nanos(input)
} else if (Array.isArray(input)) {
if (input.length === 2 && isNumber(input[0]) && isNumber(input[1])) {
return nanos(BigInt(input[0]) * bigint1e9 + BigInt(input[1]))
} else if (Array.isArray(input) && input.length === 2 && input.every(isNumber)) {
if (input[0] === -Infinity || input[1] === -Infinity || Number.isNaN(input[0]) || Number.isNaN(input[1])) {
return zero
}

if (input[0] === Infinity || input[1] === Infinity) {
return infinity
}

return nanos(BigInt(Math.round(input[0] * 1_000_000_000)) + BigInt(Math.round(input[1])))
} else if (isString(input)) {
const match = DURATION_REGEX.exec(input)
if (match) {
Expand Down
14 changes: 12 additions & 2 deletions packages/effect/test/Duration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ describe("Duration", () => {

deepStrictEqual(Duration.decode([500, 123456789]), Duration.nanos(500123456789n))
deepStrictEqual(Duration.decode([-500, 123456789]), Duration.zero)

deepStrictEqual(Duration.decode([Infinity, 0]), Duration.infinity)
deepStrictEqual(Duration.decode([-Infinity, 0]), Duration.zero)
deepStrictEqual(Duration.decode([NaN, 0]), Duration.zero)
deepStrictEqual(Duration.decode([0, Infinity]), Duration.infinity)
deepStrictEqual(Duration.decode([0, -Infinity]), Duration.zero)
deepStrictEqual(Duration.decode([0, NaN]), Duration.zero)
throws(() => Duration.decode("1.5 secs" as any), new Error("Invalid DurationInput"))
throws(() => Duration.decode(true as any), new Error("Invalid DurationInput"))
throws(() => Duration.decode({} as any), new Error("Invalid DurationInput"))
Expand Down Expand Up @@ -69,7 +74,12 @@ describe("Duration", () => {

assertSome(Duration.decodeUnknown([500, 123456789]), Duration.nanos(500123456789n))
assertSome(Duration.decodeUnknown([-500, 123456789]), Duration.zero)

assertSome(Duration.decodeUnknown([Infinity, 0]), Duration.infinity)
assertSome(Duration.decodeUnknown([-Infinity, 0]), Duration.zero)
assertSome(Duration.decodeUnknown([NaN, 0]), Duration.zero)
assertSome(Duration.decodeUnknown([0, Infinity]), Duration.infinity)
assertSome(Duration.decodeUnknown([0, -Infinity]), Duration.zero)
assertSome(Duration.decodeUnknown([0, NaN]), Duration.zero)
assertNone(Duration.decodeUnknown("1.5 secs"))
assertNone(Duration.decodeUnknown(true))
assertNone(Duration.decodeUnknown({}))
Expand Down

0 comments on commit 766113c

Please sign in to comment.