1import { IllegalArgumentError } from "../Cause.ts"
2import * as Clock from "../Clock.ts"
3import type * as DateTime from "../DateTime.ts"
4import * as Duration from "../Duration.ts"
5import type * as Effect from "../Effect.ts"
6import * as Equal from "../Equal.ts"
7import * as Equ from "../Equivalence.ts"
8import type { LazyArg } from "../Function.ts"
9import { dual } from "../Function.ts"
10import * as Hash from "../Hash.ts"
11import * as Inspectable from "../Inspectable.ts"
12import * as Option from "../Option.ts"
13import * as order from "../Order.ts"
14import { pipeArguments } from "../Pipeable.ts"
15import * as Predicate from "../Predicate.ts"
16import type { Mutable } from "../Types.ts"
17import * as effect from "./effect.ts"
18
19/** @internal */
20export const TypeId = "~effect/DateTime"
21
22/** @internal */
23export const TimeZoneTypeId = "~effect/DateTime/TimeZone"
24
25const Proto = {
26 [TypeId]: TypeId,
27 pipe() {
28 return pipeArguments(this, arguments)
29 },
30 [Inspectable.NodeInspectSymbol](this: DateTime.DateTime) {
31 return this.toString()
32 },
33 toJSON(this: DateTime.DateTime) {
34 return toDateUtc(this).toJSON()
35 }
36}
37
38const ProtoUtc = {
39 ...Proto,
40 _tag: "Utc",
41 [Hash.symbol](this: DateTime.Utc) {
42 return Hash.number(this.epochMilliseconds)
43 },
44 [Equal.symbol](this: DateTime.Utc, that: unknown) {
45 return isDateTime(that) && that._tag === "Utc" && this.epochMilliseconds === that.epochMilliseconds
46 },
47 toString(this: DateTime.Utc) {
48 return `DateTime.Utc(${toDateUtc(this).toJSON()})`
49 }
50}
51
52const ProtoZoned = {
53 ...Proto,
54 _tag: "Zoned",
55 [Hash.symbol](this: DateTime.Zoned) {
56 return Hash.combine(Hash.number(this.epochMilliseconds))(Hash.hash(this.zone))
57 },
58 [Equal.symbol](this: DateTime.Zoned, that: unknown) {
59 return isDateTime(that) && that._tag === "Zoned" && this.epochMilliseconds === that.epochMilliseconds &&
60 Equal.equals(this.zone, that.zone)
61 },
62 toString(this: DateTime.Zoned) {
63 return `DateTime.Zoned(${formatIsoZoned(this)})`
64 }
65}
66
67const ProtoTimeZone = {
68 [TimeZoneTypeId]: TimeZoneTypeId,
69 [Inspectable.NodeInspectSymbol](this: DateTime.TimeZone) {
70 return this.toString()
71 }
72}
73
74const ProtoTimeZoneNamed = {
75 ...ProtoTimeZone,
76 _tag: "Named",
77 [Hash.symbol](this: DateTime.TimeZone.Named) {
78 return Hash.string(`Named:${this.id}`)
79 },
80 [Equal.symbol](this: DateTime.TimeZone.Named, that: unknown) {
81 return isTimeZone(that) && that._tag === "Named" && this.id === that.id
82 },
83 toString(this: DateTime.TimeZone.Named) {
84 return `TimeZone.Named(${this.id})`
85 },
86 toJSON(this: DateTime.TimeZone.Named) {
87 return {
88 _id: "TimeZone",
89 _tag: "Named",
90 id: this.id
91 }
92 }
93}
94
95const ProtoTimeZoneOffset = {
96 ...ProtoTimeZone,
97 _tag: "Offset",
98 [Hash.symbol](this: DateTime.TimeZone.Offset) {
99 return Hash.string(`Offset:${this.offset}`)
100 },
101 [Equal.symbol](this: DateTime.TimeZone.Offset, that: unknown) {
102 return isTimeZone(that) && that._tag === "Offset" && this.offset === that.offset
103 },
104 toString(this: DateTime.TimeZone.Offset) {
105 return `TimeZone.Offset(${offsetToString(this.offset)})`
106 },
107 toJSON(this: DateTime.TimeZone.Offset) {
108 return {
109 _id: "TimeZone",
110 _tag: "Offset",
111 offset: this.offset
112 }
113 }
114}
115
116/** @internal */
117export const makeZonedProto = (
118 epochMillis: number,
119 zone: DateTime.TimeZone,
120 partsUtc?: DateTime.DateTime.PartsWithWeekday
121): DateTime.Zoned => {
122 const self = Object.create(ProtoZoned)
123 self.epochMilliseconds = epochMillis
124 self.zone = zone
125 Object.defineProperty(self, "partsUtc", {
126 value: partsUtc,
127 enumerable: false,
128 writable: true
129 })
130 Object.defineProperty(self, "adjustedEpochMillis", {
131 value: undefined,
132 enumerable: false,
133 writable: true
134 })
135 Object.defineProperty(self, "partsAdjusted", {
136 value: undefined,
137 enumerable: false,
138 writable: true
139 })
140 return self
141}
142
143// =============================================================================
144// guards
145// =============================================================================
146
147/** @internal */
148export const isDateTime = (u: unknown): u is DateTime.DateTime => Predicate.hasProperty(u, TypeId)
149
150const isDateTimeArgs = (args: IArguments) => isDateTime(args[0])
151
152/** @internal */
153export const isTimeZone = (u: unknown): u is DateTime.TimeZone => Predicate.hasProperty(u, TimeZoneTypeId)
154
155/** @internal */
156export const isTimeZoneOffset = (u: unknown): u is DateTime.TimeZone.Offset => isTimeZone(u) && u._tag === "Offset"
157
158/** @internal */
159export const isTimeZoneNamed = (u: unknown): u is DateTime.TimeZone.Named => isTimeZone(u) && u._tag === "Named"
160
161/** @internal */
162export const isUtc = (self: DateTime.DateTime): self is DateTime.Utc => self._tag === "Utc"
163
164/** @internal */
165export const isZoned = (self: DateTime.DateTime): self is DateTime.Zoned => self._tag === "Zoned"
166
167// =============================================================================
168// instances
169// =============================================================================
170
171/** @internal */
172export const Equivalence: Equ.Equivalence<DateTime.DateTime> = Equ.make((a, b) =>
173 a.epochMilliseconds === b.epochMilliseconds
174)
175
176/** @internal */
177export const Order: order.Order<DateTime.DateTime> = order.make((self, that) =>
178 self.epochMilliseconds < that.epochMilliseconds ? -1 : self.epochMilliseconds > that.epochMilliseconds ? 1 : 0
179)
180
181/** @internal */
182export const clamp: {
183 <Min extends DateTime.DateTime, Max extends DateTime.DateTime>(
184 options: { readonly minimum: Min; readonly maximum: Max }
185 ): <A extends DateTime.DateTime>(self: A) => A | Min | Max
186 <A extends DateTime.DateTime, Min extends DateTime.DateTime, Max extends DateTime.DateTime>(
187 self: A,
188 options: { readonly minimum: Min; readonly maximum: Max }
189 ): A | Min | Max
190} = order.clamp(Order)
191
192// =============================================================================
193// constructors
194// =============================================================================
195
196const makeUtc = (epochMillis: number): DateTime.Utc => {
197 const self = Object.create(ProtoUtc)
198 self.epochMilliseconds = epochMillis
199 Object.defineProperty(self, "partsUtc", {
200 value: undefined,
201 enumerable: false,
202 writable: true
203 })
204 return self
205}
206
207/** @internal */
208export const fromDateUnsafe = (date: Date): DateTime.Utc => {
209 const epochMillis = date.getTime()
210 if (Number.isNaN(epochMillis)) {
211 throw new IllegalArgumentError("Invalid date")
212 }
213 return makeUtc(epochMillis)
214}
215
216/** @internal */
217export const makeUnsafe = <A extends DateTime.DateTime.Input>(input: A): DateTime.DateTime.PreserveZone<A> => {
218 if (isDateTime(input)) {
219 return input as DateTime.DateTime.PreserveZone<A>
220 } else if (input instanceof Date) {
221 return fromDateUnsafe(input) as DateTime.DateTime.PreserveZone<A>
222 } else if (typeof input === "object") {
223 if ("epochMilliseconds" in input) {
224 return fromDateUnsafe(new Date(input.epochMilliseconds)) as DateTime.DateTime.PreserveZone<A>
225 }
226 const date = new Date(0)
227 setPartsDate(date, input)
228 return fromDateUnsafe(date) as DateTime.DateTime.PreserveZone<A>
229 } else if (typeof input === "string" && !hasZone(input)) {
230 return fromDateUnsafe(new Date(input + "Z")) as DateTime.DateTime.PreserveZone<A>
231 }
232 return fromDateUnsafe(new Date(input)) as DateTime.DateTime.PreserveZone<A>
233}
234
235/**
236 * Detects whether a date string already contains timezone info.
237 * Without a zone, `new Date("2024-01-01T12:00:00")` is parsed as local time,
238 * so `makeUnsafe` appends "Z" to force UTC interpretation.
239 * This check prevents appending "Z" to strings that already have a zone
240 * (e.g. "2024-01-01T12:00:00Z", "...+05:30", "...GMT"), which would produce invalid dates.
241 */
242const hasZone = (input: string): boolean => /Z|GMT|[+-]\d{2}$|[+-]\d{2}:?\d{2}$|\]$/.test(input)
243
244const minEpochMillis = -8640000000000000 + (12 * 60 * 60 * 1000)
245const maxEpochMillis = 8640000000000000 - (14 * 60 * 60 * 1000)
246
247/** @internal */
248export const makeZonedUnsafe = (input: DateTime.DateTime.Input, options?: {
249 readonly timeZone?: number | string | DateTime.TimeZone | undefined
250 readonly adjustForTimeZone?: boolean | undefined
251 readonly disambiguation?: DateTime.Disambiguation | undefined
252}): DateTime.Zoned => {
253 let timeZoneOption = options?.timeZone
254 if (timeZoneOption === undefined && isDateTime(input) && isZoned(input)) {
255 return input
256 }
257 const self = makeUnsafe(input)
258 if (self.epochMilliseconds < minEpochMillis || self.epochMilliseconds > maxEpochMillis) {
259 throw new RangeError(`Epoch millis out of range: ${self.epochMilliseconds}`)
260 }
261 if (timeZoneOption === undefined && typeof input === "object" && "timeZoneId" in input) {
262 timeZoneOption = input.timeZoneId
263 }
264 let zone: DateTime.TimeZone
265 if (timeZoneOption === undefined) {
266 const offset = new Date(self.epochMilliseconds).getTimezoneOffset() * -60 * 1000
267 zone = zoneMakeOffset(offset)
268 } else if (isTimeZone(timeZoneOption)) {
269 zone = timeZoneOption
270 } else if (typeof timeZoneOption === "number") {
271 zone = zoneMakeOffset(timeZoneOption)
272 } else {
273 const parsedZone = zoneFromString(timeZoneOption)
274 if (Option.isNone(parsedZone)) {
275 throw new IllegalArgumentError(`Invalid time zone: ${timeZoneOption}`)
276 }
277 zone = parsedZone.value
278 }
279 if (options?.adjustForTimeZone !== true) {
280 return makeZonedProto(self.epochMilliseconds, zone, self.partsUtc)
281 }
282 return makeZonedFromAdjusted(self.epochMilliseconds, zone, options?.disambiguation ?? "compatible")
283}
284
285/** @internal */
286export const makeZoned: (
287 input: DateTime.DateTime.Input,
288 options?: {
289 readonly timeZone?: number | string | DateTime.TimeZone | undefined
290 readonly adjustForTimeZone?: boolean | undefined
291 readonly disambiguation?: DateTime.Disambiguation | undefined
292 }
293) => Option.Option<DateTime.Zoned> = Option.liftThrowable(makeZonedUnsafe)
294
295/** @internal */
296export const make: <A extends DateTime.DateTime.Input>(input: A) => Option.Option<DateTime.DateTime.PreserveZone<A>> =
297 Option.liftThrowable(makeUnsafe)
298
299const zonedStringRegExp = /^(.{17,35})\[(.+)\]$/
300
301/** @internal */
302export const makeZonedFromString = (input: string): Option.Option<DateTime.Zoned> => {
303 const match = zonedStringRegExp.exec(input)
304 if (match === null) {
305 const offset = parseOffset(input)
306 return offset !== null ? makeZoned(input, { timeZone: offset }) : Option.none()
307 }
308 const [, isoString, timeZone] = match
309 return makeZoned(isoString, { timeZone })
310}
311
312/** @internal */
313export const now: Effect.Effect<DateTime.Utc> = effect.map(Clock.currentTimeMillis, makeUtc)
314
315/** @internal */
316export const nowAsDate: Effect.Effect<Date> = effect.map(Clock.currentTimeMillis, (millis) => new Date(millis))
317
318/** @internal */
319export const nowUnsafe: LazyArg<DateTime.Utc> = () => makeUtc(Date.now())
320
321// =============================================================================
322// time zones
323// =============================================================================
324
325/** @internal */
326export const toUtc = (self: DateTime.DateTime): DateTime.Utc => makeUtc(self.epochMilliseconds)
327
328/** @internal */
329export const setZone: {
330 (zone: DateTime.TimeZone, options?: {
331 readonly adjustForTimeZone?: boolean | undefined
332 readonly disambiguation?: DateTime.Disambiguation | undefined
333 }): (self: DateTime.DateTime) => DateTime.Zoned
334 (self: DateTime.DateTime, zone: DateTime.TimeZone, options?: {
335 readonly adjustForTimeZone?: boolean | undefined
336 readonly disambiguation?: DateTime.Disambiguation | undefined
337 }): DateTime.Zoned
338} = dual(isDateTimeArgs, (self: DateTime.DateTime, zone: DateTime.TimeZone, options?: {
339 readonly adjustForTimeZone?: boolean | undefined
340 readonly disambiguation?: DateTime.Disambiguation | undefined
341}): DateTime.Zoned =>
342 options?.adjustForTimeZone === true
343 ? makeZonedFromAdjusted(self.epochMilliseconds, zone, options?.disambiguation ?? "compatible")
344 : makeZonedProto(self.epochMilliseconds, zone, self.partsUtc))
345
346/** @internal */
347export const setZoneOffset: {
348 (offset: number, options?: {
349 readonly adjustForTimeZone?: boolean | undefined
350 readonly disambiguation?: DateTime.Disambiguation | undefined
351 }): (self: DateTime.DateTime) => DateTime.Zoned
352 (self: DateTime.DateTime, offset: number, options?: {
353 readonly adjustForTimeZone?: boolean | undefined
354 readonly disambiguation?: DateTime.Disambiguation | undefined
355 }): DateTime.Zoned
356} = dual(isDateTimeArgs, (self: DateTime.DateTime, offset: number, options?: {
357 readonly adjustForTimeZone?: boolean | undefined
358 readonly disambiguation?: DateTime.Disambiguation | undefined
359}): DateTime.Zoned => setZone(self, zoneMakeOffset(offset), options))
360
361const validZoneCache = new Map<string, DateTime.TimeZone.Named>()
362
363const formatOptions: Intl.DateTimeFormatOptions = {
364 day: "numeric",
365 month: "numeric",
366 year: "numeric",
367 hour: "numeric",
368 minute: "numeric",
369 second: "numeric",
370 timeZoneName: "longOffset",
371 fractionalSecondDigits: 3,
372 hourCycle: "h23"
373}
374
375const zoneMakeIntl = (format: Intl.DateTimeFormat): DateTime.TimeZone.Named => {
376 const zoneId = format.resolvedOptions().timeZone
377 const cached = validZoneCache.get(zoneId)
378 if (cached !== undefined) {
379 return cached
380 }
381 const zone = Object.create(ProtoTimeZoneNamed)
382 zone.id = zoneId
383 zone.format = format
384 validZoneCache.set(zoneId, zone)
385 return zone
386}
387
388/** @internal */
389export const zoneMakeNamedUnsafe = (zoneId: string): DateTime.TimeZone.Named => {
390 const cached = validZoneCache.get(zoneId)
391 if (cached !== undefined) {
392 return cached
393 }
394 try {
395 const zone = zoneMakeIntl(
396 new Intl.DateTimeFormat("en-US", {
397 ...formatOptions,
398 timeZone: zoneId
399 })
400 )
401 validZoneCache.set(zoneId, zone)
402 return zone
403 } catch {
404 throw new IllegalArgumentError(`Invalid time zone: ${zoneId}`)
405 }
406}
407
408/** @internal */
409export const zoneMakeOffset = (offset: number): DateTime.TimeZone.Offset => {
410 const zone = Object.create(ProtoTimeZoneOffset)
411 zone.offset = offset
412 return zone
413}
414
415/** @internal */
416export const zoneMakeNamed: (zoneId: string) => Option.Option<DateTime.TimeZone.Named> = Option.liftThrowable(
417 zoneMakeNamedUnsafe
418)
419
420/** @internal */
421export const zoneMakeNamedEffect = (zoneId: string): Effect.Effect<DateTime.TimeZone.Named, IllegalArgumentError> =>
422 effect.try({
423 try: () => zoneMakeNamedUnsafe(zoneId),
424 catch: (e) => e as IllegalArgumentError
425 })
426
427/** @internal */
428export const zoneMakeLocal = (): DateTime.TimeZone.Named =>
429 zoneMakeIntl(new Intl.DateTimeFormat("en-US", formatOptions))
430
431const offsetZoneRegExp = /^(?:GMT|[+-])/
432
433/** @internal */
434export const zoneFromString = (zone: string): Option.Option<DateTime.TimeZone> => {
435 if (offsetZoneRegExp.test(zone)) {
436 const offset = parseOffset(zone)
437 return offset === null ? Option.none() : Option.some(zoneMakeOffset(offset))
438 }
439 return zoneMakeNamed(zone)
440}
441
442/** @internal */
443export const zoneToString = (self: DateTime.TimeZone): string => {
444 if (self._tag === "Offset") {
445 return offsetToString(self.offset)
446 }
447 return self.id
448}
449
450/** @internal */
451export const setZoneNamed: {
452 (zoneId: string, options?: {
453 readonly adjustForTimeZone?: boolean | undefined
454 readonly disambiguation?: DateTime.Disambiguation | undefined
455 }): (self: DateTime.DateTime) => Option.Option<DateTime.Zoned>
456 (self: DateTime.DateTime, zoneId: string, options?: {
457 readonly adjustForTimeZone?: boolean | undefined
458 readonly disambiguation?: DateTime.Disambiguation | undefined
459 }): Option.Option<DateTime.Zoned>
460} = dual(
461 isDateTimeArgs,
462 (self: DateTime.DateTime, zoneId: string, options?: {
463 readonly adjustForTimeZone?: boolean | undefined
464 readonly disambiguation?: DateTime.Disambiguation | undefined
465 }): Option.Option<DateTime.Zoned> => Option.map(zoneMakeNamed(zoneId), (zone) => setZone(self, zone, options))
466)
467
468/** @internal */
469export const setZoneNamedUnsafe: {
470 (zoneId: string, options?: {
471 readonly adjustForTimeZone?: boolean | undefined
472 readonly disambiguation?: DateTime.Disambiguation | undefined
473 }): (self: DateTime.DateTime) => DateTime.Zoned
474 (self: DateTime.DateTime, zoneId: string, options?: {
475 readonly adjustForTimeZone?: boolean | undefined
476 readonly disambiguation?: DateTime.Disambiguation | undefined
477 }): DateTime.Zoned
478} = dual(isDateTimeArgs, (self: DateTime.DateTime, zoneId: string, options?: {
479 readonly adjustForTimeZone?: boolean | undefined
480 readonly disambiguation?: DateTime.Disambiguation | undefined
481}): DateTime.Zoned => setZone(self, zoneMakeNamedUnsafe(zoneId), options))
482
483// =============================================================================
484// comparisons
485// =============================================================================
486
487/** @internal */
488export const distance: {
489 (other: DateTime.DateTime): (self: DateTime.DateTime) => Duration.Duration
490 (self: DateTime.DateTime, other: DateTime.DateTime): Duration.Duration
491} = dual(
492 2,
493 (self: DateTime.DateTime, other: DateTime.DateTime): Duration.Duration =>
494 Duration.millis(toEpochMillis(other) - toEpochMillis(self))
495)
496
497/** @internal */
498export const min: {
499 <That extends DateTime.DateTime>(that: That): <Self extends DateTime.DateTime>(self: Self) => Self | That
500 <Self extends DateTime.DateTime, That extends DateTime.DateTime>(self: Self, that: That): Self | That
501} = order.min(Order)
502
503/** @internal */
504export const max: {
505 <That extends DateTime.DateTime>(that: That): <Self extends DateTime.DateTime>(self: Self) => Self | That
506 <Self extends DateTime.DateTime, That extends DateTime.DateTime>(self: Self, that: That): Self | That
507} = order.max(Order)
508
509/** @internal */
510export const isGreaterThan: {
511 (that: DateTime.DateTime): (self: DateTime.DateTime) => boolean
512 (self: DateTime.DateTime, that: DateTime.DateTime): boolean
513} = order.isGreaterThan(Order)
514
515/** @internal */
516export const isGreaterThanOrEqualTo: {
517 (that: DateTime.DateTime): (self: DateTime.DateTime) => boolean
518 (self: DateTime.DateTime, that: DateTime.DateTime): boolean
519} = order.isGreaterThanOrEqualTo(Order)
520
521/** @internal */
522export const isLessThan: {
523 (that: DateTime.DateTime): (self: DateTime.DateTime) => boolean
524 (self: DateTime.DateTime, that: DateTime.DateTime): boolean
525} = order.isLessThan(Order)
526
527/** @internal */
528export const isLessThanOrEqualTo: {
529 (that: DateTime.DateTime): (self: DateTime.DateTime) => boolean
530 (self: DateTime.DateTime, that: DateTime.DateTime): boolean
531} = order.isLessThanOrEqualTo(Order)
532
533/** @internal */
534export const between: {
535 (options: { minimum: DateTime.DateTime; maximum: DateTime.DateTime }): (self: DateTime.DateTime) => boolean
536 (self: DateTime.DateTime, options: { minimum: DateTime.DateTime; maximum: DateTime.DateTime }): boolean
537} = order.isBetween(Order)
538
539/** @internal */
540export const isFuture = (self: DateTime.DateTime): Effect.Effect<boolean> => effect.map(now, isLessThan(self))
541
542/** @internal */
543export const isFutureUnsafe = (self: DateTime.DateTime): boolean => isLessThan(nowUnsafe(), self)
544
545/** @internal */
546export const isPast = (self: DateTime.DateTime): Effect.Effect<boolean> => effect.map(now, isGreaterThan(self))
547
548/** @internal */
549export const isPastUnsafe = (self: DateTime.DateTime): boolean => isGreaterThan(nowUnsafe(), self)
550
551// =============================================================================
552// conversions
553// =============================================================================
554
555/** @internal */
556export const toDateUtc = (self: DateTime.DateTime): Date => new Date(self.epochMilliseconds)
557
558/** @internal */
559export const toDate = (self: DateTime.DateTime): Date => {
560 if (self._tag === "Utc") {
561 return new Date(self.epochMilliseconds)
562 } else if (self.zone._tag === "Offset") {
563 return new Date(self.epochMilliseconds + self.zone.offset)
564 } else if (self.adjustedEpochMilliseconds !== undefined) {
565 return new Date(self.adjustedEpochMilliseconds)
566 }
567 const parts = self.zone.format.formatToParts(self.epochMilliseconds).filter((_) => _.type !== "literal")
568 const date = new Date(0)
569 date.setUTCFullYear(
570 Number(parts[2].value),
571 Number(parts[0].value) - 1,
572 Number(parts[1].value)
573 )
574 date.setUTCHours(
575 Number(parts[3].value),
576 Number(parts[4].value),
577 Number(parts[5].value),
578 parts[6]?.type === "fractionalSecond"
579 ? Number(parts[6].value)
580 : ((self.epochMilliseconds % 1000) + 1000) % 1000
581 )
582 self.adjustedEpochMilliseconds = date.getTime()
583 return date
584}
585
586/** @internal */
587export const zonedOffset = (self: DateTime.Zoned): number => {
588 const date = toDate(self)
589 return date.getTime() - toEpochMillis(self)
590}
591
592const offsetToString = (offset: number): string => {
593 const abs = Math.abs(offset)
594 let hours = Math.floor(abs / (60 * 60 * 1000))
595 let minutes = Math.round((abs % (60 * 60 * 1000)) / (60 * 1000))
596 if (minutes === 60) {
597 hours += 1
598 minutes = 0
599 }
600 return `${offset < 0 ? "-" : "+"}${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}`
601}
602
603/** @internal */
604export const zonedOffsetIso = (self: DateTime.Zoned): string => offsetToString(zonedOffset(self))
605
606/** @internal */
607export const toEpochMillis = (self: DateTime.DateTime): number => self.epochMilliseconds
608
609/** @internal */
610export const toEpochSeconds = (self: DateTime.DateTime): number => Math.floor(self.epochMilliseconds / 1000)
611
612/** @internal */
613export const fromEpochSeconds = (seconds: number): DateTime.Utc => makeUtc(seconds * 1000)
614
615/** @internal */
616export const removeTime = (self: DateTime.DateTime): DateTime.Utc =>
617 withDate(self, (date) => {
618 date.setUTCHours(0, 0, 0, 0)
619 return makeUtc(date.getTime())
620 })
621
622// =============================================================================
623// parts
624// =============================================================================
625
626const dateToParts = (date: Date): DateTime.DateTime.PartsWithWeekday => ({
627 millisecond: date.getUTCMilliseconds(),
628 second: date.getUTCSeconds(),
629 minute: date.getUTCMinutes(),
630 hour: date.getUTCHours(),
631 day: date.getUTCDate(),
632 weekDay: date.getUTCDay(),
633 month: date.getUTCMonth() + 1,
634 year: date.getUTCFullYear()
635})
636
637/** @internal */
638export const toParts = (self: DateTime.DateTime): DateTime.DateTime.PartsWithWeekday => {
639 if (self._tag === "Utc") {
640 return toPartsUtc(self)
641 } else if (self.partsAdjusted !== undefined) {
642 return self.partsAdjusted
643 }
644 self.partsAdjusted = withDate(self, dateToParts)
645 return self.partsAdjusted
646}
647
648/** @internal */
649export const toPartsUtc = (self: DateTime.DateTime): DateTime.DateTime.PartsWithWeekday => {
650 if (self.partsUtc !== undefined) {
651 return self.partsUtc
652 }
653 self.partsUtc = withDateUtc(self, dateToParts)
654 return self.partsUtc
655}
656
657/** @internal */
658export const getPartUtc: {
659 (part: keyof DateTime.DateTime.PartsWithWeekday): (self: DateTime.DateTime) => number
660 (self: DateTime.DateTime, part: keyof DateTime.DateTime.PartsWithWeekday): number
661} = dual(2, (self: DateTime.DateTime, part: keyof DateTime.DateTime.PartsWithWeekday): number => toPartsUtc(self)[part])
662
663/** @internal */
664export const getPart: {
665 (part: keyof DateTime.DateTime.PartsWithWeekday): (self: DateTime.DateTime) => number
666 (self: DateTime.DateTime, part: keyof DateTime.DateTime.PartsWithWeekday): number
667} = dual(2, (self: DateTime.DateTime, part: keyof DateTime.DateTime.PartsWithWeekday): number => toParts(self)[part])
668
669const setPartsDate = (date: Date, parts: Partial<DateTime.DateTime.PartsWithWeekday>): void => {
670 if (parts.year !== undefined || parts.month !== undefined || parts.day !== undefined) {
671 date.setUTCFullYear(
672 parts.year ?? date.getUTCFullYear(),
673 parts.month !== undefined ? parts.month - 1 : date.getUTCMonth(),
674 parts.day ?? date.getUTCDate()
675 )
676 }
677 if (parts.weekDay !== undefined) {
678 const diff = parts.weekDay - date.getUTCDay()
679 date.setUTCDate(date.getUTCDate() + diff)
680 }
681 if (parts.hour !== undefined) {
682 date.setUTCHours(parts.hour)
683 }
684 if (parts.minute !== undefined) {
685 date.setUTCMinutes(parts.minute)
686 }
687 if (parts.second !== undefined) {
688 date.setUTCSeconds(parts.second)
689 }
690 if (parts.millisecond !== undefined) {
691 date.setUTCMilliseconds(parts.millisecond)
692 }
693}
694
695/** @internal */
696export const setParts: {
697 (
698 parts: Partial<DateTime.DateTime.PartsWithWeekday>
699 ): <A extends DateTime.DateTime>(self: A) => A
700 <A extends DateTime.DateTime>(
701 self: A,
702 parts: Partial<DateTime.DateTime.PartsWithWeekday>
703 ): A
704} = dual(
705 2,
706 (self: DateTime.DateTime, parts: Partial<DateTime.DateTime.PartsWithWeekday>): DateTime.DateTime =>
707 mutate(self, (date) => setPartsDate(date, parts))
708)
709
710/** @internal */
711export const setPartsUtc: {
712 (
713 parts: Partial<DateTime.DateTime.PartsWithWeekday>
714 ): <A extends DateTime.DateTime>(self: A) => A
715 <A extends DateTime.DateTime>(
716 self: A,
717 parts: Partial<DateTime.DateTime.PartsWithWeekday>
718 ): A
719} = dual(
720 2,
721 (self: DateTime.DateTime, parts: Partial<DateTime.DateTime.PartsWithWeekday>): DateTime.DateTime =>
722 mutateUtc(self, (date) => setPartsDate(date, parts))
723)
724
725// =============================================================================
726// mapping
727// =============================================================================
728
729const constDayMillis = 24 * 60 * 60 * 1000
730
731const makeZonedFromAdjusted = (
732 adjustedMillis: number,
733 zone: DateTime.TimeZone,
734 disambiguation: DateTime.Disambiguation
735): DateTime.Zoned => {
736 if (zone._tag === "Offset") {
737 return makeZonedProto(adjustedMillis - zone.offset, zone)
738 }
739 const beforeOffset = calculateNamedOffset(
740 adjustedMillis - constDayMillis,
741 adjustedMillis,
742 zone
743 )
744 const afterOffset = calculateNamedOffset(
745 adjustedMillis + constDayMillis,
746 adjustedMillis,
747 zone
748 )
749 // If there is no transition, we can return early
750 if (beforeOffset === afterOffset) {
751 return makeZonedProto(adjustedMillis - beforeOffset, zone)
752 }
753 const isForwards = beforeOffset < afterOffset
754 const transitionMillis = beforeOffset - afterOffset
755 // If the transition is forwards, we only need to check if we should move the
756 // local wall clock time forward if it is inside the gap
757 if (isForwards) {
758 const currentAfterOffset = calculateNamedOffset(
759 adjustedMillis - afterOffset,
760 adjustedMillis,
761 zone
762 )
763 if (currentAfterOffset === afterOffset) {
764 return makeZonedProto(adjustedMillis - afterOffset, zone)
765 }
766 const before = makeZonedProto(adjustedMillis - beforeOffset, zone)
767 const beforeAdjustedMillis = toDate(before).getTime()
768 // If the wall clock time has changed, we are inside the gap
769 if (adjustedMillis !== beforeAdjustedMillis) {
770 switch (disambiguation) {
771 case "reject": {
772 const formatted = new Date(adjustedMillis).toISOString()
773 throw new RangeError(`Gap time: ${formatted} does not exist in time zone ${zone.id}`)
774 }
775 case "earlier":
776 return makeZonedProto(adjustedMillis - afterOffset, zone)
777
778 case "compatible":
779 case "later":
780 return before
781 }
782 }
783 // The wall clock time is in the earlier offset, so we use that
784 return before
785 }
786
787 const currentBeforeOffset = calculateNamedOffset(
788 adjustedMillis - beforeOffset,
789 adjustedMillis,
790 zone
791 )
792 // The wall clock time is in the earlier offset, so we use that
793 if (currentBeforeOffset === beforeOffset) {
794 if (disambiguation === "earlier" || disambiguation === "compatible") {
795 return makeZonedProto(adjustedMillis - beforeOffset, zone)
796 }
797 const laterOffset = calculateNamedOffset(
798 adjustedMillis - beforeOffset + transitionMillis,
799 adjustedMillis + transitionMillis,
800 zone
801 )
802 if (laterOffset === beforeOffset) {
803 return makeZonedProto(adjustedMillis - beforeOffset, zone)
804 }
805 // If the offset changed in this period, then we are inside the period where
806 // the wall clock time occurs twice, once in the earlier offset and once in
807 // the later offset.
808 if (disambiguation === "reject") {
809 const formatted = new Date(adjustedMillis).toISOString()
810 throw new RangeError(`Ambiguous time: ${formatted} occurs twice in time zone ${zone.id}`)
811 }
812 // If the disambiguation is "later", we return the later offset below
813 }
814 return makeZonedProto(adjustedMillis - afterOffset, zone)
815}
816
817const offsetRegExp = /([+-])(\d{2}):(\d{2})$/
818const parseOffset = (offset: string): number | null => {
819 const match = offsetRegExp.exec(offset)
820 if (match === null) {
821 return null
822 }
823 const [, sign, hours, minutes] = match
824 return (sign === "+" ? 1 : -1) * (Number(hours) * 60 + Number(minutes)) * 60 * 1000
825}
826
827const calculateNamedOffset = (
828 utcMillis: number,
829 adjustedMillis: number,
830 zone: DateTime.TimeZone.Named
831): number => {
832 const offset = zone.format.formatToParts(utcMillis).find((_) => _.type === "timeZoneName")?.value ?? ""
833 if (offset === "GMT") {
834 return 0
835 }
836 const result = parseOffset(offset)
837 if (result === null) {
838 // fallback to using the adjusted date
839 return zonedOffset(makeZonedProto(adjustedMillis, zone))
840 }
841 return result
842}
843
844/** @internal */
845export const mutate: {
846 (f: (date: Date) => void, options?: {
847 readonly disambiguation?: DateTime.Disambiguation | undefined
848 }): <A extends DateTime.DateTime>(self: A) => A
849 <A extends DateTime.DateTime>(self: A, f: (date: Date) => void, options?: {
850 readonly disambiguation?: DateTime.Disambiguation | undefined
851 }): A
852} = dual(isDateTimeArgs, (self: DateTime.DateTime, f: (date: Date) => void, options?: {
853 readonly disambiguation?: DateTime.Disambiguation | undefined
854}): DateTime.DateTime => {
855 if (self._tag === "Utc") {
856 const date = toDateUtc(self)
857 f(date)
858 return makeUtc(date.getTime())
859 }
860 const adjustedDate = toDate(self)
861 const newAdjustedDate = new Date(adjustedDate.getTime())
862 f(newAdjustedDate)
863 return makeZonedFromAdjusted(newAdjustedDate.getTime(), self.zone, options?.disambiguation ?? "compatible")
864})
865
866/** @internal */
867export const mutateUtc: {
868 (f: (date: Date) => void): <A extends DateTime.DateTime>(self: A) => A
869 <A extends DateTime.DateTime>(self: A, f: (date: Date) => void): A
870} = dual(2, (self: DateTime.DateTime, f: (date: Date) => void): DateTime.DateTime =>
871 mapEpochMillis(self, (millis) => {
872 const date = new Date(millis)
873 f(date)
874 return date.getTime()
875 }))
876
877/** @internal */
878export const mapEpochMillis: {
879 (f: (millis: number) => number): <A extends DateTime.DateTime>(self: A) => A
880 <A extends DateTime.DateTime>(self: A, f: (millis: number) => number): A
881} = dual(2, (self: DateTime.DateTime, f: (millis: number) => number): DateTime.DateTime => {
882 const millis = f(toEpochMillis(self))
883 return self._tag === "Utc" ? makeUtc(millis) : makeZonedProto(millis, self.zone)
884})
885
886/** @internal */
887export const withDate: {
888 <A>(f: (date: Date) => A): (self: DateTime.DateTime) => A
889 <A>(self: DateTime.DateTime, f: (date: Date) => A): A
890} = dual(2, <A>(self: DateTime.DateTime, f: (date: Date) => A): A => f(toDate(self)))
891
892/** @internal */
893export const withDateUtc: {
894 <A>(f: (date: Date) => A): (self: DateTime.DateTime) => A
895 <A>(self: DateTime.DateTime, f: (date: Date) => A): A
896} = dual(2, <A>(self: DateTime.DateTime, f: (date: Date) => A): A => f(toDateUtc(self)))
897
898/** @internal */
899export const match: {
900 <A, B>(options: {
901 readonly onUtc: (_: DateTime.Utc) => A
902 readonly onZoned: (_: DateTime.Zoned) => B
903 }): (self: DateTime.DateTime) => A | B
904 <A, B>(self: DateTime.DateTime, options: {
905 readonly onUtc: (_: DateTime.Utc) => A
906 readonly onZoned: (_: DateTime.Zoned) => B
907 }): A | B
908} = dual(2, <A, B>(self: DateTime.DateTime, options: {
909 readonly onUtc: (_: DateTime.Utc) => A
910 readonly onZoned: (_: DateTime.Zoned) => B
911}): A | B => self._tag === "Utc" ? options.onUtc(self) : options.onZoned(self))
912
913// =============================================================================
914// math
915// =============================================================================
916
917/** @internal */
918export const addDuration: {
919 (duration: Duration.Input): <A extends DateTime.DateTime>(self: A) => A
920 <A extends DateTime.DateTime>(self: A, duration: Duration.Input): A
921} = dual(
922 2,
923 (self: DateTime.DateTime, duration: Duration.Input): DateTime.DateTime =>
924 mapEpochMillis(self, (millis) => millis + Duration.toMillis(Duration.fromInputUnsafe(duration)))
925)
926
927/** @internal */
928export const subtractDuration: {
929 (duration: Duration.Input): <A extends DateTime.DateTime>(self: A) => A
930 <A extends DateTime.DateTime>(self: A, duration: Duration.Input): A
931} = dual(
932 2,
933 (self: DateTime.DateTime, duration: Duration.Input): DateTime.DateTime =>
934 mapEpochMillis(self, (millis) => millis - Duration.toMillis(Duration.fromInputUnsafe(duration)))
935)
936
937const addMillis = (date: Date, amount: number): void => {
938 date.setTime(date.getTime() + amount)
939}
940
941/** @internal */
942export const add: {
943 (
944 parts: Partial<DateTime.DateTime.PartsForMath>
945 ): <A extends DateTime.DateTime>(self: A) => A
946 <A extends DateTime.DateTime>(
947 self: A,
948 parts: Partial<DateTime.DateTime.PartsForMath>
949 ): A
950} = dual(
951 2,
952 (self: DateTime.DateTime, parts: Partial<DateTime.DateTime.PartsForMath>): DateTime.DateTime =>
953 mutate(self, (date) => {
954 if (parts.milliseconds) {
955 addMillis(date, parts.milliseconds)
956 }
957 if (parts.seconds) {
958 addMillis(date, parts.seconds * 1000)
959 }
960 if (parts.minutes) {
961 addMillis(date, parts.minutes * 60 * 1000)
962 }
963 if (parts.hours) {
964 addMillis(date, parts.hours * 60 * 60 * 1000)
965 }
966 if (parts.days) {
967 date.setUTCDate(date.getUTCDate() + parts.days)
968 }
969 if (parts.weeks) {
970 date.setUTCDate(date.getUTCDate() + parts.weeks * 7)
971 }
972 if (parts.months) {
973 const day = date.getUTCDate()
974 date.setUTCMonth(date.getUTCMonth() + parts.months + 1, 0)
975 if (day < date.getUTCDate()) {
976 date.setUTCDate(day)
977 }
978 }
979 if (parts.years) {
980 const day = date.getUTCDate()
981 const month = date.getUTCMonth()
982 date.setUTCFullYear(
983 date.getUTCFullYear() + parts.years,
984 month + 1,
985 0
986 )
987 if (day < date.getUTCDate()) {
988 date.setUTCDate(day)
989 }
990 }
991 })
992)
993
994/** @internal */
995export const subtract: {
996 (
997 parts: Partial<DateTime.DateTime.PartsForMath>
998 ): <A extends DateTime.DateTime>(self: A) => A
999 <A extends DateTime.DateTime>(
1000 self: A,
1001 parts: Partial<DateTime.DateTime.PartsForMath>
1002 ): A
1003} = dual(2, (self: DateTime.DateTime, parts: Partial<DateTime.DateTime.PartsForMath>): DateTime.DateTime => {
1004 const newParts = {} as Partial<Mutable<DateTime.DateTime.PartsForMath>>
1005 for (const key in parts) {
1006 newParts[key as keyof DateTime.DateTime.PartsForMath] = -1 * parts[key as keyof DateTime.DateTime.PartsForMath]!
1007 }
1008 return add(self, newParts)
1009})
1010
1011const startOfDate = (date: Date, part: DateTime.DateTime.UnitSingular, options?: {
1012 readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined
1013}) => {
1014 switch (part) {
1015 case "second": {
1016 date.setUTCMilliseconds(0)
1017 break
1018 }
1019 case "minute": {
1020 date.setUTCSeconds(0, 0)
1021 break
1022 }
1023 case "hour": {
1024 date.setUTCMinutes(0, 0, 0)
1025 break
1026 }
1027 case "day": {
1028 date.setUTCHours(0, 0, 0, 0)
1029 break
1030 }
1031 case "week": {
1032 const weekStartsOn = options?.weekStartsOn ?? 0
1033 const day = date.getUTCDay()
1034 const diff = (day - weekStartsOn + 7) % 7
1035 date.setUTCDate(date.getUTCDate() - diff)
1036 date.setUTCHours(0, 0, 0, 0)
1037 break
1038 }
1039 case "month": {
1040 date.setUTCDate(1)
1041 date.setUTCHours(0, 0, 0, 0)
1042 break
1043 }
1044 case "year": {
1045 date.setUTCMonth(0, 1)
1046 date.setUTCHours(0, 0, 0, 0)
1047 break
1048 }
1049 }
1050}
1051
1052/** @internal */
1053export const startOf: {
1054 (part: DateTime.DateTime.UnitSingular, options?: {
1055 readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined
1056 }): <A extends DateTime.DateTime>(self: A) => A
1057 <A extends DateTime.DateTime>(self: A, part: DateTime.DateTime.UnitSingular, options?: {
1058 readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined
1059 }): A
1060} = dual(isDateTimeArgs, (self: DateTime.DateTime, part: DateTime.DateTime.UnitSingular, options?: {
1061 readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined
1062}): DateTime.DateTime => mutate(self, (date) => startOfDate(date, part, options)))
1063
1064const endOfDate = (date: Date, part: DateTime.DateTime.UnitSingular, options?: {
1065 readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined
1066}) => {
1067 switch (part) {
1068 case "second": {
1069 date.setUTCMilliseconds(999)
1070 break
1071 }
1072 case "minute": {
1073 date.setUTCSeconds(59, 999)
1074 break
1075 }
1076 case "hour": {
1077 date.setUTCMinutes(59, 59, 999)
1078 break
1079 }
1080 case "day": {
1081 date.setUTCHours(23, 59, 59, 999)
1082 break
1083 }
1084 case "week": {
1085 const weekStartsOn = options?.weekStartsOn ?? 0
1086 const day = date.getUTCDay()
1087 const diff = (day - weekStartsOn + 7) % 7
1088 date.setUTCDate(date.getUTCDate() - diff + 6)
1089 date.setUTCHours(23, 59, 59, 999)
1090 break
1091 }
1092 case "month": {
1093 date.setUTCMonth(date.getUTCMonth() + 1, 0)
1094 date.setUTCHours(23, 59, 59, 999)
1095 break
1096 }
1097 case "year": {
1098 date.setUTCMonth(11, 31)
1099 date.setUTCHours(23, 59, 59, 999)
1100 break
1101 }
1102 }
1103}
1104
1105/** @internal */
1106export const endOf: {
1107 (part: DateTime.DateTime.UnitSingular, options?: {
1108 readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined
1109 }): <A extends DateTime.DateTime>(self: A) => A
1110 <A extends DateTime.DateTime>(self: A, part: DateTime.DateTime.UnitSingular, options?: {
1111 readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined
1112 }): A
1113} = dual(isDateTimeArgs, (self: DateTime.DateTime, part: DateTime.DateTime.UnitSingular, options?: {
1114 readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined
1115}): DateTime.DateTime => mutate(self, (date) => endOfDate(date, part, options)))
1116
1117/** @internal */
1118export const nearest: {
1119 (part: DateTime.DateTime.UnitSingular, options?: {
1120 readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined
1121 }): <A extends DateTime.DateTime>(self: A) => A
1122 <A extends DateTime.DateTime>(self: A, part: DateTime.DateTime.UnitSingular, options?: {
1123 readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined
1124 }): A
1125} = dual(isDateTimeArgs, (self: DateTime.DateTime, part: DateTime.DateTime.UnitSingular, options?: {
1126 readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined
1127}): DateTime.DateTime =>
1128 mutate(self, (date) => {
1129 if (part === "millisecond") return
1130 const millis = date.getTime()
1131 const start = new Date(millis)
1132 startOfDate(start, part, options)
1133 const startMillis = start.getTime()
1134 const end = new Date(millis)
1135 endOfDate(end, part, options)
1136 const endMillis = end.getTime() + 1
1137 const diffStart = millis - startMillis
1138 const diffEnd = endMillis - millis
1139 if (diffStart < diffEnd) {
1140 date.setTime(startMillis)
1141 } else {
1142 date.setTime(endMillis)
1143 }
1144 }))
1145
1146// =============================================================================
1147// formatting
1148// =============================================================================
1149
1150const intlTimeZone = (self: DateTime.TimeZone): string => {
1151 if (self._tag === "Named") {
1152 return self.id
1153 }
1154 return offsetToString(self.offset)
1155}
1156
1157/** @internal */
1158export const format: {
1159 (
1160 options?:
1161 | Intl.DateTimeFormatOptions & {
1162 readonly locale?: Intl.LocalesArgument
1163 }
1164 | undefined
1165 ): (self: DateTime.DateTime) => string
1166 (
1167 self: DateTime.DateTime,
1168 options?:
1169 | Intl.DateTimeFormatOptions & {
1170 readonly locale?: Intl.LocalesArgument
1171 }
1172 | undefined
1173 ): string
1174} = dual(isDateTimeArgs, (
1175 self: DateTime.DateTime,
1176 options?:
1177 | Intl.DateTimeFormatOptions & {
1178 readonly locale?: Intl.LocalesArgument
1179 }
1180 | undefined
1181): string => {
1182 try {
1183 return new Intl.DateTimeFormat(options?.locale, {
1184 timeZone: self._tag === "Utc" ? "UTC" : intlTimeZone(self.zone),
1185 ...options
1186 }).format(self.epochMilliseconds)
1187 } catch {
1188 return new Intl.DateTimeFormat(options?.locale, {
1189 timeZone: "UTC",
1190 ...options
1191 }).format(toDate(self))
1192 }
1193})
1194
1195/** @internal */
1196export const formatLocal: {
1197 (
1198 options?:
1199 | Intl.DateTimeFormatOptions & {
1200 readonly locale?: Intl.LocalesArgument
1201 }
1202 | undefined
1203 ): (self: DateTime.DateTime) => string
1204 (
1205 self: DateTime.DateTime,
1206 options?:
1207 | Intl.DateTimeFormatOptions & {
1208 readonly locale?: Intl.LocalesArgument
1209 }
1210 | undefined
1211 ): string
1212} = dual(isDateTimeArgs, (
1213 self: DateTime.DateTime,
1214 options?:
1215 | Intl.DateTimeFormatOptions & {
1216 readonly locale?: Intl.LocalesArgument
1217 }
1218 | undefined
1219): string => new Intl.DateTimeFormat(options?.locale, options).format(self.epochMilliseconds))
1220
1221/** @internal */
1222export const formatUtc: {
1223 (
1224 options?:
1225 | Intl.DateTimeFormatOptions & {
1226 readonly locale?: Intl.LocalesArgument
1227 }
1228 | undefined
1229 ): (self: DateTime.DateTime) => string
1230 (
1231 self: DateTime.DateTime,
1232 options?:
1233 | Intl.DateTimeFormatOptions & {
1234 readonly locale?: Intl.LocalesArgument
1235 }
1236 | undefined
1237 ): string
1238} = dual(isDateTimeArgs, (
1239 self: DateTime.DateTime,
1240 options?:
1241 | Intl.DateTimeFormatOptions & {
1242 readonly locale?: Intl.LocalesArgument
1243 }
1244 | undefined
1245): string =>
1246 new Intl.DateTimeFormat(options?.locale, {
1247 ...options,
1248 timeZone: "UTC"
1249 }).format(self.epochMilliseconds))
1250
1251/** @internal */
1252export const formatIntl: {
1253 (format: Intl.DateTimeFormat): (self: DateTime.DateTime) => string
1254 (self: DateTime.DateTime, format: Intl.DateTimeFormat): string
1255} = dual(2, (self: DateTime.DateTime, format: Intl.DateTimeFormat): string => format.format(self.epochMilliseconds))
1256
1257/** @internal */
1258export const formatIso = (self: DateTime.DateTime): string => toDateUtc(self).toISOString()
1259
1260/** @internal */
1261export const formatIsoDate = (self: DateTime.DateTime): string => toDate(self).toISOString().slice(0, 10)
1262
1263/** @internal */
1264export const formatIsoDateUtc = (self: DateTime.DateTime): string => toDateUtc(self).toISOString().slice(0, 10)
1265
1266/** @internal */
1267export const formatIsoOffset = (self: DateTime.DateTime): string => {
1268 const date = toDate(self)
1269 return self._tag === "Utc" ? date.toISOString() : `${date.toISOString().slice(0, -1)}${zonedOffsetIso(self)}`
1270}
1271
1272/** @internal */
1273export const formatIsoZoned = (self: DateTime.Zoned): string =>
1274 self.zone._tag === "Offset" ? formatIsoOffset(self) : `${formatIsoOffset(self)}[${self.zone.id}]`