@js-temporal/polyfill
Version:
Polyfill for Temporal (https://github.com/tc39/proposal-temporal), an ECMA TC39 Stage 3 proposal
759 lines (704 loc) • 26.7 kB
text/typescript
import { assert } from './assert';
import * as ES from './ecmascript';
import { MakeIntrinsicClass } from './intrinsicclass';
import {
CAL_ID,
CALENDAR,
CreateSlots,
DATE,
DATETIME,
DAYS,
EPOCHNANOSECONDS,
GetSlot,
HasSlot,
HOURS,
INST,
ISO_DATE,
ISO_DATE_TIME,
LOCALE,
MD,
MICROSECONDS,
MILLISECONDS,
MINUTES,
MONTHS,
NANOSECONDS,
OPTIONS,
ORIGINAL,
ResetSlot,
SECONDS,
SetSlot,
TIME,
TIME_FMT,
TZ_CANONICAL,
TZ_ORIGINAL,
WEEKS,
YEARS,
YM
} from './slots';
import type { Temporal } from '..';
import type { DateTimeFormatParams as Params, DateTimeFormatReturn as Return } from './internaltypes';
// Save the original Intl.DateTimeFormat, it will likely be overwritten
const OriginalIntlDateTimeFormat = Intl.DateTimeFormat;
type LazySlot = typeof DATE | typeof YM | typeof MD | typeof TIME_FMT | typeof DATETIME | typeof INST;
// Construction of built-in Intl.DateTimeFormat objects is sloooooow,
// so we'll only create those instances when we need them.
// See https://bugs.chromium.org/p/v8/issues/detail?id=6528
function getSlotLazy(obj: DateTimeFormatImpl, slot: LazySlot) {
let val = GetSlot(obj, slot);
if (typeof val === 'function') {
// If we get here, `val` is an "amender function". It will take the user's
// options and transform them into suitable options to be passed into the
// built-in (non-polyfill) Intl.DateTimeFormat constructor. These options
// will vary depending on the Temporal type, so that's why we store separate
// formatters in separate props on the polyfill's DateTimeFormat instances.
// The efficiency happens because we don't create an (expensive) formatter
// until the user calls toLocaleString for that Temporal type.
val = new OriginalIntlDateTimeFormat(GetSlot(obj, LOCALE), val(GetSlot(obj, OPTIONS)));
ResetSlot(obj, slot, val);
}
return val;
}
function createDateTimeFormat(
dtf: DateTimeFormatImpl,
locale: Params['constructor'][0],
optionsParam: Params['constructor'][1]
) {
const hasOptions = typeof optionsParam !== 'undefined';
let options: Intl.DateTimeFormatOptions;
if (hasOptions) {
// Read all the options in the expected order and copy them to a
// null-prototype object with which we can do further operations
// unobservably
const props: (keyof Intl.DateTimeFormatOptions)[] = [
'localeMatcher',
'calendar',
'numberingSystem',
'hour12',
'hourCycle',
'timeZone',
'weekday',
'era',
'year',
'month',
'day',
'dayPeriod',
'hour',
'minute',
'second',
'fractionalSecondDigits',
'timeZoneName',
'formatMatcher',
'dateStyle',
'timeStyle'
];
options = ES.ToObject(optionsParam);
const newOptions = Object.create(null);
for (let i = 0; i < props.length; i++) {
const prop = props[i];
if (Object.prototype.hasOwnProperty.call(options, prop)) {
newOptions[prop] = options[prop];
}
}
options = newOptions;
} else {
options = Object.create(null);
}
const original = new OriginalIntlDateTimeFormat(locale, options);
const ro = original.resolvedOptions();
CreateSlots(dtf);
// DateTimeFormat instances are very expensive to create. Therefore, they will
// be lazily created only when needed, using the locale and options provided.
// But it's possible for callers to mutate those inputs before lazy creation
// happens. For this reason, we clone the inputs instead of caching the
// original objects. To avoid the complexity of deep cloning any inputs that
// are themselves objects (e.g. the locales array, or options property values
// that will be coerced to strings), we rely on `resolvedOptions()` to do the
// coercion and cloning for us. Unfortunately, we can't just use the resolved
// options as-is because our options-amending logic adds additional fields if
// the user doesn't supply any unit fields like year, month, day, hour, etc.
// Therefore, we limit the properties in the clone to properties that were
// present in the original input.
if (hasOptions) {
const clonedResolved = Object.assign(Object.create(null), ro);
for (const prop in clonedResolved) {
if (!Object.prototype.hasOwnProperty.call(options, prop)) {
delete clonedResolved[prop as keyof typeof clonedResolved];
}
}
// hour12/hourCycle don't show up in resolvedOptions() unless the chosen
// format includes an hour component, so copy them explicitly in case they
// would otherwise be lost
clonedResolved.hour12 = options.hour12;
clonedResolved.hourCycle = options.hourCycle;
SetSlot(dtf, OPTIONS, clonedResolved);
} else {
SetSlot(dtf, OPTIONS, options);
}
SetSlot(dtf, LOCALE, ro.locale);
SetSlot(dtf, ORIGINAL, original);
SetSlot(dtf, TZ_CANONICAL, ro.timeZone);
SetSlot(dtf, CAL_ID, ro.calendar);
SetSlot(dtf, DATE, dateAmend);
SetSlot(dtf, YM, yearMonthAmend);
SetSlot(dtf, MD, monthDayAmend);
SetSlot(dtf, TIME_FMT, timeAmend);
SetSlot(dtf, DATETIME, datetimeAmend);
SetSlot(dtf, INST, instantAmend);
// Save the original time zone, for a few reasons:
// - Clearer error messages
// - More clearly follows the spec for InitializeDateTimeFormat
// - Because it follows the spec more closely, will make it easier to integrate
// support of offset strings and other potential changes like proposal-canonical-tz.
const timeZoneOption = hasOptions ? options.timeZone : undefined;
if (timeZoneOption === undefined) {
SetSlot(dtf, TZ_ORIGINAL, ro.timeZone);
} else {
const id = ES.ToString(timeZoneOption);
if (id.startsWith('−')) {
// The initial (Node 23) implementation of offset time zones allowed use
// of the Unicode minus sign, which was disallowed by a later spec change.
throw new RangeError('Unicode minus (U+2212) is not supported in time zone offsets');
}
// store a normalized identifier
SetSlot(dtf, TZ_ORIGINAL, ES.ToTemporalTimeZoneIdentifier(id));
}
return undefined; // TODO: I couldn't satisfy TS without adding this. Is there another way?
}
function IsPatchedDateTimeFormat(item: unknown): item is DateTimeFormatImpl {
return HasSlot(item, ORIGINAL);
}
class DateTimeFormatImpl {
constructor(locales: Params['constructor'][0] = undefined, options: Params['constructor'][1] = undefined) {
createDateTimeFormat(this, locales, options);
}
get format() {
ES.CheckReceiver(this, IsPatchedDateTimeFormat);
const boundFormat = format.bind(this);
Object.defineProperties(boundFormat, {
length: { value: 1, enumerable: false, writable: false, configurable: true },
name: { value: '', enumerable: false, writable: false, configurable: true }
});
return boundFormat;
}
formatRange(a: Params['formatRange'][0], b: Params['formatRange'][1]): Return['formatRange'] {
ES.CheckReceiver(this, IsPatchedDateTimeFormat);
return formatRange.call(this, a, b);
}
formatToParts?<P extends readonly unknown[]>(
datetime: Params['formatToParts'][0],
...rest: P
): Return['formatToParts'] {
ES.CheckReceiver(this, IsPatchedDateTimeFormat);
return formatToParts.call(this, datetime, ...rest);
}
formatRangeToParts?(
a: Params['formatRangeToParts'][0],
b: Params['formatRangeToParts'][1]
): Return['formatRangeToParts'] {
ES.CheckReceiver(this, IsPatchedDateTimeFormat);
return formatRangeToParts.call(this, a, b);
}
resolvedOptions(): Return['resolvedOptions'] {
ES.CheckReceiver(this, IsPatchedDateTimeFormat);
return resolvedOptions.call(this);
}
}
if (!('formatToParts' in OriginalIntlDateTimeFormat.prototype)) {
delete DateTimeFormatImpl.prototype.formatToParts;
}
if (!('formatRangeToParts' in OriginalIntlDateTimeFormat.prototype)) {
delete DateTimeFormatImpl.prototype.formatRangeToParts;
}
export type { DateTimeFormatImpl };
interface DateTimeFormatInterface {
(locales: Params['constructor'][0], options: Params['constructor'][1]): DateTimeFormatImpl;
new (locales: Params['constructor'][0], options: Params['constructor'][1]): DateTimeFormatImpl;
supportedLocalesOf: typeof Intl.DateTimeFormat.supportedLocalesOf;
}
// A non-class constructor is needed because Intl.DateTimeFormat must be able to
// be called without 'new'
export const DateTimeFormat = function (
locales: Params['constructor'][0] = undefined,
options: Params['constructor'][1] = undefined
): DateTimeFormatImpl {
return new DateTimeFormatImpl(locales, options);
} as unknown as DateTimeFormatInterface;
DateTimeFormatImpl.prototype.constructor = DateTimeFormat;
Object.defineProperty(DateTimeFormat, 'prototype', {
value: DateTimeFormatImpl.prototype,
writable: false,
enumerable: false,
configurable: false
});
DateTimeFormat.supportedLocalesOf = OriginalIntlDateTimeFormat.supportedLocalesOf;
MakeIntrinsicClass(DateTimeFormat as unknown as typeof Intl.DateTimeFormat, 'Intl.DateTimeFormat');
function resolvedOptions(this: DateTimeFormatImpl): Return['resolvedOptions'] {
const resolved = GetSlot(this, ORIGINAL).resolvedOptions();
resolved.timeZone = GetSlot(this, TZ_ORIGINAL);
return resolved;
}
// TODO: investigate why there's a rest parameter here. Does this function really need to accept extra params?
// And if so, why doesn't formatRange also accept extra params?
function format<P extends readonly unknown[]>(
this: DateTimeFormatImpl,
datetime: Params['format'][0],
...rest: P
): Return['format'] {
let overrides = extractOverrides(datetime, this);
let formatter, formatArgs: [Params['format'][0], ...unknown[]];
if (overrides.formatter) {
formatter = overrides.formatter;
formatArgs = [ES.epochNsToMs(overrides.epochNs, 'floor')];
} else {
formatter = GetSlot(this, ORIGINAL);
formatArgs = [datetime, ...rest];
}
return formatter.format(...(formatArgs as [number | Date | undefined]));
}
function formatToParts<P extends readonly unknown[]>(
this: DateTimeFormatImpl,
datetime: Params['formatToParts'][0],
...rest: P
): Return['formatToParts'] {
let overrides = extractOverrides(datetime, this);
let formatter, formatArgs;
if (overrides.formatter) {
formatter = overrides.formatter;
formatArgs = [ES.epochNsToMs(overrides.epochNs, 'floor')];
} else {
formatter = GetSlot(this, ORIGINAL);
formatArgs = [datetime, ...rest];
}
return formatter.formatToParts(...(formatArgs as [number | Date | undefined]));
}
function formatRange(this: DateTimeFormatImpl, aParam: Params['formatRange'][0], bParam: Params['formatRange'][1]) {
if (aParam === undefined || bParam === undefined) {
throw new TypeError('Intl.DateTimeFormat.formatRange requires two values');
}
const a = toDateTimeFormattable(aParam);
const b = toDateTimeFormattable(bParam);
let formatArgs = [a, b] as const;
let formatter;
if (isTemporalObject(a) !== isTemporalObject(b)) {
throw new TypeError('Intl.DateTimeFormat.formatRange accepts two values of the same type');
}
if (isTemporalObject(a)) {
if (!sameTemporalType(a, b)) {
throw new TypeError('Intl.DateTimeFormat.formatRange accepts two values of the same type');
}
const { epochNs: aa, formatter: aformatter } = extractOverrides(a, this);
const { epochNs: bb, formatter: bformatter } = extractOverrides(b, this);
if (aformatter) {
assert(bformatter == aformatter, 'formatters for same Temporal type should be identical');
formatter = aformatter;
formatArgs = [ES.epochNsToMs(aa, 'floor'), ES.epochNsToMs(bb, 'floor')];
}
}
if (!formatter) {
formatter = GetSlot(this, ORIGINAL);
}
return formatter.formatRange(...(formatArgs as [number, number]));
}
function formatRangeToParts(
this: DateTimeFormatImpl,
aParam: Params['formatRangeToParts'][0],
bParam: Params['formatRangeToParts'][1]
) {
if (aParam === undefined || bParam === undefined) {
throw new TypeError('Intl.DateTimeFormat.formatRange requires two values');
}
const a = toDateTimeFormattable(aParam);
const b = toDateTimeFormattable(bParam);
let formatArgs = [a, b] as const;
let formatter;
if (isTemporalObject(a) !== isTemporalObject(b)) {
throw new TypeError('Intl.DateTimeFormat.formatRangeToParts accepts two values of the same type');
}
if (isTemporalObject(a)) {
if (!sameTemporalType(a, b)) {
throw new TypeError('Intl.DateTimeFormat.formatRangeToParts accepts two values of the same type');
}
const { epochNs: aa, formatter: aformatter } = extractOverrides(a, this);
const { epochNs: bb, formatter: bformatter } = extractOverrides(b, this);
if (aformatter) {
assert(bformatter == aformatter, 'formatters for same Temporal type should be identical');
formatter = aformatter;
formatArgs = [ES.epochNsToMs(aa, 'floor'), ES.epochNsToMs(bb, 'floor')];
}
}
if (!formatter) {
formatter = GetSlot(this, ORIGINAL);
}
return formatter.formatRangeToParts(...(formatArgs as [number, number]));
}
// "false" is a signal to delete this option
type MaybeFalseOptions = {
[K in keyof Intl.DateTimeFormatOptions]?: Intl.DateTimeFormatOptions[K] | false;
};
function amend(optionsParam: Intl.DateTimeFormatOptions = {}, amended: MaybeFalseOptions = {}) {
const options = Object.assign({}, optionsParam);
const props = [
'year',
'month',
'day',
'hour',
'minute',
'second',
'weekday',
'dayPeriod',
'timeZoneName',
'dateStyle',
'timeStyle'
] as const;
for (let i = 0; i < props.length; i++) {
const opt = props[i];
// TODO: can this be typed more cleanly?
type OptionMaybeFalse = (typeof options)[typeof opt] | false;
(options[opt] as OptionMaybeFalse) = opt in amended ? amended[opt] : options[opt];
if ((options[opt] as OptionMaybeFalse) === false || options[opt] === undefined) delete options[opt];
}
return options;
}
type OptionsType<T extends TypesWithToLocaleString> = NonNullable<Parameters<T['toLocaleString']>[1]>;
function timeAmend(originalOptions: OptionsType<Temporal.PlainTime>) {
const options = amend(originalOptions, {
year: false,
month: false,
day: false,
weekday: false,
timeZoneName: false,
dateStyle: false
});
if (options.timeStyle === 'long' || options.timeStyle === 'full') {
// Try to fake what timeStyle should do if not printing the time zone name
delete options.timeStyle;
Object.assign(options, { hour: 'numeric', minute: '2-digit', second: '2-digit' });
}
if (!hasTimeOptions(options)) {
if (hasAnyDateTimeOptions(originalOptions)) {
throw new TypeError(`cannot format Temporal.PlainTime with options [${Object.keys(originalOptions)}]`);
}
Object.assign(options, {
hour: 'numeric',
minute: 'numeric',
second: 'numeric'
});
}
return options;
}
function yearMonthAmend(originalOptions: OptionsType<Temporal.PlainYearMonth>) {
// Try to fake what dateStyle should do for dates without a day. This is not
// accurate for locales that always print the era
const dateStyleHacks = {
short: { year: '2-digit', month: 'numeric' },
medium: { year: 'numeric', month: 'short' },
long: { year: 'numeric', month: 'long' },
full: { year: 'numeric', month: 'long' }
};
const options = amend(originalOptions, {
day: false,
hour: false,
minute: false,
second: false,
weekday: false,
dayPeriod: false,
timeZoneName: false,
timeStyle: false
});
if ('dateStyle' in options && options.dateStyle) {
const style = options.dateStyle;
delete options.dateStyle;
Object.assign(options, dateStyleHacks[style]);
}
if (!('year' in options || 'month' in options || 'era' in options)) {
if (hasAnyDateTimeOptions(originalOptions)) {
throw new TypeError(`cannot format PlainYearMonth with options [${Object.keys(originalOptions)}]`);
}
Object.assign(options, { year: 'numeric', month: 'numeric' });
}
return options;
}
function monthDayAmend(originalOptions: OptionsType<Temporal.PlainMonthDay>) {
// Try to fake what dateStyle should do for dates without a day
const dateStyleHacks = {
short: { month: 'numeric', day: 'numeric' },
medium: { month: 'short', day: 'numeric' },
long: { month: 'long', day: 'numeric' },
full: { month: 'long', day: 'numeric' }
};
const options = amend(originalOptions, {
year: false,
hour: false,
minute: false,
second: false,
weekday: false,
dayPeriod: false,
timeZoneName: false,
timeStyle: false
});
if ('dateStyle' in options && options.dateStyle) {
const style = options.dateStyle;
delete options.dateStyle;
Object.assign(options, dateStyleHacks[style]);
}
if (!('month' in options || 'day' in options)) {
if (hasAnyDateTimeOptions(originalOptions)) {
throw new TypeError(`cannot format PlainMonthDay with options [${Object.keys(originalOptions)}]`);
}
Object.assign(options, { month: 'numeric', day: 'numeric' });
}
return options;
}
function dateAmend(originalOptions: OptionsType<Temporal.PlainDate>) {
const options = amend(originalOptions, {
hour: false,
minute: false,
second: false,
dayPeriod: false,
timeZoneName: false,
timeStyle: false
});
if (!hasDateOptions(options)) {
if (hasAnyDateTimeOptions(originalOptions)) {
throw new TypeError(`cannot format PlainDate with options [${Object.keys(originalOptions)}]`);
}
Object.assign(options, {
year: 'numeric',
month: 'numeric',
day: 'numeric'
});
}
return options;
}
function datetimeAmend(originalOptions: OptionsType<Temporal.PlainDateTime>) {
const options = amend(originalOptions, { timeZoneName: false });
if (options.timeStyle === 'long' || options.timeStyle === 'full') {
// Try to fake what timeStyle should do if not printing the time zone name
delete options.timeStyle;
Object.assign(options, { hour: 'numeric', minute: '2-digit', second: '2-digit' });
// If moving to a fake timeStyle while dateStyle is present, we also have to
// move to a fake dateStyle. dateStyle is mutually exclusive with hour etc.
if (options.dateStyle) {
const dateStyleHacks = {
short: { year: 'numeric', month: 'numeric', day: 'numeric' },
medium: { year: 'numeric', month: 'short', day: 'numeric' },
long: { year: 'numeric', month: 'long', day: 'numeric' },
full: { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' }
};
Object.assign(options, dateStyleHacks[options.dateStyle]);
delete options.dateStyle;
}
}
if (!hasTimeOptions(options) && !hasDateOptions(options)) {
if (hasAnyDateTimeOptions(originalOptions)) {
throw new TypeError(`cannot format PlainDateTime with options [${Object.keys(originalOptions)}]`);
}
Object.assign(options, {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric'
});
}
return options;
}
function instantAmend(optionsParam: OptionsType<Temporal.Instant>) {
let options = optionsParam;
if (!hasTimeOptions(options) && !hasDateOptions(options)) {
options = Object.assign({}, options, {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric'
});
}
return options;
}
function hasDateOptions(options: OptionsType<TypesWithToLocaleString>) {
return (
'year' in options ||
'month' in options ||
'day' in options ||
'weekday' in options ||
'dateStyle' in options ||
'era' in options
);
}
function hasTimeOptions(options: OptionsType<TypesWithToLocaleString>) {
return (
'hour' in options ||
'minute' in options ||
'second' in options ||
'timeStyle' in options ||
'dayPeriod' in options ||
'fractionalSecondDigits' in options
);
}
function hasAnyDateTimeOptions(originalOptions: OptionsType<TypesWithToLocaleString>) {
return (
hasDateOptions(originalOptions) ||
hasTimeOptions(originalOptions) ||
'dateStyle' in originalOptions ||
'timeStyle' in originalOptions ||
'timeZoneName' in originalOptions
);
}
function isTemporalObject(
obj: unknown
): obj is
| Temporal.PlainDate
| Temporal.PlainTime
| Temporal.PlainDateTime
| Temporal.ZonedDateTime
| Temporal.PlainYearMonth
| Temporal.PlainMonthDay
| Temporal.Instant {
return (
ES.IsTemporalDate(obj) ||
ES.IsTemporalTime(obj) ||
ES.IsTemporalDateTime(obj) ||
ES.IsTemporalZonedDateTime(obj) ||
ES.IsTemporalYearMonth(obj) ||
ES.IsTemporalMonthDay(obj) ||
ES.IsTemporalInstant(obj)
);
}
function toDateTimeFormattable(value: unknown) {
if (isTemporalObject(value)) return value;
return ES.ToNumber(value);
}
function sameTemporalType(x: unknown, y: unknown) {
if (!isTemporalObject(x) || !isTemporalObject(y)) return false;
if (ES.IsTemporalTime(x) && !ES.IsTemporalTime(y)) return false;
if (ES.IsTemporalDate(x) && !ES.IsTemporalDate(y)) return false;
if (ES.IsTemporalDateTime(x) && !ES.IsTemporalDateTime(y)) return false;
if (ES.IsTemporalZonedDateTime(x) && !ES.IsTemporalZonedDateTime(y)) return false;
if (ES.IsTemporalYearMonth(x) && !ES.IsTemporalYearMonth(y)) return false;
if (ES.IsTemporalMonthDay(x) && !ES.IsTemporalMonthDay(y)) return false;
if (ES.IsTemporalInstant(x) && !ES.IsTemporalInstant(y)) return false;
return true;
}
type TypesWithToLocaleString =
| Temporal.PlainDateTime
| Temporal.PlainDate
| Temporal.PlainTime
| Temporal.PlainYearMonth
| Temporal.PlainMonthDay
| Temporal.ZonedDateTime
| Temporal.Instant;
function extractOverrides(temporalObj: Params['format'][0], main: DateTimeFormatImpl) {
if (ES.IsTemporalTime(temporalObj)) {
const isoDateTime = {
isoDate: { year: 1970, month: 1, day: 1 },
time: GetSlot(temporalObj, TIME)
};
return {
epochNs: ES.GetEpochNanosecondsFor(GetSlot(main, TZ_CANONICAL), isoDateTime, 'compatible'),
formatter: getSlotLazy(main, TIME_FMT)
};
}
if (ES.IsTemporalYearMonth(temporalObj)) {
const calendar = GetSlot(temporalObj, CALENDAR);
const mainCalendar = GetSlot(main, CAL_ID);
if (calendar !== mainCalendar) {
throw new RangeError(
`cannot format PlainYearMonth with calendar ${calendar} in locale with calendar ${mainCalendar}`
);
}
const isoDateTime = ES.CombineISODateAndTimeRecord(GetSlot(temporalObj, ISO_DATE), ES.NoonTimeRecord());
return {
epochNs: ES.GetEpochNanosecondsFor(GetSlot(main, TZ_CANONICAL), isoDateTime, 'compatible'),
formatter: getSlotLazy(main, YM)
};
}
if (ES.IsTemporalMonthDay(temporalObj)) {
const calendar = GetSlot(temporalObj, CALENDAR);
const mainCalendar = GetSlot(main, CAL_ID);
if (calendar !== mainCalendar) {
throw new RangeError(
`cannot format PlainMonthDay with calendar ${calendar} in locale with calendar ${mainCalendar}`
);
}
const isoDateTime = ES.CombineISODateAndTimeRecord(GetSlot(temporalObj, ISO_DATE), ES.NoonTimeRecord());
return {
epochNs: ES.GetEpochNanosecondsFor(GetSlot(main, TZ_CANONICAL), isoDateTime, 'compatible'),
formatter: getSlotLazy(main, MD)
};
}
if (ES.IsTemporalDate(temporalObj)) {
const calendar = GetSlot(temporalObj, CALENDAR);
const mainCalendar = GetSlot(main, CAL_ID);
if (calendar !== 'iso8601' && calendar !== mainCalendar) {
throw new RangeError(`cannot format PlainDate with calendar ${calendar} in locale with calendar ${mainCalendar}`);
}
const isoDateTime = ES.CombineISODateAndTimeRecord(GetSlot(temporalObj, ISO_DATE), ES.NoonTimeRecord());
return {
epochNs: ES.GetEpochNanosecondsFor(GetSlot(main, TZ_CANONICAL), isoDateTime, 'compatible'),
formatter: getSlotLazy(main, DATE)
};
}
if (ES.IsTemporalDateTime(temporalObj)) {
const calendar = GetSlot(temporalObj, CALENDAR);
const mainCalendar = GetSlot(main, CAL_ID);
if (calendar !== 'iso8601' && calendar !== mainCalendar) {
throw new RangeError(
`cannot format PlainDateTime with calendar ${calendar} in locale with calendar ${mainCalendar}`
);
}
const isoDateTime = GetSlot(temporalObj, ISO_DATE_TIME);
return {
epochNs: ES.GetEpochNanosecondsFor(GetSlot(main, TZ_CANONICAL), isoDateTime, 'compatible'),
formatter: getSlotLazy(main, DATETIME)
};
}
if (ES.IsTemporalZonedDateTime(temporalObj)) {
throw new TypeError(
'Temporal.ZonedDateTime not supported in DateTimeFormat methods. Use toLocaleString() instead.'
);
}
if (ES.IsTemporalInstant(temporalObj)) {
return {
epochNs: GetSlot(temporalObj, EPOCHNANOSECONDS),
formatter: getSlotLazy(main, INST)
};
}
return {};
}
function temporalDurationToCompatibilityRecord(duration: Temporal.Duration) {
const record = Object.create(null);
record.years = GetSlot(duration, YEARS);
record.months = GetSlot(duration, MONTHS);
record.weeks = GetSlot(duration, WEEKS);
record.days = GetSlot(duration, DAYS);
record.hours = GetSlot(duration, HOURS);
record.minutes = GetSlot(duration, MINUTES);
record.seconds = GetSlot(duration, SECONDS);
record.milliseconds = GetSlot(duration, MILLISECONDS);
record.microseconds = GetSlot(duration, MICROSECONDS);
record.nanoseconds = GetSlot(duration, NANOSECONDS);
return record;
}
const { format: IntlDurationFormatPrototypeFormat, formatToParts: IntlDurationFormatPrototypeFormatToParts } =
Intl.DurationFormat?.prototype ?? Object.create(null);
export function ModifiedIntlDurationFormatPrototypeFormat(
this: Intl.DurationFormat,
durationLike: Temporal.DurationLike
) {
Intl.DurationFormat.prototype.resolvedOptions.call(this); // brand check
const duration = ES.ToTemporalDuration(durationLike);
const record = temporalDurationToCompatibilityRecord(duration);
return IntlDurationFormatPrototypeFormat.call(this, record);
}
if (Intl.DurationFormat?.prototype) {
Intl.DurationFormat.prototype.format = ModifiedIntlDurationFormatPrototypeFormat;
Intl.DurationFormat.prototype.formatToParts = function formatToParts(durationLike: Temporal.DurationLike) {
Intl.DurationFormat.prototype.resolvedOptions.call(this); // brand check
const duration = ES.ToTemporalDuration(durationLike);
const record = temporalDurationToCompatibilityRecord(duration);
return IntlDurationFormatPrototypeFormatToParts.call(this, record);
};
}