1/**
2 * Represents immutable spans of time.
3 *
4 * A `Duration` can be finite, positive infinity, or negative infinity. It is
5 * the standard representation for delays, timeouts, intervals, and
6 * time-to-live values across Effect APIs. This module includes constructors
7 * from common input shapes, unit conversions, comparisons, arithmetic,
8 * formatting, and reusable reducer or combiner helpers.
9 *
10 * @since 2.0.0
11 */
12import * as Combiner from "./Combiner.ts"
13import * as Equal from "./Equal.ts"
14import type * as Equ from "./Equivalence.ts"
15import { dual, identity } from "./Function.ts"
16import * as Hash from "./Hash.ts"
17import type * as Inspectable from "./Inspectable.ts"
18import { NodeInspectSymbol } from "./Inspectable.ts"
19import * as Option from "./Option.ts"
20import * as order from "./Order.ts"
21import type { Pipeable } from "./Pipeable.ts"
22import { pipeArguments } from "./Pipeable.ts"
23import { hasProperty, isNumber } from "./Predicate.ts"
24import * as Reducer from "./Reducer.ts"
25
26const TypeId = "~effect/Duration"
27
28const bigint0 = BigInt(0)
29const bigint1 = BigInt(1)
30const bigint2 = BigInt(2)
31const bigint10 = BigInt(10)
32const bigint24 = BigInt(24)
33const bigint60 = BigInt(60)
34const bigint1e3 = BigInt(1_000)
35const bigint1e6 = BigInt(1_000_000)
36const bigint1e9 = BigInt(1_000_000_000)
37
38const roundTiesAwayFromZero = (input: number): bigint =>
39 BigInt(input < 0 ? Math.ceil(input - 0.5) : Math.floor(input + 0.5))
40
41const roundMillisToNanos = (millis: number): bigint => roundTiesAwayFromZero(millis * 1_000_000)
42
43const parseNanos = (input: string, scale: bigint): bigint => {
44 const decimalIndex = input.indexOf(".")
45 if (decimalIndex === -1) return BigInt(input) * scale
46
47 const isNegative = input[0] === "-"
48 const fractional = input.slice(decimalIndex + 1)
49 const fractionalScale = bigint10 ** BigInt(fractional.length)
50 const scaled = (
51 BigInt(input.slice(isNegative ? 1 : 0, decimalIndex)) * fractionalScale + BigInt(fractional)
52 ) * scale
53 const rounded = scaled / fractionalScale +
54 (scaled % fractionalScale * bigint2 >= fractionalScale ? bigint1 : bigint0)
55 return isNegative ? -rounded : rounded
56}
57
58const nanosToHrTime = (nanos: bigint): [seconds: number, nanos: number] => {
59 const sign = nanos < bigint0 ? -bigint1 : bigint1
60 const absolute = nanos < bigint0 ? -nanos : nanos
61 return [
62 Number(sign * (absolute / bigint1e9)),
63 Number(sign * (absolute % bigint1e9))
64 ]
65}
66
67/**
68 * Represents a span of time with high precision, supporting operations from
69 * nanoseconds to weeks.
70 *
71 * **When to use**
72 *
73 * Use to model elapsed time, delays, timeouts, schedule intervals, and cache
74 * TTLs as immutable duration values.
75 *
76 * @see {@link Input} for values accepted by APIs that decode duration-like
77 * inputs
78 * @see {@link DurationValue} for the tagged representation exposed by the
79 * `value` field
80 *
81 * @category models
82 * @since 2.0.0
83 */
84export interface Duration extends Equal.Equal, Pipeable, Inspectable.Inspectable {
85 readonly [TypeId]: typeof TypeId
86 readonly value: DurationValue
87}
88
89/**
90 * Tagged representation of a `Duration` value.
91 *
92 * **When to use**
93 *
94 * Use when modeling or inspecting the exact tagged representation stored in a
95 * `Duration`, including finite millisecond or nanosecond values and infinite
96 * sentinels.
97 *
98 * **Details**
99 *
100 * A duration is represented as milliseconds, nanoseconds, positive infinity,
101 * or negative infinity.
102 *
103 * @see {@link Duration} for the public type whose `value` field contains this
104 * representation
105 * @see {@link match} for pattern matching without reading `value` directly
106 *
107 * @category models
108 * @since 2.0.0
109 */
110export type DurationValue =
111 | { _tag: "Millis"; millis: number }
112 | { _tag: "Nanos"; nanos: bigint }
113 | { _tag: "Infinity" }
114 | { _tag: "NegativeInfinity" }
115
116/**
117 * Valid time units that can be used in duration string representations.
118 *
119 * **When to use**
120 *
121 * Use when typing the unit portion of duration string inputs accepted by
122 * `Duration.Input`.
123 *
124 * @see {@link Input} for the full duration input union
125 *
126 * @category models
127 * @since 2.0.0
128 */
129export type Unit =
130 | "nano"
131 | "nanos"
132 | "micro"
133 | "micros"
134 | "milli"
135 | "millis"
136 | "second"
137 | "seconds"
138 | "minute"
139 | "minutes"
140 | "hour"
141 | "hours"
142 | "day"
143 | "days"
144 | "week"
145 | "weeks"
146
147/**
148 * Valid input types that can be converted to a Duration.
149 *
150 * **When to use**
151 *
152 * Use when an API should accept any value that Effect can convert into a
153 * `Duration`, including existing durations, millisecond numbers, nanosecond
154 * bigints, high-resolution tuples, duration strings, infinity strings, or
155 * duration objects.
156 *
157 * **Details**
158 *
159 * String inputs accept values like `"10 seconds"`, `"500 millis"`,
160 * `"Infinity"`, and `"-Infinity"`. Finite fractional values that are
161 * normalized to nanoseconds are rounded to the nearest nanosecond, with ties
162 * away from zero.
163 *
164 * @see {@link fromInput} for safe conversion to `Option`
165 * @see {@link fromInputUnsafe} for throwing conversion
166 * @see {@link DurationObject} for object-shaped duration input
167 * @see {@link Unit} for supported string units
168 *
169 * @category models
170 * @since 4.0.0
171 */
172export type Input =
173 | Duration
174 | number // millis
175 | bigint // nanos
176 | readonly [seconds: number, nanos: number]
177 | `${number} ${Unit}`
178 | "Infinity"
179 | "-Infinity"
180 | DurationObject
181
182/**
183 * An object with optional duration components that can be combined to create
184 * a Duration. All fields are optional and additive.
185 *
186 * **Details**
187 *
188 * Compatible with Temporal.Duration-like objects.
189 *
190 * **Example** (Combining duration object fields)
191 *
192 * ```ts import.meta.vitest
193 * import { Duration } from "effect"
194 *
195 * Duration.fromInputUnsafe({ seconds: 30 }) // => Duration.seconds(30)
196 * Duration.fromInputUnsafe({ days: 1 }) // => Duration.days(1)
197 * Duration.fromInputUnsafe({ seconds: 1, nanoseconds: 500 }) // => Duration.nanos(1_000_000_500n)
198 * ```
199 *
200 * @category models
201 * @since 4.0.0
202 */
203export interface DurationObject {
204 readonly weeks?: number | undefined
205 readonly days?: number | undefined
206 readonly hours?: number | undefined
207 readonly minutes?: number | undefined
208 readonly seconds?: number | undefined
209 readonly milliseconds?: number | undefined
210 readonly microseconds?: number | undefined
211 readonly nanoseconds?: number | undefined
212}
213
214const DURATION_REGEXP = /^(-?\d+(?:\.\d+)?)\s+(nanos?|micros?|millis?|seconds?|minutes?|hours?|days?|weeks?)$/
215
216/**
217 * Decodes a `Duration.Input` into a `Duration`.
218 *
219 * **When to use**
220 *
221 * Use when the input has already been validated or comes from a trusted source
222 * and throwing is acceptable for invalid duration syntax.
223 *
224 * **Gotchas**
225 *
226 * If the input is not a valid `Duration.Input`, it throws an error.
227 *
228 * **Example** (Decoding duration inputs)
229 *
230 * ```ts import.meta.vitest
231 * import { Duration } from "effect"
232 *
233 * Duration.fromInputUnsafe(1000) // => Duration.millis(1000)
234 * Duration.fromInputUnsafe("5 seconds") // => Duration.seconds(5)
235 * Duration.fromInputUnsafe("Infinity") // => Duration.infinity
236 * Duration.fromInputUnsafe([2, 500_000_000]) // => Duration.nanos(2_500_000_000n)
237 * ```
238 *
239 * @category constructors
240 * @since 4.0.0
241 */
242export const fromInputUnsafe = (input: Input): Duration => {
243 switch (typeof input) {
244 case "number":
245 return millis(input)
246 case "bigint":
247 return nanos(input)
248 case "string": {
249 if (input === "Infinity") {
250 return infinity
251 }
252 if (input === "-Infinity") {
253 return negativeInfinity
254 }
255 const match = DURATION_REGEXP.exec(input)
256 if (!match) break
257 const [_, valueStr, unit] = match
258 if (unit === "nano" || unit === "nanos") {
259 return nanos(parseNanos(valueStr, bigint1))
260 }
261 if (unit === "micro" || unit === "micros") {
262 return nanos(parseNanos(valueStr, bigint1e3))
263 }
264 const value = Number(valueStr)
265 switch (unit) {
266 case "milli":
267 case "millis":
268 return millis(value)
269 case "second":
270 case "seconds":
271 return seconds(value)
272 case "minute":
273 case "minutes":
274 return minutes(value)
275 case "hour":
276 case "hours":
277 return hours(value)
278 case "day":
279 case "days":
280 return days(value)
281 case "week":
282 case "weeks":
283 return weeks(value)
284 }
285 break
286 }
287 case "object": {
288 if (input === null) break
289 if (TypeId in input) return input as Duration
290 if (Array.isArray(input)) {
291 if (input.length !== 2 || !input.every(isNumber)) {
292 return invalid(input)
293 }
294 if (Number.isNaN(input[0]) || Number.isNaN(input[1])) {
295 return zero
296 }
297 if (input[0] === -Infinity || input[1] === -Infinity) {
298 return negativeInfinity
299 }
300 if (input[0] === Infinity || input[1] === Infinity) {
301 return infinity
302 }
303 return make(roundTiesAwayFromZero(input[0] * 1_000_000_000 + input[1]))
304 }
305 const obj = input as DurationObject
306 let millis = 0
307 // we can use truthy checks here, because 0 can be ignored
308 if (obj.weeks) millis += obj.weeks * 604_800_000
309 if (obj.days) millis += obj.days * 86_400_000
310 if (obj.hours) millis += obj.hours * 3_600_000
311 if (obj.minutes) millis += obj.minutes * 60_000
312 if (obj.seconds) millis += obj.seconds * 1_000
313 if (obj.milliseconds) millis += obj.milliseconds
314 if (!obj.microseconds && !obj.nanoseconds) return make(millis)
315 return make(roundTiesAwayFromZero(
316 millis * 1_000_000 + (obj.microseconds ?? 0) * 1_000 + (obj.nanoseconds ?? 0)
317 ))
318 }
319 }
320 return invalid(input)
321}
322
323const invalid = (input: unknown): never => {
324 throw new Error(`Invalid Input: ${input}`)
325}
326
327/**
328 * Decodes a `Input` value into a `Duration` safely, returning
329 * `Option.none()` if decoding fails.
330 *
331 * **Example** (Safely decoding duration inputs)
332 *
333 * ```ts import.meta.vitest
334 * import { Duration, Option } from "effect"
335 *
336 * Duration.fromInput(1000) // => Option.some(Duration.seconds(1))
337 * Duration.fromInput("invalid" as any) // => Option.none()
338 * ```
339 *
340 * @category constructors
341 * @since 4.0.0
342 */
343export const fromInput: (u: Input) => Option.Option<Duration> = Option.liftThrowable(
344 fromInputUnsafe
345)
346
347const zeroDurationValue: DurationValue = { _tag: "Millis", millis: 0 }
348const infinityDurationValue: DurationValue = { _tag: "Infinity" }
349const negativeInfinityDurationValue: DurationValue = { _tag: "NegativeInfinity" }
350
351const DurationProto: Omit<Duration, "value"> = {
352 [TypeId]: TypeId,
353 [Hash.symbol](this: Duration) {
354 // Hash equal finite durations using the same canonical nanoseconds
355 // representation used by `equals`.
356 switch (this.value._tag) {
357 case "Millis": {
358 const nanos = this.value.millis * 1_000_000
359 return Number.isFinite(nanos) ? Hash.hash(roundTiesAwayFromZero(nanos)) : Hash.number(this.value.millis)
360 }
361 case "Nanos":
362 return Hash.hash(this.value.nanos)
363 default:
364 return Hash.structure(this.value)
365 }
366 },
367 [Equal.symbol](this: Duration, that: unknown): boolean {
368 return isDuration(that) && equals(this, that)
369 },
370 toString(this: Duration) {
371 switch (this.value._tag) {
372 case "Infinity":
373 return "Infinity"
374 case "NegativeInfinity":
375 return "-Infinity"
376 case "Nanos":
377 return `${this.value.nanos} nanos`
378 case "Millis":
379 return `${this.value.millis} millis`
380 }
381 },
382 toJSON(this: Duration) {
383 switch (this.value._tag) {
384 case "Millis":
385 return { _id: "Duration", _tag: "Millis", millis: this.value.millis }
386 case "Nanos":
387 return { _id: "Duration", _tag: "Nanos", nanos: String(this.value.nanos) }
388 case "Infinity":
389 return { _id: "Duration", _tag: "Infinity" }
390 case "NegativeInfinity":
391 return { _id: "Duration", _tag: "NegativeInfinity" }
392 }
393 },
394 [NodeInspectSymbol]() {
395 return this.toJSON()
396 },
397 pipe() {
398 return pipeArguments(this, arguments)
399 }
400} as const
401
402const make = (input: number | bigint): Duration => {
403 const duration = Object.create(DurationProto)
404 if (typeof input === "number") {
405 if (isNaN(input) || input === 0 || Object.is(input, -0)) {
406 duration.value = zeroDurationValue
407 } else if (!Number.isFinite(input)) {
408 duration.value = input > 0 ? infinityDurationValue : negativeInfinityDurationValue
409 } else if (!Number.isInteger(input)) {
410 duration.value = { _tag: "Nanos", nanos: roundMillisToNanos(input) }
411 } else {
412 duration.value = { _tag: "Millis", millis: input }
413 }
414 } else if (input === bigint0) {
415 duration.value = zeroDurationValue
416 } else {
417 duration.value = { _tag: "Nanos", nanos: input }
418 }
419 return duration
420}
421
422/**
423 * Checks whether a value is a Duration.
424 *
425 * **Example** (Checking for durations)
426 *
427 * ```ts import.meta.vitest
428 * import { Duration } from "effect"
429 *
430 * Duration.isDuration(Duration.seconds(1)) // => true
431 * Duration.isDuration(1000) // => false
432 * ```
433 *
434 * @category guards
435 * @since 2.0.0
436 */
437export const isDuration = (u: unknown): u is Duration => hasProperty(u, TypeId)
438
439/**
440 * Checks whether a Duration is finite (not infinite).
441 *
442 * **Example** (Checking finite durations)
443 *
444 * ```ts import.meta.vitest
445 * import { Duration } from "effect"
446 *
447 * Duration.isFinite(Duration.seconds(5)) // => true
448 * Duration.isFinite(Duration.infinity) // => false
449 * ```
450 *
451 * @category predicates
452 * @since 2.0.0
453 */
454export const isFinite = (self: Duration): boolean =>
455 self.value._tag !== "Infinity" && self.value._tag !== "NegativeInfinity"
456
457/**
458 * Checks whether a Duration is zero.
459 *
460 * **Example** (Checking for zero durations)
461 *
462 * ```ts import.meta.vitest
463 * import { Duration } from "effect"
464 *
465 * Duration.isZero(Duration.zero) // => true
466 * Duration.isZero(Duration.seconds(1)) // => false
467 * ```
468 *
469 * @category predicates
470 * @since 3.5.0
471 */
472export const isZero = (self: Duration): boolean => {
473 switch (self.value._tag) {
474 case "Millis":
475 return self.value.millis === 0
476 case "Nanos":
477 return self.value.nanos === bigint0
478 case "Infinity":
479 case "NegativeInfinity":
480 return false
481 }
482}
483
484/**
485 * Returns `true` if the duration is negative (strictly less than zero).
486 *
487 * **Example** (Checking for negative durations)
488 *
489 * ```ts import.meta.vitest
490 * import { Duration } from "effect"
491 *
492 * Duration.isNegative(Duration.seconds(-5)) // => true
493 * Duration.isNegative(Duration.zero) // => false
494 * Duration.isNegative(Duration.negativeInfinity) // => true
495 * ```
496 *
497 * @category predicates
498 * @since 4.0.0
499 */
500export const isNegative = (self: Duration): boolean => {
501 switch (self.value._tag) {
502 case "Millis":
503 return self.value.millis < 0
504 case "Nanos":
505 return self.value.nanos < bigint0
506 case "NegativeInfinity":
507 return true
508 case "Infinity":
509 return false
510 }
511}
512
513/**
514 * Returns `true` if the duration is positive (strictly greater than zero).
515 *
516 * **Example** (Checking for positive durations)
517 *
518 * ```ts import.meta.vitest
519 * import { Duration } from "effect"
520 *
521 * Duration.isPositive(Duration.seconds(5)) // => true
522 * Duration.isPositive(Duration.zero) // => false
523 * Duration.isPositive(Duration.infinity) // => true
524 * ```
525 *
526 * @category predicates
527 * @since 4.0.0
528 */
529export const isPositive = (self: Duration): boolean => {
530 switch (self.value._tag) {
531 case "Millis":
532 return self.value.millis > 0
533 case "Nanos":
534 return self.value.nanos > bigint0
535 case "Infinity":
536 return true
537 case "NegativeInfinity":
538 return false
539 }
540}
541
542/**
543 * Returns the absolute value of the duration.
544 *
545 * **Example** (Taking absolute duration values)
546 *
547 * ```ts import.meta.vitest
548 * import { Duration } from "effect"
549 *
550 * Duration.abs(Duration.seconds(-5)) // => Duration.seconds(5)
551 * Duration.abs(Duration.negativeInfinity) // => Duration.infinity
552 * ```
553 *
554 * @category math
555 * @since 4.0.0
556 */
557export const abs = (self: Duration): Duration => {
558 switch (self.value._tag) {
559 case "Infinity":
560 case "NegativeInfinity":
561 return infinity
562 case "Millis":
563 return self.value.millis < 0 ? make(-self.value.millis) : self
564 case "Nanos":
565 return self.value.nanos < bigint0 ? make(-self.value.nanos) : self
566 }
567}
568
569/**
570 * Returns the negated duration.
571 *
572 * **Example** (Negating durations)
573 *
574 * ```ts import.meta.vitest
575 * import { Duration } from "effect"
576 *
577 * Duration.negate(Duration.seconds(5)) // => Duration.seconds(-5)
578 * Duration.negate(Duration.infinity) // => Duration.negativeInfinity
579 * ```
580 *
581 * @category math
582 * @since 4.0.0
583 */
584export const negate = (self: Duration): Duration => {
585 switch (self.value._tag) {
586 case "Infinity":
587 return negativeInfinity
588 case "NegativeInfinity":
589 return infinity
590 case "Millis":
591 return self.value.millis === 0 ? self : make(-self.value.millis)
592 case "Nanos":
593 return self.value.nanos === bigint0 ? self : make(-self.value.nanos)
594 }
595}
596
597/**
598 * A Duration representing zero time.
599 *
600 * **Example** (Referencing the zero duration)
601 *
602 * ```ts import.meta.vitest
603 * import { Duration } from "effect"
604 *
605 * Duration.toMillis(Duration.zero) // => 0
606 * ```
607 *
608 * @category constructors
609 * @since 2.0.0
610 */
611export const zero: Duration = make(0)
612
613/**
614 * A Duration representing infinite time.
615 *
616 * **Example** (Referencing infinite duration)
617 *
618 * ```ts import.meta.vitest
619 * import { Duration } from "effect"
620 *
621 * Duration.toMillis(Duration.infinity) // => Infinity
622 * ```
623 *
624 * @category constructors
625 * @since 2.0.0
626 */
627export const infinity: Duration = make(Infinity)
628
629/**
630 * A Duration representing negative infinite time.
631 *
632 * **Example** (Referencing negative infinite duration)
633 *
634 * ```ts import.meta.vitest
635 * import { Duration } from "effect"
636 *
637 * Duration.toMillis(Duration.negativeInfinity) // => -Infinity
638 * ```
639 *
640 * @category constructors
641 * @since 4.0.0
642 */
643export const negativeInfinity: Duration = make(-Infinity)
644
645/**
646 * Creates a Duration from nanoseconds.
647 *
648 * **Example** (Creating durations from nanoseconds)
649 *
650 * ```ts import.meta.vitest
651 * import { Duration } from "effect"
652 *
653 * Duration.nanos(500_000_000n) // => Duration.nanos(500_000_000n)
654 * ```
655 *
656 * @category constructors
657 * @since 2.0.0
658 */
659export const nanos = (nanos: bigint): Duration => make(nanos)
660
661/**
662 * Creates a Duration from microseconds.
663 *
664 * **Example** (Creating durations from microseconds)
665 *
666 * ```ts import.meta.vitest
667 * import { Duration } from "effect"
668 *
669 * Duration.micros(500_000n) // => Duration.nanos(500_000_000n)
670 * ```
671 *
672 * @category constructors
673 * @since 2.0.0
674 */
675export const micros = (micros: bigint): Duration => make(micros * bigint1e3)
676
677/**
678 * Creates a Duration from milliseconds.
679 *
680 * **Example** (Creating durations from milliseconds)
681 *
682 * ```ts import.meta.vitest
683 * import { Duration } from "effect"
684 *
685 * Duration.toMillis(Duration.millis(1000)) // => 1000
686 * ```
687 *
688 * @category constructors
689 * @since 2.0.0
690 */
691export const millis = (millis: number): Duration => make(millis)
692
693/**
694 * Creates a Duration from seconds.
695 *
696 * **Example** (Creating durations from seconds)
697 *
698 * ```ts import.meta.vitest
699 * import { Duration } from "effect"
700 *
701 * Duration.toMillis(Duration.seconds(30)) // => 30_000
702 * ```
703 *
704 * @category constructors
705 * @since 2.0.0
706 */
707export const seconds = (seconds: number): Duration => make(seconds * 1000)
708
709/**
710 * Creates a Duration from minutes.
711 *
712 * **Example** (Creating durations from minutes)
713 *
714 * ```ts import.meta.vitest
715 * import { Duration } from "effect"
716 *
717 * Duration.toMillis(Duration.minutes(5)) // => 300_000
718 * ```
719 *
720 * @category constructors
721 * @since 2.0.0
722 */
723export const minutes = (minutes: number): Duration => make(minutes * 60_000)
724
725/**
726 * Creates a Duration from hours.
727 *
728 * **Example** (Creating durations from hours)
729 *
730 * ```ts import.meta.vitest
731 * import { Duration } from "effect"
732 *
733 * Duration.toMillis(Duration.hours(2)) // => 7_200_000
734 * ```
735 *
736 * @category constructors
737 * @since 2.0.0
738 */
739export const hours = (hours: number): Duration => make(hours * 3_600_000)
740
741/**
742 * Creates a Duration from days.
743 *
744 * **Example** (Creating durations from days)
745 *
746 * ```ts import.meta.vitest
747 * import { Duration } from "effect"
748 *
749 * Duration.toMillis(Duration.days(1)) // => 86_400_000
750 * ```
751 *
752 * @category constructors
753 * @since 2.0.0
754 */
755export const days = (days: number): Duration => make(days * 86_400_000)
756
757/**
758 * Creates a Duration from weeks.
759 *
760 * **Example** (Creating durations from weeks)
761 *
762 * ```ts import.meta.vitest
763 * import { Duration } from "effect"
764 *
765 * Duration.toMillis(Duration.weeks(1)) // => 604_800_000
766 * ```
767 *
768 * @category constructors
769 * @since 2.0.0
770 */
771export const weeks = (weeks: number): Duration => make(weeks * 604_800_000)
772
773/**
774 * Converts a Duration to milliseconds.
775 *
776 * **Example** (Converting durations to milliseconds)
777 *
778 * ```ts import.meta.vitest
779 * import { Duration } from "effect"
780 *
781 * Duration.toMillis(Duration.seconds(5)) // => 5000
782 * Duration.toMillis(Duration.minutes(2)) // => 120_000
783 * ```
784 *
785 * @category getters
786 * @since 2.0.0
787 */
788export const toMillis = (self: Input): number =>
789 match(fromInputUnsafe(self), {
790 onMillis: identity,
791 onNanos: (nanos) => Number(nanos) / 1_000_000,
792 onInfinity: () => Infinity,
793 onNegativeInfinity: () => -Infinity
794 })
795
796/**
797 * Converts a Duration to seconds.
798 *
799 * **Example** (Converting durations to seconds)
800 *
801 * ```ts import.meta.vitest
802 * import { Duration } from "effect"
803 *
804 * Duration.toSeconds(Duration.millis(5000)) // => 5
805 * Duration.toSeconds(Duration.minutes(2)) // => 120
806 * ```
807 *
808 * @category getters
809 * @since 2.0.0
810 */
811export const toSeconds = (self: Input): number =>
812 match(fromInputUnsafe(self), {
813 onMillis: (millis) => millis / 1_000,
814 onNanos: (nanos) => Number(nanos) / 1_000_000_000,
815 onInfinity: () => Infinity,
816 onNegativeInfinity: () => -Infinity
817 })
818
819/**
820 * Converts a Duration to minutes.
821 *
822 * **Example** (Converting durations to minutes)
823 *
824 * ```ts import.meta.vitest
825 * import { Duration } from "effect"
826 *
827 * Duration.toMinutes(Duration.seconds(120)) // => 2
828 * Duration.toMinutes(Duration.hours(1)) // => 60
829 * ```
830 *
831 * @category getters
832 * @since 3.8.0
833 */
834export const toMinutes = (self: Input): number =>
835 match(fromInputUnsafe(self), {
836 onMillis: (millis) => millis / 60_000,
837 onNanos: (nanos) => Number(nanos) / 60_000_000_000,
838 onInfinity: () => Infinity,
839 onNegativeInfinity: () => -Infinity
840 })
841
842/**
843 * Converts a Duration to hours.
844 *
845 * **Example** (Converting durations to hours)
846 *
847 * ```ts import.meta.vitest
848 * import { Duration } from "effect"
849 *
850 * Duration.toHours(Duration.minutes(120)) // => 2
851 * Duration.toHours(Duration.days(1)) // => 24
852 * ```
853 *
854 * @category getters
855 * @since 3.8.0
856 */
857export const toHours = (self: Input): number =>
858 match(fromInputUnsafe(self), {
859 onMillis: (millis) => millis / 3_600_000,
860 onNanos: (nanos) => Number(nanos) / 3_600_000_000_000,
861 onInfinity: () => Infinity,
862 onNegativeInfinity: () => -Infinity
863 })
864
865/**
866 * Converts a Duration to days.
867 *
868 * **Example** (Converting durations to days)
869 *
870 * ```ts import.meta.vitest
871 * import { Duration } from "effect"
872 *
873 * Duration.toDays(Duration.hours(48)) // => 2
874 * Duration.toDays(Duration.weeks(1)) // => 7
875 * ```
876 *
877 * @category getters
878 * @since 3.8.0
879 */
880export const toDays = (self: Input): number =>
881 match(fromInputUnsafe(self), {
882 onMillis: (millis) => millis / 86_400_000,
883 onNanos: (nanos) => Number(nanos) / 86_400_000_000_000,
884 onInfinity: () => Infinity,
885 onNegativeInfinity: () => -Infinity
886 })
887
888/**
889 * Converts a Duration to weeks.
890 *
891 * **Example** (Converting durations to weeks)
892 *
893 * ```ts import.meta.vitest
894 * import { Duration } from "effect"
895 *
896 * Duration.toWeeks(Duration.days(14)) // => 2
897 * Duration.toWeeks(Duration.days(7)) // => 1
898 * ```
899 *
900 * @category getters
901 * @since 3.8.0
902 */
903export const toWeeks = (self: Input): number =>
904 match(fromInputUnsafe(self), {
905 onMillis: (millis) => millis / 604_800_000,
906 onNanos: (nanos) => Number(nanos) / 604_800_000_000_000,
907 onInfinity: () => Infinity,
908 onNegativeInfinity: () => -Infinity
909 })
910
911/**
912 * Gets the duration in nanoseconds as a bigint.
913 *
914 * **When to use**
915 *
916 * Use when the duration is known to be finite and you need the nanosecond value
917 * as a `bigint`.
918 *
919 * **Details**
920 *
921 * Millisecond-backed fractional durations are rounded to the nearest
922 * nanosecond, with ties away from zero.
923 *
924 * **Gotchas**
925 *
926 * If the duration is infinite, it throws an error.
927 *
928 * **Example** (Reading nanoseconds unsafely)
929 *
930 * ```ts import.meta.vitest
931 * import { Duration } from "effect"
932 *
933 * Duration.toNanosUnsafe(Duration.seconds(2)) // => 2_000_000_000n
934 *
935 * // Duration.toNanosUnsafe(Duration.infinity)
936 * // throws Error: "Cannot convert infinite duration to nanos"
937 * ```
938 *
939 * @category getters
940 * @since 4.0.0
941 */
942export const toNanosUnsafe = (input: Input): bigint => {
943 const self = fromInputUnsafe(input)
944 switch (self.value._tag) {
945 case "Infinity":
946 case "NegativeInfinity":
947 throw new Error("Cannot convert infinite duration to nanos")
948 case "Nanos":
949 return self.value.nanos
950 case "Millis":
951 return roundMillisToNanos(self.value.millis)
952 }
953}
954
955/**
956 * Gets the duration in nanoseconds safely as an `Option<bigint>`.
957 *
958 * **Details**
959 *
960 * If the duration is infinite, returns `Option.none()`.
961 *
962 * **Example** (Safely reading nanoseconds)
963 *
964 * ```ts import.meta.vitest
965 * import { Duration, Option } from "effect"
966 *
967 * Duration.toNanos(Duration.seconds(1)) // => Option.some(1_000_000_000n)
968 * Duration.toNanos(Duration.infinity) // => Option.none()
969 * ```
970 *
971 * @category getters
972 * @since 2.0.0
973 */
974export const toNanos: (self: Input) => Option.Option<bigint> = Option.liftThrowable(toNanosUnsafe)
975
976/**
977 * Converts a Duration to high-resolution time format [seconds, nanoseconds].
978 *
979 * **Example** (Converting durations to high-resolution time)
980 *
981 * ```ts import.meta.vitest
982 * import { Duration } from "effect"
983 *
984 * Duration.toHrTime(Duration.millis(1500)) // => [1, 500_000_000]
985 * ```
986 *
987 * @category getters
988 * @since 2.0.0
989 */
990export const toHrTime = (input: Input): [seconds: number, nanos: number] => {
991 const self = fromInputUnsafe(input)
992 switch (self.value._tag) {
993 case "Infinity":
994 return [Infinity, 0]
995 case "NegativeInfinity":
996 return [-Infinity, 0]
997 case "Nanos":
998 return nanosToHrTime(self.value.nanos)
999 case "Millis":
1000 return nanosToHrTime(roundMillisToNanos(self.value.millis))
1001 }
1002}
1003
1004/**
1005 * Pattern matches on the representation of a `Duration`.
1006 *
1007 * **Details**
1008 *
1009 * Provide handlers for millisecond-backed values, nanosecond-backed values,
1010 * and positive infinity. Use `onNegativeInfinity` to handle negative infinity
1011 * separately; otherwise negative infinity is handled by `onInfinity`.
1012 *
1013 * **Example** (Pattern matching on duration representations)
1014 *
1015 * ```ts import.meta.vitest
1016 * import { Duration } from "effect"
1017 *
1018 * Duration.match(Duration.seconds(5), {
1019 * onMillis: (millis) => `${millis} milliseconds`,
1020 * onNanos: (nanos) => `${nanos} nanoseconds`,
1021 * onInfinity: () => "infinite"
1022 * }) // => "5000 milliseconds"
1023 * ```
1024 *
1025 * @category pattern matching
1026 * @since 2.0.0
1027 */
1028export const match: {
1029 <A, B, C, D = C>(
1030 options: {
1031 readonly onMillis: (millis: number) => A
1032 readonly onNanos: (nanos: bigint) => B
1033 readonly onInfinity: () => C
1034 readonly onNegativeInfinity?: () => D
1035 }
1036 ): (self: Duration) => A | B | C | D
1037 <A, B, C, D = C>(
1038 self: Duration,
1039 options: {
1040 readonly onMillis: (millis: number) => A
1041 readonly onNanos: (nanos: bigint) => B
1042 readonly onInfinity: () => C
1043 readonly onNegativeInfinity?: () => D
1044 }
1045 ): A | B | C | D
1046} = dual(2, <A, B, C, D = C>(
1047 self: Duration,
1048 options: {
1049 readonly onMillis: (millis: number) => A
1050 readonly onNanos: (nanos: bigint) => B
1051 readonly onInfinity: () => C
1052 readonly onNegativeInfinity?: () => D
1053 }
1054): A | B | C | D => {
1055 switch (self.value._tag) {
1056 case "Millis":
1057 return options.onMillis(self.value.millis)
1058 case "Nanos":
1059 return options.onNanos(self.value.nanos)
1060 case "Infinity":
1061 return options.onInfinity()
1062 case "NegativeInfinity":
1063 return (options.onNegativeInfinity ?? options.onInfinity as unknown as () => D)()
1064 }
1065})
1066
1067/**
1068 * Pattern matches on two `Duration`s, providing handlers that receive both values.
1069 *
1070 * **Example** (Pattern matching on duration pairs)
1071 *
1072 * ```ts import.meta.vitest
1073 * import { Duration } from "effect"
1074 *
1075 * Duration.matchPair(Duration.seconds(3), Duration.seconds(2), {
1076 * onMillis: (a, b) => a + b,
1077 * onNanos: (a, b) => Number(a + b),
1078 * onInfinity: () => Infinity
1079 * }) // => 5000
1080 * ```
1081 *
1082 * @category pattern matching
1083 * @since 4.0.0
1084 */
1085export const matchPair: {
1086 <A, B, C>(
1087 that: Duration,
1088 options: {
1089 readonly onMillis: (self: number, that: number) => A
1090 readonly onNanos: (self: bigint, that: bigint) => B
1091 readonly onInfinity: (self: Duration, that: Duration) => C
1092 }
1093 ): (self: Duration) => A | B | C
1094 <A, B, C>(
1095 self: Duration,
1096 that: Duration,
1097 options: {
1098 readonly onMillis: (self: number, that: number) => A
1099 readonly onNanos: (self: bigint, that: bigint) => B
1100 readonly onInfinity: (self: Duration, that: Duration) => C
1101 }
1102 ): A | B | C
1103} = dual(3, <A, B, C>(
1104 self: Duration,
1105 that: Duration,
1106 options: {
1107 readonly onMillis: (self: number, that: number) => A
1108 readonly onNanos: (self: bigint, that: bigint) => B
1109 readonly onInfinity: (self: Duration, that: Duration) => C
1110 }
1111): A | B | C => {
1112 if (
1113 self.value._tag === "Infinity" || self.value._tag === "NegativeInfinity" ||
1114 that.value._tag === "Infinity" || that.value._tag === "NegativeInfinity"
1115 ) return options.onInfinity(self, that)
1116 if (self.value._tag === "Millis") {
1117 return that.value._tag === "Millis"
1118 ? options.onMillis(self.value.millis, that.value.millis)
1119 : options.onNanos(toNanosUnsafe(self), that.value.nanos)
1120 } else {
1121 return options.onNanos(self.value.nanos, toNanosUnsafe(that))
1122 }
1123})
1124
1125/**
1126 * Provides an `Order` instance for comparing `Duration` values.
1127 *
1128 * **Details**
1129 *
1130 * `NegativeInfinity` < any finite value < `Infinity`.
1131 *
1132 * **Example** (Sorting durations)
1133 *
1134 * ```ts import.meta.vitest
1135 * import { Duration } from "effect"
1136 *
1137 * const durations = [
1138 * Duration.seconds(3),
1139 * Duration.seconds(1),
1140 * Duration.seconds(2)
1141 * ]
1142 * durations.sort((a, b) => Duration.Order(a, b)).map(Duration.toSeconds) // => [1, 2, 3]
1143 * ```
1144 *
1145 * @category instances
1146 * @since 2.0.0
1147 */
1148export const Order: order.Order<Duration> = order.make((self, that) =>
1149 matchPair(self, that, {
1150 onMillis: (self, that) => (self < that ? -1 : self > that ? 1 : 0),
1151 onNanos: (self, that) => (self < that ? -1 : self > that ? 1 : 0),
1152 onInfinity: (self, that) => {
1153 if (self.value._tag === that.value._tag) return 0
1154 if (self.value._tag === "Infinity") return 1
1155 if (self.value._tag === "NegativeInfinity") return -1
1156 // self is finite
1157 if (that.value._tag === "Infinity") return -1
1158 // that is NegativeInfinity
1159 return 1
1160 }
1161 })
1162)
1163
1164/**
1165 * Returns `true` if a `Duration` is greater than or equal to `minimum` and
1166 * less than or equal to `maximum`, according to `Duration.Order`.
1167 *
1168 * **When to use**
1169 *
1170 * Use to test whether a duration is inside an inclusive range.
1171 *
1172 * **Details**
1173 *
1174 * Both bounds are inclusive and compared with `Duration.Order`.
1175 *
1176 * **Gotchas**
1177 *
1178 * The bounds are not normalized. If `minimum` is greater than `maximum`, the
1179 * predicate returns `false` for every duration.
1180 *
1181 * **Example** (Checking duration ranges)
1182 *
1183 * ```ts import.meta.vitest
1184 * import { Duration } from "effect"
1185 *
1186 * Duration.between(Duration.seconds(3), {
1187 * minimum: Duration.seconds(2),
1188 * maximum: Duration.seconds(5)
1189 * }) // => true
1190 * ```
1191 *
1192 * @see {@link clamp} for constraining a duration to a range
1193 * @see {@link isGreaterThanOrEqualTo} for checking only the lower bound
1194 * @see {@link isLessThanOrEqualTo} for checking only the upper bound
1195 *
1196 * @category predicates
1197 * @since 2.0.0
1198 */
1199export const between: {
1200 (options: { minimum: Duration; maximum: Duration }): (self: Duration) => boolean
1201 (self: Duration, options: { minimum: Duration; maximum: Duration }): boolean
1202} = order.isBetween(Order)
1203
1204/**
1205 * Provides an `Equivalence` instance for comparing `Duration` values.
1206 *
1207 * **Example** (Comparing durations for equivalence)
1208 *
1209 * ```ts import.meta.vitest
1210 * import { Duration } from "effect"
1211 *
1212 * Duration.Equivalence(Duration.seconds(5), Duration.millis(5000)) // => true
1213 * ```
1214 *
1215 * @category instances
1216 * @since 2.0.0
1217 */
1218export const Equivalence: Equ.Equivalence<Duration> = (self, that) =>
1219 matchPair(self, that, {
1220 onMillis: (self, that) => self === that,
1221 onNanos: (self, that) => self === that,
1222 onInfinity: (self, that) => self.value._tag === that.value._tag
1223 })
1224
1225/**
1226 * Returns the smaller of two Durations.
1227 *
1228 * **Example** (Selecting the shorter duration)
1229 *
1230 * ```ts import.meta.vitest
1231 * import { Duration } from "effect"
1232 *
1233 * Duration.min(Duration.seconds(5), Duration.seconds(3)) // => Duration.seconds(3)
1234 * ```
1235 *
1236 * @category ordering
1237 * @since 2.0.0
1238 */
1239export const min: {
1240 (that: Duration): (self: Duration) => Duration
1241 (self: Duration, that: Duration): Duration
1242} = order.min(Order)
1243
1244/**
1245 * Returns the larger of two Durations.
1246 *
1247 * **Example** (Selecting the longer duration)
1248 *
1249 * ```ts import.meta.vitest
1250 * import { Duration } from "effect"
1251 *
1252 * Duration.max(Duration.seconds(5), Duration.seconds(3)) // => Duration.seconds(5)
1253 * ```
1254 *
1255 * @category ordering
1256 * @since 2.0.0
1257 */
1258export const max: {
1259 (that: Duration): (self: Duration) => Duration
1260 (self: Duration, that: Duration): Duration
1261} = order.max(Order)
1262
1263/**
1264 * Returns a `Duration` constrained between a minimum and maximum value.
1265 *
1266 * **Example** (Clamping durations to a range)
1267 *
1268 * ```ts import.meta.vitest
1269 * import { Duration } from "effect"
1270 *
1271 * Duration.clamp(Duration.seconds(10), {
1272 * minimum: Duration.seconds(2),
1273 * maximum: Duration.seconds(5)
1274 * }) // => Duration.seconds(5)
1275 * ```
1276 *
1277 * @category ordering
1278 * @since 2.0.0
1279 */
1280export const clamp: {
1281 (options: { minimum: Duration; maximum: Duration }): (self: Duration) => Duration
1282 (self: Duration, options: { minimum: Duration; maximum: Duration }): Duration
1283} = order.clamp(Order)
1284
1285/**
1286 * Divides a `Duration` by a finite, non-zero number safely.
1287 *
1288 * **Details**
1289 *
1290 * Returns `Option.none()` for zero, negative zero, or non-finite divisors. For
1291 * nanosecond-backed durations, also returns `Option.none()` when the divisor
1292 * cannot be converted to a `bigint`, such as a fractional divisor.
1293 *
1294 * **Example** (Safely dividing durations)
1295 *
1296 * ```ts import.meta.vitest
1297 * import { Duration, Option } from "effect"
1298 *
1299 * Duration.divide(Duration.seconds(10), 2) // => Option.some(Duration.seconds(5))
1300 * Duration.divide(Duration.seconds(10), 0) // => Option.none()
1301 * ```
1302 *
1303 * @category math
1304 * @since 2.4.19
1305 */
1306export const divide: {
1307 (by: number): (self: Duration) => Option.Option<Duration>
1308 (self: Duration, by: number): Option.Option<Duration>
1309} = dual(
1310 2,
1311 (self: Duration, by: number): Option.Option<Duration> => {
1312 if (!Number.isFinite(by)) return Option.none()
1313 if (by === 0 || Object.is(by, -0)) return Option.none()
1314 return match(self, {
1315 onMillis: (millis) => Option.some(make(millis / by)),
1316 onNanos: (nanos) => {
1317 try {
1318 return Option.some(make(nanos / BigInt(by)))
1319 } catch {
1320 return Option.none()
1321 }
1322 },
1323 onInfinity: () => Option.some(by > 0 ? infinity : negativeInfinity),
1324 onNegativeInfinity: () => Option.some(by > 0 ? negativeInfinity : infinity)
1325 })
1326 }
1327)
1328
1329/**
1330 * Divides a `Duration` by a number using fallback rules instead of returning
1331 * an `Option`.
1332 *
1333 * **When to use**
1334 *
1335 * Use when dividing a `Duration` should return `Duration.zero` or signed
1336 * infinity for invalid cases instead of forcing callers to handle `Option.none`.
1337 *
1338 * **Details**
1339 *
1340 * Non-finite divisors return `Duration.zero`. Division by positive or negative
1341 * zero can produce signed infinity for non-zero finite durations, while zero
1342 * or infinite durations divided by zero produce `Duration.zero`.
1343 * Nanosecond-backed durations return `Duration.zero` when the divisor cannot
1344 * be converted to a `bigint`.
1345 *
1346 * **Example** (Dividing durations unsafely)
1347 *
1348 * ```ts import.meta.vitest
1349 * import { Duration } from "effect"
1350 *
1351 * Duration.divideUnsafe(Duration.seconds(10), 2) // => Duration.seconds(5)
1352 * Duration.divideUnsafe(Duration.seconds(10), 0) // => Duration.infinity
1353 * ```
1354 *
1355 * @category math
1356 * @since 4.0.0
1357 */
1358export const divideUnsafe: {
1359 (by: number): (self: Duration) => Duration
1360 (self: Duration, by: number): Duration
1361} = dual(
1362 2,
1363 (self: Duration, by: number): Duration => {
1364 if (!Number.isFinite(by)) return zero
1365 return match(self, {
1366 onMillis: (millis) => make(millis / by),
1367 onNanos: (nanos) => {
1368 if (Object.is(by, 0) || Object.is(by, -0)) {
1369 if (nanos === bigint0) return zero
1370 // match IEEE 754: same sign → +infinity, different sign → -infinity
1371 const positiveNanos = nanos > bigint0
1372 const positiveZero = Object.is(by, 0)
1373 return (positiveNanos === positiveZero) ? infinity : negativeInfinity
1374 }
1375 try {
1376 return make(nanos / BigInt(by))
1377 } catch {
1378 return zero
1379 }
1380 },
1381 onInfinity: () => by > 0 ? infinity : by < 0 ? negativeInfinity : zero,
1382 onNegativeInfinity: () => by > 0 ? negativeInfinity : by < 0 ? infinity : zero
1383 })
1384 }
1385)
1386
1387/**
1388 * Returns a `Duration` multiplied by a number.
1389 *
1390 * **Details**
1391 *
1392 * For nanosecond-backed durations, the multiplier must be convertible to a
1393 * `bigint`; fractional or non-finite multipliers can throw. Infinite
1394 * durations return positive infinity, negative infinity, or zero depending on
1395 * the multiplier sign.
1396 *
1397 * **Example** (Multiplying durations)
1398 *
1399 * ```ts import.meta.vitest
1400 * import { Duration } from "effect"
1401 *
1402 * Duration.times(Duration.seconds(5), 2) // => Duration.seconds(10)
1403 * ```
1404 *
1405 * @category math
1406 * @since 2.0.0
1407 */
1408export const times: {
1409 (times: number): (self: Duration) => Duration
1410 (self: Duration, times: number): Duration
1411} = dual(
1412 2,
1413 (self: Duration, times: number): Duration =>
1414 match(self, {
1415 onMillis: (millis) => make(millis * times),
1416 onNanos: (nanos) => make(nanos * BigInt(times)),
1417 onInfinity: () => times > 0 ? infinity : times < 0 ? negativeInfinity : zero,
1418 onNegativeInfinity: () => times > 0 ? negativeInfinity : times < 0 ? infinity : zero
1419 })
1420)
1421
1422/**
1423 * Subtracts one Duration from another. The result can be negative.
1424 *
1425 * **Details**
1426 *
1427 * Infinity subtraction follows signed-infinity arithmetic. Subtracting the
1428 * same infinity from itself returns zero. Positive infinity minus negative
1429 * infinity or any finite duration remains positive infinity. Negative infinity
1430 * minus positive infinity or any finite duration remains negative infinity.
1431 * Finite durations minus positive infinity produce negative infinity, and
1432 * finite durations minus negative infinity produce positive infinity.
1433 *
1434 * **Example** (Subtracting durations)
1435 *
1436 * ```ts import.meta.vitest
1437 * import { Duration } from "effect"
1438 *
1439 * Duration.subtract(Duration.seconds(10), Duration.seconds(3)) // => Duration.seconds(7)
1440 * ```
1441 *
1442 * @category math
1443 * @since 2.0.0
1444 */
1445export const subtract: {
1446 (that: Duration): (self: Duration) => Duration
1447 (self: Duration, that: Duration): Duration
1448} = dual(
1449 2,
1450 (self: Duration, that: Duration): Duration =>
1451 matchPair(self, that, {
1452 onMillis: (self, that) => make(self - that),
1453 onNanos: (self, that) => make(self - that),
1454 onInfinity: (self, that) => {
1455 const s = self.value._tag
1456 const t = that.value._tag
1457 if (s === "Infinity") return t === "Infinity" ? zero : infinity
1458 if (s === "NegativeInfinity") return t === "NegativeInfinity" ? zero : negativeInfinity
1459 return t === "Infinity" ? negativeInfinity : infinity
1460 }
1461 })
1462)
1463
1464/**
1465 * Adds two Durations together.
1466 *
1467 * **Details**
1468 *
1469 * Infinity addition follows these rules:
1470 *
1471 * - infinity + infinity = infinity
1472 * - infinity + negativeInfinity = zero
1473 * - infinity + finite = infinity
1474 * - negativeInfinity + negativeInfinity = negativeInfinity
1475 * - negativeInfinity + finite = negativeInfinity
1476 *
1477 * **Example** (Adding durations)
1478 *
1479 * ```ts import.meta.vitest
1480 * import { Duration } from "effect"
1481 *
1482 * Duration.sum(Duration.seconds(5), Duration.seconds(3)) // => Duration.seconds(8)
1483 * ```
1484 *
1485 * @category math
1486 * @since 2.0.0
1487 */
1488export const sum: {
1489 (that: Duration): (self: Duration) => Duration
1490 (self: Duration, that: Duration): Duration
1491} = dual(
1492 2,
1493 (self: Duration, that: Duration): Duration =>
1494 matchPair(self, that, {
1495 onMillis: (self, that) => make(self + that),
1496 onNanos: (self, that) => make(self + that),
1497 onInfinity: (self, that) => {
1498 const s = self.value._tag
1499 const t = that.value._tag
1500 if (s === "Infinity" && t === "NegativeInfinity") return zero
1501 if (s === "NegativeInfinity" && t === "Infinity") return zero
1502 if (s === "Infinity" || t === "Infinity") return infinity
1503 if (s === "NegativeInfinity" || t === "NegativeInfinity") return negativeInfinity
1504 // unreachable, but satisfy TS
1505 return zero
1506 }
1507 })
1508)
1509
1510/**
1511 * Checks whether the first Duration is less than the second.
1512 *
1513 * **Example** (Comparing durations with less than)
1514 *
1515 * ```ts import.meta.vitest
1516 * import { Duration } from "effect"
1517 *
1518 * Duration.isLessThan(Duration.seconds(3), Duration.seconds(5)) // => true
1519 * ```
1520 *
1521 * @category predicates
1522 * @since 4.0.0
1523 */
1524export const isLessThan: {
1525 (that: Duration): (self: Duration) => boolean
1526 (self: Duration, that: Duration): boolean
1527} = order.isLessThan(Order)
1528
1529/**
1530 * Checks whether the first Duration is less than or equal to the second.
1531 *
1532 * **Example** (Comparing durations with less than or equal)
1533 *
1534 * ```ts import.meta.vitest
1535 * import { Duration } from "effect"
1536 *
1537 * Duration.isLessThanOrEqualTo(
1538 * Duration.seconds(5),
1539 * Duration.seconds(5)
1540 * ) // => true
1541 * ```
1542 *
1543 * @category predicates
1544 * @since 4.0.0
1545 */
1546export const isLessThanOrEqualTo: {
1547 (that: Duration): (self: Duration) => boolean
1548 (self: Duration, that: Duration): boolean
1549} = order.isLessThanOrEqualTo(Order)
1550
1551/**
1552 * Checks whether the first Duration is greater than the second.
1553 *
1554 * **Example** (Comparing durations with greater than)
1555 *
1556 * ```ts import.meta.vitest
1557 * import { Duration } from "effect"
1558 *
1559 * Duration.isGreaterThan(Duration.seconds(5), Duration.seconds(3)) // => true
1560 * ```
1561 *
1562 * @category predicates
1563 * @since 4.0.0
1564 */
1565export const isGreaterThan: {
1566 (that: Duration): (self: Duration) => boolean
1567 (self: Duration, that: Duration): boolean
1568} = order.isGreaterThan(Order)
1569
1570/**
1571 * Checks whether the first Duration is greater than or equal to the second.
1572 *
1573 * **Example** (Comparing durations with greater than or equal)
1574 *
1575 * ```ts import.meta.vitest
1576 * import { Duration } from "effect"
1577 *
1578 * Duration.isGreaterThanOrEqualTo(
1579 * Duration.seconds(5),
1580 * Duration.seconds(5)
1581 * ) // => true
1582 * ```
1583 *
1584 * @category predicates
1585 * @since 4.0.0
1586 */
1587export const isGreaterThanOrEqualTo: {
1588 (that: Duration): (self: Duration) => boolean
1589 (self: Duration, that: Duration): boolean
1590} = order.isGreaterThanOrEqualTo(Order)
1591
1592/**
1593 * Checks whether two Durations are equal.
1594 *
1595 * **Example** (Checking duration equality)
1596 *
1597 * ```ts import.meta.vitest
1598 * import { Duration } from "effect"
1599 *
1600 * Duration.equals(Duration.seconds(5), Duration.millis(5000)) // => true
1601 * ```
1602 *
1603 * @category predicates
1604 * @since 2.0.0
1605 */
1606export const equals: {
1607 (that: Duration): (self: Duration) => boolean
1608 (self: Duration, that: Duration): boolean
1609} = dual(2, (self: Duration, that: Duration): boolean => Equivalence(self, that))
1610
1611/**
1612 * Decomposes a `Duration` into normalized signed components.
1613 *
1614 * **Details**
1615 *
1616 * Finite durations are returned as `{ days, hours, minutes, seconds, millis,
1617 * nanos }`. Infinite durations return every component as `Infinity` or
1618 * `-Infinity`.
1619 *
1620 * **Example** (Decomposing durations into parts)
1621 *
1622 * ```ts import.meta.vitest
1623 * import { Duration } from "effect"
1624 *
1625 * // Create a complex duration by adding multiple parts
1626 * const duration = Duration.sum(
1627 * Duration.sum(
1628 * Duration.sum(Duration.days(1), Duration.hours(2)),
1629 * Duration.sum(Duration.minutes(30), Duration.seconds(45))
1630 * ),
1631 * Duration.millis(123)
1632 * )
1633 * Duration.parts(duration) // => ({ days: 1, hours: 2, minutes: 30, seconds: 45, millis: 123, nanos: 0 })
1634 *
1635 * const complex = Duration.sum(Duration.hours(25), Duration.minutes(90))
1636 * Duration.parts(complex) // => ({ days: 1, hours: 2, minutes: 30, seconds: 0, millis: 0, nanos: 0 })
1637 * ```
1638 *
1639 * @category converting
1640 * @since 3.8.0
1641 */
1642export const parts = (self: Duration): {
1643 days: number
1644 hours: number
1645 minutes: number
1646 seconds: number
1647 millis: number
1648 nanos: number
1649} => {
1650 if (self.value._tag === "Infinity") {
1651 return {
1652 days: Infinity,
1653 hours: Infinity,
1654 minutes: Infinity,
1655 seconds: Infinity,
1656 millis: Infinity,
1657 nanos: Infinity
1658 }
1659 }
1660 if (self.value._tag === "NegativeInfinity") {
1661 return {
1662 days: -Infinity,
1663 hours: -Infinity,
1664 minutes: -Infinity,
1665 seconds: -Infinity,
1666 millis: -Infinity,
1667 nanos: -Infinity
1668 }
1669 }
1670
1671 const n = toNanosUnsafe(self)
1672 const neg = n < bigint0
1673 const a = neg ? -n : n
1674 const ms = a / bigint1e6
1675 const sec = ms / bigint1e3
1676 const min = sec / bigint60
1677 const hr = min / bigint60
1678 const d = hr / bigint24
1679 const sign = neg ? -1 : 1
1680
1681 return {
1682 days: sign * Number(d),
1683 hours: sign * Number(hr % bigint24),
1684 minutes: sign * Number(min % bigint60),
1685 seconds: sign * Number(sec % bigint60),
1686 millis: sign * Number(ms % bigint1e3),
1687 nanos: sign * Number(a % bigint1e6)
1688 }
1689}
1690
1691/**
1692 * Converts a `Duration` to a human readable string.
1693 *
1694 * **Example** (Formatting durations)
1695 *
1696 * ```ts import.meta.vitest
1697 * import { Duration } from "effect"
1698 *
1699 * Duration.format(Duration.millis(1000)) // => "1s"
1700 * Duration.format(Duration.millis(1001)) // => "1s 1ms"
1701 * ```
1702 *
1703 * @category converting
1704 * @since 2.0.0
1705 */
1706export const format = (self: Duration): string => {
1707 if (self.value._tag === "Infinity") {
1708 return "Infinity"
1709 }
1710 if (self.value._tag === "NegativeInfinity") {
1711 return "-Infinity"
1712 }
1713 if (isZero(self)) {
1714 return "0"
1715 }
1716 if (isNegative(self)) {
1717 return "-" + format(abs(self))
1718 }
1719
1720 const fragments = parts(self)
1721 const pieces = []
1722 if (fragments.days !== 0) {
1723 pieces.push(`${fragments.days}d`)
1724 }
1725
1726 if (fragments.hours !== 0) {
1727 pieces.push(`${fragments.hours}h`)
1728 }
1729
1730 if (fragments.minutes !== 0) {
1731 pieces.push(`${fragments.minutes}m`)
1732 }
1733
1734 if (fragments.seconds !== 0) {
1735 pieces.push(`${fragments.seconds}s`)
1736 }
1737
1738 if (fragments.millis !== 0) {
1739 pieces.push(`${fragments.millis}ms`)
1740 }
1741
1742 if (fragments.nanos !== 0) {
1743 pieces.push(`${fragments.nanos}ns`)
1744 }
1745
1746 return pieces.join(" ")
1747}
1748
1749/**
1750 * Reducer for summing `Duration`s.
1751 *
1752 * **When to use**
1753 *
1754 * Use to sum many `Duration` values through APIs that consume a `Reducer`.
1755 *
1756 * **Details**
1757 *
1758 * `ReducerSum` uses `sum` and starts from `zero`, so `combineAll([])` returns
1759 * `zero`.
1760 *
1761 * @see {@link sum} for adding two duration values directly
1762 * @see {@link CombinerMax} for keeping the longest duration instead of summing
1763 * @see {@link CombinerMin} for keeping the shortest duration instead of summing
1764 *
1765 * @category math
1766 * @since 4.0.0
1767 */
1768export const ReducerSum: Reducer.Reducer<Duration> = Reducer.make(sum, zero)
1769
1770/**
1771 * Combiner that returns the maximum `Duration`.
1772 *
1773 * **When to use**
1774 *
1775 * Use to keep the longest `Duration` when an API consumes a `Combiner`.
1776 *
1777 * @see {@link CombinerMin} for keeping the shortest `Duration`
1778 * @see {@link max} for comparing two `Duration` values directly
1779 *
1780 * @category math
1781 * @since 4.0.0
1782 */
1783export const CombinerMax: Combiner.Combiner<Duration> = Combiner.max(Order)
1784
1785/**
1786 * Combiner that returns the minimum `Duration`.
1787 *
1788 * **When to use**
1789 *
1790 * Use to keep the shortest `Duration` through APIs that consume a `Combiner`.
1791 *
1792 * @see {@link CombinerMax} for keeping the longest `Duration`
1793 * @see {@link min} for comparing two `Duration` values directly
1794 *
1795 * @category math
1796 * @since 4.0.0
1797 */
1798export const CombinerMin: Combiner.Combiner<Duration> = Combiner.min(Order)