effect 3.17.2 → 3.17.3

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.
@@ -245,13 +245,14 @@ const maxEpochMillis = 8640000000000000 - (14 * 60 * 60 * 1000)
245
245
  export const unsafeMakeZoned = (input: DateTime.DateTime.Input, options?: {
246
246
  readonly timeZone?: number | string | DateTime.TimeZone | undefined
247
247
  readonly adjustForTimeZone?: boolean | undefined
248
+ readonly disambiguation?: DateTime.Disambiguation | undefined
248
249
  }): DateTime.Zoned => {
249
250
  if (options?.timeZone === undefined && isDateTime(input) && isZoned(input)) {
250
251
  return input
251
252
  }
252
253
  const self = unsafeMake(input)
253
254
  if (self.epochMillis < minEpochMillis || self.epochMillis > maxEpochMillis) {
254
- throw new IllegalArgumentException(`Epoch millis out of range: ${self.epochMillis}`)
255
+ throw new RangeError(`Epoch millis out of range: ${self.epochMillis}`)
255
256
  }
256
257
  let zone: DateTime.TimeZone
257
258
  if (options?.timeZone === undefined) {
@@ -271,7 +272,7 @@ export const unsafeMakeZoned = (input: DateTime.DateTime.Input, options?: {
271
272
  if (options?.adjustForTimeZone !== true) {
272
273
  return makeZonedProto(self.epochMillis, zone, self.partsUtc)
273
274
  }
274
- return makeZonedFromAdjusted(self.epochMillis, zone)
275
+ return makeZonedFromAdjusted(self.epochMillis, zone, options?.disambiguation ?? "compatible")
275
276
  }
276
277
 
277
278
  /** @internal */
@@ -280,6 +281,7 @@ export const makeZoned: (
280
281
  options?: {
281
282
  readonly timeZone?: number | string | DateTime.TimeZone | undefined
282
283
  readonly adjustForTimeZone?: boolean | undefined
284
+ readonly disambiguation?: DateTime.Disambiguation | undefined
283
285
  }
284
286
  ) => Option.Option<DateTime.Zoned> = Option.liftThrowable(unsafeMakeZoned)
285
287
 
@@ -320,27 +322,33 @@ export const toUtc = (self: DateTime.DateTime): DateTime.Utc => makeUtc(self.epo
320
322
  export const setZone: {
321
323
  (zone: DateTime.TimeZone, options?: {
322
324
  readonly adjustForTimeZone?: boolean | undefined
325
+ readonly disambiguation?: DateTime.Disambiguation | undefined
323
326
  }): (self: DateTime.DateTime) => DateTime.Zoned
324
327
  (self: DateTime.DateTime, zone: DateTime.TimeZone, options?: {
325
328
  readonly adjustForTimeZone?: boolean | undefined
329
+ readonly disambiguation?: DateTime.Disambiguation | undefined
326
330
  }): DateTime.Zoned
327
331
  } = dual(isDateTimeArgs, (self: DateTime.DateTime, zone: DateTime.TimeZone, options?: {
328
332
  readonly adjustForTimeZone?: boolean | undefined
333
+ readonly disambiguation?: DateTime.Disambiguation | undefined
329
334
  }): DateTime.Zoned =>
330
335
  options?.adjustForTimeZone === true
331
- ? makeZonedFromAdjusted(self.epochMillis, zone)
336
+ ? makeZonedFromAdjusted(self.epochMillis, zone, options?.disambiguation ?? "compatible")
332
337
  : makeZonedProto(self.epochMillis, zone, self.partsUtc))
333
338
 
334
339
  /** @internal */
335
340
  export const setZoneOffset: {
336
341
  (offset: number, options?: {
337
342
  readonly adjustForTimeZone?: boolean | undefined
343
+ readonly disambiguation?: DateTime.Disambiguation | undefined
338
344
  }): (self: DateTime.DateTime) => DateTime.Zoned
339
345
  (self: DateTime.DateTime, offset: number, options?: {
340
346
  readonly adjustForTimeZone?: boolean | undefined
347
+ readonly disambiguation?: DateTime.Disambiguation | undefined
341
348
  }): DateTime.Zoned
342
349
  } = dual(isDateTimeArgs, (self: DateTime.DateTime, offset: number, options?: {
343
350
  readonly adjustForTimeZone?: boolean | undefined
351
+ readonly disambiguation?: DateTime.Disambiguation | undefined
344
352
  }): DateTime.Zoned => setZone(self, zoneMakeOffset(offset), options))
345
353
 
346
354
  const validZoneCache = globalValue("effect/DateTime/validZoneCache", () => new Map<string, DateTime.TimeZone.Named>())
@@ -432,14 +440,17 @@ export const zoneToString = (self: DateTime.TimeZone): string => {
432
440
  export const setZoneNamed: {
433
441
  (zoneId: string, options?: {
434
442
  readonly adjustForTimeZone?: boolean | undefined
443
+ readonly disambiguation?: DateTime.Disambiguation | undefined
435
444
  }): (self: DateTime.DateTime) => Option.Option<DateTime.Zoned>
436
445
  (self: DateTime.DateTime, zoneId: string, options?: {
437
446
  readonly adjustForTimeZone?: boolean | undefined
447
+ readonly disambiguation?: DateTime.Disambiguation | undefined
438
448
  }): Option.Option<DateTime.Zoned>
439
449
  } = dual(
440
450
  isDateTimeArgs,
441
451
  (self: DateTime.DateTime, zoneId: string, options?: {
442
452
  readonly adjustForTimeZone?: boolean | undefined
453
+ readonly disambiguation?: DateTime.Disambiguation | undefined
443
454
  }): Option.Option<DateTime.Zoned> => Option.map(zoneMakeNamed(zoneId), (zone) => setZone(self, zone, options))
444
455
  )
445
456
 
@@ -447,12 +458,15 @@ export const setZoneNamed: {
447
458
  export const unsafeSetZoneNamed: {
448
459
  (zoneId: string, options?: {
449
460
  readonly adjustForTimeZone?: boolean | undefined
461
+ readonly disambiguation?: DateTime.Disambiguation | undefined
450
462
  }): (self: DateTime.DateTime) => DateTime.Zoned
451
463
  (self: DateTime.DateTime, zoneId: string, options?: {
452
464
  readonly adjustForTimeZone?: boolean | undefined
465
+ readonly disambiguation?: DateTime.Disambiguation | undefined
453
466
  }): DateTime.Zoned
454
467
  } = dual(isDateTimeArgs, (self: DateTime.DateTime, zoneId: string, options?: {
455
468
  readonly adjustForTimeZone?: boolean | undefined
469
+ readonly disambiguation?: DateTime.Disambiguation | undefined
456
470
  }): DateTime.Zoned => setZone(self, zoneUnsafeMakeNamed(zoneId), options))
457
471
 
458
472
  // =============================================================================
@@ -715,9 +729,92 @@ export const setPartsUtc: {
715
729
  // mapping
716
730
  // =============================================================================
717
731
 
718
- const makeZonedFromAdjusted = (adjustedMillis: number, zone: DateTime.TimeZone): DateTime.Zoned => {
719
- const offset = zone._tag === "Offset" ? zone.offset : calculateNamedOffset(adjustedMillis, zone)
720
- return makeZonedProto(adjustedMillis - offset, zone)
732
+ const constDayMillis = 24 * 60 * 60 * 1000
733
+
734
+ const makeZonedFromAdjusted = (
735
+ adjustedMillis: number,
736
+ zone: DateTime.TimeZone,
737
+ disambiguation: DateTime.Disambiguation
738
+ ): DateTime.Zoned => {
739
+ if (zone._tag === "Offset") {
740
+ return makeZonedProto(adjustedMillis - zone.offset, zone)
741
+ }
742
+ const beforeOffset = calculateNamedOffset(
743
+ adjustedMillis - constDayMillis,
744
+ adjustedMillis,
745
+ zone
746
+ )
747
+ const afterOffset = calculateNamedOffset(
748
+ adjustedMillis + constDayMillis,
749
+ adjustedMillis,
750
+ zone
751
+ )
752
+ // If there is no transition, we can return early
753
+ if (beforeOffset === afterOffset) {
754
+ return makeZonedProto(adjustedMillis - beforeOffset, zone)
755
+ }
756
+ const isForwards = beforeOffset < afterOffset
757
+ const transitionMillis = beforeOffset - afterOffset
758
+ // If the transition is forwards, we only need to check if we should move the
759
+ // local wall clock time forward if it is inside the gap
760
+ if (isForwards) {
761
+ const currentAfterOffset = calculateNamedOffset(
762
+ adjustedMillis - afterOffset,
763
+ adjustedMillis,
764
+ zone
765
+ )
766
+ if (currentAfterOffset === afterOffset) {
767
+ return makeZonedProto(adjustedMillis - afterOffset, zone)
768
+ }
769
+ const before = makeZonedProto(adjustedMillis - beforeOffset, zone)
770
+ const beforeAdjustedMillis = toDate(before).getTime()
771
+ // If the wall clock time has changed, we are inside the gap
772
+ if (adjustedMillis !== beforeAdjustedMillis) {
773
+ switch (disambiguation) {
774
+ case "reject": {
775
+ const formatted = new Date(adjustedMillis).toISOString()
776
+ throw new RangeError(`Gap time: ${formatted} does not exist in time zone ${zone.id}`)
777
+ }
778
+ case "earlier":
779
+ return makeZonedProto(adjustedMillis - afterOffset, zone)
780
+
781
+ case "compatible":
782
+ case "later":
783
+ return before
784
+ }
785
+ }
786
+ // The wall clock time is in the earlier offset, so we use that
787
+ return before
788
+ }
789
+
790
+ const currentBeforeOffset = calculateNamedOffset(
791
+ adjustedMillis - beforeOffset,
792
+ adjustedMillis,
793
+ zone
794
+ )
795
+ // The wall clock time is in the earlier offset, so we use that
796
+ if (currentBeforeOffset === beforeOffset) {
797
+ if (disambiguation === "earlier" || disambiguation === "compatible") {
798
+ return makeZonedProto(adjustedMillis - beforeOffset, zone)
799
+ }
800
+ const laterOffset = calculateNamedOffset(
801
+ adjustedMillis - beforeOffset + transitionMillis,
802
+ adjustedMillis + transitionMillis,
803
+ zone
804
+ )
805
+ if (laterOffset === beforeOffset) {
806
+ return makeZonedProto(adjustedMillis - beforeOffset, zone)
807
+ }
808
+ // If the offset changed in this period, then we are inside the period where
809
+ // the wall clock time occurs twice, once in the earlier offset and once in
810
+ // the later offset.
811
+ if (disambiguation === "reject") {
812
+ const formatted = new Date(adjustedMillis).toISOString()
813
+ throw new RangeError(`Ambiguous time: ${formatted} occurs twice in time zone ${zone.id}`)
814
+ }
815
+ // If the disambiguation is "later", we return the later offset below
816
+ }
817
+ return makeZonedProto(adjustedMillis - afterOffset, zone)
721
818
  }
722
819
 
723
820
  const offsetRegex = /([+-])(\d{2}):(\d{2})$/
@@ -730,8 +827,12 @@ const parseOffset = (offset: string): number | null => {
730
827
  return (sign === "+" ? 1 : -1) * (Number(hours) * 60 + Number(minutes)) * 60 * 1000
731
828
  }
732
829
 
733
- const calculateNamedOffset = (adjustedMillis: number, zone: DateTime.TimeZone.Named): number => {
734
- const offset = zone.format.formatToParts(adjustedMillis).find((_) => _.type === "timeZoneName")?.value ?? ""
830
+ const calculateNamedOffset = (
831
+ utcMillis: number,
832
+ adjustedMillis: number,
833
+ zone: DateTime.TimeZone.Named
834
+ ): number => {
835
+ const offset = zone.format.formatToParts(utcMillis).find((_) => _.type === "timeZoneName")?.value ?? ""
735
836
  if (offset === "GMT") {
736
837
  return 0
737
838
  }
@@ -745,9 +846,15 @@ const calculateNamedOffset = (adjustedMillis: number, zone: DateTime.TimeZone.Na
745
846
 
746
847
  /** @internal */
747
848
  export const mutate: {
748
- (f: (date: Date) => void): <A extends DateTime.DateTime>(self: A) => A
749
- <A extends DateTime.DateTime>(self: A, f: (date: Date) => void): A
750
- } = dual(2, (self: DateTime.DateTime, f: (date: Date) => void): DateTime.DateTime => {
849
+ (f: (date: Date) => void, options?: {
850
+ readonly disambiguation?: DateTime.Disambiguation | undefined
851
+ }): <A extends DateTime.DateTime>(self: A) => A
852
+ <A extends DateTime.DateTime>(self: A, f: (date: Date) => void, options?: {
853
+ readonly disambiguation?: DateTime.Disambiguation | undefined
854
+ }): A
855
+ } = dual(isDateTimeArgs, (self: DateTime.DateTime, f: (date: Date) => void, options?: {
856
+ readonly disambiguation?: DateTime.Disambiguation | undefined
857
+ }): DateTime.DateTime => {
751
858
  if (self._tag === "Utc") {
752
859
  const date = toDateUtc(self)
753
860
  f(date)
@@ -756,7 +863,7 @@ export const mutate: {
756
863
  const adjustedDate = toDate(self)
757
864
  const newAdjustedDate = new Date(adjustedDate.getTime())
758
865
  f(newAdjustedDate)
759
- return makeZonedFromAdjusted(newAdjustedDate.getTime(), self.zone)
866
+ return makeZonedFromAdjusted(newAdjustedDate.getTime(), self.zone, options?.disambiguation ?? "compatible")
760
867
  })
761
868
 
762
869
  /** @internal */
@@ -1,4 +1,4 @@
1
- let moduleVersion = "3.17.2"
1
+ let moduleVersion = "3.17.3"
2
2
 
3
3
  export const getCurrentVersion = () => moduleVersion
4
4