effect 3.5.8 → 3.6.0

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.
Files changed (123) hide show
  1. package/DateTime/package.json +6 -0
  2. package/dist/cjs/Context.js.map +1 -1
  3. package/dist/cjs/DateTime.js +1514 -0
  4. package/dist/cjs/DateTime.js.map +1 -0
  5. package/dist/cjs/Effect.js +3 -3
  6. package/dist/cjs/Effect.js.map +1 -1
  7. package/dist/cjs/Either.js +3 -3
  8. package/dist/cjs/List.js.map +1 -1
  9. package/dist/cjs/Metric.js.map +1 -1
  10. package/dist/cjs/Micro.js +93 -41
  11. package/dist/cjs/Micro.js.map +1 -1
  12. package/dist/cjs/Option.js +3 -3
  13. package/dist/cjs/Predicate.js +8 -0
  14. package/dist/cjs/Predicate.js.map +1 -1
  15. package/dist/cjs/Random.js +16 -1
  16. package/dist/cjs/Random.js.map +1 -1
  17. package/dist/cjs/Stream.js +86 -7
  18. package/dist/cjs/Stream.js.map +1 -1
  19. package/dist/cjs/Struct.js +23 -1
  20. package/dist/cjs/Struct.js.map +1 -1
  21. package/dist/cjs/index.js +4 -2
  22. package/dist/cjs/index.js.map +1 -1
  23. package/dist/cjs/internal/configProvider.js.map +1 -1
  24. package/dist/cjs/internal/core.js +1 -1
  25. package/dist/cjs/internal/core.js.map +1 -1
  26. package/dist/cjs/internal/defaultServices.js +9 -2
  27. package/dist/cjs/internal/defaultServices.js.map +1 -1
  28. package/dist/cjs/internal/metric.js.map +1 -1
  29. package/dist/cjs/internal/stream/emit.js +73 -1
  30. package/dist/cjs/internal/stream/emit.js.map +1 -1
  31. package/dist/cjs/internal/stream.js +30 -22
  32. package/dist/cjs/internal/stream.js.map +1 -1
  33. package/dist/cjs/internal/version.js +1 -1
  34. package/dist/dts/ConfigProvider.d.ts +2 -2
  35. package/dist/dts/ConfigProvider.d.ts.map +1 -1
  36. package/dist/dts/Context.d.ts +3 -1
  37. package/dist/dts/Context.d.ts.map +1 -1
  38. package/dist/dts/DateTime.d.ts +1265 -0
  39. package/dist/dts/DateTime.d.ts.map +1 -0
  40. package/dist/dts/Effect.d.ts +18 -4
  41. package/dist/dts/Effect.d.ts.map +1 -1
  42. package/dist/dts/Either.d.ts +4 -4
  43. package/dist/dts/List.d.ts +2 -1
  44. package/dist/dts/List.d.ts.map +1 -1
  45. package/dist/dts/Metric.d.ts +1 -1
  46. package/dist/dts/Metric.d.ts.map +1 -1
  47. package/dist/dts/MetricRegistry.d.ts +1 -1
  48. package/dist/dts/MetricRegistry.d.ts.map +1 -1
  49. package/dist/dts/Micro.d.ts +60 -0
  50. package/dist/dts/Micro.d.ts.map +1 -1
  51. package/dist/dts/Option.d.ts +4 -4
  52. package/dist/dts/Predicate.d.ts +63 -2
  53. package/dist/dts/Predicate.d.ts.map +1 -1
  54. package/dist/dts/Random.d.ts +18 -0
  55. package/dist/dts/Random.d.ts.map +1 -1
  56. package/dist/dts/Stream.d.ts +95 -4
  57. package/dist/dts/Stream.d.ts.map +1 -1
  58. package/dist/dts/StreamEmit.d.ts +44 -0
  59. package/dist/dts/StreamEmit.d.ts.map +1 -1
  60. package/dist/dts/Struct.d.ts +21 -0
  61. package/dist/dts/Struct.d.ts.map +1 -1
  62. package/dist/dts/index.d.ts +4 -0
  63. package/dist/dts/index.d.ts.map +1 -1
  64. package/dist/dts/internal/core.d.ts.map +1 -1
  65. package/dist/dts/internal/defaultServices.d.ts.map +1 -1
  66. package/dist/dts/internal/stream.d.ts.map +1 -1
  67. package/dist/esm/Context.js.map +1 -1
  68. package/dist/esm/DateTime.js +1465 -0
  69. package/dist/esm/DateTime.js.map +1 -0
  70. package/dist/esm/Effect.js +4 -4
  71. package/dist/esm/Effect.js.map +1 -1
  72. package/dist/esm/Either.js +4 -4
  73. package/dist/esm/List.js.map +1 -1
  74. package/dist/esm/Metric.js.map +1 -1
  75. package/dist/esm/Micro.js +88 -38
  76. package/dist/esm/Micro.js.map +1 -1
  77. package/dist/esm/Option.js +4 -4
  78. package/dist/esm/Predicate.js +8 -0
  79. package/dist/esm/Predicate.js.map +1 -1
  80. package/dist/esm/Random.js +15 -0
  81. package/dist/esm/Random.js.map +1 -1
  82. package/dist/esm/Stream.js +84 -5
  83. package/dist/esm/Stream.js.map +1 -1
  84. package/dist/esm/Struct.js +21 -0
  85. package/dist/esm/Struct.js.map +1 -1
  86. package/dist/esm/index.js +4 -0
  87. package/dist/esm/index.js.map +1 -1
  88. package/dist/esm/internal/configProvider.js.map +1 -1
  89. package/dist/esm/internal/core.js +1 -1
  90. package/dist/esm/internal/core.js.map +1 -1
  91. package/dist/esm/internal/defaultServices.js +6 -0
  92. package/dist/esm/internal/defaultServices.js.map +1 -1
  93. package/dist/esm/internal/metric.js.map +1 -1
  94. package/dist/esm/internal/stream/emit.js +71 -0
  95. package/dist/esm/internal/stream/emit.js.map +1 -1
  96. package/dist/esm/internal/stream.js +24 -18
  97. package/dist/esm/internal/stream.js.map +1 -1
  98. package/dist/esm/internal/version.js +1 -1
  99. package/package.json +9 -1
  100. package/src/ConfigProvider.ts +2 -2
  101. package/src/Context.ts +3 -1
  102. package/src/DateTime.ts +2104 -0
  103. package/src/Effect.ts +28 -4
  104. package/src/Either.ts +4 -4
  105. package/src/List.ts +3 -2
  106. package/src/Metric.ts +1 -1
  107. package/src/MetricRegistry.ts +1 -1
  108. package/src/Micro.ts +117 -45
  109. package/src/Option.ts +4 -4
  110. package/src/Predicate.ts +68 -8
  111. package/src/Random.ts +24 -0
  112. package/src/Stream.ts +110 -5
  113. package/src/StreamEmit.ts +53 -0
  114. package/src/Struct.ts +22 -0
  115. package/src/index.ts +5 -0
  116. package/src/internal/configProvider.ts +20 -20
  117. package/src/internal/core.ts +37 -12
  118. package/src/internal/defaultServices.ts +14 -0
  119. package/src/internal/metric/registry.ts +1 -1
  120. package/src/internal/metric.ts +2 -2
  121. package/src/internal/stream/emit.ts +77 -0
  122. package/src/internal/stream.ts +86 -18
  123. package/src/internal/version.ts +1 -1
@@ -0,0 +1,2104 @@
1
+ /**
2
+ * @since 3.6.0
3
+ */
4
+ import { IllegalArgumentException } from "./Cause.js"
5
+ import * as Clock from "./Clock.js"
6
+ import * as Context from "./Context.js"
7
+ import * as Duration from "./Duration.js"
8
+ import * as Effect from "./Effect.js"
9
+ import * as Either from "./Either.js"
10
+ import * as Equal from "./Equal.js"
11
+ import * as Equivalence_ from "./Equivalence.js"
12
+ import type { LazyArg } from "./Function.js"
13
+ import { dual, pipe } from "./Function.js"
14
+ import { globalValue } from "./GlobalValue.js"
15
+ import * as Hash from "./Hash.js"
16
+ import * as Inspectable from "./Inspectable.js"
17
+ import * as Layer from "./Layer.js"
18
+ import * as Option from "./Option.js"
19
+ import * as order from "./Order.js"
20
+ import { type Pipeable, pipeArguments } from "./Pipeable.js"
21
+ import * as Predicate from "./Predicate.js"
22
+ import type { Mutable } from "./Types.js"
23
+
24
+ /**
25
+ * @since 3.6.0
26
+ * @category type ids
27
+ */
28
+ export const TypeId: unique symbol = Symbol.for("effect/DateTime")
29
+
30
+ /**
31
+ * @since 3.6.0
32
+ * @category type ids
33
+ */
34
+ export type TypeId = typeof TypeId
35
+
36
+ /**
37
+ * A `DateTime` represents a point in time. It can optionally have a time zone
38
+ * associated with it.
39
+ *
40
+ * @since 3.6.0
41
+ * @category models
42
+ */
43
+ export type DateTime = Utc | Zoned
44
+
45
+ /**
46
+ * @since 3.6.0
47
+ * @category models
48
+ */
49
+ export interface Utc extends DateTime.Proto {
50
+ readonly _tag: "Utc"
51
+ readonly epochMillis: number
52
+ /** @internal */
53
+ partsUtc: DateTime.PartsWithWeekday
54
+ }
55
+
56
+ /**
57
+ * @since 3.6.0
58
+ * @category models
59
+ */
60
+ export interface Zoned extends DateTime.Proto {
61
+ readonly _tag: "Zoned"
62
+ readonly epochMillis: number
63
+ readonly zone: TimeZone
64
+ /** @internal */
65
+ adjustedEpochMillis?: number
66
+ /** @internal */
67
+ partsAdjusted?: DateTime.PartsWithWeekday
68
+ /** @internal */
69
+ partsUtc?: DateTime.PartsWithWeekday
70
+ }
71
+
72
+ /**
73
+ * @since 3.6.0
74
+ * @category models
75
+ */
76
+ export declare namespace DateTime {
77
+ /**
78
+ * @since 3.6.0
79
+ * @category models
80
+ */
81
+ export type Input = DateTime | Partial<Parts> | Date | number | string
82
+
83
+ /**
84
+ * @since 3.6.0
85
+ * @category models
86
+ */
87
+ export type PreserveZone<A extends DateTime.Input> = A extends Zoned ? Zoned : Utc
88
+
89
+ /**
90
+ * @since 3.6.0
91
+ * @category models
92
+ */
93
+ export type Unit = UnitSingular | UnitPlural
94
+
95
+ /**
96
+ * @since 3.6.0
97
+ * @category models
98
+ */
99
+ export type UnitSingular =
100
+ | "milli"
101
+ | "second"
102
+ | "minute"
103
+ | "hour"
104
+ | "day"
105
+ | "week"
106
+ | "month"
107
+ | "year"
108
+
109
+ /**
110
+ * @since 3.6.0
111
+ * @category models
112
+ */
113
+ export type UnitPlural =
114
+ | "millis"
115
+ | "seconds"
116
+ | "minutes"
117
+ | "hours"
118
+ | "days"
119
+ | "weeks"
120
+ | "months"
121
+ | "years"
122
+
123
+ /**
124
+ * @since 3.6.0
125
+ * @category models
126
+ */
127
+ export interface PartsWithWeekday {
128
+ readonly millis: number
129
+ readonly seconds: number
130
+ readonly minutes: number
131
+ readonly hours: number
132
+ readonly day: number
133
+ readonly weekDay: number
134
+ readonly month: number
135
+ readonly year: number
136
+ }
137
+
138
+ /**
139
+ * @since 3.6.0
140
+ * @category models
141
+ */
142
+ export interface Parts {
143
+ readonly millis: number
144
+ readonly seconds: number
145
+ readonly minutes: number
146
+ readonly hours: number
147
+ readonly day: number
148
+ readonly month: number
149
+ readonly year: number
150
+ }
151
+
152
+ /**
153
+ * @since 3.6.0
154
+ * @category models
155
+ */
156
+ export interface PartsForMath {
157
+ readonly millis: number
158
+ readonly seconds: number
159
+ readonly minutes: number
160
+ readonly hours: number
161
+ readonly days: number
162
+ readonly weeks: number
163
+ readonly months: number
164
+ readonly years: number
165
+ }
166
+
167
+ /**
168
+ * @since 3.6.0
169
+ * @category models
170
+ */
171
+ export interface Proto extends Pipeable, Inspectable.Inspectable {
172
+ readonly [TypeId]: TypeId
173
+ }
174
+ }
175
+
176
+ /**
177
+ * @since 3.6.0
178
+ * @category type ids
179
+ */
180
+ export const TimeZoneTypeId: unique symbol = Symbol.for("effect/DateTime/TimeZone")
181
+
182
+ /**
183
+ * @since 3.6.0
184
+ * @category type ids
185
+ */
186
+ export type TimeZoneTypeId = typeof TimeZoneTypeId
187
+
188
+ /**
189
+ * @since 3.6.0
190
+ * @category models
191
+ */
192
+ export type TimeZone = TimeZone.Offset | TimeZone.Named
193
+
194
+ /**
195
+ * @since 3.6.0
196
+ * @category models
197
+ */
198
+ export declare namespace TimeZone {
199
+ /**
200
+ * @since 3.6.0
201
+ * @category models
202
+ */
203
+ export interface Proto extends Inspectable.Inspectable {
204
+ readonly [TimeZoneTypeId]: TimeZoneTypeId
205
+ }
206
+
207
+ /**
208
+ * @since 3.6.0
209
+ * @category models
210
+ */
211
+ export interface Offset extends Proto {
212
+ readonly _tag: "Offset"
213
+ readonly offset: number
214
+ }
215
+
216
+ /**
217
+ * @since 3.6.0
218
+ * @category models
219
+ */
220
+ export interface Named extends Proto {
221
+ readonly _tag: "Named"
222
+ readonly id: string
223
+ /** @internal */
224
+ readonly format: Intl.DateTimeFormat
225
+ }
226
+ }
227
+
228
+ const Proto = {
229
+ [TypeId]: TypeId,
230
+ pipe() {
231
+ return pipeArguments(this, arguments)
232
+ },
233
+ [Inspectable.NodeInspectSymbol](this: DateTime) {
234
+ return this.toString()
235
+ },
236
+ toJSON(this: DateTime) {
237
+ return toDateUtc(this).toJSON()
238
+ }
239
+ }
240
+
241
+ const ProtoUtc = {
242
+ ...Proto,
243
+ _tag: "Utc",
244
+ [Hash.symbol](this: Utc) {
245
+ return Hash.cached(this, Hash.number(this.epochMillis))
246
+ },
247
+ [Equal.symbol](this: Utc, that: unknown) {
248
+ return isDateTime(that) && that._tag === "Utc" && this.epochMillis === that.epochMillis
249
+ },
250
+ toString(this: Utc) {
251
+ return `DateTime.Utc(${toDateUtc(this).toJSON()})`
252
+ }
253
+ }
254
+
255
+ const ProtoZoned = {
256
+ ...Proto,
257
+ _tag: "Zoned",
258
+ [Hash.symbol](this: Zoned) {
259
+ return pipe(
260
+ Hash.number(this.epochMillis),
261
+ Hash.combine(Hash.hash(this.zone)),
262
+ Hash.cached(this)
263
+ )
264
+ },
265
+ [Equal.symbol](this: Zoned, that: unknown) {
266
+ return isDateTime(that) && that._tag === "Zoned" && this.epochMillis === that.epochMillis &&
267
+ Equal.equals(this.zone, that.zone)
268
+ },
269
+ toString(this: Zoned) {
270
+ return `DateTime.Zoned(${formatIsoZoned(this)})`
271
+ }
272
+ }
273
+
274
+ const ProtoTimeZone = {
275
+ [TimeZoneTypeId]: TimeZoneTypeId,
276
+ [Inspectable.NodeInspectSymbol](this: TimeZone) {
277
+ return this.toString()
278
+ }
279
+ }
280
+
281
+ const ProtoTimeZoneNamed = {
282
+ ...ProtoTimeZone,
283
+ _tag: "Named",
284
+ [Hash.symbol](this: TimeZone.Named) {
285
+ return Hash.cached(this, Hash.string(`Named:${this.id}`))
286
+ },
287
+ [Equal.symbol](this: TimeZone.Named, that: unknown) {
288
+ return isTimeZone(that) && that._tag === "Named" && this.id === that.id
289
+ },
290
+ toString(this: TimeZone.Named) {
291
+ return `TimeZone.Named(${this.id})`
292
+ },
293
+ toJSON(this: TimeZone.Named) {
294
+ return {
295
+ _id: "TimeZone",
296
+ _tag: "Named",
297
+ id: this.id
298
+ }
299
+ }
300
+ }
301
+
302
+ const ProtoTimeZoneOffset = {
303
+ ...ProtoTimeZone,
304
+ _tag: "Offset",
305
+ [Hash.symbol](this: TimeZone.Offset) {
306
+ return Hash.cached(this, Hash.string(`Offset:${this.offset}`))
307
+ },
308
+ [Equal.symbol](this: TimeZone.Offset, that: unknown) {
309
+ return isTimeZone(that) && that._tag === "Offset" && this.offset === that.offset
310
+ },
311
+ toString(this: TimeZone.Offset) {
312
+ return `TimeZone.Offset(${offsetToString(this.offset)})`
313
+ },
314
+ toJSON(this: TimeZone.Offset) {
315
+ return {
316
+ _id: "TimeZone",
317
+ _tag: "Offset",
318
+ offset: this.offset
319
+ }
320
+ }
321
+ }
322
+
323
+ const makeZonedProto = (epochMillis: number, zone: TimeZone, partsUtc?: DateTime.PartsWithWeekday): Zoned => {
324
+ const self = Object.create(ProtoZoned)
325
+ self.epochMillis = epochMillis
326
+ self.zone = zone
327
+ self.partsUtc = partsUtc
328
+ return self
329
+ }
330
+
331
+ // =============================================================================
332
+ // guards
333
+ // =============================================================================
334
+
335
+ /**
336
+ * @since 3.6.0
337
+ * @category guards
338
+ */
339
+ export const isDateTime = (u: unknown): u is DateTime => Predicate.hasProperty(u, TypeId)
340
+
341
+ const isDateTimeArgs = (args: IArguments) => isDateTime(args[0])
342
+
343
+ /**
344
+ * @since 3.6.0
345
+ * @category guards
346
+ */
347
+ export const isTimeZone = (u: unknown): u is TimeZone => Predicate.hasProperty(u, TimeZoneTypeId)
348
+
349
+ /**
350
+ * @since 3.6.0
351
+ * @category guards
352
+ */
353
+ export const isTimeZoneOffset = (u: unknown): u is TimeZone.Offset => isTimeZone(u) && u._tag === "Offset"
354
+
355
+ /**
356
+ * @since 3.6.0
357
+ * @category guards
358
+ */
359
+ export const isTimeZoneNamed = (u: unknown): u is TimeZone.Named => isTimeZone(u) && u._tag === "Named"
360
+
361
+ /**
362
+ * @since 3.6.0
363
+ * @category guards
364
+ */
365
+ export const isUtc = (self: DateTime): self is Utc => self._tag === "Utc"
366
+
367
+ /**
368
+ * @since 3.6.0
369
+ * @category guards
370
+ */
371
+ export const isZoned = (self: DateTime): self is Zoned => self._tag === "Zoned"
372
+
373
+ // =============================================================================
374
+ // instances
375
+ // =============================================================================
376
+
377
+ /**
378
+ * @since 3.6.0
379
+ * @category instances
380
+ */
381
+ export const Equivalence: Equivalence_.Equivalence<DateTime> = Equivalence_.make((a, b) =>
382
+ a.epochMillis === b.epochMillis
383
+ )
384
+
385
+ /**
386
+ * @since 3.6.0
387
+ * @category instances
388
+ */
389
+ export const Order: order.Order<DateTime> = order.make((self, that) =>
390
+ self.epochMillis < that.epochMillis ? -1 : self.epochMillis > that.epochMillis ? 1 : 0
391
+ )
392
+
393
+ /**
394
+ * @since 3.6.0
395
+ */
396
+ export const clamp: {
397
+ (options: { minimum: DateTime; maximum: DateTime }): (self: DateTime) => DateTime
398
+ (self: DateTime, options: { minimum: DateTime; maximum: DateTime }): DateTime
399
+ } = order.clamp(Order)
400
+
401
+ // =============================================================================
402
+ // constructors
403
+ // =============================================================================
404
+
405
+ const makeUtc = (epochMillis: number): Utc => {
406
+ const self = Object.create(ProtoUtc)
407
+ self.epochMillis = epochMillis
408
+ return self
409
+ }
410
+
411
+ /**
412
+ * Create a `DateTime` from a `Date`.
413
+ *
414
+ * If the `Date` is invalid, an `IllegalArgumentException` will be thrown.
415
+ *
416
+ * @since 3.6.0
417
+ * @category constructors
418
+ */
419
+ export const unsafeFromDate = (date: Date): Utc => {
420
+ const epochMillis = date.getTime()
421
+ if (Number.isNaN(epochMillis)) {
422
+ throw new IllegalArgumentException("Invalid date")
423
+ }
424
+ return makeUtc(epochMillis)
425
+ }
426
+
427
+ /**
428
+ * Create a `DateTime` from one of the following:
429
+ *
430
+ * - A `DateTime`
431
+ * - A `Date` instance (invalid dates will throw an `IllegalArgumentException`)
432
+ * - The `number` of milliseconds since the Unix epoch
433
+ * - An object with the parts of a date
434
+ * - A `string` that can be parsed by `Date.parse`
435
+ *
436
+ * @since 3.6.0
437
+ * @category constructors
438
+ * @example
439
+ * import { DateTime } from "effect"
440
+ *
441
+ * // from Date
442
+ * DateTime.unsafeMake(new Date())
443
+ *
444
+ * // from parts
445
+ * DateTime.unsafeMake({ year: 2024 })
446
+ *
447
+ * // from string
448
+ * DateTime.unsafeMake("2024-01-01")
449
+ */
450
+ export const unsafeMake = <A extends DateTime.Input>(input: A): DateTime.PreserveZone<A> => {
451
+ if (isDateTime(input)) {
452
+ return input as DateTime.PreserveZone<A>
453
+ } else if (input instanceof Date) {
454
+ return unsafeFromDate(input) as DateTime.PreserveZone<A>
455
+ } else if (typeof input === "object") {
456
+ const date = new Date(0)
457
+ setPartsDate(date, input)
458
+ return unsafeFromDate(date) as DateTime.PreserveZone<A>
459
+ }
460
+ return unsafeFromDate(new Date(input)) as DateTime.PreserveZone<A>
461
+ }
462
+
463
+ /**
464
+ * Create a `DateTime.Zoned` using `DateTime.unsafeMake` and a time zone.
465
+ *
466
+ * The input is treated as UTC and then the time zone is attached, unless
467
+ * `adjustForTimeZone` is set to `true`. In that case, the input is treated as
468
+ * already in the time zone.
469
+ *
470
+ * @since 3.6.0
471
+ * @category constructors
472
+ * @example
473
+ * import { DateTime } from "effect"
474
+ *
475
+ * DateTime.unsafeMakeZoned(new Date(), { timeZone: "Europe/London" })
476
+ */
477
+ export const unsafeMakeZoned = (input: DateTime.Input, options: {
478
+ readonly timeZone: number | string | TimeZone
479
+ readonly adjustForTimeZone?: boolean | undefined
480
+ }): Zoned => {
481
+ const self = unsafeMake(input)
482
+ let zone: TimeZone
483
+ if (isTimeZone(options.timeZone)) {
484
+ zone = options.timeZone
485
+ } else if (typeof options.timeZone === "number") {
486
+ zone = zoneMakeOffset(options.timeZone)
487
+ } else {
488
+ const parsedZone = zoneFromString(options.timeZone)
489
+ if (Option.isNone(parsedZone)) {
490
+ throw new IllegalArgumentException(`Invalid time zone: ${options.timeZone}`)
491
+ }
492
+ zone = parsedZone.value
493
+ }
494
+ if (options.adjustForTimeZone !== true) {
495
+ return makeZonedProto(self.epochMillis, zone, self.partsUtc)
496
+ }
497
+ return makeZonedFromAdjusted(self.epochMillis, zone)
498
+ }
499
+
500
+ /**
501
+ * Create a `DateTime.Zoned` using `DateTime.make` and a time zone.
502
+ *
503
+ * The input is treated as UTC and then the time zone is attached.
504
+ *
505
+ * If the date time input or time zone is invalid, `None` will be returned.
506
+ *
507
+ * @since 3.6.0
508
+ * @category constructors
509
+ * @example
510
+ * import { DateTime } from "effect"
511
+ *
512
+ * DateTime.makeZoned(new Date(), { timeZone: "Europe/London" })
513
+ */
514
+ export const makeZoned: (
515
+ input: DateTime.Input,
516
+ options: {
517
+ readonly timeZone: number | string | TimeZone
518
+ readonly adjustForTimeZone?: boolean | undefined
519
+ }
520
+ ) => Option.Option<Zoned> = Option
521
+ .liftThrowable(unsafeMakeZoned)
522
+
523
+ /**
524
+ * Create a `DateTime` from one of the following:
525
+ *
526
+ * - A `DateTime`
527
+ * - A `Date` instance (invalid dates will throw an `IllegalArgumentException`)
528
+ * - The `number` of milliseconds since the Unix epoch
529
+ * - An object with the parts of a date
530
+ * - A `string` that can be parsed by `Date.parse`
531
+ *
532
+ * If the input is invalid, `None` will be returned.
533
+ *
534
+ * @since 3.6.0
535
+ * @category constructors
536
+ * @example
537
+ * import { DateTime } from "effect"
538
+ *
539
+ * // from Date
540
+ * DateTime.make(new Date())
541
+ *
542
+ * // from parts
543
+ * DateTime.make({ year: 2024 })
544
+ *
545
+ * // from string
546
+ * DateTime.make("2024-01-01")
547
+ */
548
+ export const make: <A extends DateTime.Input>(input: A) => Option.Option<DateTime.PreserveZone<A>> = Option
549
+ .liftThrowable(unsafeMake)
550
+
551
+ const zonedStringRegex = /^(.{17,35})\[(.+)\]$/
552
+
553
+ /**
554
+ * Create a `DateTime.Zoned` from a string.
555
+ *
556
+ * It uses the format: `YYYY-MM-DDTHH:mm:ss.sss+HH:MM[Time/Zone]`.
557
+ *
558
+ * @since 3.6.0
559
+ * @category constructors
560
+ */
561
+ export const makeZonedFromString = (input: string): Option.Option<Zoned> => {
562
+ const match = zonedStringRegex.exec(input)
563
+ if (match === null) {
564
+ const offset = parseOffset(input)
565
+ return offset ? makeZoned(input, { timeZone: offset }) : Option.none()
566
+ }
567
+ const [, isoString, timeZone] = match
568
+ return makeZoned(isoString, { timeZone })
569
+ }
570
+
571
+ /**
572
+ * Get the current time using the `Clock` service and convert it to a `DateTime`.
573
+ *
574
+ * @since 3.6.0
575
+ * @category constructors
576
+ * @example
577
+ * import { DateTime, Effect } from "effect"
578
+ *
579
+ * Effect.gen(function* () {
580
+ * const now = yield* DateTime.now
581
+ * })
582
+ */
583
+ export const now: Effect.Effect<Utc> = Effect.map(Clock.currentTimeMillis, makeUtc)
584
+
585
+ /**
586
+ * Get the current time using `Date.now`.
587
+ *
588
+ * @since 3.6.0
589
+ * @category constructors
590
+ */
591
+ export const unsafeNow: LazyArg<Utc> = () => makeUtc(Date.now())
592
+
593
+ // =============================================================================
594
+ // time zones
595
+ // =============================================================================
596
+
597
+ /**
598
+ * Set the time zone of a `DateTime`, returning a new `DateTime.Zoned`.
599
+ *
600
+ * @since 3.6.0
601
+ * @category time zones
602
+ * @example
603
+ * import { DateTime, Effect } from "effect"
604
+ *
605
+ * Effect.gen(function* () {
606
+ * const now = yield* DateTime.now
607
+ * const zone = DateTime.zoneUnsafeMakeNamed("Europe/London")
608
+ *
609
+ * // set the time zone
610
+ * const zoned: DateTime.Zoned = DateTime.setZone(now, zone)
611
+ * })
612
+ */
613
+ export const setZone: {
614
+ (zone: TimeZone, options?: {
615
+ readonly adjustForTimeZone?: boolean | undefined
616
+ }): (self: DateTime) => Zoned
617
+ (self: DateTime, zone: TimeZone, options?: {
618
+ readonly adjustForTimeZone?: boolean | undefined
619
+ }): Zoned
620
+ } = dual(isDateTimeArgs, (self: DateTime, zone: TimeZone, options?: {
621
+ readonly adjustForTimeZone?: boolean | undefined
622
+ }): Zoned =>
623
+ options?.adjustForTimeZone === true
624
+ ? makeZonedFromAdjusted(self.epochMillis, zone)
625
+ : makeZonedProto(self.epochMillis, zone, self.partsUtc))
626
+
627
+ /**
628
+ * Add a fixed offset time zone to a `DateTime`.
629
+ *
630
+ * The offset is in milliseconds.
631
+ *
632
+ * @since 3.6.0
633
+ * @category time zones
634
+ * @example
635
+ * import { DateTime, Effect } from "effect"
636
+ *
637
+ * Effect.gen(function* () {
638
+ * const now = yield* DateTime.now
639
+ *
640
+ * // set the offset time zone in milliseconds
641
+ * const zoned: DateTime.Zoned = DateTime.setZoneOffset(now, 3 * 60 * 60 * 1000)
642
+ * })
643
+ */
644
+ export const setZoneOffset: {
645
+ (offset: number, options?: {
646
+ readonly adjustForTimeZone?: boolean | undefined
647
+ }): (self: DateTime) => Zoned
648
+ (self: DateTime, offset: number, options?: {
649
+ readonly adjustForTimeZone?: boolean | undefined
650
+ }): Zoned
651
+ } = dual(isDateTimeArgs, (self: DateTime, offset: number, options?: {
652
+ readonly adjustForTimeZone?: boolean | undefined
653
+ }): Zoned => setZone(self, zoneMakeOffset(offset), options))
654
+
655
+ const validZoneCache = globalValue("effect/DateTime/validZoneCache", () => new Map<string, TimeZone.Named>())
656
+
657
+ const formatOptions: Intl.DateTimeFormatOptions = {
658
+ day: "numeric",
659
+ month: "numeric",
660
+ year: "numeric",
661
+ hour: "numeric",
662
+ minute: "numeric",
663
+ second: "numeric",
664
+ timeZoneName: "longOffset",
665
+ fractionalSecondDigits: 3,
666
+ hourCycle: "h23"
667
+ }
668
+
669
+ const zoneMakeIntl = (format: Intl.DateTimeFormat): TimeZone.Named => {
670
+ const zoneId = format.resolvedOptions().timeZone
671
+ if (validZoneCache.has(zoneId)) {
672
+ return validZoneCache.get(zoneId)!
673
+ }
674
+ const zone = Object.create(ProtoTimeZoneNamed)
675
+ zone.id = zoneId
676
+ zone.format = format
677
+ validZoneCache.set(zoneId, zone)
678
+ return zone
679
+ }
680
+
681
+ /**
682
+ * Attempt to create a named time zone from a IANA time zone identifier.
683
+ *
684
+ * If the time zone is invalid, an `IllegalArgumentException` will be thrown.
685
+ *
686
+ * @since 3.6.0
687
+ * @category time zones
688
+ */
689
+ export const zoneUnsafeMakeNamed = (zoneId: string): TimeZone.Named => {
690
+ if (validZoneCache.has(zoneId)) {
691
+ return validZoneCache.get(zoneId)!
692
+ }
693
+ try {
694
+ return zoneMakeIntl(
695
+ new Intl.DateTimeFormat("en-US", {
696
+ ...formatOptions,
697
+ timeZone: zoneId
698
+ })
699
+ )
700
+ } catch (_) {
701
+ throw new IllegalArgumentException(`Invalid time zone: ${zoneId}`)
702
+ }
703
+ }
704
+
705
+ /**
706
+ * Create a fixed offset time zone.
707
+ *
708
+ * @since 3.6.0
709
+ * @category time zones
710
+ */
711
+ export const zoneMakeOffset = (offset: number): TimeZone.Offset => {
712
+ const zone = Object.create(ProtoTimeZoneOffset)
713
+ zone.offset = offset
714
+ return zone
715
+ }
716
+
717
+ /**
718
+ * Create a named time zone from a IANA time zone identifier. If the time zone
719
+ * is invalid, `None` will be returned.
720
+ *
721
+ * @since 3.6.0
722
+ * @category time zones
723
+ */
724
+ export const zoneMakeNamed: (zoneId: string) => Option.Option<TimeZone.Named> = Option.liftThrowable(
725
+ zoneUnsafeMakeNamed
726
+ )
727
+
728
+ /**
729
+ * Create a named time zone from a IANA time zone identifier. If the time zone
730
+ * is invalid, it will fail with an `IllegalArgumentException`.
731
+ *
732
+ * @since 3.6.0
733
+ * @category time zones
734
+ */
735
+ export const zoneMakeNamedEffect = (zoneId: string): Effect.Effect<TimeZone.Named, IllegalArgumentException> =>
736
+ Effect.try({
737
+ try: () => zoneUnsafeMakeNamed(zoneId),
738
+ catch: (e) => e as IllegalArgumentException
739
+ })
740
+
741
+ /**
742
+ * Create a named time zone from the system's local time zone.
743
+ *
744
+ * @since 3.6.0
745
+ * @category time zones
746
+ */
747
+ export const zoneMakeLocal = (): TimeZone.Named => zoneMakeIntl(new Intl.DateTimeFormat("en-US", formatOptions))
748
+
749
+ const offsetZoneRegex = /^(?:GMT|[+-])/
750
+
751
+ /**
752
+ * Try parse a TimeZone from a string
753
+ *
754
+ * @since 3.6.0
755
+ * @category time zones
756
+ */
757
+ export const zoneFromString = (zone: string): Option.Option<TimeZone> => {
758
+ if (offsetZoneRegex.test(zone)) {
759
+ const offset = parseOffset(zone)
760
+ return offset === null ? Option.none() : Option.some(zoneMakeOffset(offset))
761
+ }
762
+ return zoneMakeNamed(zone)
763
+ }
764
+
765
+ /**
766
+ * Format a `TimeZone` as a string.
767
+ *
768
+ * @since 3.6.0
769
+ * @category time zones
770
+ * @example
771
+ * import { DateTime, Effect } from "effect"
772
+ *
773
+ * // Outputs "+03:00"
774
+ * DateTime.zoneToString(DateTime.zoneMakeOffset(3 * 60 * 60 * 1000))
775
+ *
776
+ * // Outputs "Europe/London"
777
+ * DateTime.zoneToString(DateTime.zoneUnsafeMakeNamed("Europe/London"))
778
+ */
779
+ export const zoneToString = (self: TimeZone): string => {
780
+ if (self._tag === "Offset") {
781
+ return offsetToString(self.offset)
782
+ }
783
+ return self.id
784
+ }
785
+
786
+ /**
787
+ * Set the time zone of a `DateTime` from an IANA time zone identifier. If the
788
+ * time zone is invalid, `None` will be returned.
789
+ *
790
+ * @since 3.6.0
791
+ * @category time zones
792
+ * @example
793
+ * import { DateTime, Effect } from "effect"
794
+ *
795
+ * Effect.gen(function* () {
796
+ * const now = yield* DateTime.now
797
+ * // set the time zone, returns an Option
798
+ * DateTime.setZoneNamed(now, "Europe/London")
799
+ * })
800
+ */
801
+ export const setZoneNamed: {
802
+ (zoneId: string, options?: {
803
+ readonly adjustForTimeZone?: boolean | undefined
804
+ }): (self: DateTime) => Option.Option<Zoned>
805
+ (self: DateTime, zoneId: string, options?: {
806
+ readonly adjustForTimeZone?: boolean | undefined
807
+ }): Option.Option<Zoned>
808
+ } = dual(
809
+ isDateTimeArgs,
810
+ (self: DateTime, zoneId: string, options?: {
811
+ readonly adjustForTimeZone?: boolean | undefined
812
+ }): Option.Option<Zoned> => Option.map(zoneMakeNamed(zoneId), (zone) => setZone(self, zone, options))
813
+ )
814
+
815
+ /**
816
+ * Set the time zone of a `DateTime` from an IANA time zone identifier. If the
817
+ * time zone is invalid, an `IllegalArgumentException` will be thrown.
818
+ *
819
+ * @since 3.6.0
820
+ * @category time zones
821
+ * @example
822
+ * import { DateTime, Effect } from "effect"
823
+ *
824
+ * Effect.gen(function* () {
825
+ * const now = yield* DateTime.now
826
+ * // set the time zone
827
+ * DateTime.unsafeSetZoneNamed(now, "Europe/London")
828
+ * })
829
+ */
830
+ export const unsafeSetZoneNamed: {
831
+ (zoneId: string, options?: {
832
+ readonly adjustForTimeZone?: boolean | undefined
833
+ }): (self: DateTime) => Zoned
834
+ (self: DateTime, zoneId: string, options?: {
835
+ readonly adjustForTimeZone?: boolean | undefined
836
+ }): Zoned
837
+ } = dual(isDateTimeArgs, (self: DateTime, zoneId: string, options?: {
838
+ readonly adjustForTimeZone?: boolean | undefined
839
+ }): Zoned => setZone(self, zoneUnsafeMakeNamed(zoneId), options))
840
+
841
+ // =============================================================================
842
+ // comparisons
843
+ // =============================================================================
844
+
845
+ /**
846
+ * Calulate the difference between two `DateTime` values, returning the number
847
+ * of milliseconds the `other` DateTime is from `self`.
848
+ *
849
+ * If `other` is *after* `self`, the result will be a positive number.
850
+ *
851
+ * @since 3.6.0
852
+ * @category comparisons
853
+ * @example
854
+ * import { DateTime, Effect } from "effect"
855
+ *
856
+ * Effect.gen(function* () {
857
+ * const now = yield* DateTime.now
858
+ * const other = DateTime.add(now, { minutes: 1 })
859
+ *
860
+ * // returns 60000
861
+ * DateTime.distance(now, other)
862
+ * })
863
+ */
864
+ export const distance: {
865
+ (other: DateTime): (self: DateTime) => number
866
+ (self: DateTime, other: DateTime): number
867
+ } = dual(2, (self: DateTime, other: DateTime): number => toEpochMillis(other) - toEpochMillis(self))
868
+
869
+ /**
870
+ * Calulate the difference between two `DateTime` values.
871
+ *
872
+ * If the `other` DateTime is before `self`, the result will be a negative
873
+ * `Duration`, returned as a `Left`.
874
+ *
875
+ * If the `other` DateTime is after `self`, the result will be a positive
876
+ * `Duration`, returned as a `Right`.
877
+ *
878
+ * @since 3.6.0
879
+ * @category comparisons
880
+ * @example
881
+ * import { DateTime, Effect } from "effect"
882
+ *
883
+ * Effect.gen(function* () {
884
+ * const now = yield* DateTime.now
885
+ * const other = DateTime.add(now, { minutes: 1 })
886
+ *
887
+ * // returns Either.right(Duration.minutes(1))
888
+ * DateTime.distanceDurationEither(now, other)
889
+ *
890
+ * // returns Either.left(Duration.minutes(1))
891
+ * DateTime.distanceDurationEither(other, now)
892
+ * })
893
+ */
894
+ export const distanceDurationEither: {
895
+ (other: DateTime): (self: DateTime) => Either.Either<Duration.Duration, Duration.Duration>
896
+ (self: DateTime, other: DateTime): Either.Either<Duration.Duration, Duration.Duration>
897
+ } = dual(2, (self: DateTime, other: DateTime): Either.Either<Duration.Duration, Duration.Duration> => {
898
+ const diffMillis = distance(self, other)
899
+ return diffMillis > 0
900
+ ? Either.right(Duration.millis(diffMillis))
901
+ : Either.left(Duration.millis(-diffMillis))
902
+ })
903
+
904
+ /**
905
+ * Calulate the distance between two `DateTime` values.
906
+ *
907
+ * @since 3.6.0
908
+ * @category comparisons
909
+ * @example
910
+ * import { DateTime, Effect } from "effect"
911
+ *
912
+ * Effect.gen(function* () {
913
+ * const now = yield* DateTime.now
914
+ * const other = DateTime.add(now, { minutes: 1 })
915
+ *
916
+ * // returns Duration.minutes(1)
917
+ * DateTime.distanceDuration(now, other)
918
+ * })
919
+ */
920
+ export const distanceDuration: {
921
+ (other: DateTime): (self: DateTime) => Duration.Duration
922
+ (self: DateTime, other: DateTime): Duration.Duration
923
+ } = dual(
924
+ 2,
925
+ (self: DateTime, other: DateTime): Duration.Duration => Duration.millis(Math.abs(distance(self, other)))
926
+ )
927
+
928
+ /**
929
+ * @since 3.6.0
930
+ * @category comparisons
931
+ */
932
+ export const min: {
933
+ (that: DateTime): (self: DateTime) => DateTime
934
+ (self: DateTime, that: DateTime): DateTime
935
+ } = order.min(Order)
936
+
937
+ /**
938
+ * @since 3.6.0
939
+ * @category comparisons
940
+ */
941
+ export const max: {
942
+ (that: DateTime): (self: DateTime) => DateTime
943
+ (self: DateTime, that: DateTime): DateTime
944
+ } = order.max(Order)
945
+
946
+ /**
947
+ * @since 3.6.0
948
+ * @category comparisons
949
+ */
950
+ export const greaterThan: {
951
+ (that: DateTime): (self: DateTime) => boolean
952
+ (self: DateTime, that: DateTime): boolean
953
+ } = order.greaterThan(Order)
954
+
955
+ /**
956
+ * @since 3.6.0
957
+ * @category comparisons
958
+ */
959
+ export const greaterThanOrEqualTo: {
960
+ (that: DateTime): (self: DateTime) => boolean
961
+ (self: DateTime, that: DateTime): boolean
962
+ } = order.greaterThanOrEqualTo(Order)
963
+
964
+ /**
965
+ * @since 3.6.0
966
+ * @category comparisons
967
+ */
968
+ export const lessThan: {
969
+ (that: DateTime): (self: DateTime) => boolean
970
+ (self: DateTime, that: DateTime): boolean
971
+ } = order.lessThan(Order)
972
+
973
+ /**
974
+ * @since 3.6.0
975
+ * @category comparisons
976
+ */
977
+ export const lessThanOrEqualTo: {
978
+ (that: DateTime): (self: DateTime) => boolean
979
+ (self: DateTime, that: DateTime): boolean
980
+ } = order.lessThanOrEqualTo(Order)
981
+
982
+ /**
983
+ * @since 3.6.0
984
+ * @category comparisons
985
+ */
986
+ export const between: {
987
+ (options: { minimum: DateTime; maximum: DateTime }): (self: DateTime) => boolean
988
+ (self: DateTime, options: { minimum: DateTime; maximum: DateTime }): boolean
989
+ } = order.between(Order)
990
+
991
+ /**
992
+ * @since 3.6.0
993
+ * @category comparisons
994
+ */
995
+ export const isFuture = (self: DateTime): Effect.Effect<boolean> => Effect.map(now, lessThan(self))
996
+
997
+ /**
998
+ * @since 3.6.0
999
+ * @category comparisons
1000
+ */
1001
+ export const unsafeIsFuture = (self: DateTime): boolean => lessThan(unsafeNow(), self)
1002
+
1003
+ /**
1004
+ * @since 3.6.0
1005
+ * @category comparisons
1006
+ */
1007
+ export const isPast = (self: DateTime): Effect.Effect<boolean> => Effect.map(now, greaterThan(self))
1008
+
1009
+ /**
1010
+ * @since 3.6.0
1011
+ * @category comparisons
1012
+ */
1013
+ export const unsafeIsPast = (self: DateTime): boolean => greaterThan(unsafeNow(), self)
1014
+
1015
+ // =============================================================================
1016
+ // conversions
1017
+ // =============================================================================
1018
+
1019
+ /**
1020
+ * Get the UTC `Date` of a `DateTime`.
1021
+ *
1022
+ * @since 3.6.0
1023
+ * @category conversions
1024
+ */
1025
+ export const toDateUtc = (self: DateTime): Date => new Date(self.epochMillis)
1026
+
1027
+ /**
1028
+ * Convert a `DateTime` to a `Date`, applying the time zone first.
1029
+ *
1030
+ * @since 3.6.0
1031
+ * @category conversions
1032
+ */
1033
+ export const toDate = (self: DateTime): Date => {
1034
+ if (self._tag === "Utc") {
1035
+ return new Date(self.epochMillis)
1036
+ } else if (self.zone._tag === "Offset") {
1037
+ return new Date(self.epochMillis + self.zone.offset)
1038
+ } else if (self.adjustedEpochMillis !== undefined) {
1039
+ return new Date(self.adjustedEpochMillis)
1040
+ }
1041
+ const parts = self.zone.format.formatToParts(self.epochMillis).filter((_) => _.type !== "literal")
1042
+ const date = new Date(0)
1043
+ date.setUTCFullYear(
1044
+ Number(parts[2].value),
1045
+ Number(parts[0].value) - 1,
1046
+ Number(parts[1].value)
1047
+ )
1048
+ date.setUTCHours(
1049
+ Number(parts[3].value),
1050
+ Number(parts[4].value),
1051
+ Number(parts[5].value),
1052
+ Number(parts[6].value)
1053
+ )
1054
+ self.adjustedEpochMillis = date.getTime()
1055
+ return date
1056
+ }
1057
+
1058
+ /**
1059
+ * Calculate the time zone offset of a `DateTime.Zoned` in milliseconds.
1060
+ *
1061
+ * @since 3.6.0
1062
+ * @category conversions
1063
+ */
1064
+ export const zonedOffset = (self: Zoned): number => {
1065
+ const date = toDate(self)
1066
+ return date.getTime() - toEpochMillis(self)
1067
+ }
1068
+
1069
+ const offsetToString = (offset: number): string => {
1070
+ const abs = Math.abs(offset)
1071
+ const hours = Math.floor(abs / (60 * 60 * 1000))
1072
+ const minutes = Math.round((abs % (60 * 60 * 1000)) / (60 * 1000))
1073
+ return `${offset < 0 ? "-" : "+"}${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}`
1074
+ }
1075
+
1076
+ /**
1077
+ * Calculate the time zone offset of a `DateTime` in milliseconds.
1078
+ *
1079
+ * The offset is formatted as "±HH:MM".
1080
+ *
1081
+ * @since 3.6.0
1082
+ * @category conversions
1083
+ */
1084
+ export const zonedOffsetIso = (self: Zoned): string => offsetToString(zonedOffset(self))
1085
+
1086
+ /**
1087
+ * Get the milliseconds since the Unix epoch of a `DateTime`.
1088
+ *
1089
+ * @since 3.6.0
1090
+ * @category conversions
1091
+ */
1092
+ export const toEpochMillis = (self: DateTime): number => self.epochMillis
1093
+
1094
+ /**
1095
+ * Remove the time aspect of a `DateTime`, first adjusting for the time
1096
+ * zone. It will return a `DateTime.Utc` only containing the date.
1097
+ *
1098
+ * @since 3.6.0
1099
+ * @category conversions
1100
+ * @example
1101
+ * import { DateTime } from "effect"
1102
+ *
1103
+ * // returns "2024-01-01T00:00:00Z"
1104
+ * DateTime.unsafeMakeZoned("2024-01-01T05:00:00Z", {
1105
+ * timeZone: "Pacific/Auckland",
1106
+ * adjustForTimeZone: true
1107
+ * }).pipe(
1108
+ * DateTime.removeTime,
1109
+ * DateTime.formatIso
1110
+ * )
1111
+ */
1112
+ export const removeTime = (self: DateTime): Utc =>
1113
+ withDate(self, (date) => {
1114
+ date.setUTCHours(0, 0, 0, 0)
1115
+ return makeUtc(date.getTime())
1116
+ })
1117
+
1118
+ // =============================================================================
1119
+ // parts
1120
+ // =============================================================================
1121
+
1122
+ const dateToParts = (date: Date): DateTime.PartsWithWeekday => ({
1123
+ millis: date.getUTCMilliseconds(),
1124
+ seconds: date.getUTCSeconds(),
1125
+ minutes: date.getUTCMinutes(),
1126
+ hours: date.getUTCHours(),
1127
+ day: date.getUTCDate(),
1128
+ weekDay: date.getUTCDay(),
1129
+ month: date.getUTCMonth() + 1,
1130
+ year: date.getUTCFullYear()
1131
+ })
1132
+
1133
+ /**
1134
+ * Get the different parts of a `DateTime` as an object.
1135
+ *
1136
+ * The parts will be time zone adjusted.
1137
+ *
1138
+ * @since 3.6.0
1139
+ * @category parts
1140
+ */
1141
+ export const toParts = (self: DateTime): DateTime.PartsWithWeekday => {
1142
+ if (self._tag === "Utc") {
1143
+ return toPartsUtc(self)
1144
+ } else if (self.partsAdjusted !== undefined) {
1145
+ return self.partsAdjusted
1146
+ }
1147
+ self.partsAdjusted = withDate(self, dateToParts)
1148
+ return self.partsAdjusted
1149
+ }
1150
+
1151
+ /**
1152
+ * Get the different parts of a `DateTime` as an object.
1153
+ *
1154
+ * The parts will be in UTC.
1155
+ *
1156
+ * @since 3.6.0
1157
+ * @category parts
1158
+ */
1159
+ export const toPartsUtc = (self: DateTime): DateTime.PartsWithWeekday => {
1160
+ if (self.partsUtc !== undefined) {
1161
+ return self.partsUtc
1162
+ }
1163
+ self.partsUtc = withDateUtc(self, dateToParts)
1164
+ return self.partsUtc
1165
+ }
1166
+
1167
+ /**
1168
+ * Get a part of a `DateTime` as a number.
1169
+ *
1170
+ * The part will be in the UTC time zone.
1171
+ *
1172
+ * @since 3.6.0
1173
+ * @category parts
1174
+ * @example
1175
+ * import { DateTime } from "effect"
1176
+ *
1177
+ * const now = DateTime.unsafeMake({ year: 2024 })
1178
+ * const year = DateTime.getPartUtc(now, "year")
1179
+ * assert.strictEqual(year, 2024)
1180
+ */
1181
+ export const getPartUtc: {
1182
+ (part: keyof DateTime.PartsWithWeekday): (self: DateTime) => number
1183
+ (self: DateTime, part: keyof DateTime.PartsWithWeekday): number
1184
+ } = dual(2, (self: DateTime, part: keyof DateTime.PartsWithWeekday): number => toPartsUtc(self)[part])
1185
+
1186
+ /**
1187
+ * Get a part of a `DateTime` as a number.
1188
+ *
1189
+ * The part will be time zone adjusted.
1190
+ *
1191
+ * @since 3.6.0
1192
+ * @category parts
1193
+ * @example
1194
+ * import { DateTime } from "effect"
1195
+ *
1196
+ * const now = DateTime.unsafeMakeZoned({ year: 2024 }, { timeZone: "Europe/London" })
1197
+ * const year = DateTime.getPart(now, "year")
1198
+ * assert.strictEqual(year, 2024)
1199
+ */
1200
+ export const getPart: {
1201
+ (part: keyof DateTime.PartsWithWeekday): (self: DateTime) => number
1202
+ (self: DateTime, part: keyof DateTime.PartsWithWeekday): number
1203
+ } = dual(2, (self: DateTime, part: keyof DateTime.PartsWithWeekday): number => toParts(self)[part])
1204
+
1205
+ const setPartsDate = (date: Date, parts: Partial<DateTime.PartsWithWeekday>): void => {
1206
+ if (parts.year !== undefined) {
1207
+ date.setUTCFullYear(parts.year)
1208
+ }
1209
+ if (parts.month !== undefined) {
1210
+ date.setUTCMonth(parts.month - 1)
1211
+ }
1212
+ if (parts.day !== undefined) {
1213
+ date.setUTCDate(parts.day)
1214
+ }
1215
+ if (parts.weekDay !== undefined) {
1216
+ const diff = parts.weekDay - date.getUTCDay()
1217
+ date.setUTCDate(date.getUTCDate() + diff)
1218
+ }
1219
+ if (parts.hours !== undefined) {
1220
+ date.setUTCHours(parts.hours)
1221
+ }
1222
+ if (parts.minutes !== undefined) {
1223
+ date.setUTCMinutes(parts.minutes)
1224
+ }
1225
+ if (parts.seconds !== undefined) {
1226
+ date.setUTCSeconds(parts.seconds)
1227
+ }
1228
+ if (parts.millis !== undefined) {
1229
+ date.setUTCMilliseconds(parts.millis)
1230
+ }
1231
+ }
1232
+
1233
+ /**
1234
+ * Set the different parts of a `DateTime` as an object.
1235
+ *
1236
+ * The Date will be time zone adjusted.
1237
+ *
1238
+ * @since 3.6.0
1239
+ * @category parts
1240
+ */
1241
+ export const setParts: {
1242
+ (parts: Partial<DateTime.PartsWithWeekday>): <A extends DateTime>(self: A) => DateTime.PreserveZone<A>
1243
+ <A extends DateTime>(self: A, parts: Partial<DateTime.PartsWithWeekday>): DateTime.PreserveZone<A>
1244
+ } = dual(
1245
+ 2,
1246
+ (self: DateTime, parts: Partial<DateTime.PartsWithWeekday>): DateTime =>
1247
+ mutate(self, (date) => setPartsDate(date, parts))
1248
+ )
1249
+
1250
+ /**
1251
+ * Set the different parts of a `DateTime` as an object.
1252
+ *
1253
+ * @since 3.6.0
1254
+ * @category parts
1255
+ */
1256
+ export const setPartsUtc: {
1257
+ (parts: Partial<DateTime.PartsWithWeekday>): <A extends DateTime>(self: A) => DateTime.PreserveZone<A>
1258
+ <A extends DateTime>(self: A, parts: Partial<DateTime.PartsWithWeekday>): DateTime.PreserveZone<A>
1259
+ } = dual(
1260
+ 2,
1261
+ (self: DateTime, parts: Partial<DateTime.PartsWithWeekday>): DateTime =>
1262
+ mutateUtc(self, (date) => setPartsDate(date, parts))
1263
+ )
1264
+
1265
+ // =============================================================================
1266
+ // current time zone
1267
+ // =============================================================================
1268
+
1269
+ /**
1270
+ * @since 3.6.0
1271
+ * @category current time zone
1272
+ */
1273
+ export class CurrentTimeZone extends Context.Tag("effect/DateTime/CurrentTimeZone")<
1274
+ CurrentTimeZone,
1275
+ TimeZone
1276
+ >() {}
1277
+
1278
+ /**
1279
+ * Set the time zone of a `DateTime` to the current time zone, which is
1280
+ * determined by the `CurrentTimeZone` service.
1281
+ *
1282
+ * @since 3.6.0
1283
+ * @category current time zone
1284
+ * @example
1285
+ * import { DateTime, Effect } from "effect"
1286
+ *
1287
+ * Effect.gen(function* () {
1288
+ * const now = yield* DateTime.now
1289
+ *
1290
+ * // set the time zone to "Europe/London"
1291
+ * const zoned = yield* DateTime.setZoneCurrent(now)
1292
+ * }).pipe(DateTime.withCurrentZoneNamed("Europe/London"))
1293
+ */
1294
+ export const setZoneCurrent = (self: DateTime): Effect.Effect<Zoned, never, CurrentTimeZone> =>
1295
+ Effect.map(CurrentTimeZone, (zone) => setZone(self, zone))
1296
+
1297
+ /**
1298
+ * Provide the `CurrentTimeZone` to an effect.
1299
+ *
1300
+ * @since 3.6.0
1301
+ * @category current time zone
1302
+ * @example
1303
+ * import { DateTime, Effect } from "effect"
1304
+ *
1305
+ * const zone = DateTime.zoneUnsafeMakeNamed("Europe/London")
1306
+ *
1307
+ * Effect.gen(function* () {
1308
+ * const now = yield* DateTime.nowInCurrentZone
1309
+ * }).pipe(DateTime.withCurrentZone(zone))
1310
+ */
1311
+ export const withCurrentZone: {
1312
+ (zone: TimeZone): <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, CurrentTimeZone>>
1313
+ <A, E, R>(effect: Effect.Effect<A, E, R>, zone: TimeZone): Effect.Effect<A, E, Exclude<R, CurrentTimeZone>>
1314
+ } = dual(
1315
+ 2,
1316
+ <A, E, R>(effect: Effect.Effect<A, E, R>, zone: TimeZone): Effect.Effect<A, E, Exclude<R, CurrentTimeZone>> =>
1317
+ Effect.provideService(effect, CurrentTimeZone, zone)
1318
+ )
1319
+
1320
+ /**
1321
+ * Provide the `CurrentTimeZone` to an effect, using the system's local time
1322
+ * zone.
1323
+ *
1324
+ * @since 3.6.0
1325
+ * @category current time zone
1326
+ * @example
1327
+ * import { DateTime, Effect } from "effect"
1328
+ *
1329
+ * Effect.gen(function* () {
1330
+ * // will use the system's local time zone
1331
+ * const now = yield* DateTime.nowInCurrentZone
1332
+ * }).pipe(DateTime.withCurrentZoneLocal)
1333
+ */
1334
+ export const withCurrentZoneLocal = <A, E, R>(
1335
+ effect: Effect.Effect<A, E, R>
1336
+ ): Effect.Effect<A, E, Exclude<R, CurrentTimeZone>> =>
1337
+ Effect.provideServiceEffect(effect, CurrentTimeZone, Effect.sync(zoneMakeLocal))
1338
+
1339
+ /**
1340
+ * Provide the `CurrentTimeZone` to an effect, using a offset.
1341
+ *
1342
+ * @since 3.6.0
1343
+ * @category current time zone
1344
+ * @example
1345
+ * import { DateTime, Effect } from "effect"
1346
+ *
1347
+ * Effect.gen(function* () {
1348
+ * // will use the system's local time zone
1349
+ * const now = yield* DateTime.nowInCurrentZone
1350
+ * }).pipe(DateTime.withCurrentZoneOffset(3 * 60 * 60 * 1000))
1351
+ */
1352
+ export const withCurrentZoneOffset: {
1353
+ (offset: number): <A, E, R>(
1354
+ effect: Effect.Effect<A, E, R>
1355
+ ) => Effect.Effect<A, E, Exclude<R, CurrentTimeZone>>
1356
+ <A, E, R>(effect: Effect.Effect<A, E, R>, offset: number): Effect.Effect<A, E, Exclude<R, CurrentTimeZone>>
1357
+ } = dual(
1358
+ 2,
1359
+ <A, E, R>(effect: Effect.Effect<A, E, R>, offset: number): Effect.Effect<A, E, Exclude<R, CurrentTimeZone>> =>
1360
+ Effect.provideService(effect, CurrentTimeZone, zoneMakeOffset(offset))
1361
+ )
1362
+
1363
+ /**
1364
+ * Provide the `CurrentTimeZone` to an effect using an IANA time zone
1365
+ * identifier.
1366
+ *
1367
+ * If the time zone is invalid, it will fail with an `IllegalArgumentException`.
1368
+ *
1369
+ * @since 3.6.0
1370
+ * @category current time zone
1371
+ * @example
1372
+ * import { DateTime, Effect } from "effect"
1373
+ *
1374
+ * Effect.gen(function* () {
1375
+ * // will use the "Europe/London" time zone
1376
+ * const now = yield* DateTime.nowInCurrentZone
1377
+ * }).pipe(DateTime.withCurrentZoneNamed("Europe/London"))
1378
+ */
1379
+ export const withCurrentZoneNamed: {
1380
+ (zone: string): <A, E, R>(
1381
+ effect: Effect.Effect<A, E, R>
1382
+ ) => Effect.Effect<A, E | IllegalArgumentException, Exclude<R, CurrentTimeZone>>
1383
+ <A, E, R>(
1384
+ effect: Effect.Effect<A, E, R>,
1385
+ zone: string
1386
+ ): Effect.Effect<A, E | IllegalArgumentException, Exclude<R, CurrentTimeZone>>
1387
+ } = dual(
1388
+ 2,
1389
+ <A, E, R>(
1390
+ effect: Effect.Effect<A, E, R>,
1391
+ zone: string
1392
+ ): Effect.Effect<A, E | IllegalArgumentException, Exclude<R, CurrentTimeZone>> =>
1393
+ Effect.provideServiceEffect(effect, CurrentTimeZone, zoneMakeNamedEffect(zone))
1394
+ )
1395
+
1396
+ /**
1397
+ * Get the current time as a `DateTime.Zoned`, using the `CurrentTimeZone`.
1398
+ *
1399
+ * @since 3.6.0
1400
+ * @category current time zone
1401
+ * @example
1402
+ * import { DateTime, Effect } from "effect"
1403
+ *
1404
+ * Effect.gen(function* () {
1405
+ * // will use the "Europe/London" time zone
1406
+ * const now = yield* DateTime.nowInCurrentZone
1407
+ * }).pipe(DateTime.withCurrentZoneNamed("Europe/London"))
1408
+ */
1409
+ export const nowInCurrentZone: Effect.Effect<Zoned, never, CurrentTimeZone> = Effect.flatMap(
1410
+ now,
1411
+ setZoneCurrent
1412
+ )
1413
+
1414
+ /**
1415
+ * Create a Layer from the given time zone.
1416
+ *
1417
+ * @since 3.6.0
1418
+ * @category current time zone
1419
+ */
1420
+ export const layerCurrentZone = (zone: TimeZone): Layer.Layer<CurrentTimeZone> => Layer.succeed(CurrentTimeZone, zone)
1421
+
1422
+ /**
1423
+ * Create a Layer from the given time zone offset.
1424
+ *
1425
+ * @since 3.6.0
1426
+ * @category current time zone
1427
+ */
1428
+ export const layerCurrentZoneOffset = (offset: number): Layer.Layer<CurrentTimeZone> =>
1429
+ Layer.succeed(CurrentTimeZone, zoneMakeOffset(offset))
1430
+
1431
+ /**
1432
+ * Create a Layer from the given IANA time zone identifier.
1433
+ *
1434
+ * @since 3.6.0
1435
+ * @category current time zone
1436
+ */
1437
+ export const layerCurrentZoneNamed = (zoneId: string): Layer.Layer<CurrentTimeZone, IllegalArgumentException> =>
1438
+ Layer.effect(CurrentTimeZone, zoneMakeNamedEffect(zoneId))
1439
+
1440
+ /**
1441
+ * Create a Layer from the systems local time zone.
1442
+ *
1443
+ * @since 3.6.0
1444
+ * @category current time zone
1445
+ */
1446
+ export const layerCurrentZoneLocal: Layer.Layer<CurrentTimeZone> = Layer.sync(
1447
+ CurrentTimeZone,
1448
+ zoneMakeLocal
1449
+ )
1450
+
1451
+ // =============================================================================
1452
+ // mapping
1453
+ // =============================================================================
1454
+
1455
+ const makeZonedFromAdjusted = (adjustedMillis: number, zone: TimeZone): Zoned => {
1456
+ const offset = zone._tag === "Offset" ? zone.offset : calculateNamedOffset(adjustedMillis, zone)
1457
+ return makeZonedProto(adjustedMillis - offset, zone)
1458
+ }
1459
+
1460
+ const offsetRegex = /([+-])(\d{2}):(\d{2})$/
1461
+ const parseOffset = (offset: string): number | null => {
1462
+ const match = offsetRegex.exec(offset)
1463
+ if (match === null) {
1464
+ return null
1465
+ }
1466
+ const [, sign, hours, minutes] = match
1467
+ return (sign === "+" ? 1 : -1) * (Number(hours) * 60 + Number(minutes)) * 60 * 1000
1468
+ }
1469
+
1470
+ const calculateNamedOffset = (adjustedMillis: number, zone: TimeZone.Named): number => {
1471
+ const offset = zone.format.formatToParts(adjustedMillis).find((_) => _.type === "timeZoneName")?.value ?? ""
1472
+ if (offset === "GMT") {
1473
+ return 0
1474
+ }
1475
+ const result = parseOffset(offset)
1476
+ if (result === null) {
1477
+ // fallback to using the adjusted date
1478
+ return zonedOffset(makeZonedProto(adjustedMillis, zone))
1479
+ }
1480
+ return result
1481
+ }
1482
+
1483
+ /**
1484
+ * Modify a `DateTime` by applying a function to a cloned `Date` instance.
1485
+ *
1486
+ * The `Date` will first have the time zone applied if possible, and then be
1487
+ * converted back to a `DateTime` within the same time zone.
1488
+ *
1489
+ * @since 3.6.0
1490
+ * @category mapping
1491
+ */
1492
+ export const mutate: {
1493
+ (f: (date: Date) => void): <A extends DateTime>(self: A) => DateTime.PreserveZone<A>
1494
+ <A extends DateTime>(self: A, f: (date: Date) => void): DateTime.PreserveZone<A>
1495
+ } = dual(2, (self: DateTime, f: (date: Date) => void): DateTime => {
1496
+ if (self._tag === "Utc") {
1497
+ const date = toDateUtc(self)
1498
+ f(date)
1499
+ return makeUtc(date.getTime())
1500
+ }
1501
+ const adjustedDate = toDate(self)
1502
+ const newAdjustedDate = new Date(adjustedDate.getTime())
1503
+ f(newAdjustedDate)
1504
+ return makeZonedFromAdjusted(newAdjustedDate.getTime(), self.zone)
1505
+ })
1506
+
1507
+ /**
1508
+ * Modify a `DateTime` by applying a function to a cloned UTC `Date` instance.
1509
+ *
1510
+ * @since 3.6.0
1511
+ * @category mapping
1512
+ */
1513
+ export const mutateUtc: {
1514
+ (f: (date: Date) => void): <A extends DateTime>(self: A) => DateTime.PreserveZone<A>
1515
+ <A extends DateTime>(self: A, f: (date: Date) => void): DateTime.PreserveZone<A>
1516
+ } = dual(2, (self: DateTime, f: (date: Date) => void): DateTime =>
1517
+ mapEpochMillis(self, (millis) => {
1518
+ const date = new Date(millis)
1519
+ f(date)
1520
+ return date.getTime()
1521
+ }))
1522
+
1523
+ /**
1524
+ * Transform a `DateTime` by applying a function to the number of milliseconds
1525
+ * since the Unix epoch.
1526
+ *
1527
+ * @since 3.6.0
1528
+ * @category mapping
1529
+ * @example
1530
+ * import { DateTime } from "effect"
1531
+ *
1532
+ * // add 10 milliseconds
1533
+ * DateTime.unsafeMake(0).pipe(
1534
+ * DateTime.mapEpochMillis((millis) => millis + 10)
1535
+ * )
1536
+ */
1537
+ export const mapEpochMillis: {
1538
+ (f: (millis: number) => number): <A extends DateTime>(self: A) => DateTime.PreserveZone<A>
1539
+ <A extends DateTime>(self: A, f: (millis: number) => number): DateTime.PreserveZone<A>
1540
+ } = dual(2, (self: DateTime, f: (millis: number) => number): DateTime => {
1541
+ const millis = f(toEpochMillis(self))
1542
+ return self._tag === "Utc" ? makeUtc(millis) : makeZonedProto(millis, self.zone)
1543
+ })
1544
+
1545
+ /**
1546
+ * Using the time zone adjusted `Date`, apply a function to the `Date` and
1547
+ * return the result.
1548
+ *
1549
+ * @since 3.6.0
1550
+ * @category mapping
1551
+ * @example
1552
+ * import { DateTime } from "effect"
1553
+ *
1554
+ * // get the time zone adjusted date in milliseconds
1555
+ * DateTime.unsafeMakeZoned(0, { timeZone: "Europe/London" }).pipe(
1556
+ * DateTime.withDate((date) => date.getTime())
1557
+ * )
1558
+ */
1559
+ export const withDate: {
1560
+ <A>(f: (date: Date) => A): (self: DateTime) => A
1561
+ <A>(self: DateTime, f: (date: Date) => A): A
1562
+ } = dual(2, <A>(self: DateTime, f: (date: Date) => A): A => f(toDate(self)))
1563
+
1564
+ /**
1565
+ * Using the time zone adjusted `Date`, apply a function to the `Date` and
1566
+ * return the result.
1567
+ *
1568
+ * @since 3.6.0
1569
+ * @category mapping
1570
+ * @example
1571
+ * import { DateTime } from "effect"
1572
+ *
1573
+ * // get the date in milliseconds
1574
+ * DateTime.unsafeMake(0).pipe(
1575
+ * DateTime.withDateUtc((date) => date.getTime())
1576
+ * )
1577
+ */
1578
+ export const withDateUtc: {
1579
+ <A>(f: (date: Date) => A): (self: DateTime) => A
1580
+ <A>(self: DateTime, f: (date: Date) => A): A
1581
+ } = dual(2, <A>(self: DateTime, f: (date: Date) => A): A => f(toDateUtc(self)))
1582
+
1583
+ /**
1584
+ * @since 3.6.0
1585
+ * @category mapping
1586
+ */
1587
+ export const match: {
1588
+ <A, B>(options: {
1589
+ readonly onUtc: (_: Utc) => A
1590
+ readonly onZoned: (_: Zoned) => B
1591
+ }): (self: DateTime) => A | B
1592
+ <A, B>(self: DateTime, options: {
1593
+ readonly onUtc: (_: Utc) => A
1594
+ readonly onZoned: (_: Zoned) => B
1595
+ }): A | B
1596
+ } = dual(2, <A, B>(self: DateTime, options: {
1597
+ readonly onUtc: (_: Utc) => A
1598
+ readonly onZoned: (_: Zoned) => B
1599
+ }): A | B => self._tag === "Utc" ? options.onUtc(self) : options.onZoned(self))
1600
+
1601
+ // =============================================================================
1602
+ // math
1603
+ // =============================================================================
1604
+
1605
+ /**
1606
+ * Add the given `Duration` to a `DateTime`.
1607
+ *
1608
+ * @since 3.6.0
1609
+ * @category math
1610
+ * @example
1611
+ * import { DateTime } from "effect"
1612
+ *
1613
+ * // add 5 minutes
1614
+ * DateTime.unsafeMake(0).pipe(
1615
+ * DateTime.addDuration("5 minutes")
1616
+ * )
1617
+ */
1618
+ export const addDuration: {
1619
+ (duration: Duration.DurationInput): <A extends DateTime>(self: A) => DateTime.PreserveZone<A>
1620
+ <A extends DateTime>(self: A, duration: Duration.DurationInput): DateTime.PreserveZone<A>
1621
+ } = dual(
1622
+ 2,
1623
+ (self: DateTime, duration: Duration.DurationInput): DateTime =>
1624
+ mapEpochMillis(self, (millis) => millis + Duration.toMillis(duration))
1625
+ )
1626
+
1627
+ /**
1628
+ * Subtract the given `Duration` from a `DateTime`.
1629
+ *
1630
+ * @since 3.6.0
1631
+ * @category math
1632
+ * @example
1633
+ * import { DateTime } from "effect"
1634
+ *
1635
+ * // subtract 5 minutes
1636
+ * DateTime.unsafeMake(0).pipe(
1637
+ * DateTime.subtractDuration("5 minutes")
1638
+ * )
1639
+ */
1640
+ export const subtractDuration: {
1641
+ (duration: Duration.DurationInput): <A extends DateTime>(self: A) => DateTime.PreserveZone<A>
1642
+ <A extends DateTime>(self: A, duration: Duration.DurationInput): DateTime.PreserveZone<A>
1643
+ } = dual(
1644
+ 2,
1645
+ (self: DateTime, duration: Duration.DurationInput): DateTime =>
1646
+ mapEpochMillis(self, (millis) => millis - Duration.toMillis(duration))
1647
+ )
1648
+
1649
+ const addMillis = (date: Date, amount: number): void => {
1650
+ date.setTime(date.getTime() + amount)
1651
+ }
1652
+
1653
+ /**
1654
+ * Add the given `amount` of `unit`'s to a `DateTime`.
1655
+ *
1656
+ * The time zone is taken into account when adding days, weeks, months, and
1657
+ * years.
1658
+ *
1659
+ * @since 3.6.0
1660
+ * @category math
1661
+ * @example
1662
+ * import { DateTime } from "effect"
1663
+ *
1664
+ * // add 5 minutes
1665
+ * DateTime.unsafeMake(0).pipe(
1666
+ * DateTime.add({ minutes: 5 })
1667
+ * )
1668
+ */
1669
+ export const add: {
1670
+ (parts: Partial<DateTime.PartsForMath>): <A extends DateTime>(self: A) => DateTime.PreserveZone<A>
1671
+ <A extends DateTime>(self: A, parts: Partial<DateTime.PartsForMath>): DateTime.PreserveZone<A>
1672
+ } = dual(2, (self: DateTime, parts: Partial<DateTime.PartsForMath>): DateTime =>
1673
+ mutate(self, (date) => {
1674
+ if (parts.millis) {
1675
+ addMillis(date, parts.millis)
1676
+ }
1677
+ if (parts.seconds) {
1678
+ addMillis(date, parts.seconds * 1000)
1679
+ }
1680
+ if (parts.minutes) {
1681
+ addMillis(date, parts.minutes * 60 * 1000)
1682
+ }
1683
+ if (parts.hours) {
1684
+ addMillis(date, parts.hours * 60 * 60 * 1000)
1685
+ }
1686
+ if (parts.days) {
1687
+ date.setUTCDate(date.getUTCDate() + parts.days)
1688
+ }
1689
+ if (parts.weeks) {
1690
+ date.setUTCDate(date.getUTCDate() + parts.weeks * 7)
1691
+ }
1692
+ if (parts.months) {
1693
+ const day = date.getUTCDate()
1694
+ date.setUTCMonth(date.getUTCMonth() + parts.months + 1, 0)
1695
+ if (day < date.getUTCDate()) {
1696
+ date.setUTCDate(day)
1697
+ }
1698
+ }
1699
+ if (parts.years) {
1700
+ const day = date.getUTCDate()
1701
+ const month = date.getUTCMonth()
1702
+ date.setUTCFullYear(
1703
+ date.getUTCFullYear() + parts.years,
1704
+ month + 1,
1705
+ 0
1706
+ )
1707
+ if (day < date.getUTCDate()) {
1708
+ date.setUTCDate(day)
1709
+ }
1710
+ }
1711
+ }))
1712
+
1713
+ /**
1714
+ * Subtract the given `amount` of `unit`'s from a `DateTime`.
1715
+ *
1716
+ * @since 3.6.0
1717
+ * @category math
1718
+ * @example
1719
+ * import { DateTime } from "effect"
1720
+ *
1721
+ * // subtract 5 minutes
1722
+ * DateTime.unsafeMake(0).pipe(
1723
+ * DateTime.subtract({ minutes: 5 })
1724
+ * )
1725
+ */
1726
+ export const subtract: {
1727
+ (parts: Partial<DateTime.PartsForMath>): <A extends DateTime>(self: A) => DateTime.PreserveZone<A>
1728
+ <A extends DateTime>(self: A, parts: Partial<DateTime.PartsForMath>): DateTime.PreserveZone<A>
1729
+ } = dual(2, (self: DateTime, parts: Partial<DateTime.PartsForMath>): DateTime => {
1730
+ const newParts = {} as Partial<Mutable<DateTime.PartsForMath>>
1731
+ for (const key in parts) {
1732
+ newParts[key as keyof DateTime.PartsForMath] = -1 * parts[key as keyof DateTime.PartsForMath]!
1733
+ }
1734
+ return add(self, newParts)
1735
+ })
1736
+
1737
+ function startOfDate(date: Date, part: DateTime.UnitSingular, options?: {
1738
+ readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined
1739
+ }) {
1740
+ switch (part) {
1741
+ case "second": {
1742
+ date.setUTCMilliseconds(0)
1743
+ break
1744
+ }
1745
+ case "minute": {
1746
+ date.setUTCSeconds(0, 0)
1747
+ break
1748
+ }
1749
+ case "hour": {
1750
+ date.setUTCMinutes(0, 0, 0)
1751
+ break
1752
+ }
1753
+ case "day": {
1754
+ date.setUTCHours(0, 0, 0, 0)
1755
+ break
1756
+ }
1757
+ case "week": {
1758
+ const weekStartsOn = options?.weekStartsOn ?? 0
1759
+ const day = date.getUTCDay()
1760
+ const diff = (day - weekStartsOn + 7) % 7
1761
+ date.setUTCDate(date.getUTCDate() - diff)
1762
+ date.setUTCHours(0, 0, 0, 0)
1763
+ break
1764
+ }
1765
+ case "month": {
1766
+ date.setUTCDate(1)
1767
+ date.setUTCHours(0, 0, 0, 0)
1768
+ break
1769
+ }
1770
+ case "year": {
1771
+ date.setUTCMonth(0, 1)
1772
+ date.setUTCHours(0, 0, 0, 0)
1773
+ break
1774
+ }
1775
+ }
1776
+ }
1777
+
1778
+ /**
1779
+ * Converts a `DateTime` to the start of the given `part`.
1780
+ *
1781
+ * If the part is `week`, the `weekStartsOn` option can be used to specify the
1782
+ * day of the week that the week starts on. The default is 0 (Sunday).
1783
+ *
1784
+ * @since 3.6.0
1785
+ * @category math
1786
+ * @example
1787
+ * import { DateTime } from "effect"
1788
+ *
1789
+ * // returns "2024-01-01T00:00:00Z"
1790
+ * DateTime.unsafeMake("2024-01-01T12:00:00Z").pipe(
1791
+ * DateTime.startOf("day"),
1792
+ * DateTime.formatIso
1793
+ * )
1794
+ */
1795
+ export const startOf: {
1796
+ (part: DateTime.UnitSingular, options?: {
1797
+ readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined
1798
+ }): <A extends DateTime>(self: A) => DateTime.PreserveZone<A>
1799
+ <A extends DateTime>(self: A, part: DateTime.UnitSingular, options?: {
1800
+ readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined
1801
+ }): DateTime.PreserveZone<A>
1802
+ } = dual(isDateTimeArgs, (self: DateTime, part: DateTime.UnitSingular, options?: {
1803
+ readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined
1804
+ }): DateTime => mutate(self, (date) => startOfDate(date, part, options)))
1805
+
1806
+ function endOfDate(date: Date, part: DateTime.UnitSingular, options?: {
1807
+ readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined
1808
+ }) {
1809
+ switch (part) {
1810
+ case "second": {
1811
+ date.setUTCMilliseconds(999)
1812
+ break
1813
+ }
1814
+ case "minute": {
1815
+ date.setUTCSeconds(59, 999)
1816
+ break
1817
+ }
1818
+ case "hour": {
1819
+ date.setUTCMinutes(59, 59, 999)
1820
+ break
1821
+ }
1822
+ case "day": {
1823
+ date.setUTCHours(23, 59, 59, 999)
1824
+ break
1825
+ }
1826
+ case "week": {
1827
+ const weekStartsOn = options?.weekStartsOn ?? 0
1828
+ const day = date.getUTCDay()
1829
+ const diff = (day - weekStartsOn + 7) % 7
1830
+ date.setUTCDate(date.getUTCDate() - diff + 6)
1831
+ date.setUTCHours(23, 59, 59, 999)
1832
+ break
1833
+ }
1834
+ case "month": {
1835
+ date.setUTCMonth(date.getUTCMonth() + 1, 0)
1836
+ date.setUTCHours(23, 59, 59, 999)
1837
+ break
1838
+ }
1839
+ case "year": {
1840
+ date.setUTCMonth(11, 31)
1841
+ date.setUTCHours(23, 59, 59, 999)
1842
+ break
1843
+ }
1844
+ }
1845
+ }
1846
+
1847
+ /**
1848
+ * Converts a `DateTime` to the end of the given `part`.
1849
+ *
1850
+ * If the part is `week`, the `weekStartsOn` option can be used to specify the
1851
+ * day of the week that the week starts on. The default is 0 (Sunday).
1852
+ *
1853
+ * @since 3.6.0
1854
+ * @category math
1855
+ * @example
1856
+ * import { DateTime } from "effect"
1857
+ *
1858
+ * // returns "2024-01-01T23:59:59.999Z"
1859
+ * DateTime.unsafeMake("2024-01-01T12:00:00Z").pipe(
1860
+ * DateTime.endOf("day"),
1861
+ * DateTime.formatIso
1862
+ * )
1863
+ */
1864
+ export const endOf: {
1865
+ (part: DateTime.UnitSingular, options?: {
1866
+ readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined
1867
+ }): <A extends DateTime>(self: A) => DateTime.PreserveZone<A>
1868
+ <A extends DateTime>(self: A, part: DateTime.UnitSingular, options?: {
1869
+ readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined
1870
+ }): DateTime.PreserveZone<A>
1871
+ } = dual(isDateTimeArgs, (self: DateTime, part: DateTime.UnitSingular, options?: {
1872
+ readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined
1873
+ }): DateTime => mutate(self, (date) => endOfDate(date, part, options)))
1874
+
1875
+ /**
1876
+ * Converts a `DateTime` to the nearest given `part`.
1877
+ *
1878
+ * If the part is `week`, the `weekStartsOn` option can be used to specify the
1879
+ * day of the week that the week starts on. The default is 0 (Sunday).
1880
+ *
1881
+ * @since 3.6.0
1882
+ * @category math
1883
+ * @example
1884
+ * import { DateTime } from "effect"
1885
+ *
1886
+ * // returns "2024-01-02T00:00:00Z"
1887
+ * DateTime.unsafeMake("2024-01-01T12:01:00Z").pipe(
1888
+ * DateTime.nearest("day"),
1889
+ * DateTime.formatIso
1890
+ * )
1891
+ */
1892
+ export const nearest: {
1893
+ (part: DateTime.UnitSingular, options?: {
1894
+ readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined
1895
+ }): <A extends DateTime>(self: A) => DateTime.PreserveZone<A>
1896
+ <A extends DateTime>(self: A, part: DateTime.UnitSingular, options?: {
1897
+ readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined
1898
+ }): DateTime.PreserveZone<A>
1899
+ } = dual(isDateTimeArgs, (self: DateTime, part: DateTime.UnitSingular, options?: {
1900
+ readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined
1901
+ }): DateTime =>
1902
+ mutate(self, (date) => {
1903
+ if (part === "milli") return
1904
+ const millis = date.getTime()
1905
+ const start = new Date(millis)
1906
+ startOfDate(start, part, options)
1907
+ const startMillis = start.getTime()
1908
+ const end = new Date(millis)
1909
+ endOfDate(end, part, options)
1910
+ const endMillis = end.getTime() + 1
1911
+ const diffStart = millis - startMillis
1912
+ const diffEnd = endMillis - millis
1913
+ if (diffStart < diffEnd) {
1914
+ date.setTime(startMillis)
1915
+ } else {
1916
+ date.setTime(endMillis)
1917
+ }
1918
+ }))
1919
+
1920
+ // =============================================================================
1921
+ // formatting
1922
+ // =============================================================================
1923
+
1924
+ const intlTimeZone = (self: TimeZone): string => {
1925
+ if (self._tag === "Named") {
1926
+ return self.id
1927
+ }
1928
+ return offsetToString(self.offset)
1929
+ }
1930
+
1931
+ /**
1932
+ * Format a `DateTime` as a string using the `DateTimeFormat` API.
1933
+ *
1934
+ * The `timeZone` option is set to the offset of the time zone.
1935
+ *
1936
+ * Note: On Node versions < 22, fixed "Offset" zones will set the time zone to
1937
+ * "UTC" and use the adjusted `Date`.
1938
+ *
1939
+ * @since 3.6.0
1940
+ * @category formatting
1941
+ */
1942
+ export const format: {
1943
+ (
1944
+ options?:
1945
+ | Intl.DateTimeFormatOptions & {
1946
+ readonly locale?: string | undefined
1947
+ }
1948
+ | undefined
1949
+ ): (self: DateTime) => string
1950
+ (
1951
+ self: DateTime,
1952
+ options?:
1953
+ | Intl.DateTimeFormatOptions & {
1954
+ readonly locale?: string | undefined
1955
+ }
1956
+ | undefined
1957
+ ): string
1958
+ } = dual(isDateTimeArgs, (
1959
+ self: DateTime,
1960
+ options?:
1961
+ | Intl.DateTimeFormatOptions & {
1962
+ readonly locale?: string | undefined
1963
+ }
1964
+ | undefined
1965
+ ): string => {
1966
+ try {
1967
+ return new Intl.DateTimeFormat(options?.locale, {
1968
+ timeZone: self._tag === "Utc" ? "UTC" : intlTimeZone(self.zone),
1969
+ ...options
1970
+ }).format(self.epochMillis)
1971
+ } catch (_) {
1972
+ return new Intl.DateTimeFormat(options?.locale, {
1973
+ timeZone: "UTC",
1974
+ ...options
1975
+ }).format(toDate(self))
1976
+ }
1977
+ })
1978
+
1979
+ /**
1980
+ * Format a `DateTime` as a string using the `DateTimeFormat` API.
1981
+ *
1982
+ * It will use the system's local time zone.
1983
+ *
1984
+ * @since 3.6.0
1985
+ * @category formatting
1986
+ */
1987
+ export const formatLocal: {
1988
+ (
1989
+ options?:
1990
+ | Intl.DateTimeFormatOptions & {
1991
+ readonly locale?: string | undefined
1992
+ }
1993
+ | undefined
1994
+ ): (self: DateTime) => string
1995
+ (
1996
+ self: DateTime,
1997
+ options?:
1998
+ | Intl.DateTimeFormatOptions & {
1999
+ readonly locale?: string | undefined
2000
+ }
2001
+ | undefined
2002
+ ): string
2003
+ } = dual(isDateTimeArgs, (
2004
+ self: DateTime,
2005
+ options?:
2006
+ | Intl.DateTimeFormatOptions & {
2007
+ readonly locale?: string | undefined
2008
+ }
2009
+ | undefined
2010
+ ): string => new Intl.DateTimeFormat(options?.locale, options).format(self.epochMillis))
2011
+
2012
+ /**
2013
+ * Format a `DateTime` as a string using the `DateTimeFormat` API.
2014
+ *
2015
+ * This forces the time zone to be UTC.
2016
+ *
2017
+ * @since 3.6.0
2018
+ * @category formatting
2019
+ */
2020
+ export const formatUtc: {
2021
+ (
2022
+ options?:
2023
+ | Intl.DateTimeFormatOptions & {
2024
+ readonly locale?: string | undefined
2025
+ }
2026
+ | undefined
2027
+ ): (self: DateTime) => string
2028
+ (
2029
+ self: DateTime,
2030
+ options?:
2031
+ | Intl.DateTimeFormatOptions & {
2032
+ readonly locale?: string | undefined
2033
+ }
2034
+ | undefined
2035
+ ): string
2036
+ } = dual(isDateTimeArgs, (
2037
+ self: DateTime,
2038
+ options?:
2039
+ | Intl.DateTimeFormatOptions & {
2040
+ readonly locale?: string | undefined
2041
+ }
2042
+ | undefined
2043
+ ): string =>
2044
+ new Intl.DateTimeFormat(options?.locale, {
2045
+ ...options,
2046
+ timeZone: "UTC"
2047
+ }).format(self.epochMillis))
2048
+
2049
+ /**
2050
+ * Format a `DateTime` as a string using the `DateTimeFormat` API.
2051
+ *
2052
+ * @since 3.6.0
2053
+ * @category formatting
2054
+ */
2055
+ export const formatIntl: {
2056
+ (format: Intl.DateTimeFormat): (self: DateTime) => string
2057
+ (self: DateTime, format: Intl.DateTimeFormat): string
2058
+ } = dual(2, (self: DateTime, format: Intl.DateTimeFormat): string => format.format(self.epochMillis))
2059
+
2060
+ /**
2061
+ * Format a `DateTime` as a UTC ISO string.
2062
+ *
2063
+ * @since 3.6.0
2064
+ * @category formatting
2065
+ */
2066
+ export const formatIso = (self: DateTime): string => toDateUtc(self).toISOString()
2067
+
2068
+ /**
2069
+ * Format a `DateTime` as a time zone adjusted ISO date string.
2070
+ *
2071
+ * @since 3.6.0
2072
+ * @category formatting
2073
+ */
2074
+ export const formatIsoDate = (self: DateTime): string => toDate(self).toISOString().slice(0, 10)
2075
+
2076
+ /**
2077
+ * Format a `DateTime` as a UTC ISO date string.
2078
+ *
2079
+ * @since 3.6.0
2080
+ * @category formatting
2081
+ */
2082
+ export const formatIsoDateUtc = (self: DateTime): string => toDateUtc(self).toISOString().slice(0, 10)
2083
+
2084
+ /**
2085
+ * Format a `DateTime.Zoned` as a ISO string with an offset.
2086
+ *
2087
+ * @since 3.6.0
2088
+ * @category formatting
2089
+ */
2090
+ export const formatIsoOffset = (self: DateTime): string => {
2091
+ const date = toDate(self)
2092
+ return self._tag === "Utc" ? date.toISOString() : `${date.toISOString().slice(0, -1)}${zonedOffsetIso(self)}`
2093
+ }
2094
+
2095
+ /**
2096
+ * Format a `DateTime.Zoned` as a string.
2097
+ *
2098
+ * It uses the format: `YYYY-MM-DDTHH:mm:ss.sss+HH:MM[Time/Zone]`.
2099
+ *
2100
+ * @since 3.6.0
2101
+ * @category formatting
2102
+ */
2103
+ export const formatIsoZoned = (self: Zoned): string =>
2104
+ self.zone._tag === "Offset" ? formatIsoOffset(self) : `${formatIsoOffset(self)}[${self.zone.id}]`