@js-temporal/polyfill
Version:
Polyfill for Tc39 Stage 3 proposal Temporal (https://github.com/tc39/proposal-temporal)
1,428 lines (1,342 loc) • 214 kB
text/typescript
const ArrayIncludes = Array.prototype.includes;
const ArrayPrototypePush = Array.prototype.push;
const IntlDateTimeFormat = globalThis.Intl.DateTimeFormat;
const MathMin = Math.min;
const MathMax = Math.max;
const MathAbs = Math.abs;
const MathFloor = Math.floor;
const MathSign = Math.sign;
const MathTrunc = Math.trunc;
const NumberIsNaN = Number.isNaN;
const NumberIsFinite = Number.isFinite;
const NumberCtor = Number;
const StringCtor = String;
const NumberMaxSafeInteger = Number.MAX_SAFE_INTEGER;
const ObjectAssign = Object.assign;
const ObjectCreate = Object.create;
const ObjectDefineProperty = Object.defineProperty;
const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
const ObjectIs = Object.is;
const ReflectApply = Reflect.apply;
import { DEBUG } from './debug';
import JSBI from 'jsbi';
import type { Temporal } from '..';
import type {
AnyTemporalLikeType,
UnitSmallerThanOrEqualTo,
CalendarProtocolParams,
TimeZoneProtocolParams,
InstantParams,
PlainMonthDayParams,
ZonedDateTimeParams,
CalendarParams,
TimeZoneParams,
PlainDateParams,
PlainTimeParams,
DurationParams,
PlainDateTimeParams,
PlainYearMonthParams,
PrimitiveFieldsOf,
BuiltinCalendarId
} from './internaltypes';
import { GetIntrinsic } from './intrinsicclass';
import {
CreateSlots,
GetSlot,
HasSlot,
SetSlot,
EPOCHNANOSECONDS,
TIMEZONE_ID,
CALENDAR_ID,
INSTANT,
ISO_YEAR,
ISO_MONTH,
ISO_DAY,
ISO_HOUR,
ISO_MINUTE,
ISO_SECOND,
ISO_MILLISECOND,
ISO_MICROSECOND,
ISO_NANOSECOND,
DATE_BRAND,
YEAR_MONTH_BRAND,
MONTH_DAY_BRAND,
TIME_ZONE,
CALENDAR,
YEARS,
MONTHS,
WEEKS,
DAYS,
HOURS,
MINUTES,
SECONDS,
MILLISECONDS,
MICROSECONDS,
NANOSECONDS
} from './slots';
export const ZERO = JSBI.BigInt(0);
const ONE = JSBI.BigInt(1);
const SIXTY = JSBI.BigInt(60);
export const THOUSAND = JSBI.BigInt(1e3);
export const MILLION = JSBI.BigInt(1e6);
export const BILLION = JSBI.BigInt(1e9);
const NEGATIVE_ONE = JSBI.BigInt(-1);
const DAY_SECONDS = 86400;
const DAY_NANOS = JSBI.multiply(JSBI.BigInt(DAY_SECONDS), BILLION);
const NS_MIN = JSBI.multiply(JSBI.BigInt(-86400), JSBI.BigInt(1e17));
const NS_MAX = JSBI.multiply(JSBI.BigInt(86400), JSBI.BigInt(1e17));
const YEAR_MIN = -271821;
const YEAR_MAX = 275760;
const BEFORE_FIRST_OFFSET_TRANSITION = JSBI.multiply(JSBI.BigInt(-388152), JSBI.BigInt(1e13)); // 1847-01-01T00:00:00Z
const ABOUT_TEN_YEARS_NANOS = JSBI.multiply(DAY_NANOS, JSBI.BigInt(366 * 10));
const ABOUT_ONE_YEAR_NANOS = JSBI.multiply(DAY_NANOS, JSBI.BigInt(366 * 1));
const TWO_WEEKS_NANOS = JSBI.multiply(DAY_NANOS, JSBI.BigInt(2 * 7));
const BUILTIN_CALENDAR_IDS = [
'iso8601',
'hebrew',
'islamic',
'islamic-umalqura',
'islamic-tbla',
'islamic-civil',
'islamic-rgsa',
'islamicc',
'persian',
'ethiopic',
'ethioaa',
'coptic',
'chinese',
'dangi',
'roc',
'indian',
'buddhist',
'japanese',
'gregory'
];
function IsInteger(value: unknown): value is number {
if (typeof value !== 'number' || !NumberIsFinite(value)) return false;
const abs = MathAbs(value);
return MathFloor(abs) === abs;
}
// For unknown values, this narrows the result to a Record. But for union types
// like `Temporal.DurationLike | string`, it'll strip the primitive types while
// leaving the object type(s) unchanged.
export function IsObject<T>(
value: T
): value is Exclude<T, string | null | undefined | number | bigint | symbol | boolean>;
export function IsObject(value: unknown): value is Record<string | number | symbol, unknown> {
return (typeof value === 'object' && value !== null) || typeof value === 'function';
}
export function ToNumber(value: unknown): number {
if (typeof value === 'bigint') throw new TypeError('Cannot convert BigInt to number');
return NumberCtor(value);
}
function ToInteger(value: unknown): number {
const num = ToNumber(value);
if (NumberIsNaN(num)) return 0;
const integer = MathTrunc(num);
if (num === 0) return 0;
return integer;
}
export function ToString(value: unknown): string {
if (typeof value === 'symbol') {
throw new TypeError('Cannot convert a Symbol value to a String');
}
return StringCtor(value);
}
export function ToIntegerThrowOnInfinity(value: unknown): number {
const integer = ToInteger(value);
if (!NumberIsFinite(integer)) {
throw new RangeError('infinity is out of range');
}
return integer;
}
function ToPositiveInteger(valueParam: unknown, property?: string): number {
const value = ToInteger(valueParam);
if (!NumberIsFinite(value)) {
throw new RangeError('infinity is out of range');
}
if (value < 1) {
if (property !== undefined) {
throw new RangeError(`property '${property}' cannot be a a number less than one`);
}
throw new RangeError('Cannot convert a number less than one to a positive integer');
}
return value;
}
export function ToIntegerWithoutRounding(valueParam: unknown): number {
const value = ToNumber(valueParam);
if (NumberIsNaN(value)) return 0;
if (!NumberIsFinite(value)) {
throw new RangeError('infinity is out of range');
}
if (!IsInteger(value)) {
throw new RangeError(`unsupported fractional value ${value}`);
}
return ToInteger(value); // ℝ(value) in spec text; converts -0 to 0
}
function divmod(x: JSBI, y: JSBI): { quotient: JSBI; remainder: JSBI } {
const quotient = JSBI.divide(x, y);
const remainder = JSBI.remainder(x, y);
return { quotient, remainder };
}
function abs(x: JSBI): JSBI {
if (JSBI.lessThan(x, ZERO)) return JSBI.multiply(x, NEGATIVE_ONE);
return x;
}
export function ArrayPush<NewValue>(
arr: ReadonlyArray<NewValue>,
...newItem: readonly NewValue[]
): ReadonlyArray<NewValue> {
ArrayPrototypePush.apply(arr, newItem as any[]);
return arr;
}
type BuiltinCastFunction = (v: unknown) => string | number;
const BUILTIN_CASTS = new Map<AnyTemporalKey, BuiltinCastFunction>([
['year', ToIntegerThrowOnInfinity],
['month', ToPositiveInteger],
['monthCode', ToString],
['day', ToPositiveInteger],
['hour', ToIntegerThrowOnInfinity],
['minute', ToIntegerThrowOnInfinity],
['second', ToIntegerThrowOnInfinity],
['millisecond', ToIntegerThrowOnInfinity],
['microsecond', ToIntegerThrowOnInfinity],
['nanosecond', ToIntegerThrowOnInfinity],
['years', ToIntegerWithoutRounding],
['months', ToIntegerWithoutRounding],
['weeks', ToIntegerWithoutRounding],
['days', ToIntegerWithoutRounding],
['hours', ToIntegerWithoutRounding],
['minutes', ToIntegerWithoutRounding],
['seconds', ToIntegerWithoutRounding],
['milliseconds', ToIntegerWithoutRounding],
['microseconds', ToIntegerWithoutRounding],
['nanoseconds', ToIntegerWithoutRounding],
['era', ToString],
['eraYear', ToInteger],
['offset', ToString]
]);
const BUILTIN_DEFAULTS = new Map([
['hour', 0],
['minute', 0],
['second', 0],
['millisecond', 0],
['microsecond', 0],
['nanosecond', 0]
]);
// each item is [plural, singular, category]
const SINGULAR_PLURAL_UNITS = [
['years', 'year', 'date'],
['months', 'month', 'date'],
['weeks', 'week', 'date'],
['days', 'day', 'date'],
['hours', 'hour', 'time'],
['minutes', 'minute', 'time'],
['seconds', 'second', 'time'],
['milliseconds', 'millisecond', 'time'],
['microseconds', 'microsecond', 'time'],
['nanoseconds', 'nanosecond', 'time']
] as const;
const SINGULAR_FOR = new Map(SINGULAR_PLURAL_UNITS.map((e) => [e[0], e[1]] as const));
const PLURAL_FOR = new Map(SINGULAR_PLURAL_UNITS.map(([p, s]) => [s, p]));
const UNITS_DESCENDING = SINGULAR_PLURAL_UNITS.map(([, s]) => s);
const DURATION_FIELDS = Array.from(SINGULAR_FOR.keys()).sort();
import * as PARSE from './regex';
const IntlDateTimeFormatEnUsCache = new Map<string, Intl.DateTimeFormat>();
function getIntlDateTimeFormatEnUsForTimeZone(timeZoneIdentifier: string) {
let instance = IntlDateTimeFormatEnUsCache.get(timeZoneIdentifier);
if (instance === undefined) {
instance = new IntlDateTimeFormat('en-us', {
timeZone: StringCtor(timeZoneIdentifier),
hour12: false,
era: 'short',
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric'
});
IntlDateTimeFormatEnUsCache.set(timeZoneIdentifier, instance);
}
return instance;
}
export function IsTemporalInstant(item: unknown): item is Temporal.Instant {
return HasSlot(item, EPOCHNANOSECONDS) && !HasSlot(item, TIME_ZONE, CALENDAR);
}
export function IsTemporalTimeZone(item: unknown): item is Temporal.TimeZone {
return HasSlot(item, TIMEZONE_ID);
}
export function IsTemporalCalendar(item: unknown): item is Temporal.Calendar {
return HasSlot(item, CALENDAR_ID);
}
export function IsTemporalDuration(item: unknown): item is Temporal.Duration {
return HasSlot(item, YEARS, MONTHS, DAYS, HOURS, MINUTES, SECONDS, MILLISECONDS, MICROSECONDS, NANOSECONDS);
}
export function IsTemporalDate(item: unknown): item is Temporal.PlainDate {
return HasSlot(item, DATE_BRAND);
}
export function IsTemporalTime(item: unknown): item is Temporal.PlainTime {
return (
HasSlot(item, ISO_HOUR, ISO_MINUTE, ISO_SECOND, ISO_MILLISECOND, ISO_MICROSECOND, ISO_NANOSECOND) &&
!HasSlot(item, ISO_YEAR, ISO_MONTH, ISO_DAY)
);
}
export function IsTemporalDateTime(item: unknown): item is Temporal.PlainDateTime {
return HasSlot(
item,
ISO_YEAR,
ISO_MONTH,
ISO_DAY,
ISO_HOUR,
ISO_MINUTE,
ISO_SECOND,
ISO_MILLISECOND,
ISO_MICROSECOND,
ISO_NANOSECOND
);
}
export function IsTemporalYearMonth(item: unknown): item is Temporal.PlainYearMonth {
return HasSlot(item, YEAR_MONTH_BRAND);
}
export function IsTemporalMonthDay(item: unknown): item is Temporal.PlainMonthDay {
return HasSlot(item, MONTH_DAY_BRAND);
}
export function IsTemporalZonedDateTime(item: unknown): item is Temporal.ZonedDateTime {
return HasSlot(item, EPOCHNANOSECONDS, TIME_ZONE, CALENDAR);
}
export function RejectObjectWithCalendarOrTimeZone(item: AnyTemporalLikeType) {
if (HasSlot(item, CALENDAR) || HasSlot(item, TIME_ZONE)) {
throw new TypeError('with() does not support a calendar or timeZone property');
}
if ((item as { calendar: unknown }).calendar !== undefined) {
throw new TypeError('with() does not support a calendar property');
}
if ((item as { timeZone: unknown }).timeZone !== undefined) {
throw new TypeError('with() does not support a timeZone property');
}
}
function ParseTemporalTimeZone(stringIdent: string) {
let { ianaName, offset, z } = ParseTemporalTimeZoneString(stringIdent);
if (ianaName) return ianaName;
if (z) return 'UTC';
return offset as string; // if !ianaName && !z then offset must be present
}
function FormatCalendarAnnotation(id: string, showCalendar: Temporal.ShowCalendarOption['calendarName']) {
if (showCalendar === 'never') return '';
if (showCalendar === 'auto' && id === 'iso8601') return '';
return `[u-ca=${id}]`;
}
function ParseISODateTime(isoString: string) {
// ZDT is the superset of fields for every other Temporal type
const match = PARSE.zoneddatetime.exec(isoString);
if (!match) throw new RangeError(`invalid ISO 8601 string: ${isoString}`);
let yearString = match[1];
if (yearString[0] === '\u2212') yearString = `-${yearString.slice(1)}`;
if (yearString === '-000000') throw new RangeError(`invalid ISO 8601 string: ${isoString}`);
const year = ToInteger(yearString);
const month = ToInteger(match[2] || match[4]);
const day = ToInteger(match[3] || match[5]);
const hour = ToInteger(match[6]);
const hasTime = match[6] !== undefined;
const minute = ToInteger(match[7] || match[10]);
let second = ToInteger(match[8] || match[11]);
if (second === 60) second = 59;
const fraction = (match[9] || match[12]) + '000000000';
const millisecond = ToInteger(fraction.slice(0, 3));
const microsecond = ToInteger(fraction.slice(3, 6));
const nanosecond = ToInteger(fraction.slice(6, 9));
let offset;
let z = false;
if (match[13]) {
offset = undefined;
z = true;
} else if (match[14] && match[15]) {
const offsetSign = match[14] === '-' || match[14] === '\u2212' ? '-' : '+';
const offsetHours = match[15] || '00';
const offsetMinutes = match[16] || '00';
const offsetSeconds = match[17] || '00';
let offsetFraction = match[18] || '0';
offset = `${offsetSign}${offsetHours}:${offsetMinutes}`;
if (+offsetFraction) {
while (offsetFraction.endsWith('0')) offsetFraction = offsetFraction.slice(0, -1);
offset += `:${offsetSeconds}.${offsetFraction}`;
} else if (+offsetSeconds) {
offset += `:${offsetSeconds}`;
}
if (offset === '-00:00') offset = '+00:00';
}
let ianaName = match[19];
if (ianaName) {
try {
// Canonicalize name if it is an IANA link name or is capitalized wrong
ianaName = GetCanonicalTimeZoneIdentifier(ianaName).toString();
} catch {
// Not an IANA name, may be a custom ID, pass through unchanged
}
}
const calendar = match[20];
RejectDateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond);
return {
year,
month,
day,
hasTime,
hour,
minute,
second,
millisecond,
microsecond,
nanosecond,
ianaName,
offset,
z,
calendar
};
}
// ts-prune-ignore-next TODO: remove if test/validStrings is converted to TS.
export function ParseTemporalInstantString(isoString: string) {
const result = ParseISODateTime(isoString);
if (!result.z && !result.offset) throw new RangeError('Temporal.Instant requires a time zone offset');
return result;
}
// ts-prune-ignore-next TODO: remove if test/validStrings is converted to TS.
export function ParseTemporalZonedDateTimeString(isoString: string) {
const result = ParseISODateTime(isoString);
if (!result.ianaName) throw new RangeError('Temporal.ZonedDateTime requires a time zone ID in brackets');
return result;
}
// ts-prune-ignore-next TODO: remove if test/validStrings is converted to TS.
export function ParseTemporalDateTimeString(isoString: string) {
return ParseISODateTime(isoString);
}
// ts-prune-ignore-next TODO: remove if test/validStrings is converted to TS.
export function ParseTemporalDateString(isoString: string) {
return ParseISODateTime(isoString);
}
// ts-prune-ignore-next TODO: remove if test/validStrings is converted to TS.
export function ParseTemporalTimeString(isoString: string) {
const match = PARSE.time.exec(isoString);
let hour, minute, second, millisecond, microsecond, nanosecond, calendar;
if (match) {
hour = ToInteger(match[1]);
minute = ToInteger(match[2] || match[5]);
second = ToInteger(match[3] || match[6]);
if (second === 60) second = 59;
const fraction = (match[4] || match[7]) + '000000000';
millisecond = ToInteger(fraction.slice(0, 3));
microsecond = ToInteger(fraction.slice(3, 6));
nanosecond = ToInteger(fraction.slice(6, 9));
calendar = match[15];
} else {
let z, hasTime;
({ hasTime, hour, minute, second, millisecond, microsecond, nanosecond, calendar, z } =
ParseISODateTime(isoString));
if (!hasTime) throw new RangeError(`time is missing in string: ${isoString}`);
if (z) throw new RangeError('Z designator not supported for PlainTime');
}
// if it's a date-time string, OK
if (/[tT ][0-9][0-9]/.test(isoString)) {
return { hour, minute, second, millisecond, microsecond, nanosecond, calendar };
}
// slow but non-grammar-dependent way to ensure that time-only strings that
// are also valid PlainMonthDay and PlainYearMonth throw. corresponds to
// assertion in spec text
try {
const { month, day } = ParseTemporalMonthDayString(isoString);
RejectISODate(1972, month, day);
} catch {
try {
const { year, month } = ParseTemporalYearMonthString(isoString);
RejectISODate(year, month, 1);
} catch {
return { hour, minute, second, millisecond, microsecond, nanosecond, calendar };
}
}
throw new RangeError(`invalid ISO 8601 time-only string ${isoString}; may need a T prefix`);
}
// ts-prune-ignore-next TODO: remove if test/validStrings is converted to TS.
export function ParseTemporalYearMonthString(isoString: string) {
const match = PARSE.yearmonth.exec(isoString);
let year, month, calendar, referenceISODay;
if (match) {
let yearString = match[1];
if (yearString[0] === '\u2212') yearString = `-${yearString.slice(1)}`;
if (yearString === '-000000') throw new RangeError(`invalid ISO 8601 string: ${isoString}`);
year = ToInteger(yearString);
month = ToInteger(match[2]);
calendar = match[3];
} else {
let z;
({ year, month, calendar, day: referenceISODay, z } = ParseISODateTime(isoString));
if (z) throw new RangeError('Z designator not supported for PlainYearMonth');
}
return { year, month, calendar, referenceISODay };
}
// ts-prune-ignore-next TODO: remove if test/validStrings is converted to TS.
export function ParseTemporalMonthDayString(isoString: string) {
const match = PARSE.monthday.exec(isoString);
let month, day, calendar, referenceISOYear;
if (match) {
month = ToInteger(match[1]);
day = ToInteger(match[2]);
} else {
let z;
({ month, day, calendar, year: referenceISOYear, z } = ParseISODateTime(isoString));
if (z) throw new RangeError('Z designator not supported for PlainMonthDay');
}
return { month, day, calendar, referenceISOYear };
}
// ts-prune-ignore-next TODO: remove if test/validStrings is converted to TS.
export function ParseTemporalTimeZoneString(stringIdent: string): Partial<{
ianaName: string | undefined;
offset: string | undefined;
z: boolean | undefined;
}> {
try {
let canonicalIdent = GetCanonicalTimeZoneIdentifier(stringIdent);
if (canonicalIdent) return { ianaName: canonicalIdent.toString() };
} catch {
// fall through
}
try {
// Try parsing ISO string instead
const result = ParseISODateTime(stringIdent);
if (result.z || result.offset || result.ianaName) {
return result;
}
} catch {
// fall through
}
throw new RangeError(`Invalid time zone: ${stringIdent}`);
}
// ts-prune-ignore-next TODO: remove if test/validStrings is converted to TS.
export function ParseTemporalDurationString(isoString: string) {
const match = PARSE.duration.exec(isoString);
if (!match) throw new RangeError(`invalid duration: ${isoString}`);
if (match.slice(2).every((element) => element === undefined)) {
throw new RangeError(`invalid duration: ${isoString}`);
}
const sign = match[1] === '-' || match[1] === '\u2212' ? -1 : 1;
const years = ToInteger(match[2]) * sign;
const months = ToInteger(match[3]) * sign;
const weeks = ToInteger(match[4]) * sign;
const days = ToInteger(match[5]) * sign;
const hours = ToInteger(match[6]) * sign;
let fHours: number | string = match[7];
let minutes = ToInteger(match[8]) * sign;
let fMinutes: number | string = match[9];
let seconds = ToInteger(match[10]) * sign;
const fSeconds = match[11] + '000000000';
let milliseconds = ToInteger(fSeconds.slice(0, 3)) * sign;
let microseconds = ToInteger(fSeconds.slice(3, 6)) * sign;
let nanoseconds = ToInteger(fSeconds.slice(6, 9)) * sign;
fHours = fHours ? (sign * ToInteger(fHours)) / 10 ** fHours.length : 0;
fMinutes = fMinutes ? (sign * ToInteger(fMinutes)) / 10 ** fMinutes.length : 0;
({ minutes, seconds, milliseconds, microseconds, nanoseconds } = DurationHandleFractions(
fHours,
minutes,
fMinutes,
seconds,
milliseconds,
microseconds,
nanoseconds
));
RejectDuration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds);
return { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds };
}
// ts-prune-ignore-next TODO: remove if test/validStrings is converted to TS.
export function ParseTemporalInstant(isoString: string) {
let { year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, offset, z } =
ParseTemporalInstantString(isoString);
if (!z && !offset) throw new RangeError('Temporal.Instant requires a time zone offset');
// At least one of z or offset is defined, but TS doesn't seem to understand
// that we only use offset if z is not defined (and thus offset must be defined).
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const offsetNs = z ? 0 : ParseTimeZoneOffsetString(offset!);
({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = BalanceISODateTime(
year,
month,
day,
hour,
minute,
second,
millisecond,
microsecond,
nanosecond - offsetNs
));
const epochNs = GetEpochFromISOParts(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond);
if (epochNs === null) throw new RangeError('DateTime outside of supported range');
return epochNs;
}
export function RegulateISODate(
yearParam: number,
monthParam: number,
dayParam: number,
overflow: Temporal.ArithmeticOptions['overflow']
) {
let year = yearParam;
let month = monthParam;
let day = dayParam;
switch (overflow) {
case 'reject':
RejectISODate(year, month, day);
break;
case 'constrain':
({ year, month, day } = ConstrainISODate(year, month, day));
break;
}
return { year, month, day };
}
export function RegulateTime(
hourParam: number,
minuteParam: number,
secondParam: number,
millisecondParam: number,
microsecondParam: number,
nanosecondParam: number,
overflow: Temporal.ArithmeticOptions['overflow']
) {
let hour = hourParam;
let minute = minuteParam;
let second = secondParam;
let millisecond = millisecondParam;
let microsecond = microsecondParam;
let nanosecond = nanosecondParam;
switch (overflow) {
case 'reject':
RejectTime(hour, minute, second, millisecond, microsecond, nanosecond);
break;
case 'constrain':
({ hour, minute, second, millisecond, microsecond, nanosecond } = ConstrainTime(
hour,
minute,
second,
millisecond,
microsecond,
nanosecond
));
break;
}
return { hour, minute, second, millisecond, microsecond, nanosecond };
}
export function RegulateISOYearMonth(
yearParam: number,
monthParam: number,
overflow: Temporal.ArithmeticOptions['overflow']
) {
let year = yearParam;
let month = monthParam;
const referenceISODay = 1;
switch (overflow) {
case 'reject':
RejectISODate(year, month, referenceISODay);
break;
case 'constrain':
({ year, month } = ConstrainISODate(year, month));
break;
}
return { year, month };
}
function DurationHandleFractions(
fHoursParam: number,
minutesParam: number,
fMinutesParam: number,
secondsParam: number,
millisecondsParam: number,
microsecondsParam: number,
nanosecondsParam: number
) {
let fHours = fHoursParam;
let minutes = minutesParam;
let fMinutes = fMinutesParam;
let seconds = secondsParam;
let milliseconds = millisecondsParam;
let microseconds = microsecondsParam;
let nanoseconds = nanosecondsParam;
if (fHours !== 0) {
[minutes, fMinutes, seconds, milliseconds, microseconds, nanoseconds].forEach((val) => {
if (val !== 0) throw new RangeError('only the smallest unit can be fractional');
});
const mins = fHours * 60;
minutes = MathTrunc(mins);
fMinutes = mins % 1;
}
if (fMinutes !== 0) {
[seconds, milliseconds, microseconds, nanoseconds].forEach((val) => {
if (val !== 0) throw new RangeError('only the smallest unit can be fractional');
});
const secs = fMinutes * 60;
seconds = MathTrunc(secs);
const fSeconds = secs % 1;
if (fSeconds !== 0) {
const mils = fSeconds * 1000;
milliseconds = MathTrunc(mils);
const fMilliseconds = mils % 1;
if (fMilliseconds !== 0) {
const mics = fMilliseconds * 1000;
microseconds = MathTrunc(mics);
const fMicroseconds = mics % 1;
if (fMicroseconds !== 0) {
const nans = fMicroseconds * 1000;
nanoseconds = MathTrunc(nans);
}
}
}
}
return { minutes, seconds, milliseconds, microseconds, nanoseconds };
}
function ToTemporalDurationRecord(item: Temporal.DurationLike | string) {
if (!IsObject(item)) {
return ParseTemporalDurationString(ToString(item));
}
if (IsTemporalDuration(item)) {
return {
years: GetSlot(item, YEARS),
months: GetSlot(item, MONTHS),
weeks: GetSlot(item, WEEKS),
days: GetSlot(item, DAYS),
hours: GetSlot(item, HOURS),
minutes: GetSlot(item, MINUTES),
seconds: GetSlot(item, SECONDS),
milliseconds: GetSlot(item, MILLISECONDS),
microseconds: GetSlot(item, MICROSECONDS),
nanoseconds: GetSlot(item, NANOSECONDS)
};
}
const result = {
years: 0,
months: 0,
weeks: 0,
days: 0,
hours: 0,
minutes: 0,
seconds: 0,
milliseconds: 0,
microseconds: 0,
nanoseconds: 0
};
let partial = ToTemporalPartialDurationRecord(item);
for (const property of DURATION_FIELDS) {
const value = partial[property];
if (value !== undefined) {
result[property] = value;
}
}
let { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = result;
RejectDuration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds);
return { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds };
}
function ToTemporalPartialDurationRecord(temporalDurationLike: Temporal.DurationLike | string) {
if (!IsObject(temporalDurationLike)) {
throw new TypeError('invalid duration-like');
}
const result: Record<typeof DURATION_FIELDS[number], number | undefined> = {
years: undefined,
months: undefined,
weeks: undefined,
days: undefined,
hours: undefined,
minutes: undefined,
seconds: undefined,
milliseconds: undefined,
microseconds: undefined,
nanoseconds: undefined
};
let any = false;
for (const property of DURATION_FIELDS) {
const value = temporalDurationLike[property];
if (value !== undefined) {
any = true;
result[property] = ToIntegerWithoutRounding(value);
}
}
if (!any) {
throw new TypeError('invalid duration-like');
}
return result;
}
function ToLimitedTemporalDuration(
item: Temporal.DurationLike | string,
disallowedProperties: (keyof Temporal.DurationLike)[]
) {
let record = ToTemporalDurationRecord(item);
for (const property of disallowedProperties) {
if (record[property] !== 0) {
throw new RangeError(
`Duration field ${property} not supported by Temporal.Instant. Try Temporal.ZonedDateTime instead.`
);
}
}
return record;
}
export function ToTemporalOverflow(options: Temporal.AssignmentOptions | undefined) {
if (options === undefined) return 'constrain';
return GetOption(options, 'overflow', ['constrain', 'reject'], 'constrain');
}
export function ToTemporalDisambiguation(options: Temporal.ToInstantOptions | undefined) {
if (options === undefined) return 'compatible';
return GetOption(options, 'disambiguation', ['compatible', 'earlier', 'later', 'reject'], 'compatible');
}
export function ToTemporalRoundingMode(
options: { roundingMode?: Temporal.RoundingMode },
fallback: Temporal.RoundingMode
) {
return GetOption(options, 'roundingMode', ['ceil', 'floor', 'trunc', 'halfExpand'], fallback);
}
function NegateTemporalRoundingMode(roundingMode: Temporal.RoundingMode) {
switch (roundingMode) {
case 'ceil':
return 'floor';
case 'floor':
return 'ceil';
default:
return roundingMode;
}
}
export function ToTemporalOffset(
options: Temporal.OffsetDisambiguationOptions | undefined,
fallback: Required<Temporal.OffsetDisambiguationOptions>['offset']
) {
if (options === undefined) return fallback;
return GetOption(options, 'offset', ['prefer', 'use', 'ignore', 'reject'], fallback);
}
export function ToShowCalendarOption(options: Temporal.ShowCalendarOption) {
return GetOption(options, 'calendarName', ['auto', 'always', 'never'], 'auto');
}
export function ToShowTimeZoneNameOption(options: Temporal.ZonedDateTimeToStringOptions) {
return GetOption(options, 'timeZoneName', ['auto', 'never'], 'auto');
}
export function ToShowOffsetOption(options: Temporal.ZonedDateTimeToStringOptions) {
return GetOption(options, 'offset', ['auto', 'never'], 'auto');
}
export function ToTemporalRoundingIncrement(
options: { roundingIncrement?: number },
dividend: number | undefined,
inclusive?: boolean
) {
let maximum = Infinity;
if (dividend !== undefined) maximum = dividend;
if (!inclusive && dividend !== undefined) maximum = dividend > 1 ? dividend - 1 : 1;
const increment = GetNumberOption(options, 'roundingIncrement', 1, maximum, 1);
if (dividend !== undefined && dividend % increment !== 0) {
throw new RangeError(`Rounding increment must divide evenly into ${dividend}`);
}
return increment;
}
type TemporalDateTimeRoundingMaximumIncrements = {
year?: number;
month?: number;
week?: number;
day?: number;
hour?: number;
minute: number;
second: number;
millisecond: number;
microsecond: number;
nanosecond: number;
};
export function ToTemporalDateTimeRoundingIncrement(
options: { roundingIncrement?: number },
smallestUnit: keyof TemporalDateTimeRoundingMaximumIncrements
) {
const maximumIncrements: TemporalDateTimeRoundingMaximumIncrements = {
year: undefined,
month: undefined,
week: undefined,
day: undefined,
hour: 24,
minute: 60,
second: 60,
millisecond: 1000,
microsecond: 1000,
nanosecond: 1000
};
return ToTemporalRoundingIncrement(options, maximumIncrements[smallestUnit], false);
}
export function ToSecondsStringPrecision(options: Temporal.ToStringPrecisionOptions): {
precision: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 'auto' | 'minute';
unit: UnitSmallerThanOrEqualTo<'minute'>;
increment: number;
} {
const smallestUnit = GetTemporalUnit(options, 'smallestUnit', 'time', undefined);
if (smallestUnit === 'hour') {
const ALLOWED_UNITS = SINGULAR_PLURAL_UNITS.reduce((allowed, [p, s, c]) => {
// Weirdly, local type inference seems to understand the types of s and p, but tsc still complains.
// Maybe this is fixed in later TS versions?
if (c === 'time' && s !== 'hour') {
allowed.push(s as Temporal.TimeUnit, p as Temporal.PluralUnit<Temporal.TimeUnit>);
}
return allowed;
}, [] as Array<Temporal.TimeUnit | Temporal.PluralUnit<Temporal.TimeUnit>>);
throw new RangeError(`smallestUnit must be one of ${ALLOWED_UNITS.join(', ')}, not ${smallestUnit}`);
}
switch (smallestUnit) {
case 'minute':
return { precision: 'minute', unit: 'minute', increment: 1 };
case 'second':
return { precision: 0, unit: 'second', increment: 1 };
case 'millisecond':
return { precision: 3, unit: 'millisecond', increment: 1 };
case 'microsecond':
return { precision: 6, unit: 'microsecond', increment: 1 };
case 'nanosecond':
return { precision: 9, unit: 'nanosecond', increment: 1 };
default: // fall through if option not given
}
let digits = options.fractionalSecondDigits;
if (digits === undefined) digits = 'auto';
if (typeof digits !== 'number') {
const stringDigits = ToString(digits);
if (stringDigits === 'auto') return { precision: 'auto', unit: 'nanosecond', increment: 1 };
throw new RangeError(`fractionalSecondDigits must be 'auto' or 0 through 9, not ${stringDigits}`);
}
if (NumberIsNaN(digits) || digits < 0 || digits > 9) {
throw new RangeError(`fractionalSecondDigits must be 'auto' or 0 through 9, not ${digits}`);
}
const precision = MathFloor(digits);
switch (precision) {
case 0:
return { precision, unit: 'second', increment: 1 };
case 1:
case 2:
case 3:
return { precision, unit: 'millisecond', increment: 10 ** (3 - precision) };
case 4:
case 5:
case 6:
return { precision, unit: 'microsecond', increment: 10 ** (6 - precision) };
case 7:
case 8:
case 9:
return { precision, unit: 'nanosecond', increment: 10 ** (9 - precision) };
default:
throw new RangeError(`fractionalSecondDigits must be 'auto' or 0 through 9, not ${digits}`);
}
}
export const REQUIRED = Symbol('~required~');
interface TemporalUnitOptionsBag {
smallestUnit?: Temporal.PluralUnit<Temporal.DateTimeUnit> | Temporal.DateTimeUnit;
largestUnit?: Temporal.PluralUnit<Temporal.DateTimeUnit> | Temporal.DateTimeUnit | 'auto';
unit?: Temporal.PluralUnit<Temporal.DateTimeUnit> | Temporal.DateTimeUnit;
}
type UnitTypeMapping = {
date: Temporal.DateUnit;
time: Temporal.TimeUnit;
datetime: Temporal.DateTimeUnit;
};
// This type specifies the allowed defaults for each unit key type.
type AllowedGetTemporalUnitDefaultValues = {
smallestUnit: undefined;
largestUnit: 'auto' | undefined;
unit: undefined;
};
export function GetTemporalUnit<
U extends keyof TemporalUnitOptionsBag,
T extends keyof UnitTypeMapping,
D extends typeof REQUIRED | UnitTypeMapping[T] | AllowedGetTemporalUnitDefaultValues[U],
R extends Exclude<D, typeof REQUIRED> | UnitTypeMapping[T]
>(options: TemporalUnitOptionsBag, key: U, unitGroup: T, requiredOrDefault: D): R;
export function GetTemporalUnit<
U extends keyof TemporalUnitOptionsBag,
T extends keyof UnitTypeMapping,
D extends typeof REQUIRED | UnitTypeMapping[T] | AllowedGetTemporalUnitDefaultValues[U],
E extends 'auto' | Temporal.DateTimeUnit,
R extends UnitTypeMapping[T] | Exclude<D, typeof REQUIRED> | E
>(options: TemporalUnitOptionsBag, key: U, unitGroup: T, requiredOrDefault: D, extraValues: ReadonlyArray<E>): R;
// This signature of the function is NOT used in type-checking, so restricting
// the default value via generic binding like the other overloads isn't
// necessary.
export function GetTemporalUnit<
T extends keyof UnitTypeMapping,
D extends typeof REQUIRED | UnitTypeMapping[T] | 'auto' | undefined,
E extends 'auto' | Temporal.DateTimeUnit,
R extends UnitTypeMapping[T] | Exclude<D, typeof REQUIRED> | E
>(
options: TemporalUnitOptionsBag,
key: keyof typeof options,
unitGroup: T,
requiredOrDefault: D,
extraValues: ReadonlyArray<E> | never[] = []
): R {
const allowedSingular: Array<Temporal.DateTimeUnit | 'auto'> = [];
for (const [, singular, category] of SINGULAR_PLURAL_UNITS) {
if (unitGroup === 'datetime' || unitGroup === category) {
allowedSingular.push(singular);
}
}
allowedSingular.push(...extraValues);
let defaultVal: typeof REQUIRED | Temporal.DateTimeUnit | 'auto' | undefined = requiredOrDefault;
if (defaultVal === REQUIRED) {
defaultVal = undefined;
} else if (defaultVal !== undefined) {
allowedSingular.push(defaultVal);
}
const allowedValues: Array<Temporal.DateTimeUnit | Temporal.PluralUnit<Temporal.DateTimeUnit> | 'auto'> = [
...allowedSingular
];
for (const singular of allowedSingular) {
const plural = PLURAL_FOR.get(singular as Parameters<typeof PLURAL_FOR.get>[0]);
if (plural !== undefined) allowedValues.push(plural);
}
let retval = GetOption(options, key, allowedValues, defaultVal);
if (retval === undefined && requiredOrDefault === REQUIRED) {
throw new RangeError(`${key} is required`);
}
// Coerce any plural units into their singular form
if (SINGULAR_FOR.has(retval as Temporal.PluralUnit<Temporal.DateTimeUnit>)) {
// We just has-checked this, but tsc doesn't understand that.
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return SINGULAR_FOR.get(retval as Temporal.PluralUnit<Temporal.DateTimeUnit>)! as R;
}
return retval as R;
}
export function ToRelativeTemporalObject(options: {
relativeTo?:
| Temporal.ZonedDateTime
| Temporal.PlainDateTime
| Temporal.ZonedDateTimeLike
| Temporal.PlainDateTimeLike
| string
| undefined;
}): Temporal.ZonedDateTime | Temporal.PlainDate | undefined {
const relativeTo = options.relativeTo;
if (relativeTo === undefined) return relativeTo;
let offsetBehaviour: OffsetBehaviour = 'option';
let matchMinutes = false;
let year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, calendar, timeZone, offset;
if (IsObject(relativeTo)) {
if (IsTemporalZonedDateTime(relativeTo) || IsTemporalDate(relativeTo)) return relativeTo;
if (IsTemporalDateTime(relativeTo)) return TemporalDateTimeToDate(relativeTo);
calendar = GetTemporalCalendarWithISODefault(relativeTo);
const fieldNames = CalendarFields(calendar, [
'day',
'hour',
'microsecond',
'millisecond',
'minute',
'month',
'monthCode',
'nanosecond',
'second',
'year'
] as const);
const fields = PrepareTemporalFields(relativeTo, fieldNames, []);
const dateOptions = ObjectCreate(null);
dateOptions.overflow = 'constrain';
({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = InterpretTemporalDateTimeFields(
calendar,
fields,
dateOptions
));
// The `offset` and `timeZone` properties only exist on ZonedDateTime (or
// ZonedDateTimeLike-property bags). The assertions below are used to avoid
// TS errors while not diverging runtime code from proposal-temporal.
offset = (relativeTo as Temporal.ZonedDateTimeLike).offset;
if (offset === undefined) offsetBehaviour = 'wall';
timeZone = (relativeTo as Temporal.ZonedDateTimeLike).timeZone;
} else {
let ianaName, z;
({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, calendar, ianaName, offset, z } =
ParseISODateTime(ToString(relativeTo)));
if (ianaName) timeZone = ianaName;
if (z) {
offsetBehaviour = 'exact';
} else if (!offset) {
offsetBehaviour = 'wall';
}
if (!calendar) calendar = GetISO8601Calendar();
calendar = ToTemporalCalendar(calendar);
matchMinutes = true;
}
if (timeZone !== undefined) {
timeZone = ToTemporalTimeZone(timeZone);
let offsetNs = 0;
if (offsetBehaviour === 'option') offsetNs = ParseTimeZoneOffsetString(ToString(offset));
const epochNanoseconds = InterpretISODateTimeOffset(
year,
month,
day,
hour,
minute,
second,
millisecond,
microsecond,
nanosecond,
offsetBehaviour,
offsetNs,
timeZone,
'compatible',
'reject',
matchMinutes
);
return CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar);
}
return CreateTemporalDate(year, month, day, calendar);
}
export function DefaultTemporalLargestUnit(
years: number,
months: number,
weeks: number,
days: number,
hours: number,
minutes: number,
seconds: number,
milliseconds: number,
microseconds: number,
nanoseconds: number
): Temporal.DateTimeUnit {
for (const [prop, v] of [
['years', years],
['months', months],
['weeks', weeks],
['days', days],
['hours', hours],
['minutes', minutes],
['seconds', seconds],
['milliseconds', milliseconds],
['microseconds', microseconds],
['nanoseconds', nanoseconds]
] as const) {
if (v !== 0) {
// All the above keys are definitely in SINGULAR_FOR
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return SINGULAR_FOR.get(prop)!;
}
}
return 'nanosecond';
}
export function LargerOfTwoTemporalUnits<T1 extends Temporal.DateTimeUnit, T2 extends Temporal.DateTimeUnit>(
unit1: T1,
unit2: T2
) {
if (UNITS_DESCENDING.indexOf(unit1) > UNITS_DESCENDING.indexOf(unit2)) return unit2;
return unit1;
}
function MergeLargestUnitOption<T extends Temporal.DateTimeUnit>(
optionsParam: Temporal.DifferenceOptions<T> | undefined,
largestUnit: T
): Temporal.DifferenceOptions<T> {
let options = optionsParam;
if (options === undefined) options = ObjectCreate(null);
return ObjectAssign(ObjectCreate(null), options, { largestUnit });
}
type FieldCompleteness = 'complete' | 'partial';
interface FieldPrepareOptions {
emptySourceErrorMessage: string;
}
type AnyTemporalKey = Exclude<Keys<AnyTemporalLikeType>, symbol>;
// Keys is a conditionally-mapped version of keyof
type Keys<T> = T extends Record<string, unknown> ? keyof T : never;
// Returns all potential owners from all Temporal Like-types for a given union
// of keys in K.
// e.g.
// Owner<'nanosecond'> => PlainDateTimeLike | ZonedDateTimeLike | PlainDateTimeLike | ZonedDateTimeLike
// Owner<'nanoseconds'> => Duration (the only type with plural keys)
type Owner<K extends AnyTemporalKey> =
// Conditional typing maps over all of the types given in AnyTemporalLikeType
// union
K extends unknown ? OwnerOf<K, AnyTemporalLikeType> : 'ThisShouldNeverHappen';
// Returns T iff T has K as all of the key(s) (even if those keys are optional
// in T), never otherwise. This is a private type for use only in the Owner type
// above.
type OwnerOf<K extends AnyTemporalKey, T> =
// Distribute the union before passing to Required
// Without distributing, this is
// Required<ZonedDateTimeLike | DurationLike> extends Record
// vs (with distribution)
// Required<ZonedDateTimeLike> extends Record<....> | Required<DurationLike> extends Record<....>
T extends unknown
? // All the keys in the Like-types are optional, so in order for them to
// 'extend Record<K,...>', where K indicates the required fields, we pass T
// through Required to make all the keys non-optional.
// Note this doesn't work the other way around: using Partial<Record<K, ..>>
// will always be extended by any object (as all the keys are optional).
Required<T> extends Record<K, unknown>
? T
: // never is the 'identity' type for unions - nothing will be added or
// removed from the union.
never
: 'ThisShouldNeverHappen';
type Prop<T, K> = T extends unknown ? (K extends keyof T ? T[K] : undefined) : 'ThisShouldNeverHappen';
// Resolve copies the keys and values of a given object type so that TS will
// stop using type names in error messages / autocomplete. Generally, those
// names can be more useful, but sometimes having the primitive object shape is
// significantly easier to reason about (e.g. deeply-nested types).
// Resolve is an identity function for function types.
type Resolve<T> =
// Re-mapping doesn't work very well for functions, so exclude them
T extends (...args: never[]) => unknown
? T
: // Re-map all the keys in T to the same value. This forces TS into no longer
// using type aliases, etc.
{ [K in keyof T]: T[K] };
type FieldObjectFromOwners<OwnerT, FieldKeys extends AnyTemporalKey> = Resolve<
// The resulting object type contains:
// - All keys in FieldKeys, which are required properties and their values
// don't include undefined.
// - All the other keys in OwnerT that aren't in FieldKeys, which are optional
// properties and their value types explicitly include undefined.
{
-readonly [k in FieldKeys]: Exclude<Prop<OwnerT, k>, undefined>;
} & {
-readonly [k in Exclude<Keys<OwnerT>, FieldKeys>]?: Prop<OwnerT, k> | undefined;
}
>;
type PrepareTemporalFieldsReturn<
FieldKeys extends AnyTemporalKey,
RequiredFieldsOpt extends ReadonlyArray<FieldKeys> | FieldCompleteness,
OwnerT extends Owner<FieldKeys>
> = RequiredFieldsOpt extends 'partial' ? Partial<OwnerT> : FieldObjectFromOwners<OwnerT, FieldKeys>;
export function PrepareTemporalFields<
FieldKeys extends AnyTemporalKey,
// Constrains the Required keys to be a subset of the given field keys
// This could have been written directly into the parameter type, but that
// causes an unintended effect where the required fields are added to the list
// of field keys, even if that key isn't present in 'fields'.
// RequiredFieldKeys extends FieldKeys,
RequiredFields extends ReadonlyArray<FieldKeys> | FieldCompleteness
>(
bag: Partial<Record<FieldKeys, unknown>>,
fields: ReadonlyArray<FieldKeys>,
requiredFields: RequiredFields,
{ emptySourceErrorMessage }: FieldPrepareOptions = { emptySourceErrorMessage: 'no supported properties found' }
): PrepareTemporalFieldsReturn<FieldKeys, RequiredFields, Owner<FieldKeys>> {
const result: Partial<Record<AnyTemporalKey, unknown>> = ObjectCreate(null);
let any = false;
for (const property of fields) {
let value = bag[property];
if (value !== undefined) {
any = true;
if (BUILTIN_CASTS.has(property)) {
// We just has-checked this map access, so there will definitely be a
// value.
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
value = BUILTIN_CASTS.get(property)!(value);
}
result[property] = value;
} else if (requiredFields !== 'partial') {
// TODO: using .call in this way is not correctly type-checked by tsc.
// We might need a type-safe Call wrapper?
if (ArrayIncludes.call(requiredFields, property)) {
throw new TypeError(`required property '${property}' missing or undefined`);
}
value = BUILTIN_DEFAULTS.get(property);
result[property] = value;
}
}
if (requiredFields === 'partial' && !any) {
throw new TypeError(emptySourceErrorMessage);
}
if ((result.era === undefined) !== (result.eraYear === undefined)) {
throw new RangeError("properties 'era' and 'eraYear' must be provided together");
}
return result as unknown as PrepareTemporalFieldsReturn<FieldKeys, RequiredFields, Owner<FieldKeys>>;
}
interface TimeRecord {
hour?: number;
minute?: number;
second?: number;
microsecond?: number;
millisecond?: number;
nanosecond?: number;
}
export function ToTemporalTimeRecord(bag: Partial<Record<keyof TimeRecord, string | number>>): Required<TimeRecord>;
export function ToTemporalTimeRecord(
bag: Partial<Record<keyof TimeRecord, string | number | undefined>>,
completeness: 'partial'
): Partial<TimeRecord>;
export function ToTemporalTimeRecord(
bag: Partial<Record<keyof TimeRecord, string | number>>,
completeness: 'complete'
): Required<TimeRecord>;
export function ToTemporalTimeRecord(
bag: Partial<Record<keyof TimeRecord, string | number | undefined>>,
completeness: FieldCompleteness = 'complete'
): Partial<TimeRecord> {
// NOTE: Field order here is important.
const fields = ['hour', 'microsecond', 'millisecond', 'minute', 'nanosecond', 'second'] as const;
const partial = PrepareTemporalFields(bag, fields, 'partial', { emptySourceErrorMessage: 'invalid time-like' });
const result: Partial<TimeRecord> = {};
for (const field of fields) {
const valueDesc = ObjectGetOwnPropertyDescriptor(partial, field);
if (valueDesc !== undefined) {
result[field] = valueDesc.value;
} else if (completeness === 'complete') {
result[field] = 0;
}
}
return result;
}
export function ToTemporalDate(
itemParam: PlainDateParams['from'][0],
options?: PlainDateParams['from'][1]
): Temporal.PlainDate {
let item = itemParam;
if (IsObject(item)) {
if (IsTemporalDate(item)) return item;
if (IsTemporalZonedDateTime(item)) {
ToTemporalOverflow(options); // validate and ignore
item = BuiltinTimeZoneGetPlainDateTimeFor(
GetSlot(item, TIME_ZONE),
GetSlot(item, INSTANT),
GetSlot(item, CALENDAR)
);
}
if (IsTemporalDateTime(item)) {
ToTemporalOverflow(options); // validate and ignore
return CreateTemporalDate(
GetSlot(item, ISO_YEAR),
GetSlot(item, ISO_MONTH),
GetSlot(item, ISO_DAY),
GetSlot(item, CALENDAR)
);
}
const calendar = GetTemporalCalendarWithISODefault(item);
const fieldNames = CalendarFields(calendar, ['day', 'month', 'monthCode', 'year'] as const);
const fields = PrepareTemporalFields(item, fieldNames, []);
return CalendarDateFromFields(calendar, fields, options);
}
ToTemporalOverflow(options); // validate and ignore
const { year, month, day, calendar, z } = ParseTemporalDateString(ToString(item));
if (z) throw new RangeError('Z designator not supported for PlainDate');
const TemporalPlainDate = GetIntrinsic('%Temporal.PlainDate%');
return new TemporalPlainDate(year, month, day, calendar); // include validation
}
export function InterpretTemporalDateTimeFields(
calendar: Temporal.CalendarProtocol,
fields: PrimitiveFieldsOf<Temporal.PlainDateTimeLike> & Parameters<typeof CalendarDateFromFields>[1],
options?: Temporal.AssignmentOptions
) {
let { hour, minute, second, millisecond, microsecond, nanosecond } = ToTemporalTimeRecord(fields);
const overflow = ToTemporalOverflow(options);
const date = CalendarDateFromFields(calendar, fields, options);
const year = GetSlot(date, ISO_YEAR);
const month = GetSlot(date, ISO_MONTH);
const day = GetSlot(date, ISO_DAY);
({ hour, minute, second, millisecond, microsecond, nanosecond } = RegulateTime(
hour,
minute,
second,
millisecond,
microsecond,
nanosecond,
overflow
));
return { year, month, day, hour, minute, second, millisecond, microsecond, nanosecond };
}
export function ToTemporalDateTime(item: PlainDateTimeParams['from'][0], options?: PlainDateTimeParams['from'][1]) {
let year: number,
month: number,
day: number,
hour: number,
minute: number,
second: number,
millisecond: number,
microsecond: number,
nanosecond: number,
calendar;
if (IsObject(item)) {
if (IsTemporalDateTime(item)) return item;
if (IsTemporalZonedDateTime(item)) {
ToTemporalOv