diff --git a/.changeset/yellow-shirts-destroy.md b/.changeset/yellow-shirts-destroy.md new file mode 100644 index 0000000000..adbdeed5d7 --- /dev/null +++ b/.changeset/yellow-shirts-destroy.md @@ -0,0 +1,18 @@ +--- +"effect": patch +--- + +Add `Duration.divide` and `Duration.unsafeDivide`. + +```ts +import { Duration, Option } from "effect" +import assert from "assert" + +assert.deepStrictEqual(Duration.divide("10 seconds", 2), Option.some(Duration.decode("5 seconds"))) +assert.deepStrictEqual(Duration.divide("10 seconds", 0), Option.none()) +assert.deepStrictEqual(Duration.divide("1 nano", 1.5), Option.none()) + +assert.deepStrictEqual(Duration.unsafeDivide("10 seconds", 2), Duration.decode("5 seconds")) +assert.deepStrictEqual(Duration.unsafeDivide("10 seconds", 0), Duration.infinity) +assert.throws(() => Duration.unsafeDivide("1 nano", 1.5)) +``` diff --git a/packages/effect/dtslint/Duration.ts b/packages/effect/dtslint/Duration.ts index 2549756e3b..bf455db540 100644 --- a/packages/effect/dtslint/Duration.ts +++ b/packages/effect/dtslint/Duration.ts @@ -110,6 +110,20 @@ Duration.max("1 minutes", "2 millis") // $ExpectType Duration Duration.clamp("1 millis", { minimum: "2 millis", maximum: "3 millis" }) +// ------------------------------------------------------------------------------------- +// divide +// ------------------------------------------------------------------------------------- + +// $ExpectType Option +Duration.divide("1 seconds", 2) + +// ------------------------------------------------------------------------------------- +// unsafeDivide +// ------------------------------------------------------------------------------------- + +// $ExpectType Duration +Duration.unsafeDivide("1 seconds", 2) + // ------------------------------------------------------------------------------------- // times // ------------------------------------------------------------------------------------- diff --git a/packages/effect/src/Duration.ts b/packages/effect/src/Duration.ts index 96e0ad22c1..6185659cd3 100644 --- a/packages/effect/src/Duration.ts +++ b/packages/effect/src/Duration.ts @@ -515,6 +515,59 @@ export const clamp: { }) ) +/** + * @since 2.4.19 + * @category math + */ +export const divide: { + (by: number): (self: DurationInput) => Option.Option + (self: DurationInput, by: number): Option.Option +} = dual( + 2, + (self: DurationInput, by: number): Option.Option => + match(self, { + onMillis: (millis) => { + if (by === 0 || isNaN(by) || !Number.isFinite(by)) { + return Option.none() + } + return Option.some(make(millis / by)) + }, + onNanos: (nanos) => { + if (isNaN(by) || by <= 0 || !Number.isFinite(by)) { + return Option.none() + } + try { + return Option.some(make(nanos / BigInt(by))) + } catch (e) { + return Option.none() + } + } + }) +) + +/** + * @since 2.4.19 + * @category math + */ +export const unsafeDivide: { + (by: number): (self: DurationInput) => Duration + (self: DurationInput, by: number): Duration +} = dual( + 2, + (self: DurationInput, by: number): Duration => + match(self, { + onMillis: (millis) => make(millis / by), + onNanos: (nanos) => { + if (isNaN(by) || by < 0 || Object.is(by, -0)) { + return zero + } else if (Object.is(by, 0) || !Number.isFinite(by)) { + return infinity + } + return make(nanos / BigInt(by)) + } + }) +) + /** * @since 2.0.0 * @category math diff --git a/packages/effect/test/Duration.test.ts b/packages/effect/test/Duration.test.ts index 7de8e3daf8..c28ce3e2ed 100644 --- a/packages/effect/test/Duration.test.ts +++ b/packages/effect/test/Duration.test.ts @@ -162,6 +162,48 @@ describe("Duration", () => { })) }) + it("divide", () => { + expect(Duration.divide(Duration.minutes(1), 2)).toEqual(Option.some(Duration.seconds(30))) + expect(Duration.divide(Duration.seconds(1), 3)).toEqual(Option.some(Duration.nanos(333333333n))) + expect(Duration.divide(Duration.nanos(2n), 2)).toEqual(Option.some(Duration.nanos(1n))) + expect(Duration.divide(Duration.nanos(1n), 3)).toEqual(Option.some(Duration.zero)) + expect(Duration.divide(Duration.infinity, 2)).toEqual(Option.some(Duration.infinity)) + expect(Duration.divide(Duration.zero, 2)).toEqual(Option.some(Duration.zero)) + expect(Duration.divide(Duration.minutes(1), 0)).toEqual(Option.none()) + expect(Duration.divide(Duration.minutes(1), -0)).toEqual(Option.none()) + expect(Duration.divide(Duration.nanos(1n), 0)).toEqual(Option.none()) + expect(Duration.divide(Duration.nanos(1n), -0)).toEqual(Option.none()) + expect(Duration.divide(Duration.minutes(1), 0.5)).toEqual(Option.some(Duration.minutes(2))) + expect(Duration.divide(Duration.minutes(1), 1.5)).toEqual(Option.some(Duration.seconds(40))) + expect(Duration.divide(Duration.minutes(1), NaN)).toEqual(Option.none()) + expect(Duration.divide(Duration.nanos(1n), 0.5)).toEqual(Option.none()) + expect(Duration.divide(Duration.nanos(1n), 1.5)).toEqual(Option.none()) + expect(Duration.divide(Duration.nanos(1n), NaN)).toEqual(Option.none()) + + expect(Duration.divide("1 minute", 2)).toEqual(Option.some(Duration.seconds(30))) + }) + + it("unsafeDivide", () => { + expect(Duration.unsafeDivide(Duration.minutes(1), 2)).toEqual(Duration.seconds(30)) + expect(Duration.unsafeDivide(Duration.seconds(1), 3)).toEqual(Duration.nanos(333333333n)) + expect(Duration.unsafeDivide(Duration.nanos(2n), 2)).toEqual(Duration.nanos(1n)) + expect(Duration.unsafeDivide(Duration.nanos(1n), 3)).toEqual(Duration.zero) + expect(Duration.unsafeDivide(Duration.infinity, 2)).toEqual(Duration.infinity) + expect(Duration.unsafeDivide(Duration.zero, 2)).toEqual(Duration.zero) + expect(Duration.unsafeDivide(Duration.minutes(1), 0)).toEqual(Duration.infinity) + expect(Duration.unsafeDivide(Duration.minutes(1), -0)).toEqual(Duration.zero) + expect(Duration.unsafeDivide(Duration.nanos(1n), 0)).toEqual(Duration.infinity) + expect(Duration.unsafeDivide(Duration.nanos(1n), -0)).toEqual(Duration.zero) + expect(Duration.unsafeDivide(Duration.minutes(1), 0.5)).toEqual(Duration.minutes(2)) + expect(Duration.unsafeDivide(Duration.minutes(1), 1.5)).toEqual(Duration.seconds(40)) + expect(Duration.unsafeDivide(Duration.minutes(1), NaN)).toEqual(Duration.zero) + expect(() => Duration.unsafeDivide(Duration.nanos(1n), 0.5)).toThrow() + expect(() => Duration.unsafeDivide(Duration.nanos(1n), 1.5)).toThrow() + expect(Duration.unsafeDivide(Duration.nanos(1n), NaN)).toEqual(Duration.zero) + + expect(Duration.unsafeDivide("1 minute", 2)).toEqual(Duration.seconds(30)) + }) + it("times", () => { expect(Duration.times(Duration.seconds(1), 60)).toEqual(Duration.minutes(1)) expect(Duration.times(Duration.nanos(2n), 10)).toEqual(Duration.nanos(20n))