@js-temporal/polyfill
Version:
Polyfill for Tc39 Stage 3 proposal Temporal (https://github.com/tc39/proposal-temporal)
361 lines (333 loc) • 13.5 kB
text/typescript
import { DEBUG } from './debug';
import * as ES from './ecmascript';
import { GetIntrinsic, MakeIntrinsicClass } from './intrinsicclass';
import {
ISO_YEAR,
ISO_MONTH,
ISO_DAY,
ISO_HOUR,
ISO_MINUTE,
ISO_SECOND,
ISO_MILLISECOND,
ISO_MICROSECOND,
ISO_NANOSECOND,
CALENDAR,
EPOCHNANOSECONDS,
CreateSlots,
GetSlot,
SetSlot
} from './slots';
import type { Temporal } from '..';
import { DateTimeFormat } from './intl';
import type { PlainTimeParams as Params, PlainTimeReturn as Return } from './internaltypes';
const ObjectAssign = Object.assign;
type TemporalTimeToStringOptions = {
unit: ReturnType<typeof ES.ToSecondsStringPrecision>['unit'];
increment: ReturnType<typeof ES.ToSecondsStringPrecision>['increment'];
roundingMode: Temporal.RoundingMode;
};
function TemporalTimeToString(
time: Temporal.PlainTime,
precision: ReturnType<typeof ES.ToSecondsStringPrecision>['precision'],
options: TemporalTimeToStringOptions | undefined = undefined
) {
let hour = GetSlot(time, ISO_HOUR);
let minute = GetSlot(time, ISO_MINUTE);
let second = GetSlot(time, ISO_SECOND);
let millisecond = GetSlot(time, ISO_MILLISECOND);
let microsecond = GetSlot(time, ISO_MICROSECOND);
let nanosecond = GetSlot(time, ISO_NANOSECOND);
if (options) {
const { unit, increment, roundingMode } = options;
({ hour, minute, second, millisecond, microsecond, nanosecond } = ES.RoundTime(
hour,
minute,
second,
millisecond,
microsecond,
nanosecond,
increment,
unit,
roundingMode
));
}
const hourString = ES.ISODateTimePartString(hour);
const minuteString = ES.ISODateTimePartString(minute);
const seconds = ES.FormatSecondsStringPart(second, millisecond, microsecond, nanosecond, precision);
return `${hourString}:${minuteString}${seconds}`;
}
export class PlainTime implements Temporal.PlainTime {
constructor(
isoHourParam = 0,
isoMinuteParam = 0,
isoSecondParam = 0,
isoMillisecondParam = 0,
isoMicrosecondParam = 0,
isoNanosecondParam = 0
) {
const isoHour = ES.ToIntegerThrowOnInfinity(isoHourParam);
const isoMinute = ES.ToIntegerThrowOnInfinity(isoMinuteParam);
const isoSecond = ES.ToIntegerThrowOnInfinity(isoSecondParam);
const isoMillisecond = ES.ToIntegerThrowOnInfinity(isoMillisecondParam);
const isoMicrosecond = ES.ToIntegerThrowOnInfinity(isoMicrosecondParam);
const isoNanosecond = ES.ToIntegerThrowOnInfinity(isoNanosecondParam);
ES.RejectTime(isoHour, isoMinute, isoSecond, isoMillisecond, isoMicrosecond, isoNanosecond);
CreateSlots(this);
SetSlot(this, ISO_HOUR, isoHour);
SetSlot(this, ISO_MINUTE, isoMinute);
SetSlot(this, ISO_SECOND, isoSecond);
SetSlot(this, ISO_MILLISECOND, isoMillisecond);
SetSlot(this, ISO_MICROSECOND, isoMicrosecond);
SetSlot(this, ISO_NANOSECOND, isoNanosecond);
SetSlot(this, CALENDAR, ES.GetISO8601Calendar());
if (DEBUG) {
Object.defineProperty(this, '_repr_', {
value: `${this[Symbol.toStringTag]} <${TemporalTimeToString(this, 'auto')}>`,
writable: false,
enumerable: false,
configurable: false
});
}
}
get calendar(): Return['calendar'] {
if (!ES.IsTemporalTime(this)) throw new TypeError('invalid receiver');
// PlainTime's calendar isn't settable, so can't be a userland calendar
return GetSlot(this, CALENDAR) as Temporal.Calendar;
}
get hour(): Return['hour'] {
if (!ES.IsTemporalTime(this)) throw new TypeError('invalid receiver');
return GetSlot(this, ISO_HOUR);
}
get minute(): Return['minute'] {
if (!ES.IsTemporalTime(this)) throw new TypeError('invalid receiver');
return GetSlot(this, ISO_MINUTE);
}
get second(): Return['second'] {
if (!ES.IsTemporalTime(this)) throw new TypeError('invalid receiver');
return GetSlot(this, ISO_SECOND);
}
get millisecond(): Return['millisecond'] {
if (!ES.IsTemporalTime(this)) throw new TypeError('invalid receiver');
return GetSlot(this, ISO_MILLISECOND);
}
get microsecond(): Return['microsecond'] {
if (!ES.IsTemporalTime(this)) throw new TypeError('invalid receiver');
return GetSlot(this, ISO_MICROSECOND);
}
get nanosecond(): Return['nanosecond'] {
if (!ES.IsTemporalTime(this)) throw new TypeError('invalid receiver');
return GetSlot(this, ISO_NANOSECOND);
}
with(temporalTimeLike: Params['with'][0], optionsParam: Params['with'][1] = undefined): Return['with'] {
if (!ES.IsTemporalTime(this)) throw new TypeError('invalid receiver');
if (!ES.IsObject(temporalTimeLike)) {
throw new TypeError('invalid argument');
}
ES.RejectObjectWithCalendarOrTimeZone(temporalTimeLike);
const partialTime = ES.ToTemporalTimeRecord(temporalTimeLike, 'partial');
const options = ES.GetOptionsObject(optionsParam);
const overflow = ES.ToTemporalOverflow(options);
const fields = ES.ToTemporalTimeRecord(this);
let { hour, minute, second, millisecond, microsecond, nanosecond } = ObjectAssign(fields, partialTime);
({ hour, minute, second, millisecond, microsecond, nanosecond } = ES.RegulateTime(
hour,
minute,
second,
millisecond,
microsecond,
nanosecond,
overflow
));
return new PlainTime(hour, minute, second, millisecond, microsecond, nanosecond);
}
add(temporalDurationLike: Params['add'][0]): Return['add'] {
if (!ES.IsTemporalTime(this)) throw new TypeError('invalid receiver');
return ES.AddDurationToOrSubtractDurationFromPlainTime('add', this, temporalDurationLike);
}
subtract(temporalDurationLike: Params['subtract'][0]): Return['subtract'] {
if (!ES.IsTemporalTime(this)) throw new TypeError('invalid receiver');
return ES.AddDurationToOrSubtractDurationFromPlainTime('subtract', this, temporalDurationLike);
}
until(other: Params['until'][0], options: Params['until'][1] = undefined): Return['until'] {
if (!ES.IsTemporalTime(this)) throw new TypeError('invalid receiver');
return ES.DifferenceTemporalPlainTime('until', this, other, options);
}
since(other: Params['since'][0], options: Params['since'][1] = undefined): Return['since'] {
if (!ES.IsTemporalTime(this)) throw new TypeError('invalid receiver');
return ES.DifferenceTemporalPlainTime('since', this, other, options);
}
round(optionsParam: Params['round'][0]): Return['round'] {
if (!ES.IsTemporalTime(this)) throw new TypeError('invalid receiver');
if (optionsParam === undefined) throw new TypeError('options parameter is required');
const options =
typeof optionsParam === 'string'
? (ES.CreateOnePropObject('smallestUnit', optionsParam) as Exclude<typeof optionsParam, string>)
: ES.GetOptionsObject(optionsParam);
const smallestUnit = ES.GetTemporalUnit(options, 'smallestUnit', 'time', ES.REQUIRED);
const roundingMode = ES.ToTemporalRoundingMode(options, 'halfExpand');
const MAX_INCREMENTS = {
hour: 24,
minute: 60,
second: 60,
millisecond: 1000,
microsecond: 1000,
nanosecond: 1000
};
const roundingIncrement = ES.ToTemporalRoundingIncrement(options, MAX_INCREMENTS[smallestUnit], false);
let hour = GetSlot(this, ISO_HOUR);
let minute = GetSlot(this, ISO_MINUTE);
let second = GetSlot(this, ISO_SECOND);
let millisecond = GetSlot(this, ISO_MILLISECOND);
let microsecond = GetSlot(this, ISO_MICROSECOND);
let nanosecond = GetSlot(this, ISO_NANOSECOND);
({ hour, minute, second, millisecond, microsecond, nanosecond } = ES.RoundTime(
hour,
minute,
second,
millisecond,
microsecond,
nanosecond,
roundingIncrement,
smallestUnit,
roundingMode
));
return new PlainTime(hour, minute, second, millisecond, microsecond, nanosecond);
}
equals(otherParam: Params['equals'][0]): Return['equals'] {
if (!ES.IsTemporalTime(this)) throw new TypeError('invalid receiver');
const other = ES.ToTemporalTime(otherParam);
for (const slot of [ISO_HOUR, ISO_MINUTE, ISO_SECOND, ISO_MILLISECOND, ISO_MICROSECOND, ISO_NANOSECOND]) {
const val1 = GetSlot(this, slot);
const val2 = GetSlot(other, slot);
if (val1 !== val2) return false;
}
return true;
}
toString(optionsParam: Params['toString'][0] = undefined): string {
if (!ES.IsTemporalTime(this)) throw new TypeError('invalid receiver');
const options = ES.GetOptionsObject(optionsParam);
const { precision, unit, increment } = ES.ToSecondsStringPrecision(options);
const roundingMode = ES.ToTemporalRoundingMode(options, 'trunc');
return TemporalTimeToString(this, precision, { unit, increment, roundingMode });
}
toJSON(): Return['toJSON'] {
if (!ES.IsTemporalTime(this)) throw new TypeError('invalid receiver');
return TemporalTimeToString(this, 'auto');
}
toLocaleString(
locales: Params['toLocaleString'][0] = undefined,
options: Params['toLocaleString'][1] = undefined
): string {
if (!ES.IsTemporalTime(this)) throw new TypeError('invalid receiver');
return new DateTimeFormat(locales, options).format(this);
}
valueOf(): never {
throw new TypeError('use compare() or equals() to compare Temporal.PlainTime');
}
toPlainDateTime(temporalDateParam: Params['toPlainDateTime'][0]): Return['toPlainDateTime'] {
if (!ES.IsTemporalTime(this)) throw new TypeError('invalid receiver');
const temporalDate = ES.ToTemporalDate(temporalDateParam);
const year = GetSlot(temporalDate, ISO_YEAR);
const month = GetSlot(temporalDate, ISO_MONTH);
const day = GetSlot(temporalDate, ISO_DAY);
const calendar = GetSlot(temporalDate, CALENDAR);
const hour = GetSlot(this, ISO_HOUR);
const minute = GetSlot(this, ISO_MINUTE);
const second = GetSlot(this, ISO_SECOND);
const millisecond = GetSlot(this, ISO_MILLISECOND);
const microsecond = GetSlot(this, ISO_MICROSECOND);
const nanosecond = GetSlot(this, ISO_NANOSECOND);
return ES.CreateTemporalDateTime(
year,
month,
day,
hour,
minute,
second,
millisecond,
microsecond,
nanosecond,
calendar
);
}
toZonedDateTime(item: Params['toZonedDateTime'][0]): Return['toZonedDateTime'] {
if (!ES.IsTemporalTime(this)) throw new TypeError('invalid receiver');
if (!ES.IsObject(item)) {
throw new TypeError('invalid argument');
}
const dateLike = item.plainDate;
if (dateLike === undefined) {
throw new TypeError('missing date property');
}
const temporalDate = ES.ToTemporalDate(dateLike);
const timeZoneLike = item.timeZone;
if (timeZoneLike === undefined) {
throw new TypeError('missing timeZone property');
}
const timeZone = ES.ToTemporalTimeZone(timeZoneLike);
const year = GetSlot(temporalDate, ISO_YEAR);
const month = GetSlot(temporalDate, ISO_MONTH);
const day = GetSlot(temporalDate, ISO_DAY);
const calendar = GetSlot(temporalDate, CALENDAR);
const hour = GetSlot(this, ISO_HOUR);
const minute = GetSlot(this, ISO_MINUTE);
const second = GetSlot(this, ISO_SECOND);
const millisecond = GetSlot(this, ISO_MILLISECOND);
const microsecond = GetSlot(this, ISO_MICROSECOND);
const nanosecond = GetSlot(this, ISO_NANOSECOND);
const PlainDateTime = GetIntrinsic('%Temporal.PlainDateTime%');
const dt = new PlainDateTime(
year,
month,
day,
hour,
minute,
second,
millisecond,
microsecond,
nanosecond,
calendar
);
const instant = ES.BuiltinTimeZoneGetInstantFor(timeZone, dt, 'compatible');
return ES.CreateTemporalZonedDateTime(GetSlot(instant, EPOCHNANOSECONDS), timeZone, calendar);
}
getISOFields(): Return['getISOFields'] {
if (!ES.IsTemporalTime(this)) throw new TypeError('invalid receiver');
return {
calendar: GetSlot(this, CALENDAR) as Temporal.Calendar,
isoHour: GetSlot(this, ISO_HOUR),
isoMicrosecond: GetSlot(this, ISO_MICROSECOND),
isoMillisecond: GetSlot(this, ISO_MILLISECOND),
isoMinute: GetSlot(this, ISO_MINUTE),
isoNanosecond: GetSlot(this, ISO_NANOSECOND),
isoSecond: GetSlot(this, ISO_SECOND)
};
}
static from(item: Params['from'][0], optionsParam: Params['from'][1] = undefined): Return['from'] {
const options = ES.GetOptionsObject(optionsParam);
const overflow = ES.ToTemporalOverflow(options);
if (ES.IsTemporalTime(item)) {
return new PlainTime(
GetSlot(item, ISO_HOUR),
GetSlot(item, ISO_MINUTE),
GetSlot(item, ISO_SECOND),
GetSlot(item, ISO_MILLISECOND),
GetSlot(item, ISO_MICROSECOND),
GetSlot(item, ISO_NANOSECOND)
);
}
return ES.ToTemporalTime(item, overflow);
}
static compare(oneParam: Params['compare'][0], twoParam: Params['compare'][1]): Return['compare'] {
const one = ES.ToTemporalTime(oneParam);
const two = ES.ToTemporalTime(twoParam);
for (const slot of [ISO_HOUR, ISO_MINUTE, ISO_SECOND, ISO_MILLISECOND, ISO_MICROSECOND, ISO_NANOSECOND] as const) {
const val1 = GetSlot(one, slot);
const val2 = GetSlot(two, slot);
if (val1 !== val2) return ES.ComparisonResult(val1 - val2);
}
return 0;
}
[Symbol.toStringTag]!: 'Temporal.PlainTime';
}
MakeIntrinsicClass(PlainTime, 'Temporal.PlainTime');