effect 4.0.0-beta.79 → 4.0.0-beta.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/Duration.ts CHANGED
@@ -26,12 +26,30 @@ import * as Reducer from "./Reducer.ts"
26
26
  const TypeId = "~effect/time/Duration"
27
27
 
28
28
  const bigint0 = BigInt(0)
29
+ const bigint1 = BigInt(1)
29
30
  const bigint24 = BigInt(24)
30
31
  const bigint60 = BigInt(60)
31
32
  const bigint1e3 = BigInt(1_000)
32
33
  const bigint1e6 = BigInt(1_000_000)
33
34
  const bigint1e9 = BigInt(1_000_000_000)
34
35
 
36
+ const roundTiesAwayFromZero = (input: number): bigint =>
37
+ BigInt(input < 0 ? Math.ceil(input - 0.5) : Math.floor(input + 0.5))
38
+
39
+ const roundMillisToNanos = (millis: number): bigint => roundTiesAwayFromZero(millis * 1_000_000)
40
+
41
+ const parseNanos = (input: string, scale: bigint): bigint =>
42
+ input.includes(".") ? roundTiesAwayFromZero(Number(input) * Number(scale)) : BigInt(input) * scale
43
+
44
+ const nanosToHrTime = (nanos: bigint): [seconds: number, nanos: number] => {
45
+ const sign = nanos < bigint0 ? -bigint1 : bigint1
46
+ const absolute = nanos < bigint0 ? -nanos : nanos
47
+ return [
48
+ Number(sign * (absolute / bigint1e9)),
49
+ Number(sign * (absolute % bigint1e9))
50
+ ]
51
+ }
52
+
35
53
  /**
36
54
  * Represents a span of time with high precision, supporting operations from
37
55
  * nanoseconds to weeks.
@@ -125,7 +143,9 @@ export type Unit =
125
143
  * **Details**
126
144
  *
127
145
  * String inputs accept values like `"10 seconds"`, `"500 millis"`,
128
- * `"Infinity"`, and `"-Infinity"`.
146
+ * `"Infinity"`, and `"-Infinity"`. Finite fractional values that are
147
+ * normalized to nanoseconds are rounded to the nearest nanosecond, with ties
148
+ * away from zero.
129
149
  *
130
150
  * @see {@link fromInput} for safe conversion to `Option`
131
151
  * @see {@link fromInputUnsafe} for throwing conversion
@@ -221,14 +241,14 @@ export const fromInputUnsafe = (input: Input): Duration => {
221
241
  const match = DURATION_REGEXP.exec(input)
222
242
  if (!match) break
223
243
  const [_, valueStr, unit] = match
244
+ if (unit === "nano" || unit === "nanos") {
245
+ return nanos(parseNanos(valueStr, bigint1))
246
+ }
247
+ if (unit === "micro" || unit === "micros") {
248
+ return nanos(parseNanos(valueStr, bigint1e3))
249
+ }
224
250
  const value = Number(valueStr)
225
251
  switch (unit) {
226
- case "nano":
227
- case "nanos":
228
- return nanos(BigInt(valueStr))
229
- case "micro":
230
- case "micros":
231
- return micros(BigInt(valueStr))
232
252
  case "milli":
233
253
  case "millis":
234
254
  return millis(value)
@@ -266,7 +286,7 @@ export const fromInputUnsafe = (input: Input): Duration => {
266
286
  if (input[0] === Infinity || input[1] === Infinity) {
267
287
  return infinity
268
288
  }
269
- return make(BigInt(Math.round(input[0] * 1_000_000_000)) + BigInt(Math.round(input[1])))
289
+ return make(roundTiesAwayFromZero(input[0] * 1_000_000_000 + input[1]))
270
290
  }
271
291
  const obj = input as DurationObject
272
292
  let millis = 0
@@ -278,10 +298,9 @@ export const fromInputUnsafe = (input: Input): Duration => {
278
298
  if (obj.seconds) millis += obj.seconds * 1_000
279
299
  if (obj.milliseconds) millis += obj.milliseconds
280
300
  if (!obj.microseconds && !obj.nanoseconds) return make(millis)
281
- let nanos = BigInt(millis) * bigint1e6
282
- if (obj.microseconds) nanos += BigInt(obj.microseconds) * bigint1e3
283
- if (obj.nanoseconds) nanos += BigInt(obj.nanoseconds)
284
- return make(nanos)
301
+ return make(roundTiesAwayFromZero(
302
+ millis * 1_000_000 + (obj.microseconds ?? 0) * 1_000 + (obj.nanoseconds ?? 0)
303
+ ))
285
304
  }
286
305
  }
287
306
  return invalid(input)
@@ -364,7 +383,7 @@ const make = (input: number | bigint): Duration => {
364
383
  } else if (!Number.isFinite(input)) {
365
384
  duration.value = input > 0 ? infinityDurationValue : negativeInfinityDurationValue
366
385
  } else if (!Number.isInteger(input)) {
367
- duration.value = { _tag: "Nanos", nanos: BigInt(Math.round(input * 1_000_000)) }
386
+ duration.value = { _tag: "Nanos", nanos: roundMillisToNanos(input) }
368
387
  } else {
369
388
  duration.value = { _tag: "Millis", millis: input }
370
389
  }
@@ -881,6 +900,11 @@ export const toWeeks = (self: Input): number =>
881
900
  * Use when the duration is known to be finite and you need the nanosecond value
882
901
  * as a `bigint`.
883
902
  *
903
+ * **Details**
904
+ *
905
+ * Millisecond-backed fractional durations are rounded to the nearest
906
+ * nanosecond, with ties away from zero.
907
+ *
884
908
  * **Gotchas**
885
909
  *
886
910
  * If the duration is infinite, it throws an error.
@@ -910,7 +934,7 @@ export const toNanosUnsafe = (input: Input): bigint => {
910
934
  case "Nanos":
911
935
  return self.value.nanos
912
936
  case "Millis":
913
- return BigInt(Math.round(self.value.millis * 1_000_000))
937
+ return roundMillisToNanos(self.value.millis)
914
938
  }
915
939
  }
916
940
 
@@ -960,24 +984,10 @@ export const toHrTime = (input: Input): [seconds: number, nanos: number] => {
960
984
  return [Infinity, 0]
961
985
  case "NegativeInfinity":
962
986
  return [-Infinity, 0]
963
- case "Nanos": {
964
- const n = self.value.nanos
965
- const sign = n < bigint0 ? -BigInt(1) : BigInt(1)
966
- const a = n < bigint0 ? -n : n
967
- return [
968
- Number(sign * (a / bigint1e9)),
969
- Number(sign * (a % bigint1e9))
970
- ]
971
- }
972
- case "Millis": {
973
- const m = self.value.millis
974
- const sign = m < 0 ? -1 : 1
975
- const a = Math.abs(m)
976
- return [
977
- sign * Math.floor(a / 1000),
978
- sign * Math.round((a % 1000) * 1_000_000)
979
- ]
980
- }
987
+ case "Nanos":
988
+ return nanosToHrTime(self.value.nanos)
989
+ case "Millis":
990
+ return nanosToHrTime(roundMillisToNanos(self.value.millis))
981
991
  }
982
992
  }
983
993