UNPKG

@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
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