UNPKG

@js-temporal/polyfill

Version:

Polyfill for Temporal (https://github.com/tc39/proposal-temporal), an ECMA TC39 Stage 3 proposal

1,389 lines (1,287 loc) 185 kB
import { DEBUG, ENABLE_ASSERTS } from './debug'; import JSBI from 'jsbi'; import type { Temporal } from '..'; import { assert, assertNotReached } from './assert'; import { abs, compare, DAY_NANOS_JSBI, divmod, ensureJSBI, isEven, MILLION, ONE, TWO, ZERO } from './bigintmath'; import type { CalendarImpl } from './calendar'; import type { AnyTemporalLikeType, UnitSmallerThanOrEqualTo, InstantParams, PlainMonthDayParams, ZonedDateTimeParams, PlainDateParams, PlainTimeParams, DurationParams, PlainDateTimeParams, PlainYearMonthParams, BuiltinCalendarId, Keys, AnyTemporalKey, FieldKey, InternalDuration, ISODateTime, ISODate, TimeRecord, ISODateToFieldsType, DateDuration, CalendarFieldsRecord, MonthDayFromFieldsObject, Overflow, Resolve, AnySlottedType } from './internaltypes'; import { GetIntrinsic } from './intrinsicclass'; import { ApplyUnsignedRoundingMode, FMAPowerOf10, GetUnsignedRoundingMode, TruncatingDivModByPowerOf10 } from './math'; import { TimeDuration } from './timeduration'; import { CreateSlots, GetSlot, HasSlot, SetSlot, EPOCHNANOSECONDS, ISO_DATE, ISO_DATE_TIME, TIME, DATE_BRAND, YEAR_MONTH_BRAND, MONTH_DAY_BRAND, TIME_ZONE, CALENDAR, YEARS, MONTHS, WEEKS, DAYS, HOURS, MINUTES, SECONDS, MILLISECONDS, MICROSECONDS, NANOSECONDS } from './slots'; const DAY_MS = 86400_000; export const DAY_NANOS = DAY_MS * 1e6; const MINUTE_NANOS = 60e9; // Instant range is 100 million days (inclusive) before or after epoch. const MS_MAX = DAY_MS * 1e8; const NS_MAX = epochMsToNs(MS_MAX); const NS_MIN = JSBI.unaryMinus(NS_MAX); // PlainDateTime range is 24 hours wider (exclusive) than the Instant range on // both ends, to allow for valid Instant=>PlainDateTime conversion for all // built-in time zones (whose offsets must have a magnitude less than 24 hours). const DATETIME_NS_MIN = JSBI.add(JSBI.subtract(NS_MIN, DAY_NANOS_JSBI), ONE); const DATETIME_NS_MAX = JSBI.subtract(JSBI.add(NS_MAX, DAY_NANOS_JSBI), ONE); // The pattern of leap years in the ISO 8601 calendar repeats every 400 years. // The constant below is the number of nanoseconds in 400 years. It is used to // avoid overflows when dealing with values at the edge legacy Date's range. const MS_IN_400_YEAR_CYCLE = (400 * 365 + 97) * DAY_MS; const YEAR_MIN = -271821; const YEAR_MAX = 275760; const BEFORE_FIRST_DST = Date.UTC(1847, 0, 1); // 1847-01-01T00:00:00Z const BUILTIN_CALENDAR_IDS = [ 'iso8601', 'hebrew', 'islamic', 'islamic-umalqura', 'islamic-tbla', 'islamic-civil', 'islamic-rgsa', 'islamicc', 'persian', 'ethiopic', 'ethioaa', 'ethiopic-amete-alem', 'coptic', 'chinese', 'dangi', 'roc', 'indian', 'buddhist', 'japanese', 'gregory' ]; const ICU_LEGACY_TIME_ZONE_IDS = new Set([ 'ACT', 'AET', 'AGT', 'ART', 'AST', 'BET', 'BST', 'CAT', 'CNT', 'CST', 'CTT', 'EAT', 'ECT', 'IET', 'IST', 'JST', 'MIT', 'NET', 'NST', 'PLT', 'PNT', 'PRT', 'PST', 'SST', 'VST' ]); /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function */ /** * uncheckedAssertNarrowedType forces TypeScript to change the type of the argument to the one given in * the type parameter. This should only be used to help TS understand when variables change types, * but TS can't or won't infer this automatically. They should be used sparingly, because * if used incorrectly can lead to difficult-to-diagnose problems. * */ export function uncheckedAssertNarrowedType<T = unknown>( arg: unknown, justification: string ): asserts arg is T extends typeof arg ? T : never {} /* eslint-enable */ /** * In debug builds, this function verifies that the given argument "exists" (is not * null or undefined). This function becomes a no-op in the final bundles distributed via NPM. * @param arg */ export function assertExists<A>(arg: A): asserts arg is NonNullable<A> { if (ENABLE_ASSERTS) { if (arg == null) { throw new Error('Expected arg to be set.'); } } } /** Similar to assertExists, but returns the argument. */ function castExists<A>(arg: A): NonNullable<A> { assertExists(arg); return arg; } // 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 { // ES 2022's es-abstract made minor changes to ToNumber, but polyfilling these // changes adds zero benefit to Temporal and brings in a lot of extra code. So // we'll leave ToNumber as-is. // See https://github.com/ljharb/es-abstract/blob/main/2022/ToNumber.js if (typeof value === 'bigint') throw new TypeError('Cannot convert BigInt to number'); return Number(value); } function IsIntegralNumber(argument: unknown) { if (typeof argument !== 'number' || Number.isNaN(argument) || argument === Infinity || argument === -Infinity) { return false; } const absValue = Math.abs(argument); return Math.floor(absValue) === absValue; } export function ToString(value: unknown): string { if (typeof value === 'symbol') { throw new TypeError('Cannot convert a Symbol value to a String'); } return String(value); } export function ToIntegerWithTruncation(value: unknown): number { const number = ToNumber(value); if (number === 0) return 0; if (Number.isNaN(number) || number === Infinity || number === -Infinity) { throw new RangeError('invalid number value'); } const integer = Math.trunc(number); if (integer === 0) return 0; // ℝ(value) in spec text; converts -0 to 0 return integer; } function ToPositiveIntegerWithTruncation(valueParam: unknown, property?: string): number { const integer = ToIntegerWithTruncation(valueParam); if (integer <= 0) { 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 integer; } export function ToIntegerIfIntegral(valueParam: unknown): number { const number = ToNumber(valueParam); if (Number.isNaN(number)) throw new RangeError('not a number'); if (number === Infinity || number === -Infinity) throw new RangeError('infinity is out of range'); if (!IsIntegralNumber(number)) throw new RangeError(`unsupported fractional value ${valueParam}`); if (number === 0) return 0; // ℝ(value) in spec text; converts -0 to 0 return number; } function ToZeroPaddedDecimalString(n: number, minLength: number) { if (DEBUG) { if (!IsIntegralNumber(n) || n < 0) { throw new RangeError('Assertion failed: `${n}` must be a non-negative integer'); } } const s = String(n); return s.padStart(minLength, '0'); } // This convenience function isn't in the spec, but is useful in the polyfill // for DRY and better error messages. export function RequireString(value: unknown): string { if (typeof value !== 'string') { // Use String() to ensure that Symbols won't throw throw new TypeError(`expected a string, not ${String(value)}`); } return value; } function ToSyntacticallyValidMonthCode(valueParam: unknown) { const value = RequireString(ToPrimitive(valueParam, String)); if ( value.length < 3 || value.length > 4 || value[0] !== 'M' || '0123456789'.indexOf(value[1]) === -1 || '0123456789'.indexOf(value[2]) === -1 || (value[1] + value[2] === '00' && value[3] !== 'L') || (value[3] !== 'L' && value[3] !== undefined) ) { throw new RangeError(`bad month code ${value}; must match M01-M99 or M00L-M99L`); } return value; } function ToOffsetString(valueParam: unknown) { const value = RequireString(ToPrimitive(valueParam, String)); ParseDateTimeUTCOffset(value); return value; } // Limited implementation of ToPrimitive that only handles the string case, // because that's all that's used in this polyfill. function ToPrimitive(value: unknown, preferredType: typeof String): string | number { assertExists(preferredType === String); if (IsObject(value)) { const result = value?.toString(); if (typeof result === 'string' || typeof result === 'number') return result; throw new TypeError('Cannot convert object to primitive value'); } return value; } const CALENDAR_FIELD_KEYS: readonly FieldKey[] = [ 'era', 'eraYear', 'year', 'month', 'monthCode', 'day', 'hour', 'minute', 'second', 'millisecond', 'microsecond', 'nanosecond', 'offset', 'timeZone' ] as const; type BuiltinCastFunction = (v: unknown) => string | number; const BUILTIN_CASTS: Partial<Record<FieldKey, BuiltinCastFunction>> = { era: ToString, eraYear: ToIntegerWithTruncation, year: ToIntegerWithTruncation, month: ToPositiveIntegerWithTruncation, monthCode: ToSyntacticallyValidMonthCode, day: ToPositiveIntegerWithTruncation, hour: ToIntegerWithTruncation, minute: ToIntegerWithTruncation, second: ToIntegerWithTruncation, millisecond: ToIntegerWithTruncation, microsecond: ToIntegerWithTruncation, nanosecond: ToIntegerWithTruncation, offset: ToOffsetString, timeZone: ToTemporalTimeZoneIdentifier }; const BUILTIN_DEFAULTS: Partial<Record<FieldKey, number>> = { hour: 0, minute: 0, second: 0, millisecond: 0, microsecond: 0, nanosecond: 0 }; // each item is [plural, singular, category, (length in ns)] const TEMPORAL_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 = Object.fromEntries(TEMPORAL_UNITS.map((e) => [e[0], e[1]] as const)); const PLURAL_FOR = Object.fromEntries(TEMPORAL_UNITS.map(([p, s]) => [s, p] as const)); const UNITS_DESCENDING = TEMPORAL_UNITS.map(([, s]) => s); type TimeUnitOrDay = Temporal.TimeUnit | 'day'; const NS_PER_TIME_UNIT = { day: DAY_NANOS, hour: 3600e9, minute: 60e9, second: 1e9, millisecond: 1e6, microsecond: 1e3, nanosecond: 1 }; const DURATION_FIELDS = [ 'days', 'hours', 'microseconds', 'milliseconds', 'minutes', 'months', 'nanoseconds', 'seconds', 'weeks', 'years' ] as const; import * as PARSE from './regex'; // Save the original Intl.DateTimeFormat, it will likely be overwritten with the // one from this polyfill. Caching the formatter below may be reentrant, so we // need to use the original one const OriginalIntlDateTimeFormat = Intl.DateTimeFormat; const IntlDateTimeFormatEnUsCache = new Map<string, Intl.DateTimeFormat>(); function getIntlDateTimeFormatEnUsForTimeZone(timeZoneIdentifier: string) { const lowercaseIdentifier = ASCIILowercase(timeZoneIdentifier); let instance = IntlDateTimeFormatEnUsCache.get(lowercaseIdentifier); if (instance === undefined) { instance = new OriginalIntlDateTimeFormat('en-us', { timeZone: lowercaseIdentifier, hour12: false, era: 'short', year: 'numeric', month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric' }); IntlDateTimeFormatEnUsCache.set(lowercaseIdentifier, instance); } return instance; } export function ToObject<T>(value: T): T extends Record<string, unknown> ? T : Record<PropertyKey, unknown> { if (typeof value === 'undefined' || value === null) { throw new TypeError(`Expected object not ${value}`); } return Object(value); } // Adapted from https://github.com/ljharb/es-abstract/blob/main/2022/CopyDataProperties.js // but simplified (e.g. removed assertions) for this polyfill to reduce bundle size. export function CopyDataProperties<K extends PropertyKey, T extends Record<K, unknown>>( target: T, source: T | undefined, excludedKeys: K[], excludedValues?: unknown[] ) { if (typeof source === 'undefined' || source === null) return; const keys = Reflect.ownKeys(source) as (keyof T)[]; for (let index = 0; index < keys.length; index++) { const nextKey = keys[index]; if (excludedKeys.some((e) => Object.is(e, nextKey))) continue; if (Object.prototype.propertyIsEnumerable.call(source, nextKey)) { const propValue = source[nextKey]; if (excludedValues && excludedValues.some((e) => Object.is(e, propValue))) continue; target[nextKey] = propValue; } } } export function IsTemporalInstant(item: unknown): item is Temporal.Instant { return HasSlot(item, EPOCHNANOSECONDS) && !HasSlot(item, TIME_ZONE, CALENDAR); } 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, TIME); } export function IsTemporalDateTime(item: unknown): item is Temporal.PlainDateTime { return HasSlot(item, ISO_DATE_TIME); } 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 CheckReceiver<T extends AnySlottedType>( item: unknown, test: (item: unknown) => item is T ): asserts item is T { if (!test(item)) throw new TypeError('invalid receiver: method called with the wrong type of this-object'); } export function RejectTemporalLikeObject(item: AnyTemporalLikeType) { if (HasSlot(item, CALENDAR) || HasSlot(item, TIME_ZONE)) { throw new TypeError('with() does not support a calendar or timeZone property'); } if (IsTemporalTime(item)) { throw new TypeError('with() does not accept Temporal.PlainTime, use withPlainTime() instead'); } 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 FormatCalendarAnnotation(id: BuiltinCalendarId, showCalendar: Temporal.ShowCalendarOption['calendarName']) { if (showCalendar === 'never') return ''; if (showCalendar === 'auto' && id === 'iso8601') return ''; const flag = showCalendar === 'critical' ? '!' : ''; return `[${flag}u-ca=${id}]`; } // Not a separate abstract operation in the spec, because it only occurs in one // place: ParseISODateTime. In the code it's more convenient to split up // ParseISODateTime for the YYYY-MM, MM-DD, and THH:MM:SS parse goals, so it's // repeated four times. function processAnnotations(annotations: string) { let calendar; let calendarWasCritical = false; // Avoid the user code minefield of matchAll. let match; PARSE.annotation.lastIndex = 0; while ((match = PARSE.annotation.exec(annotations))) { const { 1: critical, 2: key, 3: value } = match; if (key === 'u-ca') { if (calendar === undefined) { calendar = value; calendarWasCritical = critical === '!'; } else if (critical === '!' || calendarWasCritical) { throw new RangeError(`Invalid annotations in ${annotations}: more than one u-ca present with critical flag`); } } else if (critical === '!') { throw new RangeError(`Unrecognized annotation: !${key}=${value}`); } } return calendar; } 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 RFC 9557 string: ${isoString}`); const calendar = processAnnotations(match[16]); let yearString = match[1]; if (yearString === '-000000') throw new RangeError(`invalid RFC 9557 string: ${isoString}`); const year = +yearString; const month = +(match[2] ?? match[4] ?? 1); const day = +(match[3] ?? match[5] ?? 1); const hasTime = match[6] !== undefined; const hour = +(match[6] ?? 0); const minute = +(match[7] ?? match[10] ?? 0); let second = +(match[8] ?? match[11] ?? 0); if (second === 60) second = 59; const fraction = (match[9] ?? match[12] ?? '') + '000000000'; const millisecond = +fraction.slice(0, 3); const microsecond = +fraction.slice(3, 6); const nanosecond = +fraction.slice(6, 9); let offset; let z = false; if (match[13]) { offset = undefined; z = true; } else if (match[14]) { offset = match[14]; } const tzAnnotation = match[15]; RejectDateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond); return { year, month, day, time: hasTime ? { hour, minute, second, millisecond, microsecond, nanosecond } : ('start-of-day' as const), tzAnnotation, 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.tzAnnotation) 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) { calendar = processAnnotations(match[10]); hour = +(match[1] ?? 0); minute = +(match[2] ?? match[5] ?? 0); second = +(match[3] ?? match[6] ?? 0); if (second === 60) second = 59; const fraction = (match[4] ?? match[7] ?? '') + '000000000'; millisecond = +fraction.slice(0, 3); microsecond = +fraction.slice(3, 6); nanosecond = +fraction.slice(6, 9); if (match[8]) throw new RangeError('Z designator not supported for PlainTime'); } else { let time, z; ({ time, z, calendar } = ParseISODateTime(isoString)); if (time === 'start-of-day') throw new RangeError(`time is missing in string: ${isoString}`); if (z) throw new RangeError('Z designator not supported for PlainTime'); ({ hour, minute, second, millisecond, microsecond, nanosecond } = time); } RejectTime(hour, minute, second, millisecond, microsecond, nanosecond); // if it's a date-time string, OK if (/[tT ][0-9][0-9]/.test(isoString)) { return { hour, minute, second, millisecond, microsecond, nanosecond, calendar }; } 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 RFC 9557 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) { calendar = processAnnotations(match[3]); let yearString = match[1]; if (yearString === '-000000') throw new RangeError(`invalid RFC 9557 string: ${isoString}`); year = +yearString; month = +match[2]; referenceISODay = 1; if (calendar !== undefined && calendar !== 'iso8601') { throw new RangeError('YYYY-MM format is only valid with iso8601 calendar'); } } 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) { calendar = processAnnotations(match[3]); month = +match[1]; day = +match[2]; if (calendar !== undefined && calendar !== 'iso8601') { throw new RangeError('MM-DD format is only valid with iso8601 calendar'); } } 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 }; } const TIMEZONE_IDENTIFIER = new RegExp(`^${PARSE.timeZoneID.source}$`, 'i'); const OFFSET_IDENTIFIER = new RegExp(`^${PARSE.offsetIdentifier.source}$`); function throwBadTimeZoneStringError(timeZoneString: string): never { // Offset identifiers only support minute precision, but offsets in ISO // strings support nanosecond precision. If the identifier is invalid but // it's a valid ISO offset, then it has sub-minute precision. Show a clearer // error message in that case. const msg = OFFSET.test(timeZoneString) ? 'Seconds not allowed in offset time zone' : 'Invalid time zone'; throw new RangeError(`${msg}: ${timeZoneString}`); } export function ParseTimeZoneIdentifier( identifier: string ): { tzName: string; offsetMinutes?: undefined } | { tzName?: undefined; offsetMinutes: number } { if (!TIMEZONE_IDENTIFIER.test(identifier)) { throwBadTimeZoneStringError(identifier); } if (OFFSET_IDENTIFIER.test(identifier)) { const offsetNanoseconds = ParseDateTimeUTCOffset(identifier); // The regex limits the input to minutes precision, so we know that the // division below will result in an integer. return { offsetMinutes: offsetNanoseconds / 60e9 }; } return { tzName: identifier }; } // This operation doesn't exist in the spec, but in the polyfill it's split from // ParseTemporalTimeZoneString so that parsing can be tested separately from the // logic of converting parsed values into a named or offset identifier. // ts-prune-ignore-next TODO: remove if test/validStrings is converted to TS. export function ParseTemporalTimeZoneStringRaw(timeZoneString: string): { tzAnnotation: string; offset: string | undefined; z: boolean; } { if (TIMEZONE_IDENTIFIER.test(timeZoneString)) { return { tzAnnotation: timeZoneString, offset: undefined, z: false }; } try { // Try parsing ISO string instead const { tzAnnotation, offset, z } = ParseISODateTime(timeZoneString); if (z || tzAnnotation || offset) { return { tzAnnotation, offset, z }; } } catch { // fall through } throwBadTimeZoneStringError(timeZoneString); } function ParseTemporalTimeZoneString(stringIdent: string): ReturnType<typeof ParseTimeZoneIdentifier> { const { tzAnnotation, offset, z } = ParseTemporalTimeZoneStringRaw(stringIdent); if (tzAnnotation) return ParseTimeZoneIdentifier(tzAnnotation); if (z) return ParseTimeZoneIdentifier('UTC'); if (offset) return ParseTimeZoneIdentifier(offset); /* c8 ignore next */ assertNotReached(); } // ts-prune-ignore-next TODO: remove if test/validStrings is converted to TS. export function ParseTemporalDurationStringRaw(isoString: string) { const match = PARSE.duration.exec(isoString); if (!match) throw new RangeError(`invalid duration: ${isoString}`); if (match.every((part, i) => i < 2 || part === undefined)) { throw new RangeError(`invalid duration: ${isoString}`); } const sign = match[1] === '-' ? -1 : 1; const years = match[2] === undefined ? 0 : ToIntegerWithTruncation(match[2]) * sign; const months = match[3] === undefined ? 0 : ToIntegerWithTruncation(match[3]) * sign; const weeks = match[4] === undefined ? 0 : ToIntegerWithTruncation(match[4]) * sign; const days = match[5] === undefined ? 0 : ToIntegerWithTruncation(match[5]) * sign; const hours = match[6] === undefined ? 0 : ToIntegerWithTruncation(match[6]) * sign; const fHours = match[7]; const minutesStr = match[8]; const fMinutes = match[9]; const secondsStr = match[10]; const fSeconds = match[11]; let minutes = 0; let seconds = 0; // fractional hours, minutes, or seconds, expressed in whole nanoseconds: let excessNanoseconds = 0; if (fHours !== undefined) { if (minutesStr ?? fMinutes ?? secondsStr ?? fSeconds ?? false) { throw new RangeError('only the smallest unit can be fractional'); } excessNanoseconds = ToIntegerWithTruncation((fHours + '000000000').slice(0, 9)) * 3600 * sign; } else { minutes = minutesStr === undefined ? 0 : ToIntegerWithTruncation(minutesStr) * sign; if (fMinutes !== undefined) { if (secondsStr ?? fSeconds ?? false) { throw new RangeError('only the smallest unit can be fractional'); } excessNanoseconds = ToIntegerWithTruncation((fMinutes + '000000000').slice(0, 9)) * 60 * sign; } else { seconds = secondsStr === undefined ? 0 : ToIntegerWithTruncation(secondsStr) * sign; if (fSeconds !== undefined) { excessNanoseconds = ToIntegerWithTruncation((fSeconds + '000000000').slice(0, 9)) * sign; } } } const nanoseconds = excessNanoseconds % 1000; const microseconds = Math.trunc(excessNanoseconds / 1000) % 1000; const milliseconds = Math.trunc(excessNanoseconds / 1e6) % 1000; seconds += Math.trunc(excessNanoseconds / 1e9) % 60; minutes += Math.trunc(excessNanoseconds / 60e9); RejectDuration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds); return { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds }; } function ParseTemporalDurationString(isoString: string) { const { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ParseTemporalDurationStringRaw(isoString); const TemporalDuration = GetIntrinsic('%Temporal.Duration%'); return new TemporalDuration( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds ); } export function RegulateISODate(yearParam: number, monthParam: number, dayParam: number, overflow: 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: 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 = ConstrainToRange(hour, 0, 23); minute = ConstrainToRange(minute, 0, 59); second = ConstrainToRange(second, 0, 59); millisecond = ConstrainToRange(millisecond, 0, 999); microsecond = ConstrainToRange(microsecond, 0, 999); nanosecond = ConstrainToRange(nanosecond, 0, 999); break; } return { hour, minute, second, millisecond, microsecond, nanosecond }; } export 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 (let index = 0; index < DURATION_FIELDS.length; index++) { const property = DURATION_FIELDS[index]; const value = temporalDurationLike[property]; if (value !== undefined) { any = true; result[property] = ToIntegerIfIntegral(value); } } if (!any) { throw new TypeError('invalid duration-like'); } return result; } export function AdjustDateDurationRecord( { years, months, weeks, days }: DateDuration, newDays: number, newWeeks?: number, newMonths?: number ) { return { years, months: newMonths ?? months, weeks: newWeeks ?? weeks, days: newDays ?? days }; } export function ZeroDateDuration() { return { years: 0, months: 0, weeks: 0, days: 0 }; } export function CombineISODateAndTimeRecord(isoDate: ISODate, time: TimeRecord) { return { isoDate, time }; } export function MidnightTimeRecord() { return { deltaDays: 0, hour: 0, minute: 0, second: 0, millisecond: 0, microsecond: 0, nanosecond: 0 }; } export function NoonTimeRecord() { return { deltaDays: 0, hour: 12, minute: 0, second: 0, millisecond: 0, microsecond: 0, nanosecond: 0 }; } export function GetTemporalOverflowOption(options: Temporal.AssignmentOptions) { return GetOption(options, 'overflow', ['constrain', 'reject'], 'constrain'); } export function GetTemporalDisambiguationOption(options: Temporal.ToInstantOptions) { return GetOption(options, 'disambiguation', ['compatible', 'earlier', 'later', 'reject'], 'compatible'); } export function GetRoundingModeOption( options: { roundingMode?: Temporal.RoundingMode }, fallback: Temporal.RoundingMode ) { return GetOption( options, 'roundingMode', ['ceil', 'floor', 'expand', 'trunc', 'halfCeil', 'halfFloor', 'halfExpand', 'halfTrunc', 'halfEven'], fallback ); } function NegateRoundingMode(roundingMode: Temporal.RoundingMode) { switch (roundingMode) { case 'ceil': return 'floor'; case 'floor': return 'ceil'; case 'halfCeil': return 'halfFloor'; case 'halfFloor': return 'halfCeil'; default: return roundingMode; } } export function GetTemporalOffsetOption( options: Temporal.OffsetDisambiguationOptions, fallback: Required<Temporal.OffsetDisambiguationOptions>['offset'] ) { return GetOption(options, 'offset', ['prefer', 'use', 'ignore', 'reject'], fallback); } export function GetTemporalShowCalendarNameOption(options: Temporal.ShowCalendarOption) { return GetOption(options, 'calendarName', ['auto', 'always', 'never', 'critical'], 'auto'); } export function GetTemporalShowTimeZoneNameOption(options: Temporal.ZonedDateTimeToStringOptions) { return GetOption(options, 'timeZoneName', ['auto', 'never', 'critical'], 'auto'); } export function GetTemporalShowOffsetOption(options: Temporal.ZonedDateTimeToStringOptions) { return GetOption(options, 'offset', ['auto', 'never'], 'auto'); } export function GetDirectionOption(options: { direction?: 'next' | 'previous' }) { return GetOption(options, 'direction', ['next', 'previous'], REQUIRED); } export function GetTemporalRoundingIncrementOption(options: { roundingIncrement?: number }) { let increment = options.roundingIncrement; if (increment === undefined) return 1; const integerIncrement = ToIntegerWithTruncation(increment); if (integerIncrement < 1 || integerIncrement > 1e9) { throw new RangeError(`roundingIncrement must be at least 1 and at most 1e9, not ${increment}`); } return integerIncrement; } export function ValidateTemporalRoundingIncrement(increment: number, dividend: number, inclusive: boolean) { const maximum = inclusive ? dividend : dividend - 1; if (increment > maximum) { throw new RangeError(`roundingIncrement must be at least 1 and less than ${maximum}, not ${increment}`); } if (dividend % increment !== 0) { throw new RangeError(`Rounding increment must divide evenly into ${dividend}`); } } export function GetTemporalFractionalSecondDigitsOption( normalizedOptions: Temporal.ToStringPrecisionOptions ): Temporal.ToStringPrecisionOptions['fractionalSecondDigits'] { const digitsValue = normalizedOptions.fractionalSecondDigits; if (digitsValue === undefined) return 'auto'; if (typeof digitsValue !== 'number') { if (ToString(digitsValue) !== 'auto') { throw new RangeError(`fractionalSecondDigits must be 'auto' or 0 through 9, not ${digitsValue}`); } return 'auto'; } const digitCount = Math.floor(digitsValue); if (!Number.isFinite(digitCount) || digitCount < 0 || digitCount > 9) { throw new RangeError(`fractionalSecondDigits must be 'auto' or 0 through 9, not ${digitsValue}`); } return digitCount as Exclude<Temporal.ToStringPrecisionOptions['fractionalSecondDigits'], 'auto'>; } interface SecondsStringPrecisionRecord { precision: Temporal.ToStringPrecisionOptions['fractionalSecondDigits'] | 'minute'; unit: UnitSmallerThanOrEqualTo<'minute'>; increment: number; } export function ToSecondsStringPrecisionRecord( smallestUnit: Temporal.ToStringPrecisionOptions['smallestUnit'], precision: Temporal.ToStringPrecisionOptions['fractionalSecondDigits'] ): SecondsStringPrecisionRecord { 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 } switch (precision) { case 'auto': return { precision, unit: 'nanosecond', increment: 1 }; 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 ${precision}`); } } 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 GetTemporalUnitValuedOption< 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 GetTemporalUnitValuedOption< 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 GetTemporalUnitValuedOption< 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 { let allowedSingular: Array<Temporal.DateTimeUnit | 'auto'> = []; for (let index = 0; index < TEMPORAL_UNITS.length; index++) { const unitInfo = TEMPORAL_UNITS[index]; const singular = unitInfo[1]; const category = unitInfo[2]; if (unitGroup === 'datetime' || unitGroup === category) { allowedSingular.push(singular); } } allowedSingular = allowedSingular.concat(extraValues); let defaultVal: typeof REQUIRED | Temporal.DateTimeUnit | 'auto' | undefined = requiredOrDefault; if (defaultVal === REQUIRED) { defaultVal = undefined; } else if (defaultVal !== undefined) { allowedSingular.push(defaultVal); } let allowedValues: Array<Temporal.DateTimeUnit | Temporal.PluralUnit<Temporal.DateTimeUnit> | 'auto'> = []; allowedValues = allowedValues.concat(allowedSingular); for (let index = 0; index < allowedSingular.length; index++) { const singular = allowedSingular[index]; const plural = PLURAL_FOR[singular]; 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 return (retval && retval in SINGULAR_FOR ? SINGULAR_FOR[retval] : retval) as R; } export function GetTemporalRelativeToOption(options: { relativeTo?: | Temporal.ZonedDateTime | Temporal.PlainDateTime | Temporal.ZonedDateTimeLike | Temporal.PlainDateTimeLike | string | undefined; }): | { zonedRelativeTo?: Temporal.ZonedDateTime; plainRelativeTo?: never } | { plainRelativeTo?: Temporal.PlainDate; zonedRelativeTo?: never } { const relativeTo = options.relativeTo; if (relativeTo === undefined) return {}; let offsetBehaviour: OffsetBehaviour = 'option'; let matchMinutes = false; let isoDate, time, calendar, timeZone, offset; if (IsObject(relativeTo)) { if (IsTemporalZonedDateTime(relativeTo)) { return { zonedRelativeTo: relativeTo }; } if (IsTemporalDate(relativeTo)) return { plainRelativeTo: relativeTo }; if (IsTemporalDateTime(relativeTo)) { return { plainRelativeTo: CreateTemporalDate(GetSlot(relativeTo, ISO_DATE_TIME).isoDate, GetSlot(relativeTo, CALENDAR)) }; } calendar = GetTemporalCalendarIdentifierWithISODefault(relativeTo); const fields = PrepareCalendarFields( calendar, relativeTo, ['year', 'month', 'monthCode', 'day'], ['hour', 'minute', 'second', 'millisecond', 'microsecond', 'nanosecond', 'offset', 'timeZone'], [] ); ({ isoDate, time } = InterpretTemporalDateTimeFields(calendar, fields, 'constrain')); ({ offset, timeZone } = fields); if (offset === undefined) offsetBehaviour = 'wall'; } else { let tzAnnotation, z, year, month, day; ({ year, month, day, time, calendar, tzAnnotation, offset, z } = ParseISODateTime(RequireString(relativeTo))); if (tzAnnotation) { timeZone = ToTemporalTimeZoneIdentifier(tzAnnotation); if (z) { offsetBehaviour = 'exact'; } else if (!offset) { offsetBehaviour = 'wall'; } matchMinutes = true; } else if (z) { throw new RangeError( 'Z designator not supported for PlainDate relativeTo; either remove the Z or add a bracketed time zone' ); } if (!calendar) calendar = 'iso8601'; calendar = CanonicalizeCalendar(calendar); isoDate = { year, month, day }; } if (timeZone === undefined) { return { plainRelativeTo: CreateTemporalDate(isoDate, calendar) }; } const offsetNs = offsetBehaviour === 'option' ? ParseDateTimeUTCOffset(castExists(offset)) : 0; const epochNanoseconds = InterpretISODateTimeOffset( isoDate, time, offsetBehaviour, offsetNs, timeZone, 'compatible', 'reject', matchMinutes ); return { zonedRelativeTo: CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar) }; } export function DefaultTemporalLargestUnit(duration: Temporal.Duration) { if (GetSlot(duration, YEARS) !== 0) return 'year'; if (GetSlot(duration, MONTHS) !== 0) return 'month'; if (GetSlot(duration, WEEKS) !== 0) return 'week'; if (GetSlot(duration, DAYS) !== 0) return 'day'; if (GetSlot(duration, HOURS) !== 0) return 'hour'; if (GetSlot(duration, MINUTES) !== 0) return 'minute'; if (GetSlot(duration, SECONDS) !== 0) return 'second'; if (GetSlot(duration, MILLISECONDS) !== 0) return 'millisecond'; if (GetSlot(duration, MICROSECONDS) !== 0) return 'microsecond'; return 'nanosecond'; } export function LargerOfTwoTemporalUnits<T1 extends Temporal.DateTimeUnit, T2 extends Temporal.DateTimeUnit>( unit1: T1, unit2: T2 ) { const i1 = UNITS_DESCENDING.indexOf(unit1); const i2 = UNITS_DESCENDING.indexOf(unit2); if (i1 > i2) { return unit2; } return unit1; } export function IsCalendarUnit(unit: Temporal.DateTimeUnit): unit is Exclude<Temporal.DateUnit, 'day'> { return unit === 'year' || unit === 'month' || unit === 'week'; } export function TemporalUnitCategory(unit: Temporal.DateTimeUnit) { if (IsCalendarUnit(unit) || unit === 'day') return 'date'; return 'time'; } function calendarImplForID(calendar: BuiltinCalendarId) { return GetIntrinsic('%calendarImpl%')(calendar); } export function calendarImplForObj( temporalObj: | Temporal.PlainDate | Temporal.PlainDateTime | Temporal.PlainMonthDay | Temporal.PlainYearMonth | Temporal.ZonedDateTime ) { return GetIntrinsic('%calendarImpl%')(GetSlot(temporalObj, CALENDAR)); } type ISODateToFieldsReturn<Type extends ISODateToFieldsType> = Resolve<{ year: Type extends 'date' | 'year-month' ? number : never; monthCode: string; day: Type extends 'date' | 'month-day' ? number : never; }>; export function ISODateToFields(calendar: BuiltinCalendarId, isoDate: ISODate): ISODateToFieldsReturn<'date'>; export function ISODateToFields<T extends ISODateToFieldsType>( calendar: BuiltinCalendarId, isoDate: ISODate, type: T ): ISODateToFieldsReturn<T>; export function ISODateToFields(calendar: BuiltinCalendarId, isoDate: ISODate, type = 'date') { const fields = Object.create(null); const calendarImpl = calendarImplForID(calendar); const calendarDate = calendarImpl.isoToDate(isoDate, { year: true, monthCode: true, day: true }); fields.monthCode = calendarDate.monthCode; if (type === 'month-day' || type === 'date') { fields.day = calendarDate.day; } if (type === 'year-month' || type === 'date') { fields.year = calendarDate.year; } return fields; } type Prop<T, K> = T extends unknown ? (K extends keyof T ? T[K] : undefined) : 'ThisShouldNeverHappen'; type FieldObjectWithRequired<FieldKeys extends FieldKey> = 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 CalendarFieldsRecord that aren't in FieldKeys, // which are optional properties and their value types explicitly include // undefined. { -readonly [k in FieldKeys]: Exclude<Prop<CalendarFieldsRecord, k>, undefined>; } & { -readonly [k in Exclude<Keys<CalendarFieldsRecord>, FieldKeys>]?: Prop<CalendarFieldsRecord, k> | undefined; } >; type PrepareCalendarFieldsReturn< FieldKeys extends FieldKey, RequiredFieldsOpt extends ReadonlyArray<FieldKey> | 'partial' > = RequiredFieldsOpt extends 'partial' ? Partial<CalendarFieldsRecord> : FieldObjectWithRequired<FieldKeys>; export function PrepareCalendarFields< FieldKeys extends FieldKey, RequiredFields extends ReadonlyArray<FieldKey> | 'partial' >( calendar: BuiltinCalendarId, bag: Partial<Record<FieldKeys, unknown>>, calendarFieldNames: Array<FieldKeys>, nonCalendarFieldNames: Array<FieldKeys>, requiredFields: RequiredFields ): PrepareCalendarFieldsReturn<FieldKeys, RequiredFields> { const extraFieldNames = calendarImplForID(calendar).extraFields(calendarFieldNames) as FieldKeys[]; const fields = calendarFieldNames.concat(nonCalendarFieldNames, extraFieldNames); const result: Partial<Record<AnyTemporalKey, unknown>> = Object.create(null); let any = false; fields.sort(); for (let index = 0; index < fields.length; index++) { const property = fields[index]; const value = bag[property]; if (value !== undefined) { any = true; result[property] = castExists(BUILTIN_CASTS[property])(value); } else if (requiredFields !== 'partial') { if (requiredFields.includes(property)) { throw new TypeError(`required property '${property}' missing or undefined`); } result[property] = BUILTIN_DEFAULTS[property]; } } if (requiredFields === 'partial' && !any) { throw new TypeError('no supported properties found'); } return result as unknown as PrepareCalendarFieldsReturn<FieldKeys, RequiredFields>; } type FieldCompleteness = 'complete' | 'partial'; export function ToTemporalTimeRecord(bag: Partial<Record<keyof TimeRecord, string | number>>): 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' ): TimeRecord; export function ToTemporalTimeRecord( bag: Partial<Record<keyof TimeRecord, string | number | undefined>>, completeness: FieldCompleteness = 'complete' ): Partial<TimeRecord> { // NOTE: Field order is sorted to make the sort in PrepareTemporalFields more efficient. const fields: (keyof TimeRecord)[] = ['hour', 'microsecond', 'millisecond', 'minute', 'nanosecond', 'second']; let any = false; const result: Partial<TimeRecord> = Object.create(null); for (let index = 0; index < fields.length; index++) { const field = fields[index]; const value = bag[field]; if (value !== undefined) { result[field] = ToIntegerWithTruncation(value); any = true; } else if (completeness === 'complete') { result[field] = 0; } } if (!any) throw new TypeError('invalid time-like'); return result; } export function ToTemporalDate( item: PlainDateParams['from'][0], options?: PlainDateParams['from'][1] ): Temporal.PlainDate { if (IsObject(item)) { if (IsTemporalDate(item)) { GetTemporalOverflowOption(GetOptionsObject(options)); return CreateTemporalDate(GetSlot(item, ISO_DATE), GetSlot(item, CALENDAR)); } if (IsTemporalZonedDateTime(item)) { const isoDateTime = GetISODateTimeFor(GetSlot(item, TIME_ZONE), GetSlot(item, EPOCHNANOSECONDS)); GetTemporalOverflowOption(GetOptionsObject(options)); // validate and ignore const isoDate = isoDateTime.isoDate; return CreateTemporalDate(isoDate, GetSlot(item, CALENDAR)); } if (IsTemporalDateTime(item)) { GetTemporalOverflowOption(GetOptionsObject(options)); // validate and ignore return CreateTemporalDate(GetSlot(item, ISO_DATE_TIME).isoDate, GetSlot(item, CALENDAR)); } const calendar = GetTemporalCalendarIdentifierWithISODefault(item); const fields = PrepareCalendarFields(calendar, item, ['year', 'month', 'monthCode', 'day'], [], []); const overflow = GetTemporalOverflowOption(GetOptionsObject(options)); const isoDate = CalendarDateFromFields(calendar, fields, overflow); return CreateTemporalDate(isoDate, calendar); } let { year, month, day, calendar, z } = ParseTemporalDateString(RequireString(item)); if (z) throw new RangeError('Z designator not supported for PlainDate'); if (!calendar) calendar = 'iso8601'; calendar = CanonicalizeCalendar(calendar); uncheckedAssertNarrowedType<BuiltinCalendarId>(calendar, 'lowercased and canonicalized'); GetTemporalOverflowOption(GetOptionsObject(options)); // validate and ignore return CreateTemporalDate({ year, month, day }, calendar); } export function InterpretTemporalDateTimeFields( calendar: BuiltinC