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.
- package/DateTime/package.json +6 -0
- package/dist/cjs/Context.js.map +1 -1
- package/dist/cjs/DateTime.js +1514 -0
- package/dist/cjs/DateTime.js.map +1 -0
- package/dist/cjs/Effect.js +3 -3
- package/dist/cjs/Effect.js.map +1 -1
- package/dist/cjs/Either.js +3 -3
- package/dist/cjs/List.js.map +1 -1
- package/dist/cjs/Metric.js.map +1 -1
- package/dist/cjs/Micro.js +93 -41
- package/dist/cjs/Micro.js.map +1 -1
- package/dist/cjs/Option.js +3 -3
- package/dist/cjs/Predicate.js +8 -0
- package/dist/cjs/Predicate.js.map +1 -1
- package/dist/cjs/Random.js +16 -1
- package/dist/cjs/Random.js.map +1 -1
- package/dist/cjs/Stream.js +86 -7
- package/dist/cjs/Stream.js.map +1 -1
- package/dist/cjs/Struct.js +23 -1
- package/dist/cjs/Struct.js.map +1 -1
- package/dist/cjs/index.js +4 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/internal/configProvider.js.map +1 -1
- package/dist/cjs/internal/core.js +1 -1
- package/dist/cjs/internal/core.js.map +1 -1
- package/dist/cjs/internal/defaultServices.js +9 -2
- package/dist/cjs/internal/defaultServices.js.map +1 -1
- package/dist/cjs/internal/metric.js.map +1 -1
- package/dist/cjs/internal/stream/emit.js +73 -1
- package/dist/cjs/internal/stream/emit.js.map +1 -1
- package/dist/cjs/internal/stream.js +30 -22
- package/dist/cjs/internal/stream.js.map +1 -1
- package/dist/cjs/internal/version.js +1 -1
- package/dist/dts/ConfigProvider.d.ts +2 -2
- package/dist/dts/ConfigProvider.d.ts.map +1 -1
- package/dist/dts/Context.d.ts +3 -1
- package/dist/dts/Context.d.ts.map +1 -1
- package/dist/dts/DateTime.d.ts +1265 -0
- package/dist/dts/DateTime.d.ts.map +1 -0
- package/dist/dts/Effect.d.ts +18 -4
- package/dist/dts/Effect.d.ts.map +1 -1
- package/dist/dts/Either.d.ts +4 -4
- package/dist/dts/List.d.ts +2 -1
- package/dist/dts/List.d.ts.map +1 -1
- package/dist/dts/Metric.d.ts +1 -1
- package/dist/dts/Metric.d.ts.map +1 -1
- package/dist/dts/MetricRegistry.d.ts +1 -1
- package/dist/dts/MetricRegistry.d.ts.map +1 -1
- package/dist/dts/Micro.d.ts +60 -0
- package/dist/dts/Micro.d.ts.map +1 -1
- package/dist/dts/Option.d.ts +4 -4
- package/dist/dts/Predicate.d.ts +63 -2
- package/dist/dts/Predicate.d.ts.map +1 -1
- package/dist/dts/Random.d.ts +18 -0
- package/dist/dts/Random.d.ts.map +1 -1
- package/dist/dts/Stream.d.ts +95 -4
- package/dist/dts/Stream.d.ts.map +1 -1
- package/dist/dts/StreamEmit.d.ts +44 -0
- package/dist/dts/StreamEmit.d.ts.map +1 -1
- package/dist/dts/Struct.d.ts +21 -0
- package/dist/dts/Struct.d.ts.map +1 -1
- package/dist/dts/index.d.ts +4 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/internal/core.d.ts.map +1 -1
- package/dist/dts/internal/defaultServices.d.ts.map +1 -1
- package/dist/dts/internal/stream.d.ts.map +1 -1
- package/dist/esm/Context.js.map +1 -1
- package/dist/esm/DateTime.js +1465 -0
- package/dist/esm/DateTime.js.map +1 -0
- package/dist/esm/Effect.js +4 -4
- package/dist/esm/Effect.js.map +1 -1
- package/dist/esm/Either.js +4 -4
- package/dist/esm/List.js.map +1 -1
- package/dist/esm/Metric.js.map +1 -1
- package/dist/esm/Micro.js +88 -38
- package/dist/esm/Micro.js.map +1 -1
- package/dist/esm/Option.js +4 -4
- package/dist/esm/Predicate.js +8 -0
- package/dist/esm/Predicate.js.map +1 -1
- package/dist/esm/Random.js +15 -0
- package/dist/esm/Random.js.map +1 -1
- package/dist/esm/Stream.js +84 -5
- package/dist/esm/Stream.js.map +1 -1
- package/dist/esm/Struct.js +21 -0
- package/dist/esm/Struct.js.map +1 -1
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal/configProvider.js.map +1 -1
- package/dist/esm/internal/core.js +1 -1
- package/dist/esm/internal/core.js.map +1 -1
- package/dist/esm/internal/defaultServices.js +6 -0
- package/dist/esm/internal/defaultServices.js.map +1 -1
- package/dist/esm/internal/metric.js.map +1 -1
- package/dist/esm/internal/stream/emit.js +71 -0
- package/dist/esm/internal/stream/emit.js.map +1 -1
- package/dist/esm/internal/stream.js +24 -18
- package/dist/esm/internal/stream.js.map +1 -1
- package/dist/esm/internal/version.js +1 -1
- package/package.json +9 -1
- package/src/ConfigProvider.ts +2 -2
- package/src/Context.ts +3 -1
- package/src/DateTime.ts +2104 -0
- package/src/Effect.ts +28 -4
- package/src/Either.ts +4 -4
- package/src/List.ts +3 -2
- package/src/Metric.ts +1 -1
- package/src/MetricRegistry.ts +1 -1
- package/src/Micro.ts +117 -45
- package/src/Option.ts +4 -4
- package/src/Predicate.ts +68 -8
- package/src/Random.ts +24 -0
- package/src/Stream.ts +110 -5
- package/src/StreamEmit.ts +53 -0
- package/src/Struct.ts +22 -0
- package/src/index.ts +5 -0
- package/src/internal/configProvider.ts +20 -20
- package/src/internal/core.ts +37 -12
- package/src/internal/defaultServices.ts +14 -0
- package/src/internal/metric/registry.ts +1 -1
- package/src/internal/metric.ts +2 -2
- package/src/internal/stream/emit.ts +77 -0
- package/src/internal/stream.ts +86 -18
- package/src/internal/version.ts +1 -1
package/src/DateTime.ts
ADDED
|
@@ -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}]`
|