import Duration, {DurationLike, toDuration} from './duration.js'; import {RoundingMode} from './types.js'; import { balanceTimeDuration, differenceInstantWithRounding, maxTemporalUnit, parseUnit, roundToIncrement, } from './utilities.js'; import {ZonedDateTime} from './zoned-date-time.js'; import {DAY, PER_UNIT} from './units.js'; import type {TimeZone} from './time-zone.js'; interface InstantRoundParameters { smallestUnit: 'hour' | 'minute' | 'second' | 'millisecond'; roundingIncrement?: number; roundingMode?: RoundingMode; } interface InstantDeltaParameters { /// The largest unit of time to round to in the resulting duration, defaults to 'auto'. largestUnit?: 'auto' | 'hour' | 'minute' | 'second' | 'millisecond'; /// The smallest unit of time to round to in the resulting duration, defaults to 'millisecond', which means no rounding. smallestUnit?: 'hour' | 'minute' | 'second' | 'millisecond'; /// The granularity to round to, of the unit given by smallestUnit. Defaults to `1`. roundingIncrement?: number; /// Rounding method to use roundingMode?: RoundingMode; } export type InstantLike = string | Instant; export class Instant { private raw: number; constructor(epochMilisecond: number) { this.raw = epochMilisecond; } get epochMilliseconds() { return this.raw; } add(durationLike: DurationLike) { let duration = toDuration(durationLike); if (duration.days || duration.months || duration.weeks || duration.years) { throw new RangeError('Duration field days | months | weeks | years not supported by Instant'); } return Instant.fromEpochMilliseconds(this.epochMilliseconds + duration.total('millisecond')); } subtract(durationLike: DurationLike) { let duration = toDuration(durationLike).negated(); return this.add(duration); } until(other: InstantLike, options: InstantDeltaParameters = {}) { let smallestUnit = parseUnit(options.smallestUnit, 'millisecond'); let largestUnit = parseUnit(options.largestUnit, 'second'); if (largestUnit === 'auto') { largestUnit = maxTemporalUnit('hour', smallestUnit); } let record = differenceInstantWithRounding( this, toInstant(other), smallestUnit, options.roundingIncrement ?? 1, options.roundingMode ?? 'trunc', ); return Duration.from(balanceTimeDuration(record.timeDuration, largestUnit)); } since(other: InstantLike, options?: InstantDeltaParameters) { return this.until(other, options).negated(); } toZonedDateTimeISO(timeZone: string | TimeZone) { return new ZonedDateTime(this.epochMilliseconds, timeZone); } equals(other: InstantLike) { return this.epochMilliseconds === toInstant(other).epochMilliseconds; } toJSON() { return this.toString(); } toString() { return new Date(this.raw).toISOString(); } toLocaleString() { throw new Error('Missing implementation'); } valueOf() { throw new Error('Built-in arithmetic operators isn’t supported on Instant'); } round(options: InstantRoundParameters['smallestUnit'] | InstantRoundParameters) { if (typeof options === 'string') { options = { smallestUnit: options, }; } let smallestUnit = parseUnit(options.smallestUnit); let roundingIncrement = options.roundingIncrement ?? 1; let roundingMode = options.roundingMode ?? 'halfExpand'; let remainder = this.epochMilliseconds % DAY; let wholeDays = this.epochMilliseconds - remainder; let roundedRemainder = roundToIncrement(remainder, PER_UNIT[smallestUnit] * roundingIncrement, roundingMode); return Instant.fromEpochMilliseconds(wholeDays + roundedRemainder); } static from(instantLike: InstantLike) { if (instantLike instanceof Instant) { return new Instant(instantLike.epochMilliseconds); } else if (typeof instantLike === 'string') { return new Instant(Date.parse(instantLike)); } throw new RangeError('Invalid string'); } static fromEpochMilliseconds(epochMilisecond: number) { return new Instant(epochMilisecond); } static compare(one: InstantLike, two: InstantLike): number { return Math.sign(toInstant(one).epochMilliseconds - toInstant(two).epochMilliseconds); } } export function toInstant(instantLike: InstantLike) { return instantLike instanceof Instant ? instantLike : Instant.from(instantLike); }