6561 lines
232 KiB
TypeScript
6561 lines
232 KiB
TypeScript
|
|
const ArrayIncludes = Array.prototype.includes;
|
|||
|
|
const ArrayPrototypePush = Array.prototype.push;
|
|||
|
|
const IntlDateTimeFormat = globalThis.Intl.DateTimeFormat;
|
|||
|
|
const MathMin = Math.min;
|
|||
|
|
const MathMax = Math.max;
|
|||
|
|
const MathAbs = Math.abs;
|
|||
|
|
const MathFloor = Math.floor;
|
|||
|
|
const MathSign = Math.sign;
|
|||
|
|
const MathTrunc = Math.trunc;
|
|||
|
|
const NumberIsNaN = Number.isNaN;
|
|||
|
|
const NumberIsFinite = Number.isFinite;
|
|||
|
|
const NumberCtor = Number;
|
|||
|
|
const StringCtor = String;
|
|||
|
|
const NumberMaxSafeInteger = Number.MAX_SAFE_INTEGER;
|
|||
|
|
const ObjectCreate = Object.create;
|
|||
|
|
const ObjectDefineProperty = Object.defineProperty;
|
|||
|
|
const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|||
|
|
const ReflectApply = Reflect.apply;
|
|||
|
|
const ReflectOwnKeys = Reflect.ownKeys;
|
|||
|
|
|
|||
|
|
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,
|
|||
|
|
Keys,
|
|||
|
|
AnyTemporalKey,
|
|||
|
|
CalendarSlot,
|
|||
|
|
TimeZoneSlot
|
|||
|
|
} 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);
|
|||
|
|
const TWENTY_FOUR = JSBI.BigInt(24);
|
|||
|
|
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 HOUR_SECONDS = 3600;
|
|||
|
|
export const HOUR_NANOS = JSBI.multiply(JSBI.BigInt(HOUR_SECONDS), BILLION);
|
|||
|
|
const MINUTE_NANOS = JSBI.multiply(SIXTY, BILLION);
|
|||
|
|
const DAY_NANOS = JSBI.multiply(HOUR_NANOS, TWENTY_FOUR);
|
|||
|
|
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'
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
/* 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 */
|
|||
|
|
|
|||
|
|
type ArrayElement<ArrayType> = ArrayType extends readonly (infer ElementType)[] ? ElementType : never;
|
|||
|
|
type ArrayWithNewKeys<T, Keys> = Array<ArrayElement<T> | Keys>;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 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 (DEBUG) {
|
|||
|
|
if (arg != null) {
|
|||
|
|
throw new Error('Expected arg to be set.');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function isZero(value: JSBI): boolean {
|
|||
|
|
return JSBI.equal(value, ZERO);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type Stringless<T> = Exclude<T, string>;
|
|||
|
|
|
|||
|
|
function GetMethod<T extends { [s in M]?: (...args: any[]) => unknown }, M extends string & keyof T>(
|
|||
|
|
obj: T,
|
|||
|
|
methodName: M
|
|||
|
|
): T[M];
|
|||
|
|
function GetMethod<
|
|||
|
|
T extends string | { [s in M]?: (...args: any[]) => unknown },
|
|||
|
|
M extends string & keyof Stringless<T>
|
|||
|
|
>(obj: T, methodName: M): Stringless<T>[M] | undefined;
|
|||
|
|
function GetMethod<
|
|||
|
|
T extends string | { [s in M]?: undefined | ((...args: any[]) => unknown) },
|
|||
|
|
M extends string & keyof T
|
|||
|
|
>(obj: T, methodName: M): T[M] | undefined {
|
|||
|
|
const result = obj[methodName];
|
|||
|
|
if (result === undefined) return undefined;
|
|||
|
|
if (DEBUG) {
|
|||
|
|
if (typeof result !== 'function') throw new TypeError(`'${methodName}' must be a function`);
|
|||
|
|
}
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function Call<T, A extends readonly any[], R>(
|
|||
|
|
target: (this: T, ...args: A) => R,
|
|||
|
|
thisArgument: T,
|
|||
|
|
argumentsList: Readonly<A>
|
|||
|
|
): R {
|
|||
|
|
const args = arguments.length > 2 ? argumentsList : [];
|
|||
|
|
if (DEBUG) {
|
|||
|
|
if (!Array.isArray(argumentsList)) {
|
|||
|
|
throw new TypeError('Assertion failed: optional `argumentsList`, if provided, must be an array');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return ReflectApply(target, thisArgument, args);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 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 NumberCtor(value);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function ToIntegerOrInfinity(value: unknown) {
|
|||
|
|
const number = ToNumber(value);
|
|||
|
|
if (NumberIsNaN(number) || number === 0) {
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
if (!NumberIsFinite(number)) {
|
|||
|
|
return number;
|
|||
|
|
}
|
|||
|
|
const integer = MathFloor(MathAbs(number));
|
|||
|
|
if (integer === 0) {
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
return MathSign(number) * integer;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function IsIntegralNumber(argument: unknown) {
|
|||
|
|
if (typeof argument !== 'number' || NumberIsNaN(argument) || !NumberIsFinite(argument)) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
const absValue = MathAbs(argument);
|
|||
|
|
return MathFloor(absValue) === absValue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
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 ToIntegerWithTruncation(value: unknown): number {
|
|||
|
|
const number = ToNumber(value);
|
|||
|
|
if (number === 0) return 0;
|
|||
|
|
if (NumberIsNaN(number) || !NumberIsFinite(number)) {
|
|||
|
|
throw new RangeError('invalid number value');
|
|||
|
|
}
|
|||
|
|
const integer = MathTrunc(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 (!NumberIsFinite(number)) 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 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 isNegativeJSBI(value: JSBI): boolean {
|
|||
|
|
return JSBI.lessThan(value, ZERO);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function signJSBI(value: JSBI): 1 | 0 | -1 {
|
|||
|
|
if (isZero(value)) return 0;
|
|||
|
|
if (isNegativeJSBI(value)) return -1;
|
|||
|
|
return 1;
|
|||
|
|
}
|
|||
|
|
function abs(x: JSBI): JSBI {
|
|||
|
|
if (JSBI.lessThan(x, ZERO)) return JSBI.multiply(x, NEGATIVE_ONE);
|
|||
|
|
return x;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type BuiltinCastFunction = (v: unknown) => string | number;
|
|||
|
|
const BUILTIN_CASTS = new Map<AnyTemporalKey, BuiltinCastFunction>([
|
|||
|
|
['year', ToIntegerWithTruncation],
|
|||
|
|
['month', ToPositiveIntegerWithTruncation],
|
|||
|
|
['monthCode', ToString],
|
|||
|
|
['day', ToPositiveIntegerWithTruncation],
|
|||
|
|
['hour', ToIntegerWithTruncation],
|
|||
|
|
['minute', ToIntegerWithTruncation],
|
|||
|
|
['second', ToIntegerWithTruncation],
|
|||
|
|
['millisecond', ToIntegerWithTruncation],
|
|||
|
|
['microsecond', ToIntegerWithTruncation],
|
|||
|
|
['nanosecond', ToIntegerWithTruncation],
|
|||
|
|
['years', ToIntegerIfIntegral],
|
|||
|
|
['months', ToIntegerIfIntegral],
|
|||
|
|
['weeks', ToIntegerIfIntegral],
|
|||
|
|
['days', ToIntegerIfIntegral],
|
|||
|
|
['hours', ToIntegerIfIntegral],
|
|||
|
|
['minutes', ToIntegerIfIntegral],
|
|||
|
|
['seconds', ToIntegerIfIntegral],
|
|||
|
|
['milliseconds', ToIntegerIfIntegral],
|
|||
|
|
['microseconds', ToIntegerIfIntegral],
|
|||
|
|
['nanoseconds', ToIntegerIfIntegral],
|
|||
|
|
['era', ToString],
|
|||
|
|
['eraYear', ToIntegerOrInfinity],
|
|||
|
|
['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 ToObject<T>(value: T): T extends Record<string, unknown> ? T : object {
|
|||
|
|
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 string | symbol, T extends Record<K, unknown>>(
|
|||
|
|
target: T,
|
|||
|
|
source: T | undefined,
|
|||
|
|
excludedKeys: K[],
|
|||
|
|
excludedValues?: unknown[]
|
|||
|
|
) {
|
|||
|
|
if (typeof source === 'undefined' || source === null) return;
|
|||
|
|
|
|||
|
|
const keys = ReflectOwnKeys(source) as (keyof T)[];
|
|||
|
|
for (const nextKey of keys) {
|
|||
|
|
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 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 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 ParseTemporalTimeZone(stringIdent: string) {
|
|||
|
|
const { ianaName, offset, z } = ParseTemporalTimeZoneString(stringIdent);
|
|||
|
|
if (ianaName) return GetCanonicalTimeZoneIdentifier(ianaName);
|
|||
|
|
if (z) return 'UTC';
|
|||
|
|
// if !ianaName && !z then offset must be present
|
|||
|
|
assertExists(offset);
|
|||
|
|
const offsetNs = ParseTimeZoneOffsetString(offset);
|
|||
|
|
return FormatTimeZoneOffsetString(offsetNs);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function MaybeFormatCalendarAnnotation(
|
|||
|
|
calendar: CalendarSlot,
|
|||
|
|
showCalendar: Temporal.ShowCalendarOption['calendarName']
|
|||
|
|
): string {
|
|||
|
|
if (showCalendar === 'never') return '';
|
|||
|
|
return FormatCalendarAnnotation(ToTemporalCalendarIdentifier(calendar), showCalendar);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function FormatCalendarAnnotation(id: string, showCalendar: Temporal.ShowCalendarOption['calendarName']) {
|
|||
|
|
if (showCalendar === 'never') return '';
|
|||
|
|
if (showCalendar === 'auto' && id === 'iso8601') return '';
|
|||
|
|
const flag = showCalendar === 'critical' ? '!' : '';
|
|||
|
|
return `[${flag}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 = ToIntegerOrInfinity(yearString);
|
|||
|
|
const month = ToIntegerOrInfinity(match[2] || match[4]);
|
|||
|
|
const day = ToIntegerOrInfinity(match[3] || match[5]);
|
|||
|
|
const hour = ToIntegerOrInfinity(match[6]);
|
|||
|
|
const hasTime = match[6] !== undefined;
|
|||
|
|
const minute = ToIntegerOrInfinity(match[7] || match[10]);
|
|||
|
|
let second = ToIntegerOrInfinity(match[8] || match[11]);
|
|||
|
|
if (second === 60) second = 59;
|
|||
|
|
const fraction = (match[9] || match[12]) + '000000000';
|
|||
|
|
const millisecond = ToIntegerOrInfinity(fraction.slice(0, 3));
|
|||
|
|
const microsecond = ToIntegerOrInfinity(fraction.slice(3, 6));
|
|||
|
|
const nanosecond = ToIntegerOrInfinity(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';
|
|||
|
|
}
|
|||
|
|
const ianaName = match[19];
|
|||
|
|
const annotations = match[20];
|
|||
|
|
let calendar;
|
|||
|
|
for (const [, critical, key, value] of annotations.matchAll(PARSE.annotation)) {
|
|||
|
|
if (key === 'u-ca') {
|
|||
|
|
if (calendar === undefined) calendar = value;
|
|||
|
|
} else if (critical === '!') {
|
|||
|
|
throw new RangeError(`Unrecognized annotation: !${key}=${value}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
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, annotations;
|
|||
|
|
if (match) {
|
|||
|
|
hour = ToIntegerOrInfinity(match[1]);
|
|||
|
|
minute = ToIntegerOrInfinity(match[2] || match[5]);
|
|||
|
|
second = ToIntegerOrInfinity(match[3] || match[6]);
|
|||
|
|
if (second === 60) second = 59;
|
|||
|
|
const fraction = (match[4] || match[7]) + '000000000';
|
|||
|
|
millisecond = ToIntegerOrInfinity(fraction.slice(0, 3));
|
|||
|
|
microsecond = ToIntegerOrInfinity(fraction.slice(3, 6));
|
|||
|
|
nanosecond = ToIntegerOrInfinity(fraction.slice(6, 9));
|
|||
|
|
annotations = match[14];
|
|||
|
|
for (const [, critical, key, value] of annotations.matchAll(PARSE.annotation)) {
|
|||
|
|
if (key !== 'u-ca' && critical === '!') {
|
|||
|
|
throw new RangeError(`Unrecognized annotation: !${key}=${value}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (match[8]) throw new RangeError('Z designator not supported for PlainTime');
|
|||
|
|
} else {
|
|||
|
|
let z, hasTime;
|
|||
|
|
({ hasTime, hour, minute, second, millisecond, microsecond, nanosecond, 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 };
|
|||
|
|
}
|
|||
|
|
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 };
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
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 = ToIntegerOrInfinity(yearString);
|
|||
|
|
month = ToIntegerOrInfinity(match[2]);
|
|||
|
|
const annotations = match[3];
|
|||
|
|
for (const [, critical, key, value] of annotations.matchAll(PARSE.annotation)) {
|
|||
|
|
if (key === 'u-ca') {
|
|||
|
|
if (calendar === undefined) calendar = value;
|
|||
|
|
} else if (critical === '!') {
|
|||
|
|
throw new RangeError(`Unrecognized annotation: !${key}=${value}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
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) {
|
|||
|
|
month = ToIntegerOrInfinity(match[1]);
|
|||
|
|
day = ToIntegerOrInfinity(match[2]);
|
|||
|
|
const annotations = match[3];
|
|||
|
|
for (const [, critical, key, value] of annotations.matchAll(PARSE.annotation)) {
|
|||
|
|
if (key === 'u-ca') {
|
|||
|
|
if (calendar === undefined) calendar = value;
|
|||
|
|
} else if (critical === '!') {
|
|||
|
|
throw new RangeError(`Unrecognized annotation: !${key}=${value}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
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 };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 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;
|
|||
|
|
}> {
|
|||
|
|
const bareID = new RegExp(`^${PARSE.timeZoneID.source}$`, 'i');
|
|||
|
|
if (bareID.test(stringIdent)) return { ianaName: stringIdent };
|
|||
|
|
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 = 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 = ToIntegerOrInfinity((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 = ToIntegerOrInfinity((fMinutes + '000000000').slice(0, 9)) * 60 * sign;
|
|||
|
|
} else {
|
|||
|
|
seconds = secondsStr === undefined ? 0 : ToIntegerWithTruncation(secondsStr) * sign;
|
|||
|
|
if (fSeconds !== undefined) {
|
|||
|
|
excessNanoseconds = ToIntegerOrInfinity((fSeconds + '000000000').slice(0, 9)) * sign;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const nanoseconds = excessNanoseconds % 1000;
|
|||
|
|
const microseconds = MathTrunc(excessNanoseconds / 1000) % 1000;
|
|||
|
|
const milliseconds = MathTrunc(excessNanoseconds / 1e6) % 1000;
|
|||
|
|
seconds += MathTrunc(excessNanoseconds / 1e9) % 60;
|
|||
|
|
minutes += MathTrunc(excessNanoseconds / 6e10);
|
|||
|
|
|
|||
|
|
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, @typescript-eslint/no-unnecessary-type-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 = GetUTCEpochNanoseconds(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 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] = ToIntegerIfIntegral(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', 'expand', 'trunc', 'halfCeil', 'halfFloor', 'halfExpand', 'halfTrunc', 'halfEven'],
|
|||
|
|
fallback
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function NegateTemporalRoundingMode(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 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 ToCalendarNameOption(options: Temporal.ShowCalendarOption) {
|
|||
|
|
return GetOption(options, 'calendarName', ['auto', 'always', 'never', 'critical'], 'auto');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function ToTimeZoneNameOption(options: Temporal.ZonedDateTimeToStringOptions) {
|
|||
|
|
return GetOption(options, 'timeZoneName', ['auto', 'never', 'critical'], 'auto');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function ToShowOffsetOption(options: Temporal.ZonedDateTimeToStringOptions) {
|
|||
|
|
return GetOption(options, 'offset', ['auto', 'never'], 'auto');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function ToTemporalRoundingIncrement(options: { roundingIncrement?: number }) {
|
|||
|
|
let increment = options.roundingIncrement;
|
|||
|
|
if (increment === undefined) return 1;
|
|||
|
|
increment = ToNumber(increment);
|
|||
|
|
if (!NumberIsFinite(increment)) {
|
|||
|
|
throw new RangeError('roundingIncrement must be finite');
|
|||
|
|
}
|
|||
|
|
const integerIncrement = MathTrunc(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 ToFractionalSecondDigits(
|
|||
|
|
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 = MathFloor(digitsValue);
|
|||
|
|
if (!NumberIsFinite(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'>;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function ToSecondsStringPrecisionRecord(
|
|||
|
|
smallestUnit: Temporal.ToStringPrecisionOptions['smallestUnit'],
|
|||
|
|
precision: Temporal.ToStringPrecisionOptions['fractionalSecondDigits']
|
|||
|
|
): {
|
|||
|
|
precision: Temporal.ToStringPrecisionOptions['fractionalSecondDigits'] | 'minute';
|
|||
|
|
unit: UnitSmallerThanOrEqualTo<'minute'>;
|
|||
|
|
increment: number;
|
|||
|
|
} {
|
|||
|
|
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 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 = GetTemporalCalendarSlotValueWithISODefault(relativeTo);
|
|||
|
|
const fieldNames = CalendarFields(calendar, [
|
|||
|
|
'day',
|
|||
|
|
'hour',
|
|||
|
|
'microsecond',
|
|||
|
|
'millisecond',
|
|||
|
|
'minute',
|
|||
|
|
'month',
|
|||
|
|
'monthCode',
|
|||
|
|
'nanosecond',
|
|||
|
|
'second',
|
|||
|
|
'year'
|
|||
|
|
] as const);
|
|||
|
|
type FieldNamesWithTimeZoneAndOffset = ArrayWithNewKeys<typeof fieldNames, 'timeZone' | 'offset'>;
|
|||
|
|
(fieldNames as FieldNamesWithTimeZoneAndOffset).push('timeZone', 'offset');
|
|||
|
|
const fields = PrepareTemporalFields(relativeTo, fieldNames, []);
|
|||
|
|
const dateOptions = ObjectCreate(null) as Temporal.AssignmentOptions;
|
|||
|
|
dateOptions.overflow = 'constrain';
|
|||
|
|
({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = InterpretTemporalDateTimeFields(
|
|||
|
|
calendar,
|
|||
|
|
fields,
|
|||
|
|
dateOptions
|
|||
|
|
));
|
|||
|
|
offset = fields.offset;
|
|||
|
|
if (offset === undefined) offsetBehaviour = 'wall';
|
|||
|
|
timeZone = fields.timeZone;
|
|||
|
|
if (timeZone !== undefined) timeZone = ToTemporalTimeZoneSlotValue(timeZone);
|
|||
|
|
} else {
|
|||
|
|
let ianaName, z;
|
|||
|
|
({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, calendar, ianaName, offset, z } =
|
|||
|
|
ParseISODateTime(ToString(relativeTo)));
|
|||
|
|
if (ianaName) {
|
|||
|
|
timeZone = ToTemporalTimeZoneSlotValue(ianaName);
|
|||
|
|
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';
|
|||
|
|
if (!IsBuiltinCalendar(calendar)) throw new RangeError(`invalid calendar identifier ${calendar}`);
|
|||
|
|
calendar = ASCIILowercase(calendar);
|
|||
|
|
}
|
|||
|
|
if (timeZone === undefined) return CreateTemporalDate(year, month, day, calendar);
|
|||
|
|
// If offset is missing here, then offsetBehavior will never be be 'option'.
|
|||
|
|
assertExists(offset);
|
|||
|
|
const offsetNs = offsetBehaviour === 'option' ? ParseTimeZoneOffsetString(offset) : 0;
|
|||
|
|
const epochNanoseconds = InterpretISODateTimeOffset(
|
|||
|
|
year,
|
|||
|
|
month,
|
|||
|
|
day,
|
|||
|
|
hour,
|
|||
|
|
minute,
|
|||
|
|
second,
|
|||
|
|
millisecond,
|
|||
|
|
microsecond,
|
|||
|
|
nanosecond,
|
|||
|
|
offsetBehaviour,
|
|||
|
|
offsetNs,
|
|||
|
|
timeZone,
|
|||
|
|
'compatible',
|
|||
|
|
'reject',
|
|||
|
|
matchMinutes
|
|||
|
|
);
|
|||
|
|
return CreateTemporalZonedDateTime(epochNanoseconds, timeZone, 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;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type FieldCompleteness = 'complete' | 'partial';
|
|||
|
|
interface FieldPrepareOptions {
|
|||
|
|
emptySourceErrorMessage: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 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: Array<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;
|
|||
|
|
fields.sort();
|
|||
|
|
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);
|
|||
|
|
}
|
|||
|
|
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 is sorted to make the sort in PrepareTemporalFields more efficient.
|
|||
|
|
const fields: (keyof TimeRecord)[] = ['hour', 'microsecond', 'millisecond', 'minute', 'nanosecond', 'second'];
|
|||
|
|
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 = GetPlainDateTimeFor(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 = GetTemporalCalendarSlotValueWithISODefault(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
|
|||
|
|
let { year, month, day, calendar, z } = ParseTemporalDateString(ToString(item));
|
|||
|
|
if (z) throw new RangeError('Z designator not supported for PlainDate');
|
|||
|
|
if (!calendar) calendar = 'iso8601';
|
|||
|
|
if (!IsBuiltinCalendar(calendar)) throw new RangeError(`invalid calendar identifier ${calendar}`);
|
|||
|
|
calendar = ASCIILowercase(calendar);
|
|||
|
|
return CreateTemporalDate(year, month, day, calendar);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function InterpretTemporalDateTimeFields(
|
|||
|
|
calendar: CalendarSlot,
|
|||
|
|
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)) {
|
|||
|
|
ToTemporalOverflow(options); // validate and ignore
|
|||
|
|
return GetPlainDateTimeFor(GetSlot(item, TIME_ZONE), GetSlot(item, INSTANT), GetSlot(item, CALENDAR));
|
|||
|
|
}
|
|||
|
|
if (IsTemporalDate(item)) {
|
|||
|
|
ToTemporalOverflow(options); // validate and ignore
|
|||
|
|
return CreateTemporalDateTime(
|
|||
|
|
GetSlot(item, ISO_YEAR),
|
|||
|
|
GetSlot(item, ISO_MONTH),
|
|||
|
|
GetSlot(item, ISO_DAY),
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
GetSlot(item, CALENDAR)
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
calendar = GetTemporalCalendarSlotValueWithISODefault(item);
|
|||
|
|
const fieldNames = CalendarFields(calendar, [
|
|||
|
|
'day',
|
|||
|
|
'hour',
|
|||
|
|
'microsecond',
|
|||
|
|
'millisecond',
|
|||
|
|
'minute',
|
|||
|
|
'month',
|
|||
|
|
'monthCode',
|
|||
|
|
'nanosecond',
|
|||
|
|
'second',
|
|||
|
|
'year'
|
|||
|
|
] as const);
|
|||
|
|
const fields = PrepareTemporalFields(item, fieldNames, []);
|
|||
|
|
({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = InterpretTemporalDateTimeFields(
|
|||
|
|
calendar,
|
|||
|
|
fields,
|
|||
|
|
options
|
|||
|
|
));
|
|||
|
|
} else {
|
|||
|
|
ToTemporalOverflow(options); // validate and ignore
|
|||
|
|
let z;
|
|||
|
|
({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, calendar, z } =
|
|||
|
|
ParseTemporalDateTimeString(ToString(item)));
|
|||
|
|
if (z) throw new RangeError('Z designator not supported for PlainDateTime');
|
|||
|
|
RejectDateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond);
|
|||
|
|
if (!calendar) calendar = 'iso8601';
|
|||
|
|
if (!IsBuiltinCalendar(calendar)) throw new RangeError(`invalid calendar identifier ${calendar}`);
|
|||
|
|
calendar = ASCIILowercase(calendar);
|
|||
|
|
}
|
|||
|
|
return CreateTemporalDateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, calendar);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function ToTemporalDuration(item: DurationParams['from'][0]) {
|
|||
|
|
if (IsTemporalDuration(item)) return item;
|
|||
|
|
let { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } =
|
|||
|
|
ToTemporalDurationRecord(item);
|
|||
|
|
const TemporalDuration = GetIntrinsic('%Temporal.Duration%');
|
|||
|
|
return new TemporalDuration(
|
|||
|
|
years,
|
|||
|
|
months,
|
|||
|
|
weeks,
|
|||
|
|
days,
|
|||
|
|
hours,
|
|||
|
|
minutes,
|
|||
|
|
seconds,
|
|||
|
|
milliseconds,
|
|||
|
|
microseconds,
|
|||
|
|
nanoseconds
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function ToTemporalInstant(item: InstantParams['from'][0]) {
|
|||
|
|
if (IsTemporalInstant(item)) return item;
|
|||
|
|
if (IsTemporalZonedDateTime(item)) {
|
|||
|
|
const TemporalInstant = GetIntrinsic('%Temporal.Instant%');
|
|||
|
|
return new TemporalInstant(GetSlot(item, EPOCHNANOSECONDS));
|
|||
|
|
}
|
|||
|
|
const ns = ParseTemporalInstant(ToString(item));
|
|||
|
|
const TemporalInstant = GetIntrinsic('%Temporal.Instant%');
|
|||
|
|
return new TemporalInstant(ns);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function ToTemporalMonthDay(
|
|||
|
|
itemParam: PlainMonthDayParams['from'][0],
|
|||
|
|
options?: PlainMonthDayParams['from'][1]
|
|||
|
|
) {
|
|||
|
|
let item = itemParam;
|
|||
|
|
if (IsObject(item)) {
|
|||
|
|
if (IsTemporalMonthDay(item)) return item;
|
|||
|
|
let calendar: CalendarSlot, calendarAbsent: boolean;
|
|||
|
|
if (HasSlot(item, CALENDAR)) {
|
|||
|
|
calendar = GetSlot(item, CALENDAR);
|
|||
|
|
calendarAbsent = false;
|
|||
|
|
} else {
|
|||
|
|
let calendarFromItem = item.calendar;
|
|||
|
|
calendarAbsent = calendarFromItem === undefined;
|
|||
|
|
if (calendarFromItem === undefined) calendarFromItem = 'iso8601';
|
|||
|
|
calendar = ToTemporalCalendarSlotValue(calendarFromItem);
|
|||
|
|
}
|
|||
|
|
// HasSlot above adjusts the type of 'item' to include
|
|||
|
|
// TypesWithCalendarUnits, which causes type-inference failures below.
|
|||
|
|
// This is probably indicative of problems with HasSlot's typing.
|
|||
|
|
const fieldNames = CalendarFields(calendar, ['day', 'month', 'monthCode', 'year'] as const);
|
|||
|
|
const fields = PrepareTemporalFields(item, fieldNames, []);
|
|||
|
|
// Callers who omit the calendar are not writing calendar-independent
|
|||
|
|
// code. In that case, `monthCode`/`year` can be omitted; `month` and
|
|||
|
|
// `day` are sufficient. Add a `year` to satisfy calendar validation.
|
|||
|
|
if (calendarAbsent && fields.month !== undefined && fields.monthCode === undefined && fields.year === undefined) {
|
|||
|
|
fields.year = 1972;
|
|||
|
|
}
|
|||
|
|
return CalendarMonthDayFromFields(calendar, fields, options);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
ToTemporalOverflow(options); // validate and ignore
|
|||
|
|
let { month, day, referenceISOYear, calendar } = ParseTemporalMonthDayString(ToString(item));
|
|||
|
|
if (calendar === undefined) calendar = 'iso8601';
|
|||
|
|
if (!IsBuiltinCalendar(calendar)) throw new RangeError(`invalid calendar identifier ${calendar}`);
|
|||
|
|
calendar = ASCIILowercase(calendar);
|
|||
|
|
|
|||
|
|
if (referenceISOYear === undefined) {
|
|||
|
|
RejectISODate(1972, month, day);
|
|||
|
|
return CreateTemporalMonthDay(month, day, calendar);
|
|||
|
|
}
|
|||
|
|
const result = CreateTemporalMonthDay(month, day, calendar, referenceISOYear);
|
|||
|
|
return CalendarMonthDayFromFields(calendar, result);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function ToTemporalTime(
|
|||
|
|
itemParam: PlainTimeParams['from'][0],
|
|||
|
|
overflow: NonNullable<PlainTimeParams['from'][1]>['overflow'] = 'constrain'
|
|||
|
|
) {
|
|||
|
|
let item = itemParam;
|
|||
|
|
let hour, minute, second, millisecond, microsecond, nanosecond;
|
|||
|
|
if (IsObject(item)) {
|
|||
|
|
if (IsTemporalTime(item)) return item;
|
|||
|
|
if (IsTemporalZonedDateTime(item)) {
|
|||
|
|
item = GetPlainDateTimeFor(GetSlot(item, TIME_ZONE), GetSlot(item, INSTANT), GetSlot(item, CALENDAR));
|
|||
|
|
}
|
|||
|
|
if (IsTemporalDateTime(item)) {
|
|||
|
|
const TemporalPlainTime = GetIntrinsic('%Temporal.PlainTime%');
|
|||
|
|
return new TemporalPlainTime(
|
|||
|
|
GetSlot(item, ISO_HOUR),
|
|||
|
|
GetSlot(item, ISO_MINUTE),
|
|||
|
|
GetSlot(item, ISO_SECOND),
|
|||
|
|
GetSlot(item, ISO_MILLISECOND),
|
|||
|
|
GetSlot(item, ISO_MICROSECOND),
|
|||
|
|
GetSlot(item, ISO_NANOSECOND)
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
({ hour, minute, second, millisecond, microsecond, nanosecond } = ToTemporalTimeRecord(item));
|
|||
|
|
({ hour, minute, second, millisecond, microsecond, nanosecond } = RegulateTime(
|
|||
|
|
hour,
|
|||
|
|
minute,
|
|||
|
|
second,
|
|||
|
|
millisecond,
|
|||
|
|
microsecond,
|
|||
|
|
nanosecond,
|
|||
|
|
overflow
|
|||
|
|
));
|
|||
|
|
} else {
|
|||
|
|
({ hour, minute, second, millisecond, microsecond, nanosecond } = ParseTemporalTimeString(ToString(item)));
|
|||
|
|
RejectTime(hour, minute, second, millisecond, microsecond, nanosecond);
|
|||
|
|
}
|
|||
|
|
const TemporalPlainTime = GetIntrinsic('%Temporal.PlainTime%');
|
|||
|
|
return new TemporalPlainTime(hour, minute, second, millisecond, microsecond, nanosecond);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function ToTemporalYearMonth(
|
|||
|
|
item: PlainYearMonthParams['from'][0],
|
|||
|
|
options?: PlainYearMonthParams['from'][1]
|
|||
|
|
): Temporal.PlainYearMonth {
|
|||
|
|
if (IsObject(item)) {
|
|||
|
|
if (IsTemporalYearMonth(item)) return item;
|
|||
|
|
const calendar = GetTemporalCalendarSlotValueWithISODefault(item);
|
|||
|
|
const fieldNames = CalendarFields(calendar, ['month', 'monthCode', 'year'] as const);
|
|||
|
|
const fields = PrepareTemporalFields(item, fieldNames, []);
|
|||
|
|
return CalendarYearMonthFromFields(calendar, fields, options);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
ToTemporalOverflow(options); // validate and ignore
|
|||
|
|
let { year, month, referenceISODay, calendar } = ParseTemporalYearMonthString(ToString(item));
|
|||
|
|
if (calendar === undefined) calendar = 'iso8601';
|
|||
|
|
if (!IsBuiltinCalendar(calendar)) throw new RangeError(`invalid calendar identifier ${calendar}`);
|
|||
|
|
calendar = ASCIILowercase(calendar);
|
|||
|
|
|
|||
|
|
if (referenceISODay === undefined) {
|
|||
|
|
RejectISODate(year, month, 1);
|
|||
|
|
return CreateTemporalYearMonth(year, month, calendar);
|
|||
|
|
}
|
|||
|
|
const result = CreateTemporalYearMonth(year, month, calendar, referenceISODay);
|
|||
|
|
return CalendarYearMonthFromFields(calendar, result);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type OffsetBehaviour = 'wall' | 'exact' | 'option';
|
|||
|
|
|
|||
|
|
export function InterpretISODateTimeOffset(
|
|||
|
|
year: number,
|
|||
|
|
month: number,
|
|||
|
|
day: number,
|
|||
|
|
hour: number,
|
|||
|
|
minute: number,
|
|||
|
|
second: number,
|
|||
|
|
millisecond: number,
|
|||
|
|
microsecond: number,
|
|||
|
|
nanosecond: number,
|
|||
|
|
offsetBehaviour: OffsetBehaviour,
|
|||
|
|
offsetNs: number,
|
|||
|
|
timeZone: string | Temporal.TimeZoneProtocol,
|
|||
|
|
disambiguation: NonNullable<Temporal.ToInstantOptions['disambiguation']>,
|
|||
|
|
offsetOpt: Temporal.OffsetDisambiguationOptions['offset'],
|
|||
|
|
matchMinute: boolean
|
|||
|
|
) {
|
|||
|
|
const DateTime = GetIntrinsic('%Temporal.PlainDateTime%');
|
|||
|
|
const dt = new DateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond);
|
|||
|
|
|
|||
|
|
if (offsetBehaviour === 'wall' || offsetOpt === 'ignore') {
|
|||
|
|
// Simple case: ISO string without a TZ offset (or caller wants to ignore
|
|||
|
|
// the offset), so just convert DateTime to Instant in the given time zone
|
|||
|
|
const instant = GetInstantFor(timeZone, dt, disambiguation);
|
|||
|
|
return GetSlot(instant, EPOCHNANOSECONDS);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// The caller wants the offset to always win ('use') OR the caller is OK
|
|||
|
|
// with the offset winning ('prefer' or 'reject') as long as it's valid
|
|||
|
|
// for this timezone and date/time.
|
|||
|
|
if (offsetBehaviour === 'exact' || offsetOpt === 'use') {
|
|||
|
|
// Calculate the instant for the input's date/time and offset
|
|||
|
|
const epochNs = GetUTCEpochNanoseconds(
|
|||
|
|
year,
|
|||
|
|
month,
|
|||
|
|
day,
|
|||
|
|
hour,
|
|||
|
|
minute,
|
|||
|
|
second,
|
|||
|
|
millisecond,
|
|||
|
|
microsecond,
|
|||
|
|
nanosecond
|
|||
|
|
);
|
|||
|
|
if (epochNs === null) throw new RangeError('ZonedDateTime outside of supported range');
|
|||
|
|
return JSBI.subtract(epochNs, JSBI.BigInt(offsetNs));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// "prefer" or "reject"
|
|||
|
|
const possibleInstants = GetPossibleInstantsFor(timeZone, dt);
|
|||
|
|
for (const candidate of possibleInstants) {
|
|||
|
|
const candidateOffset = GetOffsetNanosecondsFor(timeZone, candidate);
|
|||
|
|
const roundedCandidateOffset = JSBI.toNumber(
|
|||
|
|
RoundNumberToIncrement(JSBI.BigInt(candidateOffset), MINUTE_NANOS, 'halfExpand')
|
|||
|
|
);
|
|||
|
|
if (candidateOffset === offsetNs || (matchMinute && roundedCandidateOffset === offsetNs)) {
|
|||
|
|
return GetSlot(candidate, EPOCHNANOSECONDS);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// the user-provided offset doesn't match any instants for this time
|
|||
|
|
// zone and date/time.
|
|||
|
|
if (offsetOpt === 'reject') {
|
|||
|
|
const offsetStr = FormatTimeZoneOffsetString(offsetNs);
|
|||
|
|
const timeZoneString = IsTemporalTimeZone(timeZone) ? GetSlot(timeZone, TIMEZONE_ID) : 'time zone';
|
|||
|
|
// The tsc emit for this line rewrites to invoke the PlainDateTime's valueOf method, NOT
|
|||
|
|
// toString (which is invoked by Node when using template literals directly).
|
|||
|
|
// See https://github.com/microsoft/TypeScript/issues/39744 for the proposed fix in tsc emit
|
|||
|
|
throw new RangeError(`Offset ${offsetStr} is invalid for ${dt.toString()} in ${timeZoneString}`);
|
|||
|
|
}
|
|||
|
|
// fall through: offsetOpt === 'prefer', but the offset doesn't match
|
|||
|
|
// so fall back to use the time zone instead.
|
|||
|
|
const instant = DisambiguatePossibleInstants(possibleInstants, timeZone, dt, disambiguation);
|
|||
|
|
return GetSlot(instant, EPOCHNANOSECONDS);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function ToTemporalZonedDateTime(
|
|||
|
|
item: ZonedDateTimeParams['from'][0],
|
|||
|
|
options?: ZonedDateTimeParams['from'][1]
|
|||
|
|
) {
|
|||
|
|
let year: number,
|
|||
|
|
month: number,
|
|||
|
|
day: number,
|
|||
|
|
hour: number,
|
|||
|
|
minute: number,
|
|||
|
|
second: number,
|
|||
|
|
millisecond: number,
|
|||
|
|
microsecond: number,
|
|||
|
|
nanosecond: number,
|
|||
|
|
timeZone,
|
|||
|
|
offset: string | undefined,
|
|||
|
|
calendar: string | Temporal.CalendarProtocol | undefined;
|
|||
|
|
let disambiguation: NonNullable<Temporal.ToInstantOptions['disambiguation']>;
|
|||
|
|
let offsetOpt: NonNullable<Temporal.OffsetDisambiguationOptions['offset']>;
|
|||
|
|
let matchMinute = false;
|
|||
|
|
let offsetBehaviour: OffsetBehaviour = 'option';
|
|||
|
|
if (IsObject(item)) {
|
|||
|
|
if (IsTemporalZonedDateTime(item)) return item;
|
|||
|
|
calendar = GetTemporalCalendarSlotValueWithISODefault(item);
|
|||
|
|
const fieldNames: (keyof Temporal.ZonedDateTimeLike)[] = CalendarFields(calendar, [
|
|||
|
|
'day',
|
|||
|
|
'hour',
|
|||
|
|
'microsecond',
|
|||
|
|
'millisecond',
|
|||
|
|
'minute',
|
|||
|
|
'month',
|
|||
|
|
'monthCode',
|
|||
|
|
'nanosecond',
|
|||
|
|
'second',
|
|||
|
|
'year'
|
|||
|
|
] as const);
|
|||
|
|
fieldNames.push('timeZone', 'offset');
|
|||
|
|
const fields = PrepareTemporalFields(item, fieldNames, ['timeZone']);
|
|||
|
|
timeZone = ToTemporalTimeZoneSlotValue(fields.timeZone);
|
|||
|
|
offset = fields.offset;
|
|||
|
|
if (offset === undefined) {
|
|||
|
|
offsetBehaviour = 'wall';
|
|||
|
|
}
|
|||
|
|
disambiguation = ToTemporalDisambiguation(options);
|
|||
|
|
offsetOpt = ToTemporalOffset(options, 'reject');
|
|||
|
|
({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = InterpretTemporalDateTimeFields(
|
|||
|
|
calendar,
|
|||
|
|
fields,
|
|||
|
|
options
|
|||
|
|
));
|
|||
|
|
} else {
|
|||
|
|
let ianaName, z;
|
|||
|
|
({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, ianaName, offset, z, calendar } =
|
|||
|
|
ParseTemporalZonedDateTimeString(ToString(item)));
|
|||
|
|
timeZone = ToTemporalTimeZoneSlotValue(ianaName);
|
|||
|
|
if (z) {
|
|||
|
|
offsetBehaviour = 'exact';
|
|||
|
|
} else if (!offset) {
|
|||
|
|
offsetBehaviour = 'wall';
|
|||
|
|
}
|
|||
|
|
if (!calendar) calendar = 'iso8601';
|
|||
|
|
if (!IsBuiltinCalendar(calendar)) throw new RangeError(`invalid calendar identifier ${calendar}`);
|
|||
|
|
calendar = ASCIILowercase(calendar);
|
|||
|
|
matchMinute = true; // ISO strings may specify offset with less precision
|
|||
|
|
disambiguation = ToTemporalDisambiguation(options);
|
|||
|
|
offsetOpt = ToTemporalOffset(options, 'reject');
|
|||
|
|
ToTemporalOverflow(options); // validate and ignore
|
|||
|
|
}
|
|||
|
|
let offsetNs = 0;
|
|||
|
|
// The code above guarantees that if offsetBehaviour === 'option', then
|
|||
|
|
// `offset` is not undefined.
|
|||
|
|
if (offsetBehaviour === 'option') offsetNs = ParseTimeZoneOffsetString(offset as string);
|
|||
|
|
const epochNanoseconds = InterpretISODateTimeOffset(
|
|||
|
|
year,
|
|||
|
|
month,
|
|||
|
|
day,
|
|||
|
|
hour,
|
|||
|
|
minute,
|
|||
|
|
second,
|
|||
|
|
millisecond,
|
|||
|
|
microsecond,
|
|||
|
|
nanosecond,
|
|||
|
|
offsetBehaviour,
|
|||
|
|
offsetNs,
|
|||
|
|
timeZone,
|
|||
|
|
disambiguation,
|
|||
|
|
offsetOpt,
|
|||
|
|
matchMinute
|
|||
|
|
);
|
|||
|
|
return CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CreateTemporalDateSlots(
|
|||
|
|
result: Temporal.PlainDate,
|
|||
|
|
isoYear: number,
|
|||
|
|
isoMonth: number,
|
|||
|
|
isoDay: number,
|
|||
|
|
calendar: CalendarSlot
|
|||
|
|
) {
|
|||
|
|
RejectISODate(isoYear, isoMonth, isoDay);
|
|||
|
|
RejectDateRange(isoYear, isoMonth, isoDay);
|
|||
|
|
|
|||
|
|
CreateSlots(result);
|
|||
|
|
SetSlot(result, ISO_YEAR, isoYear);
|
|||
|
|
SetSlot(result, ISO_MONTH, isoMonth);
|
|||
|
|
SetSlot(result, ISO_DAY, isoDay);
|
|||
|
|
SetSlot(result, CALENDAR, calendar);
|
|||
|
|
SetSlot(result, DATE_BRAND, true);
|
|||
|
|
|
|||
|
|
if (DEBUG) {
|
|||
|
|
ObjectDefineProperty(result, '_repr_', {
|
|||
|
|
value: `${result[Symbol.toStringTag]} <${TemporalDateToString(result)}>`,
|
|||
|
|
writable: false,
|
|||
|
|
enumerable: false,
|
|||
|
|
configurable: false
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CreateTemporalDate(
|
|||
|
|
isoYear: number,
|
|||
|
|
isoMonth: number,
|
|||
|
|
isoDay: number,
|
|||
|
|
calendar: CalendarSlot = 'iso8601'
|
|||
|
|
) {
|
|||
|
|
const TemporalPlainDate = GetIntrinsic('%Temporal.PlainDate%');
|
|||
|
|
const result = ObjectCreate(TemporalPlainDate.prototype);
|
|||
|
|
CreateTemporalDateSlots(result, isoYear, isoMonth, isoDay, calendar);
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CreateTemporalDateTimeSlots(
|
|||
|
|
result: Temporal.PlainDateTime,
|
|||
|
|
isoYear: number,
|
|||
|
|
isoMonth: number,
|
|||
|
|
isoDay: number,
|
|||
|
|
h: number,
|
|||
|
|
min: number,
|
|||
|
|
s: number,
|
|||
|
|
ms: number,
|
|||
|
|
µs: number,
|
|||
|
|
ns: number,
|
|||
|
|
calendar: CalendarSlot
|
|||
|
|
) {
|
|||
|
|
RejectDateTime(isoYear, isoMonth, isoDay, h, min, s, ms, µs, ns);
|
|||
|
|
RejectDateTimeRange(isoYear, isoMonth, isoDay, h, min, s, ms, µs, ns);
|
|||
|
|
|
|||
|
|
CreateSlots(result);
|
|||
|
|
SetSlot(result, ISO_YEAR, isoYear);
|
|||
|
|
SetSlot(result, ISO_MONTH, isoMonth);
|
|||
|
|
SetSlot(result, ISO_DAY, isoDay);
|
|||
|
|
SetSlot(result, ISO_HOUR, h);
|
|||
|
|
SetSlot(result, ISO_MINUTE, min);
|
|||
|
|
SetSlot(result, ISO_SECOND, s);
|
|||
|
|
SetSlot(result, ISO_MILLISECOND, ms);
|
|||
|
|
SetSlot(result, ISO_MICROSECOND, µs);
|
|||
|
|
SetSlot(result, ISO_NANOSECOND, ns);
|
|||
|
|
SetSlot(result, CALENDAR, calendar);
|
|||
|
|
|
|||
|
|
if (DEBUG) {
|
|||
|
|
Object.defineProperty(result, '_repr_', {
|
|||
|
|
value: `${result[Symbol.toStringTag]} <${TemporalDateTimeToString(result, 'auto')}>`,
|
|||
|
|
writable: false,
|
|||
|
|
enumerable: false,
|
|||
|
|
configurable: false
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CreateTemporalDateTime(
|
|||
|
|
isoYear: number,
|
|||
|
|
isoMonth: number,
|
|||
|
|
isoDay: number,
|
|||
|
|
h: number,
|
|||
|
|
min: number,
|
|||
|
|
s: number,
|
|||
|
|
ms: number,
|
|||
|
|
µs: number,
|
|||
|
|
ns: number,
|
|||
|
|
calendar: CalendarSlot = 'iso8601'
|
|||
|
|
) {
|
|||
|
|
const TemporalPlainDateTime = GetIntrinsic('%Temporal.PlainDateTime%');
|
|||
|
|
const result = ObjectCreate(TemporalPlainDateTime.prototype);
|
|||
|
|
CreateTemporalDateTimeSlots(result, isoYear, isoMonth, isoDay, h, min, s, ms, µs, ns, calendar);
|
|||
|
|
return result as Temporal.PlainDateTime;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CreateTemporalMonthDaySlots(
|
|||
|
|
result: Temporal.PlainMonthDay,
|
|||
|
|
isoMonth: number,
|
|||
|
|
isoDay: number,
|
|||
|
|
calendar: CalendarSlot,
|
|||
|
|
referenceISOYear: number
|
|||
|
|
) {
|
|||
|
|
RejectISODate(referenceISOYear, isoMonth, isoDay);
|
|||
|
|
RejectDateRange(referenceISOYear, isoMonth, isoDay);
|
|||
|
|
|
|||
|
|
CreateSlots(result);
|
|||
|
|
SetSlot(result, ISO_MONTH, isoMonth);
|
|||
|
|
SetSlot(result, ISO_DAY, isoDay);
|
|||
|
|
SetSlot(result, ISO_YEAR, referenceISOYear);
|
|||
|
|
SetSlot(result, CALENDAR, calendar);
|
|||
|
|
SetSlot(result, MONTH_DAY_BRAND, true);
|
|||
|
|
|
|||
|
|
if (DEBUG) {
|
|||
|
|
Object.defineProperty(result, '_repr_', {
|
|||
|
|
value: `${result[Symbol.toStringTag]} <${TemporalMonthDayToString(result)}>`,
|
|||
|
|
writable: false,
|
|||
|
|
enumerable: false,
|
|||
|
|
configurable: false
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CreateTemporalMonthDay(
|
|||
|
|
isoMonth: number,
|
|||
|
|
isoDay: number,
|
|||
|
|
calendar: CalendarSlot = 'iso8601',
|
|||
|
|
referenceISOYear = 1972
|
|||
|
|
) {
|
|||
|
|
const TemporalPlainMonthDay = GetIntrinsic('%Temporal.PlainMonthDay%');
|
|||
|
|
const result = ObjectCreate(TemporalPlainMonthDay.prototype);
|
|||
|
|
CreateTemporalMonthDaySlots(result, isoMonth, isoDay, calendar, referenceISOYear);
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CreateTemporalYearMonthSlots(
|
|||
|
|
result: Temporal.PlainYearMonth,
|
|||
|
|
isoYear: number,
|
|||
|
|
isoMonth: number,
|
|||
|
|
calendar: CalendarSlot,
|
|||
|
|
referenceISODay: number
|
|||
|
|
) {
|
|||
|
|
RejectISODate(isoYear, isoMonth, referenceISODay);
|
|||
|
|
RejectYearMonthRange(isoYear, isoMonth);
|
|||
|
|
|
|||
|
|
CreateSlots(result);
|
|||
|
|
SetSlot(result, ISO_YEAR, isoYear);
|
|||
|
|
SetSlot(result, ISO_MONTH, isoMonth);
|
|||
|
|
SetSlot(result, ISO_DAY, referenceISODay);
|
|||
|
|
SetSlot(result, CALENDAR, calendar);
|
|||
|
|
SetSlot(result, YEAR_MONTH_BRAND, true);
|
|||
|
|
|
|||
|
|
if (DEBUG) {
|
|||
|
|
Object.defineProperty(result, '_repr_', {
|
|||
|
|
value: `${result[Symbol.toStringTag]} <${TemporalYearMonthToString(result)}>`,
|
|||
|
|
writable: false,
|
|||
|
|
enumerable: false,
|
|||
|
|
configurable: false
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CreateTemporalYearMonth(
|
|||
|
|
isoYear: number,
|
|||
|
|
isoMonth: number,
|
|||
|
|
calendar: CalendarSlot = 'iso8601',
|
|||
|
|
referenceISODay = 1
|
|||
|
|
) {
|
|||
|
|
const TemporalPlainYearMonth = GetIntrinsic('%Temporal.PlainYearMonth%');
|
|||
|
|
const result = ObjectCreate(TemporalPlainYearMonth.prototype);
|
|||
|
|
CreateTemporalYearMonthSlots(result, isoYear, isoMonth, calendar, referenceISODay);
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CreateTemporalZonedDateTimeSlots(
|
|||
|
|
result: Temporal.ZonedDateTime,
|
|||
|
|
epochNanoseconds: JSBI,
|
|||
|
|
timeZone: string | Temporal.TimeZoneProtocol,
|
|||
|
|
calendar: CalendarSlot
|
|||
|
|
) {
|
|||
|
|
ValidateEpochNanoseconds(epochNanoseconds);
|
|||
|
|
|
|||
|
|
CreateSlots(result);
|
|||
|
|
SetSlot(result, EPOCHNANOSECONDS, epochNanoseconds);
|
|||
|
|
SetSlot(result, TIME_ZONE, timeZone);
|
|||
|
|
SetSlot(result, CALENDAR, calendar);
|
|||
|
|
|
|||
|
|
const TemporalInstant = GetIntrinsic('%Temporal.Instant%');
|
|||
|
|
const instant = new TemporalInstant(GetSlot(result, EPOCHNANOSECONDS));
|
|||
|
|
SetSlot(result, INSTANT, instant);
|
|||
|
|
|
|||
|
|
if (DEBUG) {
|
|||
|
|
Object.defineProperty(result, '_repr_', {
|
|||
|
|
value: `${result[Symbol.toStringTag]} <${TemporalZonedDateTimeToString(result, 'auto')}>`,
|
|||
|
|
writable: false,
|
|||
|
|
enumerable: false,
|
|||
|
|
configurable: false
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CreateTemporalZonedDateTime(
|
|||
|
|
epochNanoseconds: JSBI,
|
|||
|
|
timeZone: string | Temporal.TimeZoneProtocol,
|
|||
|
|
calendar: CalendarSlot = 'iso8601'
|
|||
|
|
) {
|
|||
|
|
const TemporalZonedDateTime = GetIntrinsic('%Temporal.ZonedDateTime%');
|
|||
|
|
const result = ObjectCreate(TemporalZonedDateTime.prototype);
|
|||
|
|
CreateTemporalZonedDateTimeSlots(result, epochNanoseconds, timeZone, calendar);
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TODO: should (can?) we make this generic so the field names are checked
|
|||
|
|
// against the type that the calendar is a property of?
|
|||
|
|
export function CalendarFields<K extends AnyTemporalKey>(calendar: CalendarSlot, fieldNamesParam: ReadonlyArray<K>) {
|
|||
|
|
if (typeof calendar === 'string') {
|
|||
|
|
const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%');
|
|||
|
|
const calendarObj = new TemporalCalendar(calendar);
|
|||
|
|
return Call(GetIntrinsic('%Temporal.Calendar.prototype.fields%'), calendarObj, [fieldNamesParam]) as K[];
|
|||
|
|
}
|
|||
|
|
const fields = GetMethod(calendar, 'fields');
|
|||
|
|
const fieldNames = Call(fields, calendar, [fieldNamesParam]);
|
|||
|
|
const result: K[] = [];
|
|||
|
|
for (const name of fieldNames) {
|
|||
|
|
if (typeof name !== 'string') throw new TypeError('bad return from calendar.fields()');
|
|||
|
|
ArrayPrototypePush.call(result, name);
|
|||
|
|
}
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CalendarMergeFields<Base extends Record<string, unknown>, ToAdd extends Record<string, unknown>>(
|
|||
|
|
calendar: CalendarSlot,
|
|||
|
|
fields: Base,
|
|||
|
|
additionalFields: ToAdd
|
|||
|
|
) {
|
|||
|
|
if (typeof calendar === 'string') {
|
|||
|
|
const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%');
|
|||
|
|
const calendarObj = new TemporalCalendar(calendar);
|
|||
|
|
return Call(GetIntrinsic('%Temporal.Calendar.prototype.mergeFields%'), calendarObj, [
|
|||
|
|
fields,
|
|||
|
|
additionalFields
|
|||
|
|
]) as Base & ToAdd;
|
|||
|
|
}
|
|||
|
|
const mergeFields = GetMethod(calendar, 'mergeFields');
|
|||
|
|
const result = Call(mergeFields, calendar, [fields, additionalFields]);
|
|||
|
|
if (!IsObject(result)) throw new TypeError('bad return from calendar.mergeFields()');
|
|||
|
|
return result as Base & ToAdd;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CalendarDateAdd(
|
|||
|
|
calendar: CalendarSlot,
|
|||
|
|
date: CalendarProtocolParams['dateAdd'][0],
|
|||
|
|
duration: CalendarProtocolParams['dateAdd'][1],
|
|||
|
|
options: CalendarProtocolParams['dateAdd'][2],
|
|||
|
|
dateAddParam?: Temporal.CalendarProtocol['dateAdd'] | undefined
|
|||
|
|
) {
|
|||
|
|
let dateAdd = dateAddParam;
|
|||
|
|
if (typeof calendar === 'string') {
|
|||
|
|
const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%');
|
|||
|
|
const calendarObj = new TemporalCalendar(calendar);
|
|||
|
|
return Call(GetIntrinsic('%Temporal.Calendar.prototype.dateAdd%'), calendarObj, [date, duration, options]);
|
|||
|
|
}
|
|||
|
|
if (dateAdd === undefined) {
|
|||
|
|
dateAdd = GetMethod(calendar, 'dateAdd');
|
|||
|
|
}
|
|||
|
|
const result = ReflectApply(dateAdd, calendar, [date, duration, options]);
|
|||
|
|
if (!IsTemporalDate(result)) throw new TypeError('invalid result');
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function CalendarDateUntil(
|
|||
|
|
calendar: CalendarSlot,
|
|||
|
|
date: CalendarProtocolParams['dateUntil'][0],
|
|||
|
|
otherDate: CalendarProtocolParams['dateUntil'][1],
|
|||
|
|
options: CalendarProtocolParams['dateUntil'][2],
|
|||
|
|
dateUntilParam?: Temporal.CalendarProtocol['dateUntil'] | undefined
|
|||
|
|
) {
|
|||
|
|
let dateUntil = dateUntilParam;
|
|||
|
|
if (typeof calendar === 'string') {
|
|||
|
|
const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%');
|
|||
|
|
const calendarObj = new TemporalCalendar(calendar);
|
|||
|
|
return Call(GetIntrinsic('%Temporal.Calendar.prototype.dateUntil%'), calendarObj, [date, otherDate, options]);
|
|||
|
|
}
|
|||
|
|
if (dateUntil === undefined) {
|
|||
|
|
dateUntil = GetMethod(calendar, 'dateUntil');
|
|||
|
|
}
|
|||
|
|
const result = ReflectApply(dateUntil, calendar, [date, otherDate, options]);
|
|||
|
|
if (!IsTemporalDuration(result)) throw new TypeError('invalid result');
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CalendarYear(calendar: CalendarSlot, dateLike: CalendarProtocolParams['year'][0]) {
|
|||
|
|
if (typeof calendar === 'string') {
|
|||
|
|
const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%');
|
|||
|
|
const calendarObj = new TemporalCalendar(calendar);
|
|||
|
|
return Call(GetIntrinsic('%Temporal.Calendar.prototype.year%'), calendarObj, [dateLike]);
|
|||
|
|
}
|
|||
|
|
const year = GetMethod(calendar, 'year');
|
|||
|
|
let result = Call(year, calendar, [dateLike]);
|
|||
|
|
if (typeof result !== 'number') {
|
|||
|
|
throw new TypeError('calendar year result must be an integer');
|
|||
|
|
}
|
|||
|
|
if (!IsIntegralNumber(result)) {
|
|||
|
|
throw new RangeError('calendar year result must be an integer');
|
|||
|
|
}
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CalendarMonth(calendar: CalendarSlot, dateLike: CalendarProtocolParams['month'][0]) {
|
|||
|
|
if (typeof calendar === 'string') {
|
|||
|
|
const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%');
|
|||
|
|
const calendarObj = new TemporalCalendar(calendar);
|
|||
|
|
return Call(GetIntrinsic('%Temporal.Calendar.prototype.month%'), calendarObj, [dateLike]);
|
|||
|
|
}
|
|||
|
|
const month = GetMethod(calendar, 'month');
|
|||
|
|
let result = Call(month, calendar, [dateLike]);
|
|||
|
|
if (typeof result !== 'number') {
|
|||
|
|
throw new TypeError('calendar month result must be a positive integer');
|
|||
|
|
}
|
|||
|
|
if (!IsIntegralNumber(result) || result < 1) {
|
|||
|
|
throw new RangeError('calendar month result must be a positive integer');
|
|||
|
|
}
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CalendarMonthCode(calendar: CalendarSlot, dateLike: CalendarProtocolParams['monthCode'][0]) {
|
|||
|
|
if (typeof calendar === 'string') {
|
|||
|
|
const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%');
|
|||
|
|
const calendarObj = new TemporalCalendar(calendar);
|
|||
|
|
return Call(GetIntrinsic('%Temporal.Calendar.prototype.monthCode%'), calendarObj, [dateLike]);
|
|||
|
|
}
|
|||
|
|
const monthCode = GetMethod(calendar, 'monthCode');
|
|||
|
|
let result = Call(monthCode, calendar, [dateLike]);
|
|||
|
|
if (typeof result !== 'string') {
|
|||
|
|
throw new TypeError('calendar monthCode result must be a string');
|
|||
|
|
}
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CalendarDay(calendar: CalendarSlot, dateLike: CalendarProtocolParams['era'][0]) {
|
|||
|
|
if (typeof calendar === 'string') {
|
|||
|
|
const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%');
|
|||
|
|
const calendarObj = new TemporalCalendar(calendar);
|
|||
|
|
return Call(GetIntrinsic('%Temporal.Calendar.prototype.day%'), calendarObj, [dateLike]);
|
|||
|
|
}
|
|||
|
|
const day = GetMethod(calendar, 'day');
|
|||
|
|
const result = Call(day, calendar, [dateLike]);
|
|||
|
|
if (typeof result !== 'number') {
|
|||
|
|
throw new TypeError('calendar day result must be a positive integer');
|
|||
|
|
}
|
|||
|
|
if (!IsIntegralNumber(result) || result < 1) {
|
|||
|
|
throw new RangeError('calendar day result must be a positive integer');
|
|||
|
|
}
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CalendarEra(calendar: CalendarSlot, dateLike: CalendarProtocolParams['era'][0]) {
|
|||
|
|
if (typeof calendar === 'string') {
|
|||
|
|
const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%');
|
|||
|
|
const calendarObj = new TemporalCalendar(calendar);
|
|||
|
|
return Call(GetIntrinsic('%Temporal.Calendar.prototype.era%'), calendarObj, [dateLike]);
|
|||
|
|
}
|
|||
|
|
const era = GetMethod(calendar, 'era');
|
|||
|
|
let result = Call(era, calendar, [dateLike]);
|
|||
|
|
if (result === undefined) {
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
if (typeof result !== 'string') {
|
|||
|
|
throw new TypeError('calendar era result must be a string or undefined');
|
|||
|
|
}
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CalendarEraYear(calendar: CalendarSlot, dateLike: CalendarProtocolParams['era'][0]) {
|
|||
|
|
if (typeof calendar === 'string') {
|
|||
|
|
const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%');
|
|||
|
|
const calendarObj = new TemporalCalendar(calendar);
|
|||
|
|
return Call(GetIntrinsic('%Temporal.Calendar.prototype.eraYear%'), calendarObj, [dateLike]);
|
|||
|
|
}
|
|||
|
|
const eraYear = GetMethod(calendar, 'eraYear');
|
|||
|
|
let result = Call(eraYear, calendar, [dateLike]);
|
|||
|
|
if (result === undefined) {
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
if (typeof result !== 'number') {
|
|||
|
|
throw new TypeError('calendar eraYear result must be an integer or undefined');
|
|||
|
|
}
|
|||
|
|
if (!IsIntegralNumber(result)) {
|
|||
|
|
throw new RangeError('calendar eraYear result must be an integer or undefined');
|
|||
|
|
}
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CalendarDayOfWeek(calendar: CalendarSlot, dateLike: CalendarProtocolParams['era'][0]) {
|
|||
|
|
if (typeof calendar === 'string') {
|
|||
|
|
const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%');
|
|||
|
|
const calendarObj = new TemporalCalendar(calendar);
|
|||
|
|
return Call(GetIntrinsic('%Temporal.Calendar.prototype.dayOfWeek%'), calendarObj, [dateLike]);
|
|||
|
|
}
|
|||
|
|
const dayOfWeek = GetMethod(calendar, 'dayOfWeek');
|
|||
|
|
const result = Call(dayOfWeek, calendar, [dateLike]);
|
|||
|
|
if (typeof result !== 'number') {
|
|||
|
|
throw new TypeError('calendar dayOfWeek result must be a positive integer');
|
|||
|
|
}
|
|||
|
|
if (!IsIntegralNumber(result) || result < 1) {
|
|||
|
|
throw new RangeError('calendar dayOfWeek result must be a positive integer');
|
|||
|
|
}
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CalendarDayOfYear(calendar: CalendarSlot, dateLike: CalendarProtocolParams['era'][0]) {
|
|||
|
|
if (typeof calendar === 'string') {
|
|||
|
|
const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%');
|
|||
|
|
const calendarObj = new TemporalCalendar(calendar);
|
|||
|
|
return Call(GetIntrinsic('%Temporal.Calendar.prototype.dayOfYear%'), calendarObj, [dateLike]);
|
|||
|
|
}
|
|||
|
|
const dayOfYear = GetMethod(calendar, 'dayOfYear');
|
|||
|
|
const result = Call(dayOfYear, calendar, [dateLike]);
|
|||
|
|
if (typeof result !== 'number') {
|
|||
|
|
throw new TypeError('calendar dayOfYear result must be a positive integer');
|
|||
|
|
}
|
|||
|
|
if (!IsIntegralNumber(result) || result < 1) {
|
|||
|
|
throw new RangeError('calendar dayOfYear result must be a positive integer');
|
|||
|
|
}
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CalendarWeekOfYear(calendar: CalendarSlot, dateLike: CalendarProtocolParams['era'][0]) {
|
|||
|
|
if (typeof calendar === 'string') {
|
|||
|
|
const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%');
|
|||
|
|
const calendarObj = new TemporalCalendar(calendar);
|
|||
|
|
return Call(GetIntrinsic('%Temporal.Calendar.prototype.weekOfYear%'), calendarObj, [dateLike]);
|
|||
|
|
}
|
|||
|
|
const weekOfYear = GetMethod(calendar, 'weekOfYear');
|
|||
|
|
const result = Call(weekOfYear, calendar, [dateLike]);
|
|||
|
|
if (typeof result !== 'number') {
|
|||
|
|
throw new TypeError('calendar weekOfYear result must be a positive integer');
|
|||
|
|
}
|
|||
|
|
if (!IsIntegralNumber(result) || result < 1) {
|
|||
|
|
throw new RangeError('calendar weekOfYear result must be a positive integer');
|
|||
|
|
}
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CalendarYearOfWeek(calendar: CalendarSlot, dateLike: CalendarProtocolParams['era'][0]) {
|
|||
|
|
if (typeof calendar === 'string') {
|
|||
|
|
const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%');
|
|||
|
|
const calendarObj = new TemporalCalendar(calendar);
|
|||
|
|
return Call(GetIntrinsic('%Temporal.Calendar.prototype.yearOfWeek%'), calendarObj, [dateLike]);
|
|||
|
|
}
|
|||
|
|
const yearOfWeek = GetMethod(calendar, 'yearOfWeek');
|
|||
|
|
const result = Call(yearOfWeek, calendar, [dateLike]);
|
|||
|
|
if (typeof result !== 'number') {
|
|||
|
|
throw new TypeError('calendar yearOfWeek result must be an integer');
|
|||
|
|
}
|
|||
|
|
if (!IsIntegralNumber(result)) {
|
|||
|
|
throw new RangeError('calendar yearOfWeek result must be an integer');
|
|||
|
|
}
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CalendarDaysInWeek(calendar: CalendarSlot, dateLike: CalendarProtocolParams['era'][0]) {
|
|||
|
|
if (typeof calendar === 'string') {
|
|||
|
|
const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%');
|
|||
|
|
const calendarObj = new TemporalCalendar(calendar);
|
|||
|
|
return Call(GetIntrinsic('%Temporal.Calendar.prototype.daysInWeek%'), calendarObj, [dateLike]);
|
|||
|
|
}
|
|||
|
|
const daysInWeek = GetMethod(calendar, 'daysInWeek');
|
|||
|
|
const result = Call(daysInWeek, calendar, [dateLike]);
|
|||
|
|
if (typeof result !== 'number') {
|
|||
|
|
throw new TypeError('calendar daysInWeek result must be a positive integer');
|
|||
|
|
}
|
|||
|
|
if (!IsIntegralNumber(result) || result < 1) {
|
|||
|
|
throw new RangeError('calendar daysInWeek result must be a positive integer');
|
|||
|
|
}
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CalendarDaysInMonth(calendar: CalendarSlot, dateLike: CalendarProtocolParams['era'][0]) {
|
|||
|
|
if (typeof calendar === 'string') {
|
|||
|
|
const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%');
|
|||
|
|
const calendarObj = new TemporalCalendar(calendar);
|
|||
|
|
return Call(GetIntrinsic('%Temporal.Calendar.prototype.daysInMonth%'), calendarObj, [dateLike]);
|
|||
|
|
}
|
|||
|
|
const daysInMonth = GetMethod(calendar, 'daysInMonth');
|
|||
|
|
const result = Call(daysInMonth, calendar, [dateLike]);
|
|||
|
|
if (typeof result !== 'number') {
|
|||
|
|
throw new TypeError('calendar daysInMonth result must be a positive integer');
|
|||
|
|
}
|
|||
|
|
if (!IsIntegralNumber(result) || result < 1) {
|
|||
|
|
throw new RangeError('calendar daysInMonth result must be a positive integer');
|
|||
|
|
}
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CalendarDaysInYear(calendar: CalendarSlot, dateLike: CalendarProtocolParams['era'][0]) {
|
|||
|
|
if (typeof calendar === 'string') {
|
|||
|
|
const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%');
|
|||
|
|
const calendarObj = new TemporalCalendar(calendar);
|
|||
|
|
return Call(GetIntrinsic('%Temporal.Calendar.prototype.daysInYear%'), calendarObj, [dateLike]);
|
|||
|
|
}
|
|||
|
|
const daysInYear = GetMethod(calendar, 'daysInYear');
|
|||
|
|
const result = Call(daysInYear, calendar, [dateLike]);
|
|||
|
|
if (typeof result !== 'number') {
|
|||
|
|
throw new TypeError('calendar daysInYear result must be a positive integer');
|
|||
|
|
}
|
|||
|
|
if (!IsIntegralNumber(result) || result < 1) {
|
|||
|
|
throw new RangeError('calendar daysInYear result must be a positive integer');
|
|||
|
|
}
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CalendarMonthsInYear(calendar: CalendarSlot, dateLike: CalendarProtocolParams['era'][0]) {
|
|||
|
|
if (typeof calendar === 'string') {
|
|||
|
|
const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%');
|
|||
|
|
const calendarObj = new TemporalCalendar(calendar);
|
|||
|
|
return Call(GetIntrinsic('%Temporal.Calendar.prototype.monthsInYear%'), calendarObj, [dateLike]);
|
|||
|
|
}
|
|||
|
|
const monthsInYear = GetMethod(calendar, 'monthsInYear');
|
|||
|
|
const result = Call(monthsInYear, calendar, [dateLike]);
|
|||
|
|
if (typeof result !== 'number') {
|
|||
|
|
throw new TypeError('calendar monthsInYear result must be a positive integer');
|
|||
|
|
}
|
|||
|
|
if (!IsIntegralNumber(result) || result < 1) {
|
|||
|
|
throw new RangeError('calendar monthsInYear result must be a positive integer');
|
|||
|
|
}
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CalendarInLeapYear(calendar: CalendarSlot, dateLike: CalendarProtocolParams['era'][0]) {
|
|||
|
|
if (typeof calendar === 'string') {
|
|||
|
|
const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%');
|
|||
|
|
const calendarObj = new TemporalCalendar(calendar);
|
|||
|
|
return Call(GetIntrinsic('%Temporal.Calendar.prototype.inLeapYear%'), calendarObj, [dateLike]);
|
|||
|
|
}
|
|||
|
|
const inLeapYear = GetMethod(calendar, 'inLeapYear');
|
|||
|
|
const result = Call(inLeapYear, calendar, [dateLike]);
|
|||
|
|
if (typeof result !== 'boolean') {
|
|||
|
|
throw new TypeError('calendar inLeapYear result must be a boolean');
|
|||
|
|
}
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type MaybeCalendarProtocol = Partial<Omit<Temporal.CalendarProtocol, 'toString' | 'toJSON'>>;
|
|||
|
|
function ObjectImplementsTemporalCalendarProtocol(object: MaybeCalendarProtocol) {
|
|||
|
|
if (IsTemporalCalendar(object)) return true;
|
|||
|
|
return (
|
|||
|
|
'dateAdd' in object &&
|
|||
|
|
'dateFromFields' in object &&
|
|||
|
|
'dateUntil' in object &&
|
|||
|
|
'day' in object &&
|
|||
|
|
'dayOfWeek' in object &&
|
|||
|
|
'dayOfYear' in object &&
|
|||
|
|
'daysInMonth' in object &&
|
|||
|
|
'daysInWeek' in object &&
|
|||
|
|
'daysInYear' in object &&
|
|||
|
|
'fields' in object &&
|
|||
|
|
'id' in object &&
|
|||
|
|
'inLeapYear' in object &&
|
|||
|
|
'mergeFields' in object &&
|
|||
|
|
'month' in object &&
|
|||
|
|
'monthCode' in object &&
|
|||
|
|
'monthDayFromFields' in object &&
|
|||
|
|
'monthsInYear' in object &&
|
|||
|
|
'weekOfYear' in object &&
|
|||
|
|
'year' in object &&
|
|||
|
|
'yearMonthFromFields' in object &&
|
|||
|
|
'yearOfWeek' in object
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function ToTemporalCalendarSlotValue(calendarLike: string): string;
|
|||
|
|
export function ToTemporalCalendarSlotValue(calendarLike: Temporal.CalendarProtocol): Temporal.CalendarProtocol;
|
|||
|
|
export function ToTemporalCalendarSlotValue(calendarLike: Temporal.CalendarLike): string | Temporal.CalendarProtocol;
|
|||
|
|
export function ToTemporalCalendarSlotValue(calendarLike: CalendarParams['from'][0]) {
|
|||
|
|
if (IsObject(calendarLike)) {
|
|||
|
|
if (HasSlot(calendarLike, CALENDAR)) return GetSlot(calendarLike, CALENDAR);
|
|||
|
|
if (!ObjectImplementsTemporalCalendarProtocol(calendarLike)) {
|
|||
|
|
throw new TypeError('expected a Temporal.Calendar or object implementing the Temporal.Calendar protocol');
|
|||
|
|
}
|
|||
|
|
return calendarLike;
|
|||
|
|
}
|
|||
|
|
const identifier = ToString(calendarLike);
|
|||
|
|
if (IsBuiltinCalendar(identifier)) return ASCIILowercase(identifier);
|
|||
|
|
let calendar;
|
|||
|
|
try {
|
|||
|
|
({ calendar } = ParseISODateTime(identifier));
|
|||
|
|
} catch {
|
|||
|
|
try {
|
|||
|
|
({ calendar } = ParseTemporalYearMonthString(identifier));
|
|||
|
|
} catch {
|
|||
|
|
({ calendar } = ParseTemporalMonthDayString(identifier));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (!calendar) calendar = 'iso8601';
|
|||
|
|
if (!IsBuiltinCalendar(calendar)) throw new RangeError(`invalid calendar identifier ${calendar}`);
|
|||
|
|
return ASCIILowercase(calendar);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function GetTemporalCalendarSlotValueWithISODefault(item: { calendar?: Temporal.CalendarLike }): CalendarSlot {
|
|||
|
|
if (HasSlot(item, CALENDAR)) return GetSlot(item, CALENDAR);
|
|||
|
|
const { calendar } = item;
|
|||
|
|
if (calendar === undefined) return 'iso8601';
|
|||
|
|
return ToTemporalCalendarSlotValue(calendar);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function ToTemporalCalendarIdentifier(slotValue: CalendarSlot) {
|
|||
|
|
if (typeof slotValue === 'string') return slotValue;
|
|||
|
|
const result = slotValue.id;
|
|||
|
|
if (typeof result !== 'string') throw new TypeError('calendar.id should be a string');
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function ToTemporalCalendarObject(slotValue: CalendarSlot) {
|
|||
|
|
if (IsObject(slotValue)) return slotValue;
|
|||
|
|
const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%');
|
|||
|
|
return new TemporalCalendar(slotValue);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CalendarEquals(one: CalendarSlot, two: CalendarSlot) {
|
|||
|
|
if (one === two) return true;
|
|||
|
|
const cal1 = ToTemporalCalendarIdentifier(one);
|
|||
|
|
const cal2 = ToTemporalCalendarIdentifier(two);
|
|||
|
|
return cal1 === cal2;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// This operation is not in the spec, it implements the following:
|
|||
|
|
// "If ? CalendarEquals(one, two) is false, throw a RangeError exception."
|
|||
|
|
// This is so that we can build an informative error message without
|
|||
|
|
// re-getting the .id properties.
|
|||
|
|
function ThrowIfCalendarsNotEqual(one: CalendarSlot, two: CalendarSlot, errorMessageAction: string) {
|
|||
|
|
if (one === two) return;
|
|||
|
|
const cal1 = ToTemporalCalendarIdentifier(one);
|
|||
|
|
const cal2 = ToTemporalCalendarIdentifier(two);
|
|||
|
|
if (cal1 !== cal2) {
|
|||
|
|
throw new RangeError(`cannot ${errorMessageAction} of ${cal1} and ${cal2} calendars`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function ConsolidateCalendars(one: CalendarSlot, two: CalendarSlot) {
|
|||
|
|
if (one === two) return two;
|
|||
|
|
const sOne = ToTemporalCalendarIdentifier(one);
|
|||
|
|
const sTwo = ToTemporalCalendarIdentifier(two);
|
|||
|
|
if (sOne === sTwo || sOne === 'iso8601') {
|
|||
|
|
return two;
|
|||
|
|
} else if (sTwo === 'iso8601') {
|
|||
|
|
return one;
|
|||
|
|
} else {
|
|||
|
|
throw new RangeError('irreconcilable calendars');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CalendarDateFromFields(
|
|||
|
|
calendar: CalendarSlot,
|
|||
|
|
fields: CalendarProtocolParams['dateFromFields'][0],
|
|||
|
|
options?: Partial<CalendarProtocolParams['dateFromFields'][1]>,
|
|||
|
|
dateFromFieldsParam?: Temporal.CalendarProtocol['dateFromFields']
|
|||
|
|
) {
|
|||
|
|
if (typeof calendar === 'string') {
|
|||
|
|
const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%');
|
|||
|
|
const calendarObj = new TemporalCalendar(calendar);
|
|||
|
|
return Call(GetIntrinsic('%Temporal.Calendar.prototype.dateFromFields%'), calendarObj, [fields, options]);
|
|||
|
|
}
|
|||
|
|
const dateFromFields = dateFromFieldsParam ?? GetMethod(calendar, 'dateFromFields');
|
|||
|
|
const result = Call(dateFromFields, calendar, [fields, options]);
|
|||
|
|
if (!IsTemporalDate(result)) throw new TypeError('invalid result');
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CalendarYearMonthFromFields(
|
|||
|
|
calendar: CalendarSlot,
|
|||
|
|
fields: CalendarProtocolParams['yearMonthFromFields'][0],
|
|||
|
|
options?: CalendarProtocolParams['yearMonthFromFields'][1]
|
|||
|
|
) {
|
|||
|
|
if (typeof calendar === 'string') {
|
|||
|
|
const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%');
|
|||
|
|
const calendarObj = new TemporalCalendar(calendar);
|
|||
|
|
return Call(GetIntrinsic('%Temporal.Calendar.prototype.yearMonthFromFields%'), calendarObj, [fields, options]);
|
|||
|
|
}
|
|||
|
|
const yearMonthFromFields = GetMethod(calendar, 'yearMonthFromFields');
|
|||
|
|
let result = Call(yearMonthFromFields, calendar, [fields, options]);
|
|||
|
|
if (!IsTemporalYearMonth(result)) throw new TypeError('invalid result');
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CalendarMonthDayFromFields(
|
|||
|
|
calendar: CalendarSlot,
|
|||
|
|
fields: CalendarProtocolParams['monthDayFromFields'][0],
|
|||
|
|
options?: CalendarProtocolParams['monthDayFromFields'][1]
|
|||
|
|
) {
|
|||
|
|
if (typeof calendar === 'string') {
|
|||
|
|
const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%');
|
|||
|
|
const calendarObj = new TemporalCalendar(calendar);
|
|||
|
|
return Call(GetIntrinsic('%Temporal.Calendar.prototype.monthDayFromFields%'), calendarObj, [fields, options]);
|
|||
|
|
}
|
|||
|
|
const monthDayFromFields = GetMethod(calendar, 'monthDayFromFields');
|
|||
|
|
let result = Call(monthDayFromFields, calendar, [fields, options]);
|
|||
|
|
if (!IsTemporalMonthDay(result)) throw new TypeError('invalid result');
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type MaybeTimeZoneProtocol = Partial<
|
|||
|
|
Pick<Temporal.TimeZoneProtocol, 'getOffsetNanosecondsFor' | 'getPossibleInstantsFor'>
|
|||
|
|
>;
|
|||
|
|
function ObjectImplementsTemporalTimeZoneProtocol(object: MaybeTimeZoneProtocol) {
|
|||
|
|
if (IsTemporalTimeZone(object)) return true;
|
|||
|
|
return 'getOffsetNanosecondsFor' in object && 'getPossibleInstantsFor' in object && 'id' in object;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function ToTemporalTimeZoneSlotValue(temporalTimeZoneLike: string): string;
|
|||
|
|
export function ToTemporalTimeZoneSlotValue(temporalTimeZoneLike: Temporal.TimeZoneProtocol): Temporal.TimeZoneProtocol;
|
|||
|
|
export function ToTemporalTimeZoneSlotValue(
|
|||
|
|
temporalTimeZoneLike: Temporal.TimeZoneLike
|
|||
|
|
): string | Temporal.TimeZoneProtocol;
|
|||
|
|
export function ToTemporalTimeZoneSlotValue(temporalTimeZoneLike: TimeZoneParams['from'][0]) {
|
|||
|
|
if (IsObject(temporalTimeZoneLike)) {
|
|||
|
|
if (IsTemporalZonedDateTime(temporalTimeZoneLike)) return GetSlot(temporalTimeZoneLike, TIME_ZONE);
|
|||
|
|
if (!ObjectImplementsTemporalTimeZoneProtocol(temporalTimeZoneLike)) {
|
|||
|
|
throw new TypeError('expected a Temporal.TimeZone or object implementing the Temporal.TimeZone protocol');
|
|||
|
|
}
|
|||
|
|
return temporalTimeZoneLike;
|
|||
|
|
}
|
|||
|
|
const identifier = ToString(temporalTimeZoneLike);
|
|||
|
|
return ParseTemporalTimeZone(identifier);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function ToTemporalTimeZoneIdentifier(slotValue: TimeZoneSlot) {
|
|||
|
|
if (typeof slotValue === 'string') return slotValue;
|
|||
|
|
const result = slotValue.id;
|
|||
|
|
if (typeof result !== 'string') throw new TypeError('timeZone.id should be a string');
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function ToTemporalTimeZoneObject(slotValue: TimeZoneSlot) {
|
|||
|
|
if (IsObject(slotValue)) return slotValue;
|
|||
|
|
const TemporalTimeZone = GetIntrinsic('%Temporal.TimeZone%');
|
|||
|
|
return new TemporalTimeZone(slotValue);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function TimeZoneEquals(one: string | Temporal.TimeZoneProtocol, two: string | Temporal.TimeZoneProtocol) {
|
|||
|
|
if (one === two) return true;
|
|||
|
|
const tz1 = ToTemporalTimeZoneIdentifier(one);
|
|||
|
|
const tz2 = ToTemporalTimeZoneIdentifier(two);
|
|||
|
|
return tz1 === tz2;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function TemporalDateTimeToDate(dateTime: Temporal.PlainDateTime) {
|
|||
|
|
return CreateTemporalDate(
|
|||
|
|
GetSlot(dateTime, ISO_YEAR),
|
|||
|
|
GetSlot(dateTime, ISO_MONTH),
|
|||
|
|
GetSlot(dateTime, ISO_DAY),
|
|||
|
|
GetSlot(dateTime, CALENDAR)
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function TemporalDateTimeToTime(dateTime: Temporal.PlainDateTime) {
|
|||
|
|
const Time = GetIntrinsic('%Temporal.PlainTime%');
|
|||
|
|
return new Time(
|
|||
|
|
GetSlot(dateTime, ISO_HOUR),
|
|||
|
|
GetSlot(dateTime, ISO_MINUTE),
|
|||
|
|
GetSlot(dateTime, ISO_SECOND),
|
|||
|
|
GetSlot(dateTime, ISO_MILLISECOND),
|
|||
|
|
GetSlot(dateTime, ISO_MICROSECOND),
|
|||
|
|
GetSlot(dateTime, ISO_NANOSECOND)
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function GetOffsetNanosecondsFor(
|
|||
|
|
timeZone: string | Temporal.TimeZoneProtocol,
|
|||
|
|
instant: TimeZoneProtocolParams['getOffsetNanosecondsFor'][0],
|
|||
|
|
getOffsetNanosecondsForParam?: Temporal.TimeZoneProtocol['getOffsetNanosecondsFor']
|
|||
|
|
) {
|
|||
|
|
if (typeof timeZone === 'string') {
|
|||
|
|
const TemporalTimeZone = GetIntrinsic('%Temporal.TimeZone%');
|
|||
|
|
const timeZoneObject = new TemporalTimeZone(timeZone);
|
|||
|
|
return Call(GetIntrinsic('%Temporal.TimeZone.prototype.getOffsetNanosecondsFor%'), timeZoneObject, [instant]);
|
|||
|
|
}
|
|||
|
|
const getOffsetNanosecondsFor = getOffsetNanosecondsForParam ?? GetMethod(timeZone, 'getOffsetNanosecondsFor');
|
|||
|
|
const offsetNs = Call(getOffsetNanosecondsFor, timeZone, [instant]);
|
|||
|
|
if (typeof offsetNs !== 'number') {
|
|||
|
|
throw new TypeError('bad return from getOffsetNanosecondsFor');
|
|||
|
|
}
|
|||
|
|
if (!IsIntegralNumber(offsetNs) || MathAbs(offsetNs) >= 86400e9) {
|
|||
|
|
throw new RangeError('out-of-range return from getOffsetNanosecondsFor');
|
|||
|
|
}
|
|||
|
|
return offsetNs;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function GetOffsetStringFor(timeZone: string | Temporal.TimeZoneProtocol, instant: Temporal.Instant) {
|
|||
|
|
const offsetNs = GetOffsetNanosecondsFor(timeZone, instant);
|
|||
|
|
return FormatTimeZoneOffsetString(offsetNs);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function GetPlainDateTimeFor(
|
|||
|
|
timeZone: string | Temporal.TimeZoneProtocol,
|
|||
|
|
instant: Temporal.Instant,
|
|||
|
|
calendar: CalendarSlot
|
|||
|
|
) {
|
|||
|
|
const ns = GetSlot(instant, EPOCHNANOSECONDS);
|
|||
|
|
const offsetNs = GetOffsetNanosecondsFor(timeZone, instant);
|
|||
|
|
let { year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = GetISOPartsFromEpoch(ns);
|
|||
|
|
({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = BalanceISODateTime(
|
|||
|
|
year,
|
|||
|
|
month,
|
|||
|
|
day,
|
|||
|
|
hour,
|
|||
|
|
minute,
|
|||
|
|
second,
|
|||
|
|
millisecond,
|
|||
|
|
microsecond,
|
|||
|
|
nanosecond + offsetNs
|
|||
|
|
));
|
|||
|
|
return CreateTemporalDateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, calendar);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function GetInstantFor(
|
|||
|
|
timeZone: string | Temporal.TimeZoneProtocol,
|
|||
|
|
dateTime: Temporal.PlainDateTime,
|
|||
|
|
disambiguation: NonNullable<Temporal.ToInstantOptions['disambiguation']>
|
|||
|
|
) {
|
|||
|
|
const possibleInstants = GetPossibleInstantsFor(timeZone, dateTime);
|
|||
|
|
return DisambiguatePossibleInstants(possibleInstants, timeZone, dateTime, disambiguation);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function DisambiguatePossibleInstants(
|
|||
|
|
possibleInstants: Temporal.Instant[],
|
|||
|
|
timeZone: string | Temporal.TimeZoneProtocol,
|
|||
|
|
dateTime: Temporal.PlainDateTime,
|
|||
|
|
disambiguation: NonNullable<Temporal.ToInstantOptions['disambiguation']>
|
|||
|
|
) {
|
|||
|
|
const Instant = GetIntrinsic('%Temporal.Instant%');
|
|||
|
|
const numInstants = possibleInstants.length;
|
|||
|
|
|
|||
|
|
if (numInstants === 1) return possibleInstants[0];
|
|||
|
|
if (numInstants) {
|
|||
|
|
switch (disambiguation) {
|
|||
|
|
case 'compatible':
|
|||
|
|
// fall through because 'compatible' means 'earlier' for "fall back" transitions
|
|||
|
|
case 'earlier':
|
|||
|
|
return possibleInstants[0];
|
|||
|
|
case 'later':
|
|||
|
|
return possibleInstants[numInstants - 1];
|
|||
|
|
case 'reject': {
|
|||
|
|
throw new RangeError('multiple instants found');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const year = GetSlot(dateTime, ISO_YEAR);
|
|||
|
|
const month = GetSlot(dateTime, ISO_MONTH);
|
|||
|
|
const day = GetSlot(dateTime, ISO_DAY);
|
|||
|
|
const hour = GetSlot(dateTime, ISO_HOUR);
|
|||
|
|
const minute = GetSlot(dateTime, ISO_MINUTE);
|
|||
|
|
const second = GetSlot(dateTime, ISO_SECOND);
|
|||
|
|
const millisecond = GetSlot(dateTime, ISO_MILLISECOND);
|
|||
|
|
const microsecond = GetSlot(dateTime, ISO_MICROSECOND);
|
|||
|
|
const nanosecond = GetSlot(dateTime, ISO_NANOSECOND);
|
|||
|
|
const utcns = GetUTCEpochNanoseconds(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond);
|
|||
|
|
if (utcns === null) throw new RangeError('DateTime outside of supported range');
|
|||
|
|
const dayBefore = new Instant(JSBI.subtract(utcns, DAY_NANOS));
|
|||
|
|
const dayAfter = new Instant(JSBI.add(utcns, DAY_NANOS));
|
|||
|
|
const offsetBefore = GetOffsetNanosecondsFor(timeZone, dayBefore);
|
|||
|
|
const offsetAfter = GetOffsetNanosecondsFor(timeZone, dayAfter);
|
|||
|
|
const nanoseconds = offsetAfter - offsetBefore;
|
|||
|
|
switch (disambiguation) {
|
|||
|
|
case 'earlier': {
|
|||
|
|
const calendar = GetSlot(dateTime, CALENDAR);
|
|||
|
|
const PlainDateTime = GetIntrinsic('%Temporal.PlainDateTime%');
|
|||
|
|
const earlier = AddDateTime(
|
|||
|
|
year,
|
|||
|
|
month,
|
|||
|
|
day,
|
|||
|
|
hour,
|
|||
|
|
minute,
|
|||
|
|
second,
|
|||
|
|
millisecond,
|
|||
|
|
microsecond,
|
|||
|
|
nanosecond,
|
|||
|
|
calendar,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
-nanoseconds,
|
|||
|
|
undefined
|
|||
|
|
);
|
|||
|
|
const earlierPlainDateTime = new PlainDateTime(
|
|||
|
|
earlier.year,
|
|||
|
|
earlier.month,
|
|||
|
|
earlier.day,
|
|||
|
|
earlier.hour,
|
|||
|
|
earlier.minute,
|
|||
|
|
earlier.second,
|
|||
|
|
earlier.millisecond,
|
|||
|
|
earlier.microsecond,
|
|||
|
|
earlier.nanosecond,
|
|||
|
|
calendar
|
|||
|
|
);
|
|||
|
|
return GetPossibleInstantsFor(timeZone, earlierPlainDateTime)[0];
|
|||
|
|
}
|
|||
|
|
case 'compatible':
|
|||
|
|
// fall through because 'compatible' means 'later' for "spring forward" transitions
|
|||
|
|
case 'later': {
|
|||
|
|
const calendar = GetSlot(dateTime, CALENDAR);
|
|||
|
|
const PlainDateTime = GetIntrinsic('%Temporal.PlainDateTime%');
|
|||
|
|
const later = AddDateTime(
|
|||
|
|
year,
|
|||
|
|
month,
|
|||
|
|
day,
|
|||
|
|
hour,
|
|||
|
|
minute,
|
|||
|
|
second,
|
|||
|
|
millisecond,
|
|||
|
|
microsecond,
|
|||
|
|
nanosecond,
|
|||
|
|
calendar,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
nanoseconds,
|
|||
|
|
undefined
|
|||
|
|
);
|
|||
|
|
const laterPlainDateTime = new PlainDateTime(
|
|||
|
|
later.year,
|
|||
|
|
later.month,
|
|||
|
|
later.day,
|
|||
|
|
later.hour,
|
|||
|
|
later.minute,
|
|||
|
|
later.second,
|
|||
|
|
later.millisecond,
|
|||
|
|
later.microsecond,
|
|||
|
|
later.nanosecond,
|
|||
|
|
calendar
|
|||
|
|
);
|
|||
|
|
const possible = GetPossibleInstantsFor(timeZone, laterPlainDateTime);
|
|||
|
|
return possible[possible.length - 1];
|
|||
|
|
}
|
|||
|
|
case 'reject': {
|
|||
|
|
throw new RangeError('no such instant found');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function GetPossibleInstantsFor(
|
|||
|
|
timeZone: string | Temporal.TimeZoneProtocol,
|
|||
|
|
dateTime: TimeZoneProtocolParams['getPossibleInstantsFor'][0],
|
|||
|
|
getPossibleInstantsForParam?: Temporal.TimeZoneProtocol['getPossibleInstantsFor']
|
|||
|
|
) {
|
|||
|
|
if (typeof timeZone === 'string') {
|
|||
|
|
const TemporalTimeZone = GetIntrinsic('%Temporal.TimeZone%');
|
|||
|
|
const timeZoneObject = new TemporalTimeZone(timeZone);
|
|||
|
|
return Call(GetIntrinsic('%Temporal.TimeZone.prototype.getPossibleInstantsFor%'), timeZoneObject, [dateTime]);
|
|||
|
|
}
|
|||
|
|
const getPossibleInstantsFor = getPossibleInstantsForParam ?? GetMethod(timeZone, 'getPossibleInstantsFor');
|
|||
|
|
const possibleInstants = Call(getPossibleInstantsFor, timeZone, [dateTime]);
|
|||
|
|
const result: Temporal.Instant[] = [];
|
|||
|
|
for (const instant of possibleInstants) {
|
|||
|
|
if (!IsTemporalInstant(instant)) {
|
|||
|
|
throw new TypeError('bad return from getPossibleInstantsFor');
|
|||
|
|
}
|
|||
|
|
ArrayPrototypePush.call(result, instant);
|
|||
|
|
}
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function ISOYearString(year: number) {
|
|||
|
|
let yearString;
|
|||
|
|
if (year < 0 || year > 9999) {
|
|||
|
|
const sign = year < 0 ? '-' : '+';
|
|||
|
|
const yearNumber = MathAbs(year);
|
|||
|
|
yearString = sign + `000000${yearNumber}`.slice(-6);
|
|||
|
|
} else {
|
|||
|
|
yearString = `0000${year}`.slice(-4);
|
|||
|
|
}
|
|||
|
|
return yearString;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function ISODateTimePartString(part: number) {
|
|||
|
|
return `00${part}`.slice(-2);
|
|||
|
|
}
|
|||
|
|
export function FormatSecondsStringPart(
|
|||
|
|
second: number,
|
|||
|
|
millisecond: number,
|
|||
|
|
microsecond: number,
|
|||
|
|
nanosecond: number,
|
|||
|
|
precision: ReturnType<typeof ToSecondsStringPrecisionRecord>['precision']
|
|||
|
|
) {
|
|||
|
|
if (precision === 'minute') return '';
|
|||
|
|
|
|||
|
|
const secs = `:${ISODateTimePartString(second)}`;
|
|||
|
|
let fractionNumber = millisecond * 1e6 + microsecond * 1e3 + nanosecond;
|
|||
|
|
let fraction: string;
|
|||
|
|
|
|||
|
|
if (precision === 'auto') {
|
|||
|
|
if (fractionNumber === 0) return secs;
|
|||
|
|
fraction = `${fractionNumber}`.padStart(9, '0');
|
|||
|
|
while (fraction[fraction.length - 1] === '0') fraction = fraction.slice(0, -1);
|
|||
|
|
} else {
|
|||
|
|
if (precision === 0) return secs;
|
|||
|
|
fraction = `${fractionNumber}`.padStart(9, '0').slice(0, precision);
|
|||
|
|
}
|
|||
|
|
return `${secs}.${fraction}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function TemporalInstantToString(
|
|||
|
|
instant: Temporal.Instant,
|
|||
|
|
timeZone: string | Temporal.TimeZoneProtocol | undefined,
|
|||
|
|
precision: ReturnType<typeof ToSecondsStringPrecisionRecord>['precision']
|
|||
|
|
) {
|
|||
|
|
let outputTimeZone = timeZone;
|
|||
|
|
if (outputTimeZone === undefined) outputTimeZone = 'UTC';
|
|||
|
|
const dateTime = GetPlainDateTimeFor(outputTimeZone, instant, 'iso8601');
|
|||
|
|
const year = ISOYearString(GetSlot(dateTime, ISO_YEAR));
|
|||
|
|
const month = ISODateTimePartString(GetSlot(dateTime, ISO_MONTH));
|
|||
|
|
const day = ISODateTimePartString(GetSlot(dateTime, ISO_DAY));
|
|||
|
|
const hour = ISODateTimePartString(GetSlot(dateTime, ISO_HOUR));
|
|||
|
|
const minute = ISODateTimePartString(GetSlot(dateTime, ISO_MINUTE));
|
|||
|
|
const seconds = FormatSecondsStringPart(
|
|||
|
|
GetSlot(dateTime, ISO_SECOND),
|
|||
|
|
GetSlot(dateTime, ISO_MILLISECOND),
|
|||
|
|
GetSlot(dateTime, ISO_MICROSECOND),
|
|||
|
|
GetSlot(dateTime, ISO_NANOSECOND),
|
|||
|
|
precision
|
|||
|
|
);
|
|||
|
|
let timeZoneString = 'Z';
|
|||
|
|
if (timeZone !== undefined) {
|
|||
|
|
const offsetNs = GetOffsetNanosecondsFor(outputTimeZone, instant);
|
|||
|
|
timeZoneString = FormatISOTimeZoneOffsetString(offsetNs);
|
|||
|
|
}
|
|||
|
|
return `${year}-${month}-${day}T${hour}:${minute}${seconds}${timeZoneString}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface ToStringOptions {
|
|||
|
|
unit: ReturnType<typeof ToSecondsStringPrecisionRecord>['unit'];
|
|||
|
|
increment: number;
|
|||
|
|
roundingMode: ReturnType<typeof ToTemporalRoundingMode>;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function TemporalDurationToString(
|
|||
|
|
duration: Temporal.Duration,
|
|||
|
|
precision: Temporal.ToStringPrecisionOptions['fractionalSecondDigits'] = 'auto',
|
|||
|
|
options: ToStringOptions | undefined = undefined
|
|||
|
|
) {
|
|||
|
|
function formatNumber(num: number) {
|
|||
|
|
if (num <= NumberMaxSafeInteger) return num.toString(10);
|
|||
|
|
return JSBI.BigInt(num).toString(10);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const years = GetSlot(duration, YEARS);
|
|||
|
|
const months = GetSlot(duration, MONTHS);
|
|||
|
|
const weeks = GetSlot(duration, WEEKS);
|
|||
|
|
const days = GetSlot(duration, DAYS);
|
|||
|
|
const hours = GetSlot(duration, HOURS);
|
|||
|
|
const minutes = GetSlot(duration, MINUTES);
|
|||
|
|
let seconds = GetSlot(duration, SECONDS);
|
|||
|
|
let ms = GetSlot(duration, MILLISECONDS);
|
|||
|
|
let µs = GetSlot(duration, MICROSECONDS);
|
|||
|
|
let ns = GetSlot(duration, NANOSECONDS);
|
|||
|
|
const sign = DurationSign(years, months, weeks, days, hours, minutes, seconds, ms, µs, ns);
|
|||
|
|
|
|||
|
|
if (options) {
|
|||
|
|
const { unit, increment, roundingMode } = options;
|
|||
|
|
({
|
|||
|
|
seconds,
|
|||
|
|
milliseconds: ms,
|
|||
|
|
microseconds: µs,
|
|||
|
|
nanoseconds: ns
|
|||
|
|
} = RoundDuration(0, 0, 0, 0, 0, 0, seconds, ms, µs, ns, increment, unit, roundingMode));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const dateParts: string[] = [];
|
|||
|
|
if (years) dateParts.push(`${formatNumber(MathAbs(years))}Y`);
|
|||
|
|
if (months) dateParts.push(`${formatNumber(MathAbs(months))}M`);
|
|||
|
|
if (weeks) dateParts.push(`${formatNumber(MathAbs(weeks))}W`);
|
|||
|
|
if (days) dateParts.push(`${formatNumber(MathAbs(days))}D`);
|
|||
|
|
|
|||
|
|
const timeParts: string[] = [];
|
|||
|
|
if (hours) timeParts.push(`${formatNumber(MathAbs(hours))}H`);
|
|||
|
|
if (minutes) timeParts.push(`${formatNumber(MathAbs(minutes))}M`);
|
|||
|
|
|
|||
|
|
const secondParts: string[] = [];
|
|||
|
|
let total = TotalDurationNanoseconds(0, 0, 0, seconds, ms, µs, ns, 0);
|
|||
|
|
let nsBigInt: JSBI, µsBigInt: JSBI, msBigInt: JSBI, secondsBigInt: JSBI;
|
|||
|
|
({ quotient: total, remainder: nsBigInt } = divmod(total, THOUSAND));
|
|||
|
|
({ quotient: total, remainder: µsBigInt } = divmod(total, THOUSAND));
|
|||
|
|
({ quotient: secondsBigInt, remainder: msBigInt } = divmod(total, THOUSAND));
|
|||
|
|
const fraction =
|
|||
|
|
MathAbs(JSBI.toNumber(msBigInt)) * 1e6 + MathAbs(JSBI.toNumber(µsBigInt)) * 1e3 + MathAbs(JSBI.toNumber(nsBigInt));
|
|||
|
|
let decimalPart;
|
|||
|
|
if (precision === 'auto') {
|
|||
|
|
if (fraction !== 0) {
|
|||
|
|
decimalPart = `${fraction}`.padStart(9, '0');
|
|||
|
|
while (decimalPart[decimalPart.length - 1] === '0') {
|
|||
|
|
decimalPart = decimalPart.slice(0, -1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else if (precision !== 0) {
|
|||
|
|
decimalPart = `${fraction}`.padStart(9, '0').slice(0, precision);
|
|||
|
|
}
|
|||
|
|
if (decimalPart) secondParts.unshift('.', decimalPart);
|
|||
|
|
if (!JSBI.equal(secondsBigInt, ZERO) || secondParts.length || precision !== 'auto') {
|
|||
|
|
secondParts.unshift(abs(secondsBigInt).toString());
|
|||
|
|
}
|
|||
|
|
if (secondParts.length) timeParts.push(`${secondParts.join('')}S`);
|
|||
|
|
if (timeParts.length) timeParts.unshift('T');
|
|||
|
|
if (!dateParts.length && !timeParts.length) return 'PT0S';
|
|||
|
|
return `${sign < 0 ? '-' : ''}P${dateParts.join('')}${timeParts.join('')}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function TemporalDateToString(
|
|||
|
|
date: Temporal.PlainDate,
|
|||
|
|
showCalendar: Temporal.ShowCalendarOption['calendarName'] = 'auto'
|
|||
|
|
) {
|
|||
|
|
const year = ISOYearString(GetSlot(date, ISO_YEAR));
|
|||
|
|
const month = ISODateTimePartString(GetSlot(date, ISO_MONTH));
|
|||
|
|
const day = ISODateTimePartString(GetSlot(date, ISO_DAY));
|
|||
|
|
const calendar = MaybeFormatCalendarAnnotation(GetSlot(date, CALENDAR), showCalendar);
|
|||
|
|
return `${year}-${month}-${day}${calendar}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function TemporalDateTimeToString(
|
|||
|
|
dateTime: Temporal.PlainDateTime,
|
|||
|
|
precision: ReturnType<typeof ToSecondsStringPrecisionRecord>['precision'],
|
|||
|
|
showCalendar: ReturnType<typeof ToCalendarNameOption> = 'auto',
|
|||
|
|
options: ToStringOptions | undefined = undefined
|
|||
|
|
) {
|
|||
|
|
let year = GetSlot(dateTime, ISO_YEAR);
|
|||
|
|
let month = GetSlot(dateTime, ISO_MONTH);
|
|||
|
|
let day = GetSlot(dateTime, ISO_DAY);
|
|||
|
|
let hour = GetSlot(dateTime, ISO_HOUR);
|
|||
|
|
let minute = GetSlot(dateTime, ISO_MINUTE);
|
|||
|
|
let second = GetSlot(dateTime, ISO_SECOND);
|
|||
|
|
let millisecond = GetSlot(dateTime, ISO_MILLISECOND);
|
|||
|
|
let microsecond = GetSlot(dateTime, ISO_MICROSECOND);
|
|||
|
|
let nanosecond = GetSlot(dateTime, ISO_NANOSECOND);
|
|||
|
|
|
|||
|
|
if (options) {
|
|||
|
|
const { unit, increment, roundingMode } = options;
|
|||
|
|
({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = RoundISODateTime(
|
|||
|
|
year,
|
|||
|
|
month,
|
|||
|
|
day,
|
|||
|
|
hour,
|
|||
|
|
minute,
|
|||
|
|
second,
|
|||
|
|
millisecond,
|
|||
|
|
microsecond,
|
|||
|
|
nanosecond,
|
|||
|
|
increment,
|
|||
|
|
unit,
|
|||
|
|
roundingMode
|
|||
|
|
));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const yearString = ISOYearString(year);
|
|||
|
|
const monthString = ISODateTimePartString(month);
|
|||
|
|
const dayString = ISODateTimePartString(day);
|
|||
|
|
const hourString = ISODateTimePartString(hour);
|
|||
|
|
const minuteString = ISODateTimePartString(minute);
|
|||
|
|
const secondsString = FormatSecondsStringPart(second, millisecond, microsecond, nanosecond, precision);
|
|||
|
|
const calendar = MaybeFormatCalendarAnnotation(GetSlot(dateTime, CALENDAR), showCalendar);
|
|||
|
|
return `${yearString}-${monthString}-${dayString}T${hourString}:${minuteString}${secondsString}${calendar}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function TemporalMonthDayToString(
|
|||
|
|
monthDay: Temporal.PlainMonthDay,
|
|||
|
|
showCalendar: Temporal.ShowCalendarOption['calendarName'] = 'auto'
|
|||
|
|
) {
|
|||
|
|
const month = ISODateTimePartString(GetSlot(monthDay, ISO_MONTH));
|
|||
|
|
const day = ISODateTimePartString(GetSlot(monthDay, ISO_DAY));
|
|||
|
|
let resultString = `${month}-${day}`;
|
|||
|
|
const calendar = GetSlot(monthDay, CALENDAR);
|
|||
|
|
const calendarID = ToTemporalCalendarIdentifier(calendar);
|
|||
|
|
if (showCalendar === 'always' || showCalendar === 'critical' || calendarID !== 'iso8601') {
|
|||
|
|
const year = ISOYearString(GetSlot(monthDay, ISO_YEAR));
|
|||
|
|
resultString = `${year}-${resultString}`;
|
|||
|
|
}
|
|||
|
|
const calendarString = FormatCalendarAnnotation(calendarID, showCalendar);
|
|||
|
|
if (calendarString) resultString += calendarString;
|
|||
|
|
return resultString;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function TemporalYearMonthToString(
|
|||
|
|
yearMonth: Temporal.PlainYearMonth,
|
|||
|
|
showCalendar: Temporal.ShowCalendarOption['calendarName'] = 'auto'
|
|||
|
|
) {
|
|||
|
|
const year = ISOYearString(GetSlot(yearMonth, ISO_YEAR));
|
|||
|
|
const month = ISODateTimePartString(GetSlot(yearMonth, ISO_MONTH));
|
|||
|
|
let resultString = `${year}-${month}`;
|
|||
|
|
const calendar = GetSlot(yearMonth, CALENDAR);
|
|||
|
|
const calendarID = ToTemporalCalendarIdentifier(calendar);
|
|||
|
|
if (showCalendar === 'always' || showCalendar === 'critical' || calendarID !== 'iso8601') {
|
|||
|
|
const day = ISODateTimePartString(GetSlot(yearMonth, ISO_DAY));
|
|||
|
|
resultString += `-${day}`;
|
|||
|
|
}
|
|||
|
|
const calendarString = FormatCalendarAnnotation(calendarID, showCalendar);
|
|||
|
|
if (calendarString) resultString += calendarString;
|
|||
|
|
return resultString;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function TemporalZonedDateTimeToString(
|
|||
|
|
zdt: Temporal.ZonedDateTime,
|
|||
|
|
precision: ReturnType<typeof ToSecondsStringPrecisionRecord>['precision'],
|
|||
|
|
showCalendar: ReturnType<typeof ToCalendarNameOption> = 'auto',
|
|||
|
|
showTimeZone: ReturnType<typeof ToTimeZoneNameOption> = 'auto',
|
|||
|
|
showOffset: ReturnType<typeof ToShowOffsetOption> = 'auto',
|
|||
|
|
options: ToStringOptions | undefined = undefined
|
|||
|
|
) {
|
|||
|
|
let instant = GetSlot(zdt, INSTANT);
|
|||
|
|
|
|||
|
|
if (options) {
|
|||
|
|
const { unit, increment, roundingMode } = options;
|
|||
|
|
const ns = RoundInstant(GetSlot(zdt, EPOCHNANOSECONDS), increment, unit, roundingMode);
|
|||
|
|
const TemporalInstant = GetIntrinsic('%Temporal.Instant%');
|
|||
|
|
instant = new TemporalInstant(ns);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const tz = GetSlot(zdt, TIME_ZONE);
|
|||
|
|
const dateTime = GetPlainDateTimeFor(tz, instant, 'iso8601');
|
|||
|
|
|
|||
|
|
const year = ISOYearString(GetSlot(dateTime, ISO_YEAR));
|
|||
|
|
const month = ISODateTimePartString(GetSlot(dateTime, ISO_MONTH));
|
|||
|
|
const day = ISODateTimePartString(GetSlot(dateTime, ISO_DAY));
|
|||
|
|
const hour = ISODateTimePartString(GetSlot(dateTime, ISO_HOUR));
|
|||
|
|
const minute = ISODateTimePartString(GetSlot(dateTime, ISO_MINUTE));
|
|||
|
|
const seconds = FormatSecondsStringPart(
|
|||
|
|
GetSlot(dateTime, ISO_SECOND),
|
|||
|
|
GetSlot(dateTime, ISO_MILLISECOND),
|
|||
|
|
GetSlot(dateTime, ISO_MICROSECOND),
|
|||
|
|
GetSlot(dateTime, ISO_NANOSECOND),
|
|||
|
|
precision
|
|||
|
|
);
|
|||
|
|
let result = `${year}-${month}-${day}T${hour}:${minute}${seconds}`;
|
|||
|
|
if (showOffset !== 'never') {
|
|||
|
|
const offsetNs = GetOffsetNanosecondsFor(tz, instant);
|
|||
|
|
result += FormatISOTimeZoneOffsetString(offsetNs);
|
|||
|
|
}
|
|||
|
|
if (showTimeZone !== 'never') {
|
|||
|
|
const identifier = ToTemporalTimeZoneIdentifier(tz);
|
|||
|
|
const flag = showTimeZone === 'critical' ? '!' : '';
|
|||
|
|
result += `[${flag}${identifier}]`;
|
|||
|
|
}
|
|||
|
|
result += MaybeFormatCalendarAnnotation(GetSlot(zdt, CALENDAR), showCalendar);
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function IsTimeZoneOffsetString(string: string) {
|
|||
|
|
return OFFSET.test(StringCtor(string));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function ParseTimeZoneOffsetString(string: string): number {
|
|||
|
|
const match = OFFSET.exec(StringCtor(string));
|
|||
|
|
if (!match) {
|
|||
|
|
throw new RangeError(`invalid time zone offset: ${string}`);
|
|||
|
|
}
|
|||
|
|
const sign = match[1] === '-' || match[1] === '\u2212' ? -1 : +1;
|
|||
|
|
const hours = +match[2];
|
|||
|
|
const minutes = +(match[3] || 0);
|
|||
|
|
const seconds = +(match[4] || 0);
|
|||
|
|
const nanoseconds = +((match[5] || 0) + '000000000').slice(0, 9);
|
|||
|
|
return sign * (((hours * 60 + minutes) * 60 + seconds) * 1e9 + nanoseconds);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function GetCanonicalTimeZoneIdentifier(timeZoneIdentifier: string): string {
|
|||
|
|
if (IsTimeZoneOffsetString(timeZoneIdentifier)) {
|
|||
|
|
const offsetNs = ParseTimeZoneOffsetString(timeZoneIdentifier);
|
|||
|
|
return FormatTimeZoneOffsetString(offsetNs);
|
|||
|
|
}
|
|||
|
|
const formatter = getIntlDateTimeFormatEnUsForTimeZone(StringCtor(timeZoneIdentifier));
|
|||
|
|
return formatter.resolvedOptions().timeZone;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function GetNamedTimeZoneOffsetNanoseconds(id: string, epochNanoseconds: JSBI) {
|
|||
|
|
const { year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } =
|
|||
|
|
GetNamedTimeZoneDateTimeParts(id, epochNanoseconds);
|
|||
|
|
|
|||
|
|
// The pattern of leap years in the ISO 8601 calendar repeats every 400
|
|||
|
|
// years. To avoid overflowing at the edges of the range, we reduce the year
|
|||
|
|
// to the remainder after dividing by 400, and then add back all the
|
|||
|
|
// nanoseconds from the multiples of 400 years at the end.
|
|||
|
|
const reducedYear = year % 400;
|
|||
|
|
const yearCycles = (year - reducedYear) / 400;
|
|||
|
|
const nsIn400YearCycle = JSBI.multiply(JSBI.BigInt(400 * 365 + 97), DAY_NANOS);
|
|||
|
|
|
|||
|
|
const reducedUTC = GetUTCEpochNanoseconds(
|
|||
|
|
reducedYear,
|
|||
|
|
month,
|
|||
|
|
day,
|
|||
|
|
hour,
|
|||
|
|
minute,
|
|||
|
|
second,
|
|||
|
|
millisecond,
|
|||
|
|
microsecond,
|
|||
|
|
nanosecond
|
|||
|
|
);
|
|||
|
|
assertExists(reducedUTC);
|
|||
|
|
const utc = JSBI.add(reducedUTC, JSBI.multiply(nsIn400YearCycle, JSBI.BigInt(yearCycles)));
|
|||
|
|
return JSBI.toNumber(JSBI.subtract(utc, epochNanoseconds));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function FormatTimeZoneOffsetString(offsetNanosecondsParam: number): string {
|
|||
|
|
const sign = offsetNanosecondsParam < 0 ? '-' : '+';
|
|||
|
|
const offsetNanoseconds = MathAbs(offsetNanosecondsParam);
|
|||
|
|
const nanoseconds = offsetNanoseconds % 1e9;
|
|||
|
|
const seconds = MathFloor(offsetNanoseconds / 1e9) % 60;
|
|||
|
|
const minutes = MathFloor(offsetNanoseconds / 60e9) % 60;
|
|||
|
|
const hours = MathFloor(offsetNanoseconds / 3600e9);
|
|||
|
|
|
|||
|
|
const hourString = ISODateTimePartString(hours);
|
|||
|
|
const minuteString = ISODateTimePartString(minutes);
|
|||
|
|
const secondString = ISODateTimePartString(seconds);
|
|||
|
|
let post = '';
|
|||
|
|
if (nanoseconds) {
|
|||
|
|
let fraction = `${nanoseconds}`.padStart(9, '0');
|
|||
|
|
while (fraction[fraction.length - 1] === '0') fraction = fraction.slice(0, -1);
|
|||
|
|
post = `:${secondString}.${fraction}`;
|
|||
|
|
} else if (seconds) {
|
|||
|
|
post = `:${secondString}`;
|
|||
|
|
}
|
|||
|
|
return `${sign}${hourString}:${minuteString}${post}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function FormatISOTimeZoneOffsetString(offsetNanosecondsParam: number): string {
|
|||
|
|
let offsetNanoseconds = JSBI.toNumber(
|
|||
|
|
RoundNumberToIncrement(JSBI.BigInt(offsetNanosecondsParam), MINUTE_NANOS, 'halfExpand')
|
|||
|
|
);
|
|||
|
|
const sign = offsetNanoseconds < 0 ? '-' : '+';
|
|||
|
|
offsetNanoseconds = MathAbs(offsetNanoseconds);
|
|||
|
|
const minutes = (offsetNanoseconds / 60e9) % 60;
|
|||
|
|
const hours = MathFloor(offsetNanoseconds / 3600e9);
|
|||
|
|
|
|||
|
|
const hourString = ISODateTimePartString(hours);
|
|||
|
|
const minuteString = ISODateTimePartString(minutes);
|
|||
|
|
return `${sign}${hourString}:${minuteString}`;
|
|||
|
|
}
|
|||
|
|
export function GetUTCEpochNanoseconds(
|
|||
|
|
year: number,
|
|||
|
|
month: number,
|
|||
|
|
day: number,
|
|||
|
|
hour: number,
|
|||
|
|
minute: number,
|
|||
|
|
second: number,
|
|||
|
|
millisecond: number,
|
|||
|
|
microsecond: number,
|
|||
|
|
nanosecond: number
|
|||
|
|
) {
|
|||
|
|
// Note: Date.UTC() interprets one and two-digit years as being in the
|
|||
|
|
// 20th century, so don't use it
|
|||
|
|
const legacyDate = new Date();
|
|||
|
|
legacyDate.setUTCHours(hour, minute, second, millisecond);
|
|||
|
|
legacyDate.setUTCFullYear(year, month - 1, day);
|
|||
|
|
const ms = legacyDate.getTime();
|
|||
|
|
if (NumberIsNaN(ms)) return null;
|
|||
|
|
let ns = JSBI.multiply(JSBI.BigInt(ms), MILLION);
|
|||
|
|
ns = JSBI.add(ns, JSBI.multiply(JSBI.BigInt(microsecond), THOUSAND));
|
|||
|
|
ns = JSBI.add(ns, JSBI.BigInt(nanosecond));
|
|||
|
|
if (JSBI.lessThan(ns, NS_MIN) || JSBI.greaterThan(ns, NS_MAX)) return null;
|
|||
|
|
return ns;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function GetISOPartsFromEpoch(epochNanoseconds: JSBI) {
|
|||
|
|
const { quotient, remainder } = divmod(epochNanoseconds, MILLION);
|
|||
|
|
let epochMilliseconds = JSBI.toNumber(quotient);
|
|||
|
|
let nanos = JSBI.toNumber(remainder);
|
|||
|
|
if (nanos < 0) {
|
|||
|
|
nanos += 1e6;
|
|||
|
|
epochMilliseconds -= 1;
|
|||
|
|
}
|
|||
|
|
const microsecond = MathFloor(nanos / 1e3) % 1e3;
|
|||
|
|
const nanosecond = nanos % 1e3;
|
|||
|
|
|
|||
|
|
const item = new Date(epochMilliseconds);
|
|||
|
|
const year = item.getUTCFullYear();
|
|||
|
|
const month = item.getUTCMonth() + 1;
|
|||
|
|
const day = item.getUTCDate();
|
|||
|
|
const hour = item.getUTCHours();
|
|||
|
|
const minute = item.getUTCMinutes();
|
|||
|
|
const second = item.getUTCSeconds();
|
|||
|
|
const millisecond = item.getUTCMilliseconds();
|
|||
|
|
|
|||
|
|
return { epochMilliseconds, year, month, day, hour, minute, second, millisecond, microsecond, nanosecond };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ts-prune-ignore-next TODO: remove this after tests are converted to TS
|
|||
|
|
export function GetNamedTimeZoneDateTimeParts(id: string, epochNanoseconds: JSBI) {
|
|||
|
|
const { epochMilliseconds, millisecond, microsecond, nanosecond } = GetISOPartsFromEpoch(epochNanoseconds);
|
|||
|
|
const { year, month, day, hour, minute, second } = GetFormatterParts(id, epochMilliseconds);
|
|||
|
|
return BalanceISODateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function maxJSBI(one: JSBI, two: JSBI) {
|
|||
|
|
return JSBI.lessThan(one, two) ? two : one;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Our best guess at how far in advance new rules will be put into the TZDB for
|
|||
|
|
* future offset transitions. We'll pick 10 years but can always revise it if
|
|||
|
|
* we find that countries are being unusually proactive in their announcing
|
|||
|
|
* of offset changes.
|
|||
|
|
*/
|
|||
|
|
function afterLatestPossibleTzdbRuleChange() {
|
|||
|
|
return JSBI.add(SystemUTCEpochNanoSeconds(), ABOUT_TEN_YEARS_NANOS);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function GetNamedTimeZoneNextTransition(id: string, epochNanoseconds: JSBI): JSBI | null {
|
|||
|
|
if (JSBI.lessThan(epochNanoseconds, BEFORE_FIRST_OFFSET_TRANSITION)) {
|
|||
|
|
return GetNamedTimeZoneNextTransition(id, BEFORE_FIRST_OFFSET_TRANSITION);
|
|||
|
|
}
|
|||
|
|
// Decide how far in the future after `epochNanoseconds` we'll look for an
|
|||
|
|
// offset change. There are two cases:
|
|||
|
|
// 1. If it's a past date (or a date in the near future) then it's possible
|
|||
|
|
// that the time zone may have newly added DST in the next few years. So
|
|||
|
|
// we'll have to look from the provided time until a few years after the
|
|||
|
|
// current system time. (Changes to DST policy are usually announced a few
|
|||
|
|
// years in the future.) Note that the first DST anywhere started in 1847,
|
|||
|
|
// so we'll start checks in 1847 instead of wasting cycles on years where
|
|||
|
|
// there will never be transitions.
|
|||
|
|
// 2. If it's a future date beyond the next few years, then we'll just assume
|
|||
|
|
// that the latest DST policy in TZDB will still be in effect. In this
|
|||
|
|
// case, we only need to look one year in the future to see if there are
|
|||
|
|
// any DST transitions. We actually only need to look 9-10 months because
|
|||
|
|
// DST has two transitions per year, but we'll use a year just to be safe.
|
|||
|
|
const oneYearLater = JSBI.add(epochNanoseconds, ABOUT_ONE_YEAR_NANOS);
|
|||
|
|
const uppercap = maxJSBI(afterLatestPossibleTzdbRuleChange(), oneYearLater);
|
|||
|
|
// The first transition (in any timezone) recorded in the TZDB was in 1847, so
|
|||
|
|
// start there if an earlier date is supplied.
|
|||
|
|
let leftNanos = maxJSBI(BEFORE_FIRST_OFFSET_TRANSITION, epochNanoseconds);
|
|||
|
|
const leftOffsetNs = GetNamedTimeZoneOffsetNanoseconds(id, leftNanos);
|
|||
|
|
let rightNanos = leftNanos;
|
|||
|
|
let rightOffsetNs = leftOffsetNs;
|
|||
|
|
while (leftOffsetNs === rightOffsetNs && JSBI.lessThan(JSBI.BigInt(leftNanos), uppercap)) {
|
|||
|
|
rightNanos = JSBI.add(leftNanos, TWO_WEEKS_NANOS);
|
|||
|
|
if (JSBI.greaterThan(rightNanos, NS_MAX)) return null;
|
|||
|
|
rightOffsetNs = GetNamedTimeZoneOffsetNanoseconds(id, rightNanos);
|
|||
|
|
if (leftOffsetNs === rightOffsetNs) {
|
|||
|
|
leftNanos = rightNanos;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (leftOffsetNs === rightOffsetNs) return null;
|
|||
|
|
const result = bisect(
|
|||
|
|
(epochNs: JSBI) => GetNamedTimeZoneOffsetNanoseconds(id, epochNs),
|
|||
|
|
leftNanos,
|
|||
|
|
rightNanos,
|
|||
|
|
leftOffsetNs,
|
|||
|
|
rightOffsetNs
|
|||
|
|
);
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function GetNamedTimeZonePreviousTransition(id: string, epochNanoseconds: JSBI): JSBI | null {
|
|||
|
|
// If a time zone uses DST (at the time of `epochNanoseconds`), then we only
|
|||
|
|
// have to look back one year to find a transition. But if it doesn't use DST,
|
|||
|
|
// then we need to look all the way back to 1847 (the earliest rule in the
|
|||
|
|
// TZDB) to see if it had other offset transitions in the past. Looping back
|
|||
|
|
// from a far-future date to 1847 is very slow (minutes of 100% CPU!), and is
|
|||
|
|
// also unnecessary because DST rules aren't put into the TZDB more than a few
|
|||
|
|
// years in the future because the political changes in time zones happen with
|
|||
|
|
// only a few years' warning. Therefore, if a far-future date is provided,
|
|||
|
|
// then we'll run the check in two parts:
|
|||
|
|
// 1. First, we'll look back for up to one year to see if the latest TZDB
|
|||
|
|
// rules have DST.
|
|||
|
|
// 2. If not, then we'll "fast-reverse" back to a few years later than the
|
|||
|
|
// current system time, and then look back to 1847. This reduces the
|
|||
|
|
// worst-case loop from 273K years to 175 years, for a ~1500x improvement
|
|||
|
|
// in worst-case perf.
|
|||
|
|
const afterLatestRule = afterLatestPossibleTzdbRuleChange();
|
|||
|
|
const isFarFuture = JSBI.greaterThan(epochNanoseconds, afterLatestRule);
|
|||
|
|
const lowercap = isFarFuture ? JSBI.subtract(epochNanoseconds, ABOUT_ONE_YEAR_NANOS) : BEFORE_FIRST_OFFSET_TRANSITION;
|
|||
|
|
|
|||
|
|
// TODO: proposal-temporal polyfill has different code for very similar
|
|||
|
|
// optimizations as above, as well as in GetNamedTimeZonePreviousTransition.
|
|||
|
|
// We should figure out if we should change one polyfill to match the other.
|
|||
|
|
|
|||
|
|
// We assume most time zones either have regular DST rules that extend
|
|||
|
|
// indefinitely into the future, or they have no DST transitions between now
|
|||
|
|
// and next year. Africa/Casablanca and Africa/El_Aaiun are unique cases
|
|||
|
|
// that fit neither of these. Their irregular DST transitions are
|
|||
|
|
// precomputed until 2087 in the current time zone database, so requesting
|
|||
|
|
// the previous transition for an instant far in the future may take an
|
|||
|
|
// extremely long time as it loops backward 2 weeks at a time.
|
|||
|
|
if (id === 'Africa/Casablanca' || id === 'Africa/El_Aaiun') {
|
|||
|
|
const lastPrecomputed = GetSlot(ToTemporalInstant('2088-01-01T00Z'), EPOCHNANOSECONDS);
|
|||
|
|
if (JSBI.lessThan(lastPrecomputed, epochNanoseconds)) {
|
|||
|
|
return GetNamedTimeZonePreviousTransition(id, lastPrecomputed);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let rightNanos = JSBI.subtract(epochNanoseconds, ONE);
|
|||
|
|
if (JSBI.lessThan(rightNanos, BEFORE_FIRST_OFFSET_TRANSITION)) return null;
|
|||
|
|
const rightOffsetNs = GetNamedTimeZoneOffsetNanoseconds(id, rightNanos);
|
|||
|
|
let leftNanos = rightNanos;
|
|||
|
|
let leftOffsetNs = rightOffsetNs;
|
|||
|
|
while (rightOffsetNs === leftOffsetNs && JSBI.greaterThan(rightNanos, lowercap)) {
|
|||
|
|
leftNanos = JSBI.subtract(rightNanos, TWO_WEEKS_NANOS);
|
|||
|
|
if (JSBI.lessThan(leftNanos, BEFORE_FIRST_OFFSET_TRANSITION)) return null;
|
|||
|
|
leftOffsetNs = GetNamedTimeZoneOffsetNanoseconds(id, leftNanos);
|
|||
|
|
if (rightOffsetNs === leftOffsetNs) {
|
|||
|
|
rightNanos = leftNanos;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (rightOffsetNs === leftOffsetNs) {
|
|||
|
|
if (isFarFuture) {
|
|||
|
|
// There was no DST after looking back one year, which means that the most
|
|||
|
|
// recent TZDB rules don't have any recurring transitions. To check for
|
|||
|
|
// transitions in older rules, back up to a few years after the current
|
|||
|
|
// date and then look all the way back to 1847. Note that we move back one
|
|||
|
|
// day from the latest possible rule so that when the recursion runs it
|
|||
|
|
// won't consider the new time to be "far future" because the system clock
|
|||
|
|
// has advanced in the meantime.
|
|||
|
|
const newTimeToCheck = JSBI.subtract(afterLatestRule, DAY_NANOS);
|
|||
|
|
return GetNamedTimeZonePreviousTransition(id, newTimeToCheck);
|
|||
|
|
}
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
const result = bisect(
|
|||
|
|
(epochNs: JSBI) => GetNamedTimeZoneOffsetNanoseconds(id, epochNs),
|
|||
|
|
leftNanos,
|
|||
|
|
rightNanos,
|
|||
|
|
leftOffsetNs,
|
|||
|
|
rightOffsetNs
|
|||
|
|
);
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ts-prune-ignore-next TODO: remove this after tests are converted to TS
|
|||
|
|
export function parseFromEnUsFormat(datetime: string) {
|
|||
|
|
const parts = datetime.split(/[^\w]+/);
|
|||
|
|
|
|||
|
|
if (parts.length !== 7) {
|
|||
|
|
throw new RangeError(`expected 7 parts in "${datetime}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const month = +parts[0];
|
|||
|
|
const day = +parts[1];
|
|||
|
|
let year = +parts[2];
|
|||
|
|
const era = parts[3].toUpperCase();
|
|||
|
|
if (era === 'B' || era === 'BC') {
|
|||
|
|
year = -year + 1;
|
|||
|
|
} else if (era !== 'A' && era !== 'AD') {
|
|||
|
|
throw new RangeError(`Unknown era ${era} in "${datetime}`);
|
|||
|
|
}
|
|||
|
|
let hour = +parts[4];
|
|||
|
|
if (hour === 24) {
|
|||
|
|
// bugs.chromium.org/p/chromium/issues/detail?id=1045791
|
|||
|
|
hour = 0;
|
|||
|
|
}
|
|||
|
|
const minute = +parts[5];
|
|||
|
|
const second = +parts[6];
|
|||
|
|
|
|||
|
|
if (
|
|||
|
|
!NumberIsFinite(year) ||
|
|||
|
|
!NumberIsFinite(month) ||
|
|||
|
|
!NumberIsFinite(day) ||
|
|||
|
|
!NumberIsFinite(hour) ||
|
|||
|
|
!NumberIsFinite(minute) ||
|
|||
|
|
!NumberIsFinite(second)
|
|||
|
|
) {
|
|||
|
|
throw new RangeError(`Invalid number in "${datetime}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return { year, month, day, hour, minute, second };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ts-prune-ignore-next TODO: remove this after tests are converted to TS
|
|||
|
|
export function GetFormatterParts(timeZone: string, epochMilliseconds: number) {
|
|||
|
|
const formatter = getIntlDateTimeFormatEnUsForTimeZone(timeZone);
|
|||
|
|
// Using `format` instead of `formatToParts` for compatibility with older clients
|
|||
|
|
const datetime = formatter.format(new Date(epochMilliseconds));
|
|||
|
|
return parseFromEnUsFormat(datetime);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function GetNamedTimeZoneEpochNanoseconds(
|
|||
|
|
id: string,
|
|||
|
|
year: number,
|
|||
|
|
month: number,
|
|||
|
|
day: number,
|
|||
|
|
hour: number,
|
|||
|
|
minute: number,
|
|||
|
|
second: number,
|
|||
|
|
millisecond: number,
|
|||
|
|
microsecond: number,
|
|||
|
|
nanosecond: number
|
|||
|
|
) {
|
|||
|
|
const ns = GetUTCEpochNanoseconds(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond);
|
|||
|
|
if (ns === null) throw new RangeError('DateTime outside of supported range');
|
|||
|
|
let nsEarlier = JSBI.subtract(ns, DAY_NANOS);
|
|||
|
|
if (JSBI.lessThan(nsEarlier, NS_MIN)) nsEarlier = ns;
|
|||
|
|
let nsLater = JSBI.add(ns, DAY_NANOS);
|
|||
|
|
if (JSBI.greaterThan(nsLater, NS_MAX)) nsLater = ns;
|
|||
|
|
const earliest = GetNamedTimeZoneOffsetNanoseconds(id, nsEarlier);
|
|||
|
|
const latest = GetNamedTimeZoneOffsetNanoseconds(id, nsLater);
|
|||
|
|
const found = earliest === latest ? [earliest] : [earliest, latest];
|
|||
|
|
return found
|
|||
|
|
.map((offsetNanoseconds) => {
|
|||
|
|
const epochNanoseconds = JSBI.subtract(ns, JSBI.BigInt(offsetNanoseconds));
|
|||
|
|
const parts = GetNamedTimeZoneDateTimeParts(id, epochNanoseconds);
|
|||
|
|
if (
|
|||
|
|
year !== parts.year ||
|
|||
|
|
month !== parts.month ||
|
|||
|
|
day !== parts.day ||
|
|||
|
|
hour !== parts.hour ||
|
|||
|
|
minute !== parts.minute ||
|
|||
|
|
second !== parts.second ||
|
|||
|
|
millisecond !== parts.millisecond ||
|
|||
|
|
microsecond !== parts.microsecond ||
|
|||
|
|
nanosecond !== parts.nanosecond
|
|||
|
|
) {
|
|||
|
|
return undefined;
|
|||
|
|
}
|
|||
|
|
return epochNanoseconds;
|
|||
|
|
})
|
|||
|
|
.filter((x) => x !== undefined) as JSBI[];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function LeapYear(year: number) {
|
|||
|
|
if (undefined === year) return false;
|
|||
|
|
const isDiv4 = year % 4 === 0;
|
|||
|
|
const isDiv100 = year % 100 === 0;
|
|||
|
|
const isDiv400 = year % 400 === 0;
|
|||
|
|
return isDiv4 && (!isDiv100 || isDiv400);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function ISODaysInMonth(year: number, month: number) {
|
|||
|
|
const DoM = {
|
|||
|
|
standard: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
|
|||
|
|
leapyear: [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|||
|
|
};
|
|||
|
|
return DoM[LeapYear(year) ? 'leapyear' : 'standard'][month - 1];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function DayOfWeek(year: number, month: number, day: number) {
|
|||
|
|
const m = month + (month < 3 ? 10 : -2);
|
|||
|
|
const Y = year - (month < 3 ? 1 : 0);
|
|||
|
|
|
|||
|
|
const c = MathFloor(Y / 100);
|
|||
|
|
const y = Y - c * 100;
|
|||
|
|
const d = day;
|
|||
|
|
|
|||
|
|
const pD = d;
|
|||
|
|
const pM = MathFloor(2.6 * m - 0.2);
|
|||
|
|
const pY = y + MathFloor(y / 4);
|
|||
|
|
const pC = MathFloor(c / 4) - 2 * c;
|
|||
|
|
|
|||
|
|
const dow = (pD + pM + pY + pC) % 7;
|
|||
|
|
|
|||
|
|
return dow + (dow <= 0 ? 7 : 0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function DayOfYear(year: number, month: number, day: number) {
|
|||
|
|
let days = day;
|
|||
|
|
for (let m = month - 1; m > 0; m--) {
|
|||
|
|
days += ISODaysInMonth(year, m);
|
|||
|
|
}
|
|||
|
|
return days;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function WeekOfYear(year: number, month: number, day: number) {
|
|||
|
|
const doy = DayOfYear(year, month, day);
|
|||
|
|
const dow = DayOfWeek(year, month, day) || 7;
|
|||
|
|
const doj = DayOfWeek(year, 1, 1);
|
|||
|
|
|
|||
|
|
const week = MathFloor((doy - dow + 10) / 7);
|
|||
|
|
|
|||
|
|
if (week < 1) {
|
|||
|
|
if (doj === 5 || (doj === 6 && LeapYear(year - 1))) {
|
|||
|
|
return { week: 53, year: year - 1 };
|
|||
|
|
} else {
|
|||
|
|
return { week: 52, year: year - 1 };
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (week === 53) {
|
|||
|
|
if ((LeapYear(year) ? 366 : 365) - doy < 4 - dow) {
|
|||
|
|
return { week: 1, year: year + 1 };
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return { week, year };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function DurationSign(
|
|||
|
|
y: number,
|
|||
|
|
mon: number,
|
|||
|
|
w: number,
|
|||
|
|
d: number,
|
|||
|
|
h: number,
|
|||
|
|
min: number,
|
|||
|
|
s: number,
|
|||
|
|
ms: number,
|
|||
|
|
µs: number,
|
|||
|
|
ns: number
|
|||
|
|
) {
|
|||
|
|
for (const prop of [y, mon, w, d, h, min, s, ms, µs, ns]) {
|
|||
|
|
if (prop !== 0) return prop < 0 ? -1 : 1;
|
|||
|
|
}
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function BalanceISOYearMonth(yearParam: number, monthParam: number) {
|
|||
|
|
let year = yearParam;
|
|||
|
|
let month = monthParam;
|
|||
|
|
if (!NumberIsFinite(year) || !NumberIsFinite(month)) throw new RangeError('infinity is out of range');
|
|||
|
|
month -= 1;
|
|||
|
|
year += MathFloor(month / 12);
|
|||
|
|
month %= 12;
|
|||
|
|
if (month < 0) month += 12;
|
|||
|
|
month += 1;
|
|||
|
|
return { year, month };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function BalanceISODate(yearParam: number, monthParam: number, dayParam: number) {
|
|||
|
|
let year = yearParam;
|
|||
|
|
let month = monthParam;
|
|||
|
|
let day = dayParam;
|
|||
|
|
if (!NumberIsFinite(day)) throw new RangeError('infinity is out of range');
|
|||
|
|
({ year, month } = BalanceISOYearMonth(year, month));
|
|||
|
|
|
|||
|
|
// The pattern of leap years in the ISO 8601 calendar repeats every 400
|
|||
|
|
// years. So if we have more than 400 years in days, there's no need to
|
|||
|
|
// convert days to a year 400 times. We can convert a multiple of 400 all at
|
|||
|
|
// once.
|
|||
|
|
const daysIn400YearCycle = 400 * 365 + 97;
|
|||
|
|
if (MathAbs(day) > daysIn400YearCycle) {
|
|||
|
|
const nCycles = MathTrunc(day / daysIn400YearCycle);
|
|||
|
|
year += 400 * nCycles;
|
|||
|
|
day -= nCycles * daysIn400YearCycle;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let daysInYear = 0;
|
|||
|
|
let testYear = month > 2 ? year : year - 1;
|
|||
|
|
while (((daysInYear = LeapYear(testYear) ? 366 : 365), day < -daysInYear)) {
|
|||
|
|
year -= 1;
|
|||
|
|
testYear -= 1;
|
|||
|
|
day += daysInYear;
|
|||
|
|
}
|
|||
|
|
testYear += 1;
|
|||
|
|
while (((daysInYear = LeapYear(testYear) ? 366 : 365), day > daysInYear)) {
|
|||
|
|
year += 1;
|
|||
|
|
testYear += 1;
|
|||
|
|
day -= daysInYear;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
while (day < 1) {
|
|||
|
|
({ year, month } = BalanceISOYearMonth(year, month - 1));
|
|||
|
|
day += ISODaysInMonth(year, month);
|
|||
|
|
}
|
|||
|
|
while (day > ISODaysInMonth(year, month)) {
|
|||
|
|
day -= ISODaysInMonth(year, month);
|
|||
|
|
({ year, month } = BalanceISOYearMonth(year, month + 1));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return { year, month, day };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function BalanceISODateTime(
|
|||
|
|
yearParam: number,
|
|||
|
|
monthParam: number,
|
|||
|
|
dayParam: number,
|
|||
|
|
hourParam: number,
|
|||
|
|
minuteParam: number,
|
|||
|
|
secondParam: number,
|
|||
|
|
millisecondParam: number,
|
|||
|
|
microsecondParam: number,
|
|||
|
|
nanosecondParam: number
|
|||
|
|
) {
|
|||
|
|
const { deltaDays, hour, minute, second, millisecond, microsecond, nanosecond } = BalanceTime(
|
|||
|
|
hourParam,
|
|||
|
|
minuteParam,
|
|||
|
|
secondParam,
|
|||
|
|
millisecondParam,
|
|||
|
|
microsecondParam,
|
|||
|
|
nanosecondParam
|
|||
|
|
);
|
|||
|
|
const { year, month, day } = BalanceISODate(yearParam, monthParam, dayParam + deltaDays);
|
|||
|
|
return { year, month, day, hour, minute, second, millisecond, microsecond, nanosecond };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function BalanceTime(
|
|||
|
|
hourParam: number,
|
|||
|
|
minuteParam: number,
|
|||
|
|
secondParam: number,
|
|||
|
|
millisecondParam: number,
|
|||
|
|
microsecondParam: number,
|
|||
|
|
nanosecondParam: number
|
|||
|
|
) {
|
|||
|
|
let hour = JSBI.BigInt(hourParam);
|
|||
|
|
let minute = JSBI.BigInt(minuteParam);
|
|||
|
|
let second = JSBI.BigInt(secondParam);
|
|||
|
|
let millisecond = JSBI.BigInt(millisecondParam);
|
|||
|
|
let microsecond = JSBI.BigInt(microsecondParam);
|
|||
|
|
let nanosecond = JSBI.BigInt(nanosecondParam);
|
|||
|
|
let quotient;
|
|||
|
|
|
|||
|
|
({ quotient, remainder: nanosecond } = NonNegativeBigIntDivmod(nanosecond, THOUSAND));
|
|||
|
|
microsecond = JSBI.add(microsecond, quotient);
|
|||
|
|
|
|||
|
|
({ quotient, remainder: microsecond } = NonNegativeBigIntDivmod(microsecond, THOUSAND));
|
|||
|
|
millisecond = JSBI.add(millisecond, quotient);
|
|||
|
|
|
|||
|
|
({ quotient, remainder: millisecond } = NonNegativeBigIntDivmod(millisecond, THOUSAND));
|
|||
|
|
second = JSBI.add(second, quotient);
|
|||
|
|
|
|||
|
|
({ quotient, remainder: second } = NonNegativeBigIntDivmod(second, SIXTY));
|
|||
|
|
minute = JSBI.add(minute, quotient);
|
|||
|
|
|
|||
|
|
({ quotient, remainder: minute } = NonNegativeBigIntDivmod(minute, SIXTY));
|
|||
|
|
hour = JSBI.add(hour, quotient);
|
|||
|
|
|
|||
|
|
({ quotient, remainder: hour } = NonNegativeBigIntDivmod(hour, TWENTY_FOUR));
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
deltaDays: JSBI.toNumber(quotient),
|
|||
|
|
hour: JSBI.toNumber(hour),
|
|||
|
|
minute: JSBI.toNumber(minute),
|
|||
|
|
second: JSBI.toNumber(second),
|
|||
|
|
millisecond: JSBI.toNumber(millisecond),
|
|||
|
|
microsecond: JSBI.toNumber(microsecond),
|
|||
|
|
nanosecond: JSBI.toNumber(nanosecond)
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function TotalDurationNanoseconds(
|
|||
|
|
daysParam: number,
|
|||
|
|
hoursParam: number | JSBI,
|
|||
|
|
minutesParam: number | JSBI,
|
|||
|
|
secondsParam: number | JSBI,
|
|||
|
|
millisecondsParam: number | JSBI,
|
|||
|
|
microsecondsParam: number | JSBI,
|
|||
|
|
nanosecondsParam: number | JSBI,
|
|||
|
|
offsetShift: number
|
|||
|
|
) {
|
|||
|
|
const days: JSBI = JSBI.BigInt(daysParam);
|
|||
|
|
let nanoseconds: JSBI = JSBI.BigInt(nanosecondsParam);
|
|||
|
|
if (daysParam !== 0) nanoseconds = JSBI.subtract(JSBI.BigInt(nanosecondsParam), JSBI.BigInt(offsetShift));
|
|||
|
|
const hours = JSBI.add(JSBI.BigInt(hoursParam), JSBI.multiply(days, JSBI.BigInt(24)));
|
|||
|
|
const minutes = JSBI.add(JSBI.BigInt(minutesParam), JSBI.multiply(hours, SIXTY));
|
|||
|
|
const seconds = JSBI.add(JSBI.BigInt(secondsParam), JSBI.multiply(minutes, SIXTY));
|
|||
|
|
const milliseconds = JSBI.add(JSBI.BigInt(millisecondsParam), JSBI.multiply(seconds, THOUSAND));
|
|||
|
|
const microseconds = JSBI.add(JSBI.BigInt(microsecondsParam), JSBI.multiply(milliseconds, THOUSAND));
|
|||
|
|
return JSBI.add(JSBI.BigInt(nanoseconds), JSBI.multiply(microseconds, THOUSAND));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function NanosecondsToDays(nanosecondsParam: JSBI, relativeTo: ReturnType<typeof ToRelativeTemporalObject>) {
|
|||
|
|
const TemporalInstant = GetIntrinsic('%Temporal.Instant%');
|
|||
|
|
const sign = MathSign(JSBI.toNumber(nanosecondsParam));
|
|||
|
|
let nanoseconds = JSBI.BigInt(nanosecondsParam);
|
|||
|
|
let dayLengthNs = 86400e9;
|
|||
|
|
if (sign === 0) return { days: 0, nanoseconds: ZERO, dayLengthNs };
|
|||
|
|
if (!IsTemporalZonedDateTime(relativeTo)) {
|
|||
|
|
let days: JSBI;
|
|||
|
|
({ quotient: days, remainder: nanoseconds } = divmod(nanoseconds, JSBI.BigInt(dayLengthNs)));
|
|||
|
|
return { days: JSBI.toNumber(days), nanoseconds, dayLengthNs };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const startNs = GetSlot(relativeTo, EPOCHNANOSECONDS);
|
|||
|
|
const start = GetSlot(relativeTo, INSTANT);
|
|||
|
|
const endNs = JSBI.add(startNs, nanoseconds);
|
|||
|
|
const end = new TemporalInstant(endNs);
|
|||
|
|
const timeZone = GetSlot(relativeTo, TIME_ZONE);
|
|||
|
|
const calendar = GetSlot(relativeTo, CALENDAR);
|
|||
|
|
|
|||
|
|
// Find the difference in days only.
|
|||
|
|
const dtStart = GetPlainDateTimeFor(timeZone, start, calendar);
|
|||
|
|
const dtEnd = GetPlainDateTimeFor(timeZone, end, calendar);
|
|||
|
|
let { days: daysNumber } = DifferenceISODateTime(
|
|||
|
|
GetSlot(dtStart, ISO_YEAR),
|
|||
|
|
GetSlot(dtStart, ISO_MONTH),
|
|||
|
|
GetSlot(dtStart, ISO_DAY),
|
|||
|
|
GetSlot(dtStart, ISO_HOUR),
|
|||
|
|
GetSlot(dtStart, ISO_MINUTE),
|
|||
|
|
GetSlot(dtStart, ISO_SECOND),
|
|||
|
|
GetSlot(dtStart, ISO_MILLISECOND),
|
|||
|
|
GetSlot(dtStart, ISO_MICROSECOND),
|
|||
|
|
GetSlot(dtStart, ISO_NANOSECOND),
|
|||
|
|
GetSlot(dtEnd, ISO_YEAR),
|
|||
|
|
GetSlot(dtEnd, ISO_MONTH),
|
|||
|
|
GetSlot(dtEnd, ISO_DAY),
|
|||
|
|
GetSlot(dtEnd, ISO_HOUR),
|
|||
|
|
GetSlot(dtEnd, ISO_MINUTE),
|
|||
|
|
GetSlot(dtEnd, ISO_SECOND),
|
|||
|
|
GetSlot(dtEnd, ISO_MILLISECOND),
|
|||
|
|
GetSlot(dtEnd, ISO_MICROSECOND),
|
|||
|
|
GetSlot(dtEnd, ISO_NANOSECOND),
|
|||
|
|
calendar,
|
|||
|
|
'day',
|
|||
|
|
ObjectCreate(null) as Temporal.DifferenceOptions<Temporal.DateTimeUnit>
|
|||
|
|
);
|
|||
|
|
let intermediateNs = AddZonedDateTime(start, timeZone, calendar, 0, 0, 0, daysNumber, 0, 0, 0, 0, 0, 0);
|
|||
|
|
// may disambiguate
|
|||
|
|
|
|||
|
|
// If clock time after addition was in the middle of a skipped period, the
|
|||
|
|
// endpoint was disambiguated to a later clock time. So it's possible that
|
|||
|
|
// the resulting disambiguated result is later than endNs. If so, then back
|
|||
|
|
// up one day and try again. Repeat if necessary (some transitions are
|
|||
|
|
// > 24 hours) until either there's zero days left or the date duration is
|
|||
|
|
// back inside the period where it belongs. Note that this case only can
|
|||
|
|
// happen for positive durations because the only direction that
|
|||
|
|
// `disambiguation: 'compatible'` can change clock time is forwards.
|
|||
|
|
let daysBigInt = JSBI.BigInt(daysNumber);
|
|||
|
|
if (sign === 1) {
|
|||
|
|
while (JSBI.greaterThan(daysBigInt, ZERO) && JSBI.greaterThan(intermediateNs, endNs)) {
|
|||
|
|
daysBigInt = JSBI.subtract(daysBigInt, ONE);
|
|||
|
|
intermediateNs = AddZonedDateTime(
|
|||
|
|
start,
|
|||
|
|
timeZone,
|
|||
|
|
calendar,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
JSBI.toNumber(daysBigInt),
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0
|
|||
|
|
);
|
|||
|
|
// may do disambiguation
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
nanoseconds = JSBI.subtract(endNs, intermediateNs);
|
|||
|
|
|
|||
|
|
let isOverflow = false;
|
|||
|
|
let relativeInstant = new TemporalInstant(intermediateNs);
|
|||
|
|
do {
|
|||
|
|
// calculate length of the next day (day that contains the time remainder)
|
|||
|
|
const oneDayFartherNs = AddZonedDateTime(relativeInstant, timeZone, calendar, 0, 0, 0, sign, 0, 0, 0, 0, 0, 0);
|
|||
|
|
const relativeNs = GetSlot(relativeInstant, EPOCHNANOSECONDS);
|
|||
|
|
dayLengthNs = JSBI.toNumber(JSBI.subtract(oneDayFartherNs, relativeNs));
|
|||
|
|
isOverflow = JSBI.greaterThanOrEqual(
|
|||
|
|
JSBI.multiply(JSBI.subtract(nanoseconds, JSBI.BigInt(dayLengthNs)), JSBI.BigInt(sign)),
|
|||
|
|
ZERO
|
|||
|
|
);
|
|||
|
|
if (isOverflow) {
|
|||
|
|
nanoseconds = JSBI.subtract(nanoseconds, JSBI.BigInt(dayLengthNs));
|
|||
|
|
relativeInstant = new TemporalInstant(oneDayFartherNs);
|
|||
|
|
daysBigInt = JSBI.add(daysBigInt, JSBI.BigInt(sign));
|
|||
|
|
}
|
|||
|
|
} while (isOverflow);
|
|||
|
|
if (!isZero(daysBigInt) && signJSBI(daysBigInt) !== sign) {
|
|||
|
|
throw new RangeError('Time zone or calendar converted nanoseconds into a number of days with the opposite sign');
|
|||
|
|
}
|
|||
|
|
if (!isZero(nanoseconds) && signJSBI(nanoseconds) !== sign) {
|
|||
|
|
if (isNegativeJSBI(nanoseconds) && sign === 1) {
|
|||
|
|
throw new Error('assert not reached');
|
|||
|
|
}
|
|||
|
|
throw new RangeError('Time zone or calendar ended up with a remainder of nanoseconds with the opposite sign');
|
|||
|
|
}
|
|||
|
|
if (JSBI.greaterThanOrEqual(abs(nanoseconds), abs(JSBI.BigInt(dayLengthNs)))) {
|
|||
|
|
throw new Error('assert not reached');
|
|||
|
|
}
|
|||
|
|
return { days: JSBI.toNumber(daysBigInt), nanoseconds, dayLengthNs: MathAbs(dayLengthNs) };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function BalanceDuration(
|
|||
|
|
daysParam: number,
|
|||
|
|
hoursParam: number | JSBI,
|
|||
|
|
minutesParam: number | JSBI,
|
|||
|
|
secondsParam: number | JSBI,
|
|||
|
|
millisecondsParam: number | JSBI,
|
|||
|
|
microsecondsParam: number | JSBI,
|
|||
|
|
nanosecondsParam: number | JSBI,
|
|||
|
|
largestUnit: Temporal.DateTimeUnit,
|
|||
|
|
relativeTo: ReturnType<typeof ToRelativeTemporalObject> = undefined
|
|||
|
|
) {
|
|||
|
|
let result = BalancePossiblyInfiniteDuration(
|
|||
|
|
daysParam,
|
|||
|
|
hoursParam,
|
|||
|
|
minutesParam,
|
|||
|
|
secondsParam,
|
|||
|
|
millisecondsParam,
|
|||
|
|
microsecondsParam,
|
|||
|
|
nanosecondsParam,
|
|||
|
|
largestUnit,
|
|||
|
|
relativeTo
|
|||
|
|
);
|
|||
|
|
if (result === 'positive overflow' || result === 'negative overflow') {
|
|||
|
|
throw new RangeError('Duration out of range');
|
|||
|
|
} else {
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function BalancePossiblyInfiniteDuration(
|
|||
|
|
daysParam: number,
|
|||
|
|
hoursParam: number | JSBI,
|
|||
|
|
minutesParam: number | JSBI,
|
|||
|
|
secondsParam: number | JSBI,
|
|||
|
|
millisecondsParam: number | JSBI,
|
|||
|
|
microsecondsParam: number | JSBI,
|
|||
|
|
nanosecondsParam: number | JSBI,
|
|||
|
|
largestUnit: Temporal.DateTimeUnit,
|
|||
|
|
relativeTo: ReturnType<typeof ToRelativeTemporalObject> = undefined
|
|||
|
|
) {
|
|||
|
|
let days = daysParam;
|
|||
|
|
let nanosecondsBigInt: JSBI,
|
|||
|
|
microsecondsBigInt: JSBI,
|
|||
|
|
millisecondsBigInt: JSBI,
|
|||
|
|
secondsBigInt: JSBI,
|
|||
|
|
minutesBigInt: JSBI,
|
|||
|
|
hoursBigInt: JSBI;
|
|||
|
|
if (IsTemporalZonedDateTime(relativeTo)) {
|
|||
|
|
const endNs = AddZonedDateTime(
|
|||
|
|
GetSlot(relativeTo, INSTANT),
|
|||
|
|
GetSlot(relativeTo, TIME_ZONE),
|
|||
|
|
GetSlot(relativeTo, CALENDAR),
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
days,
|
|||
|
|
hoursParam,
|
|||
|
|
minutesParam,
|
|||
|
|
secondsParam,
|
|||
|
|
millisecondsParam,
|
|||
|
|
microsecondsParam,
|
|||
|
|
nanosecondsParam
|
|||
|
|
);
|
|||
|
|
const startNs = GetSlot(relativeTo, EPOCHNANOSECONDS);
|
|||
|
|
nanosecondsBigInt = JSBI.subtract(endNs, startNs);
|
|||
|
|
} else {
|
|||
|
|
nanosecondsBigInt = TotalDurationNanoseconds(
|
|||
|
|
days,
|
|||
|
|
hoursParam,
|
|||
|
|
minutesParam,
|
|||
|
|
secondsParam,
|
|||
|
|
millisecondsParam,
|
|||
|
|
microsecondsParam,
|
|||
|
|
nanosecondsParam,
|
|||
|
|
0
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
if (largestUnit === 'year' || largestUnit === 'month' || largestUnit === 'week' || largestUnit === 'day') {
|
|||
|
|
({ days, nanoseconds: nanosecondsBigInt } = NanosecondsToDays(nanosecondsBigInt, relativeTo));
|
|||
|
|
} else {
|
|||
|
|
days = 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const sign = JSBI.lessThan(nanosecondsBigInt, ZERO) ? -1 : 1;
|
|||
|
|
nanosecondsBigInt = abs(nanosecondsBigInt);
|
|||
|
|
microsecondsBigInt = millisecondsBigInt = secondsBigInt = minutesBigInt = hoursBigInt = ZERO;
|
|||
|
|
|
|||
|
|
switch (largestUnit) {
|
|||
|
|
case 'year':
|
|||
|
|
case 'month':
|
|||
|
|
case 'week':
|
|||
|
|
case 'day':
|
|||
|
|
case 'hour':
|
|||
|
|
({ quotient: microsecondsBigInt, remainder: nanosecondsBigInt } = divmod(nanosecondsBigInt, THOUSAND));
|
|||
|
|
({ quotient: millisecondsBigInt, remainder: microsecondsBigInt } = divmod(microsecondsBigInt, THOUSAND));
|
|||
|
|
({ quotient: secondsBigInt, remainder: millisecondsBigInt } = divmod(millisecondsBigInt, THOUSAND));
|
|||
|
|
({ quotient: minutesBigInt, remainder: secondsBigInt } = divmod(secondsBigInt, SIXTY));
|
|||
|
|
({ quotient: hoursBigInt, remainder: minutesBigInt } = divmod(minutesBigInt, SIXTY));
|
|||
|
|
break;
|
|||
|
|
case 'minute':
|
|||
|
|
({ quotient: microsecondsBigInt, remainder: nanosecondsBigInt } = divmod(nanosecondsBigInt, THOUSAND));
|
|||
|
|
({ quotient: millisecondsBigInt, remainder: microsecondsBigInt } = divmod(microsecondsBigInt, THOUSAND));
|
|||
|
|
({ quotient: secondsBigInt, remainder: millisecondsBigInt } = divmod(millisecondsBigInt, THOUSAND));
|
|||
|
|
({ quotient: minutesBigInt, remainder: secondsBigInt } = divmod(secondsBigInt, SIXTY));
|
|||
|
|
break;
|
|||
|
|
case 'second':
|
|||
|
|
({ quotient: microsecondsBigInt, remainder: nanosecondsBigInt } = divmod(nanosecondsBigInt, THOUSAND));
|
|||
|
|
({ quotient: millisecondsBigInt, remainder: microsecondsBigInt } = divmod(microsecondsBigInt, THOUSAND));
|
|||
|
|
({ quotient: secondsBigInt, remainder: millisecondsBigInt } = divmod(millisecondsBigInt, THOUSAND));
|
|||
|
|
break;
|
|||
|
|
case 'millisecond':
|
|||
|
|
({ quotient: microsecondsBigInt, remainder: nanosecondsBigInt } = divmod(nanosecondsBigInt, THOUSAND));
|
|||
|
|
({ quotient: millisecondsBigInt, remainder: microsecondsBigInt } = divmod(microsecondsBigInt, THOUSAND));
|
|||
|
|
break;
|
|||
|
|
case 'microsecond':
|
|||
|
|
({ quotient: microsecondsBigInt, remainder: nanosecondsBigInt } = divmod(nanosecondsBigInt, THOUSAND));
|
|||
|
|
break;
|
|||
|
|
case 'nanosecond':
|
|||
|
|
break;
|
|||
|
|
default:
|
|||
|
|
throw new Error('assert not reached');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const hours = JSBI.toNumber(hoursBigInt) * sign;
|
|||
|
|
const minutes = JSBI.toNumber(minutesBigInt) * sign;
|
|||
|
|
const seconds = JSBI.toNumber(secondsBigInt) * sign;
|
|||
|
|
const milliseconds = JSBI.toNumber(millisecondsBigInt) * sign;
|
|||
|
|
const microseconds = JSBI.toNumber(microsecondsBigInt) * sign;
|
|||
|
|
const nanoseconds = JSBI.toNumber(nanosecondsBigInt) * sign;
|
|||
|
|
|
|||
|
|
for (const prop of [days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds]) {
|
|||
|
|
if (!NumberIsFinite(prop)) {
|
|||
|
|
if (sign === 1) {
|
|||
|
|
return 'positive overflow' as const;
|
|||
|
|
} else {
|
|||
|
|
return 'negative overflow' as const;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return { days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function UnbalanceDurationRelative(
|
|||
|
|
yearsParam: number,
|
|||
|
|
monthsParam: number,
|
|||
|
|
weeksParam: number,
|
|||
|
|
daysParam: number,
|
|||
|
|
largestUnit: Temporal.DateTimeUnit,
|
|||
|
|
relativeToParam: ReturnType<typeof ToRelativeTemporalObject>
|
|||
|
|
): {
|
|||
|
|
years: number;
|
|||
|
|
months: number;
|
|||
|
|
weeks: number;
|
|||
|
|
days: number;
|
|||
|
|
} {
|
|||
|
|
const TemporalDuration = GetIntrinsic('%Temporal.Duration%');
|
|||
|
|
const sign = DurationSign(yearsParam, monthsParam, weeksParam, daysParam, 0, 0, 0, 0, 0, 0);
|
|||
|
|
if (sign === 0) return { years: yearsParam, months: monthsParam, weeks: weeksParam, days: daysParam };
|
|||
|
|
const signBI = JSBI.BigInt(sign);
|
|||
|
|
|
|||
|
|
let years = JSBI.BigInt(yearsParam);
|
|||
|
|
let months = JSBI.BigInt(monthsParam);
|
|||
|
|
let weeks = JSBI.BigInt(weeksParam);
|
|||
|
|
let days = JSBI.BigInt(daysParam);
|
|||
|
|
|
|||
|
|
let calendar;
|
|||
|
|
let relativeTo: Temporal.PlainDate | undefined;
|
|||
|
|
if (relativeToParam) {
|
|||
|
|
relativeTo = ToTemporalDate(relativeToParam);
|
|||
|
|
calendar = GetSlot(relativeTo, CALENDAR);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const oneYear = new TemporalDuration(sign);
|
|||
|
|
const oneMonth = new TemporalDuration(0, sign);
|
|||
|
|
const oneWeek = new TemporalDuration(0, 0, sign);
|
|||
|
|
|
|||
|
|
switch (largestUnit) {
|
|||
|
|
case 'year':
|
|||
|
|
// no-op
|
|||
|
|
break;
|
|||
|
|
case 'month':
|
|||
|
|
{
|
|||
|
|
if (!calendar) throw new RangeError('a starting point is required for months balancing');
|
|||
|
|
assertExists(relativeTo);
|
|||
|
|
// balance years down to months
|
|||
|
|
let dateAdd, dateUntil;
|
|||
|
|
if (typeof calendar !== 'string') {
|
|||
|
|
dateAdd = GetMethod(calendar, 'dateAdd');
|
|||
|
|
dateUntil = GetMethod(calendar, 'dateUntil');
|
|||
|
|
}
|
|||
|
|
while (!isZero(years)) {
|
|||
|
|
const newRelativeTo = CalendarDateAdd(calendar, relativeTo, oneYear, undefined, dateAdd);
|
|||
|
|
const untilOptions = ObjectCreate(null) as Temporal.DifferenceOptions<typeof largestUnit>;
|
|||
|
|
untilOptions.largestUnit = 'month';
|
|||
|
|
const untilResult = CalendarDateUntil(calendar, relativeTo, newRelativeTo, untilOptions, dateUntil);
|
|||
|
|
const oneYearMonths = JSBI.BigInt(GetSlot(untilResult, MONTHS));
|
|||
|
|
relativeTo = newRelativeTo;
|
|||
|
|
months = JSBI.add(months, oneYearMonths);
|
|||
|
|
years = JSBI.subtract(years, signBI);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
case 'week': {
|
|||
|
|
if (!calendar) throw new RangeError('a starting point is required for weeks balancing');
|
|||
|
|
assertExists(relativeTo);
|
|||
|
|
const dateAdd = typeof calendar !== 'string' ? GetMethod(calendar, 'dateAdd') : undefined;
|
|||
|
|
// balance years down to days
|
|||
|
|
while (!isZero(years)) {
|
|||
|
|
let oneYearDays;
|
|||
|
|
({ relativeTo, days: oneYearDays } = MoveRelativeDate(calendar, relativeTo, oneYear, dateAdd));
|
|||
|
|
days = JSBI.add(days, JSBI.BigInt(oneYearDays));
|
|||
|
|
years = JSBI.subtract(years, signBI);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// balance months down to days
|
|||
|
|
while (!isZero(months)) {
|
|||
|
|
let oneMonthDays;
|
|||
|
|
({ relativeTo, days: oneMonthDays } = MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd));
|
|||
|
|
days = JSBI.add(days, JSBI.BigInt(oneMonthDays));
|
|||
|
|
months = JSBI.subtract(months, signBI);
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
default: {
|
|||
|
|
// balance years down to days
|
|||
|
|
if (isZero(years) && isZero(months) && isZero(weeks)) break;
|
|||
|
|
if (!calendar) throw new RangeError('a starting point is required for balancing calendar units');
|
|||
|
|
const dateAdd = typeof calendar !== 'string' ? GetMethod(calendar, 'dateAdd') : undefined;
|
|||
|
|
while (!isZero(years)) {
|
|||
|
|
assertExists(relativeTo);
|
|||
|
|
let oneYearDays;
|
|||
|
|
({ relativeTo, days: oneYearDays } = MoveRelativeDate(calendar, relativeTo, oneYear, dateAdd));
|
|||
|
|
days = JSBI.add(days, JSBI.BigInt(oneYearDays));
|
|||
|
|
years = JSBI.subtract(years, signBI);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// balance months down to days
|
|||
|
|
while (!isZero(months)) {
|
|||
|
|
assertExists(relativeTo);
|
|||
|
|
let oneMonthDays;
|
|||
|
|
({ relativeTo, days: oneMonthDays } = MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd));
|
|||
|
|
days = JSBI.add(days, JSBI.BigInt(oneMonthDays));
|
|||
|
|
months = JSBI.subtract(months, signBI);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// balance weeks down to days
|
|||
|
|
while (!isZero(weeks)) {
|
|||
|
|
assertExists(relativeTo);
|
|||
|
|
let oneWeekDays;
|
|||
|
|
({ relativeTo, days: oneWeekDays } = MoveRelativeDate(calendar, relativeTo, oneWeek, dateAdd));
|
|||
|
|
days = JSBI.add(days, JSBI.BigInt(oneWeekDays));
|
|||
|
|
weeks = JSBI.subtract(weeks, signBI);
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
years: JSBI.toNumber(years),
|
|||
|
|
months: JSBI.toNumber(months),
|
|||
|
|
weeks: JSBI.toNumber(weeks),
|
|||
|
|
days: JSBI.toNumber(days)
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function BalanceDurationRelative(
|
|||
|
|
yearsParam: number,
|
|||
|
|
monthsParam: number,
|
|||
|
|
weeksParam: number,
|
|||
|
|
daysParam: number,
|
|||
|
|
largestUnit: Temporal.DateTimeUnit,
|
|||
|
|
relativeToParam: ReturnType<typeof ToRelativeTemporalObject>
|
|||
|
|
): {
|
|||
|
|
years: number;
|
|||
|
|
months: number;
|
|||
|
|
weeks: number;
|
|||
|
|
days: number;
|
|||
|
|
} {
|
|||
|
|
const TemporalDuration = GetIntrinsic('%Temporal.Duration%');
|
|||
|
|
const sign = DurationSign(yearsParam, monthsParam, weeksParam, daysParam, 0, 0, 0, 0, 0, 0);
|
|||
|
|
if (sign === 0) return { years: yearsParam, months: monthsParam, weeks: weeksParam, days: daysParam };
|
|||
|
|
const signBI = JSBI.BigInt(sign);
|
|||
|
|
|
|||
|
|
let years = JSBI.BigInt(yearsParam);
|
|||
|
|
let months = JSBI.BigInt(monthsParam);
|
|||
|
|
let weeks = JSBI.BigInt(weeksParam);
|
|||
|
|
let days = JSBI.BigInt(daysParam);
|
|||
|
|
|
|||
|
|
let calendar;
|
|||
|
|
let relativeTo: Temporal.PlainDate | undefined;
|
|||
|
|
if (relativeToParam) {
|
|||
|
|
relativeTo = ToTemporalDate(relativeToParam);
|
|||
|
|
calendar = GetSlot(relativeTo, CALENDAR);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const oneYear = new TemporalDuration(sign);
|
|||
|
|
const oneMonth = new TemporalDuration(0, sign);
|
|||
|
|
const oneWeek = new TemporalDuration(0, 0, sign);
|
|||
|
|
|
|||
|
|
switch (largestUnit) {
|
|||
|
|
case 'year': {
|
|||
|
|
if (!calendar) throw new RangeError('a starting point is required for years balancing');
|
|||
|
|
assertExists(relativeTo);
|
|||
|
|
const dateAdd = typeof calendar !== 'string' ? GetMethod(calendar, 'dateAdd') : undefined;
|
|||
|
|
// balance days up to years
|
|||
|
|
let newRelativeTo, oneYearDays;
|
|||
|
|
({ relativeTo: newRelativeTo, days: oneYearDays } = MoveRelativeDate(calendar, relativeTo, oneYear, dateAdd));
|
|||
|
|
while (JSBI.greaterThanOrEqual(abs(days), JSBI.BigInt(MathAbs(oneYearDays)))) {
|
|||
|
|
days = JSBI.subtract(days, JSBI.BigInt(oneYearDays));
|
|||
|
|
years = JSBI.add(years, signBI);
|
|||
|
|
relativeTo = newRelativeTo;
|
|||
|
|
({ relativeTo: newRelativeTo, days: oneYearDays } = MoveRelativeDate(calendar, relativeTo, oneYear, dateAdd));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// balance days up to months
|
|||
|
|
let oneMonthDays;
|
|||
|
|
({ relativeTo: newRelativeTo, days: oneMonthDays } = MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd));
|
|||
|
|
while (JSBI.greaterThanOrEqual(abs(days), JSBI.BigInt(MathAbs(oneMonthDays)))) {
|
|||
|
|
days = JSBI.subtract(days, JSBI.BigInt(oneMonthDays));
|
|||
|
|
months = JSBI.add(months, signBI);
|
|||
|
|
relativeTo = newRelativeTo;
|
|||
|
|
({ relativeTo: newRelativeTo, days: oneMonthDays } = MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// balance months up to years
|
|||
|
|
newRelativeTo = CalendarDateAdd(calendar, relativeTo, oneYear, undefined, dateAdd);
|
|||
|
|
const dateUntil = typeof calendar !== 'string' ? GetMethod(calendar, 'dateUntil') : undefined;
|
|||
|
|
const untilOptions = ObjectCreate(null) as Temporal.DifferenceOptions<'month'>;
|
|||
|
|
untilOptions.largestUnit = 'month';
|
|||
|
|
let untilResult = CalendarDateUntil(calendar, relativeTo, newRelativeTo, untilOptions, dateUntil);
|
|||
|
|
let oneYearMonths = GetSlot(untilResult, MONTHS);
|
|||
|
|
while (JSBI.greaterThanOrEqual(abs(months), JSBI.BigInt(MathAbs(oneYearMonths)))) {
|
|||
|
|
months = JSBI.subtract(months, JSBI.BigInt(oneYearMonths));
|
|||
|
|
years = JSBI.add(years, signBI);
|
|||
|
|
relativeTo = newRelativeTo;
|
|||
|
|
newRelativeTo = CalendarDateAdd(calendar, relativeTo, oneYear, undefined, dateAdd);
|
|||
|
|
const untilOptions = ObjectCreate(null) as Temporal.DifferenceOptions<'month'>;
|
|||
|
|
untilOptions.largestUnit = 'month';
|
|||
|
|
untilResult = CalendarDateUntil(calendar, relativeTo, newRelativeTo, untilOptions, dateUntil);
|
|||
|
|
oneYearMonths = GetSlot(untilResult, MONTHS);
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
case 'month': {
|
|||
|
|
if (!calendar) throw new RangeError('a starting point is required for months balancing');
|
|||
|
|
assertExists(relativeTo);
|
|||
|
|
const dateAdd = typeof calendar !== 'string' ? GetMethod(calendar, 'dateAdd') : undefined;
|
|||
|
|
// balance days up to months
|
|||
|
|
let newRelativeTo, oneMonthDays;
|
|||
|
|
({ relativeTo: newRelativeTo, days: oneMonthDays } = MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd));
|
|||
|
|
while (JSBI.greaterThanOrEqual(abs(days), JSBI.BigInt(MathAbs(oneMonthDays)))) {
|
|||
|
|
days = JSBI.subtract(days, JSBI.BigInt(oneMonthDays));
|
|||
|
|
months = JSBI.add(months, signBI);
|
|||
|
|
relativeTo = newRelativeTo;
|
|||
|
|
({ relativeTo: newRelativeTo, days: oneMonthDays } = MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd));
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
case 'week': {
|
|||
|
|
if (!calendar) throw new RangeError('a starting point is required for weeks balancing');
|
|||
|
|
assertExists(relativeTo);
|
|||
|
|
const dateAdd = typeof calendar !== 'string' ? GetMethod(calendar, 'dateAdd') : undefined;
|
|||
|
|
// balance days up to weeks
|
|||
|
|
let newRelativeTo, oneWeekDays;
|
|||
|
|
({ relativeTo: newRelativeTo, days: oneWeekDays } = MoveRelativeDate(calendar, relativeTo, oneWeek, dateAdd));
|
|||
|
|
while (JSBI.greaterThanOrEqual(abs(days), JSBI.BigInt(MathAbs(oneWeekDays)))) {
|
|||
|
|
days = JSBI.subtract(days, JSBI.BigInt(oneWeekDays));
|
|||
|
|
weeks = JSBI.add(weeks, signBI);
|
|||
|
|
relativeTo = newRelativeTo;
|
|||
|
|
({ relativeTo: newRelativeTo, days: oneWeekDays } = MoveRelativeDate(calendar, relativeTo, oneWeek, dateAdd));
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
default:
|
|||
|
|
// no-op
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
years: JSBI.toNumber(years),
|
|||
|
|
months: JSBI.toNumber(months),
|
|||
|
|
weeks: JSBI.toNumber(weeks),
|
|||
|
|
days: JSBI.toNumber(days)
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CalculateOffsetShift(
|
|||
|
|
relativeTo: ReturnType<typeof ToRelativeTemporalObject>,
|
|||
|
|
y: number,
|
|||
|
|
mon: number,
|
|||
|
|
w: number,
|
|||
|
|
d: number
|
|||
|
|
) {
|
|||
|
|
if (IsTemporalZonedDateTime(relativeTo)) {
|
|||
|
|
const instant = GetSlot(relativeTo, INSTANT);
|
|||
|
|
const timeZone = GetSlot(relativeTo, TIME_ZONE);
|
|||
|
|
const calendar = GetSlot(relativeTo, CALENDAR);
|
|||
|
|
const offsetBefore = GetOffsetNanosecondsFor(timeZone, instant);
|
|||
|
|
const after = AddZonedDateTime(instant, timeZone, calendar, y, mon, w, d, 0, 0, 0, 0, 0, 0);
|
|||
|
|
const TemporalInstant = GetIntrinsic('%Temporal.Instant%');
|
|||
|
|
const instantAfter = new TemporalInstant(after);
|
|||
|
|
const offsetAfter = GetOffsetNanosecondsFor(timeZone, instantAfter);
|
|||
|
|
return offsetAfter - offsetBefore;
|
|||
|
|
}
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CreateNegatedTemporalDuration(duration: Temporal.Duration) {
|
|||
|
|
const TemporalDuration = GetIntrinsic('%Temporal.Duration%');
|
|||
|
|
return new TemporalDuration(
|
|||
|
|
-GetSlot(duration, YEARS),
|
|||
|
|
-GetSlot(duration, MONTHS),
|
|||
|
|
-GetSlot(duration, WEEKS),
|
|||
|
|
-GetSlot(duration, DAYS),
|
|||
|
|
-GetSlot(duration, HOURS),
|
|||
|
|
-GetSlot(duration, MINUTES),
|
|||
|
|
-GetSlot(duration, SECONDS),
|
|||
|
|
-GetSlot(duration, MILLISECONDS),
|
|||
|
|
-GetSlot(duration, MICROSECONDS),
|
|||
|
|
-GetSlot(duration, NANOSECONDS)
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function ConstrainToRange(value: number | undefined, min: number, max: number) {
|
|||
|
|
// Math.Max accepts undefined values and returns NaN. Undefined values are
|
|||
|
|
// used for optional params in the method below.
|
|||
|
|
return MathMin(max, MathMax(min, value as number));
|
|||
|
|
}
|
|||
|
|
function ConstrainISODate(year: number, monthParam: number, dayParam?: number) {
|
|||
|
|
const month = ConstrainToRange(monthParam, 1, 12);
|
|||
|
|
const day = ConstrainToRange(dayParam, 1, ISODaysInMonth(year, month));
|
|||
|
|
return { year, month, day };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function ConstrainTime(
|
|||
|
|
hourParam: number,
|
|||
|
|
minuteParam: number,
|
|||
|
|
secondParam: number,
|
|||
|
|
millisecondParam: number,
|
|||
|
|
microsecondParam: number,
|
|||
|
|
nanosecondParam: number
|
|||
|
|
) {
|
|||
|
|
const hour = ConstrainToRange(hourParam, 0, 23);
|
|||
|
|
const minute = ConstrainToRange(minuteParam, 0, 59);
|
|||
|
|
const second = ConstrainToRange(secondParam, 0, 59);
|
|||
|
|
const millisecond = ConstrainToRange(millisecondParam, 0, 999);
|
|||
|
|
const microsecond = ConstrainToRange(microsecondParam, 0, 999);
|
|||
|
|
const nanosecond = ConstrainToRange(nanosecondParam, 0, 999);
|
|||
|
|
return { hour, minute, second, millisecond, microsecond, nanosecond };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function RejectToRange(value: number, min: number, max: number) {
|
|||
|
|
if (value < min || value > max) throw new RangeError(`value out of range: ${min} <= ${value} <= ${max}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function RejectISODate(year: number, month: number, day: number) {
|
|||
|
|
RejectToRange(month, 1, 12);
|
|||
|
|
RejectToRange(day, 1, ISODaysInMonth(year, month));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function RejectDateRange(year: number, month: number, day: number) {
|
|||
|
|
// Noon avoids trouble at edges of DateTime range (excludes midnight)
|
|||
|
|
RejectDateTimeRange(year, month, day, 12, 0, 0, 0, 0, 0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function RejectTime(
|
|||
|
|
hour: number,
|
|||
|
|
minute: number,
|
|||
|
|
second: number,
|
|||
|
|
millisecond: number,
|
|||
|
|
microsecond: number,
|
|||
|
|
nanosecond: number
|
|||
|
|
) {
|
|||
|
|
RejectToRange(hour, 0, 23);
|
|||
|
|
RejectToRange(minute, 0, 59);
|
|||
|
|
RejectToRange(second, 0, 59);
|
|||
|
|
RejectToRange(millisecond, 0, 999);
|
|||
|
|
RejectToRange(microsecond, 0, 999);
|
|||
|
|
RejectToRange(nanosecond, 0, 999);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function RejectDateTime(
|
|||
|
|
year: number,
|
|||
|
|
month: number,
|
|||
|
|
day: number,
|
|||
|
|
hour: number,
|
|||
|
|
minute: number,
|
|||
|
|
second: number,
|
|||
|
|
millisecond: number,
|
|||
|
|
microsecond: number,
|
|||
|
|
nanosecond: number
|
|||
|
|
) {
|
|||
|
|
RejectISODate(year, month, day);
|
|||
|
|
RejectTime(hour, minute, second, millisecond, microsecond, nanosecond);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function RejectDateTimeRange(
|
|||
|
|
year: number,
|
|||
|
|
month: number,
|
|||
|
|
day: number,
|
|||
|
|
hour: number,
|
|||
|
|
minute: number,
|
|||
|
|
second: number,
|
|||
|
|
millisecond: number,
|
|||
|
|
microsecond: number,
|
|||
|
|
nanosecond: number
|
|||
|
|
) {
|
|||
|
|
RejectToRange(year, YEAR_MIN, YEAR_MAX);
|
|||
|
|
// Reject any DateTime 24 hours or more outside the Instant range
|
|||
|
|
if (
|
|||
|
|
(year === YEAR_MIN &&
|
|||
|
|
null ==
|
|||
|
|
GetUTCEpochNanoseconds(year, month, day + 1, hour, minute, second, millisecond, microsecond, nanosecond - 1)) ||
|
|||
|
|
(year === YEAR_MAX &&
|
|||
|
|
null ==
|
|||
|
|
GetUTCEpochNanoseconds(year, month, day - 1, hour, minute, second, millisecond, microsecond, nanosecond + 1))
|
|||
|
|
) {
|
|||
|
|
throw new RangeError('DateTime outside of supported range');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function ValidateEpochNanoseconds(epochNanoseconds: JSBI) {
|
|||
|
|
if (JSBI.lessThan(epochNanoseconds, NS_MIN) || JSBI.greaterThan(epochNanoseconds, NS_MAX)) {
|
|||
|
|
throw new RangeError('Instant outside of supported range');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function RejectYearMonthRange(year: number, month: number) {
|
|||
|
|
RejectToRange(year, YEAR_MIN, YEAR_MAX);
|
|||
|
|
if (year === YEAR_MIN) {
|
|||
|
|
RejectToRange(month, 4, 12);
|
|||
|
|
} else if (year === YEAR_MAX) {
|
|||
|
|
RejectToRange(month, 1, 9);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function RejectDuration(
|
|||
|
|
y: number,
|
|||
|
|
mon: number,
|
|||
|
|
w: number,
|
|||
|
|
d: number,
|
|||
|
|
h: number,
|
|||
|
|
min: number,
|
|||
|
|
s: number,
|
|||
|
|
ms: number,
|
|||
|
|
µs: number,
|
|||
|
|
ns: number
|
|||
|
|
) {
|
|||
|
|
const sign = DurationSign(y, mon, w, d, h, min, s, ms, µs, ns);
|
|||
|
|
for (const prop of [y, mon, w, d, h, min, s, ms, µs, ns]) {
|
|||
|
|
if (!NumberIsFinite(prop)) throw new RangeError('infinite values not allowed as duration fields');
|
|||
|
|
const propSign = MathSign(prop);
|
|||
|
|
if (propSign !== 0 && propSign !== sign) throw new RangeError('mixed-sign values not allowed as duration fields');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function DifferenceISODate<Allowed extends Temporal.DateTimeUnit>(
|
|||
|
|
y1: number,
|
|||
|
|
m1: number,
|
|||
|
|
d1: number,
|
|||
|
|
y2: number,
|
|||
|
|
m2: number,
|
|||
|
|
d2: number,
|
|||
|
|
largestUnit: Allowed
|
|||
|
|
) {
|
|||
|
|
switch (largestUnit) {
|
|||
|
|
case 'year':
|
|||
|
|
case 'month': {
|
|||
|
|
const sign = -CompareISODate(y1, m1, d1, y2, m2, d2);
|
|||
|
|
if (sign === 0) return { years: 0, months: 0, weeks: 0, days: 0 };
|
|||
|
|
|
|||
|
|
const start = { year: y1, month: m1, day: d1 };
|
|||
|
|
const end = { year: y2, month: m2, day: d2 };
|
|||
|
|
|
|||
|
|
let years = end.year - start.year;
|
|||
|
|
let mid = AddISODate(y1, m1, d1, years, 0, 0, 0, 'constrain');
|
|||
|
|
let midSign = -CompareISODate(mid.year, mid.month, mid.day, y2, m2, d2);
|
|||
|
|
if (midSign === 0) {
|
|||
|
|
return largestUnit === 'year'
|
|||
|
|
? { years, months: 0, weeks: 0, days: 0 }
|
|||
|
|
: { years: 0, months: years * 12, weeks: 0, days: 0 };
|
|||
|
|
}
|
|||
|
|
let months = end.month - start.month;
|
|||
|
|
if (midSign !== sign) {
|
|||
|
|
years -= sign;
|
|||
|
|
months += sign * 12;
|
|||
|
|
}
|
|||
|
|
mid = AddISODate(y1, m1, d1, years, months, 0, 0, 'constrain');
|
|||
|
|
midSign = -CompareISODate(mid.year, mid.month, mid.day, y2, m2, d2);
|
|||
|
|
if (midSign === 0) {
|
|||
|
|
return largestUnit === 'year'
|
|||
|
|
? { years, months, weeks: 0, days: 0 }
|
|||
|
|
: { years: 0, months: months + years * 12, weeks: 0, days: 0 };
|
|||
|
|
}
|
|||
|
|
if (midSign !== sign) {
|
|||
|
|
// The end date is later in the month than mid date (or earlier for
|
|||
|
|
// negative durations). Back up one month.
|
|||
|
|
months -= sign;
|
|||
|
|
if (months === -sign) {
|
|||
|
|
years -= sign;
|
|||
|
|
months = 11 * sign;
|
|||
|
|
}
|
|||
|
|
mid = AddISODate(y1, m1, d1, years, months, 0, 0, 'constrain');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let days = 0;
|
|||
|
|
// If we get here, months and years are correct (no overflow), and `mid`
|
|||
|
|
// is within the range from `start` to `end`. To count the days between
|
|||
|
|
// `mid` and `end`, there are 3 cases:
|
|||
|
|
// 1) same month: use simple subtraction
|
|||
|
|
// 2) end is previous month from intermediate (negative duration)
|
|||
|
|
// 3) end is next month from intermediate (positive duration)
|
|||
|
|
if (mid.month === end.month) {
|
|||
|
|
// 1) same month: use simple subtraction
|
|||
|
|
days = end.day - mid.day;
|
|||
|
|
} else if (sign < 0) {
|
|||
|
|
// 2) end is previous month from intermediate (negative duration)
|
|||
|
|
// Example: intermediate: Feb 1, end: Jan 30, DaysInMonth = 31, days = -2
|
|||
|
|
days = -mid.day - (ISODaysInMonth(end.year, end.month) - end.day);
|
|||
|
|
} else {
|
|||
|
|
// 3) end is next month from intermediate (positive duration)
|
|||
|
|
// Example: intermediate: Jan 29, end: Feb 1, DaysInMonth = 31, days = 3
|
|||
|
|
days = end.day + (ISODaysInMonth(mid.year, mid.month) - mid.day);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (largestUnit === 'month') {
|
|||
|
|
months += years * 12;
|
|||
|
|
years = 0;
|
|||
|
|
}
|
|||
|
|
return { years, months, weeks: 0, days };
|
|||
|
|
}
|
|||
|
|
case 'week':
|
|||
|
|
case 'day': {
|
|||
|
|
let larger, smaller, sign;
|
|||
|
|
if (CompareISODate(y1, m1, d1, y2, m2, d2) < 0) {
|
|||
|
|
smaller = { year: y1, month: m1, day: d1 };
|
|||
|
|
larger = { year: y2, month: m2, day: d2 };
|
|||
|
|
sign = 1;
|
|||
|
|
} else {
|
|||
|
|
smaller = { year: y2, month: m2, day: d2 };
|
|||
|
|
larger = { year: y1, month: m1, day: d1 };
|
|||
|
|
sign = -1;
|
|||
|
|
}
|
|||
|
|
let days = DayOfYear(larger.year, larger.month, larger.day) - DayOfYear(smaller.year, smaller.month, smaller.day);
|
|||
|
|
for (let year = smaller.year; year < larger.year; ++year) {
|
|||
|
|
days += LeapYear(year) ? 366 : 365;
|
|||
|
|
}
|
|||
|
|
let weeks = 0;
|
|||
|
|
if (largestUnit === 'week') {
|
|||
|
|
weeks = MathFloor(days / 7);
|
|||
|
|
days %= 7;
|
|||
|
|
}
|
|||
|
|
weeks *= sign;
|
|||
|
|
days *= sign;
|
|||
|
|
return { years: 0, months: 0, weeks, days };
|
|||
|
|
}
|
|||
|
|
default:
|
|||
|
|
throw new Error('assert not reached');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function DifferenceTime(
|
|||
|
|
h1: number,
|
|||
|
|
min1: number,
|
|||
|
|
s1: number,
|
|||
|
|
ms1: number,
|
|||
|
|
µs1: number,
|
|||
|
|
ns1: number,
|
|||
|
|
h2: number,
|
|||
|
|
min2: number,
|
|||
|
|
s2: number,
|
|||
|
|
ms2: number,
|
|||
|
|
µs2: number,
|
|||
|
|
ns2: number
|
|||
|
|
) {
|
|||
|
|
let hours = h2 - h1;
|
|||
|
|
let minutes = min2 - min1;
|
|||
|
|
let seconds = s2 - s1;
|
|||
|
|
let milliseconds = ms2 - ms1;
|
|||
|
|
let microseconds = µs2 - µs1;
|
|||
|
|
let nanoseconds = ns2 - ns1;
|
|||
|
|
|
|||
|
|
const sign = DurationSign(0, 0, 0, 0, hours, minutes, seconds, milliseconds, microseconds, nanoseconds);
|
|||
|
|
hours *= sign;
|
|||
|
|
minutes *= sign;
|
|||
|
|
seconds *= sign;
|
|||
|
|
milliseconds *= sign;
|
|||
|
|
microseconds *= sign;
|
|||
|
|
nanoseconds *= sign;
|
|||
|
|
|
|||
|
|
let deltaDays = 0;
|
|||
|
|
({
|
|||
|
|
deltaDays,
|
|||
|
|
hour: hours,
|
|||
|
|
minute: minutes,
|
|||
|
|
second: seconds,
|
|||
|
|
millisecond: milliseconds,
|
|||
|
|
microsecond: microseconds,
|
|||
|
|
nanosecond: nanoseconds
|
|||
|
|
} = BalanceTime(hours, minutes, seconds, milliseconds, microseconds, nanoseconds));
|
|||
|
|
|
|||
|
|
if (deltaDays != 0) throw new Error('assertion failure in DifferenceTime: _bt_.[[Days]] should be 0');
|
|||
|
|
hours *= sign;
|
|||
|
|
minutes *= sign;
|
|||
|
|
seconds *= sign;
|
|||
|
|
milliseconds *= sign;
|
|||
|
|
microseconds *= sign;
|
|||
|
|
nanoseconds *= sign;
|
|||
|
|
|
|||
|
|
return { hours, minutes, seconds, milliseconds, microseconds, nanoseconds };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function DifferenceInstant(
|
|||
|
|
ns1: JSBI,
|
|||
|
|
ns2: JSBI,
|
|||
|
|
increment: number,
|
|||
|
|
smallestUnit: keyof typeof nsPerTimeUnit,
|
|||
|
|
largestUnit: keyof typeof nsPerTimeUnit,
|
|||
|
|
roundingMode: Temporal.RoundingMode
|
|||
|
|
) {
|
|||
|
|
const diff = JSBI.subtract(ns2, ns1);
|
|||
|
|
|
|||
|
|
let hours = 0;
|
|||
|
|
let minutes = 0;
|
|||
|
|
let nanoseconds = JSBI.toNumber(JSBI.remainder(diff, THOUSAND));
|
|||
|
|
let microseconds = JSBI.toNumber(JSBI.remainder(JSBI.divide(diff, THOUSAND), THOUSAND));
|
|||
|
|
let milliseconds = JSBI.toNumber(JSBI.remainder(JSBI.divide(diff, MILLION), THOUSAND));
|
|||
|
|
let seconds = JSBI.toNumber(JSBI.divide(diff, BILLION));
|
|||
|
|
|
|||
|
|
({ hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = RoundDuration(
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
seconds,
|
|||
|
|
milliseconds,
|
|||
|
|
microseconds,
|
|||
|
|
nanoseconds,
|
|||
|
|
increment,
|
|||
|
|
smallestUnit,
|
|||
|
|
roundingMode
|
|||
|
|
));
|
|||
|
|
return BalanceDuration(0, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, largestUnit);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function DifferenceISODateTime(
|
|||
|
|
y1Param: number,
|
|||
|
|
mon1Param: number,
|
|||
|
|
d1Param: number,
|
|||
|
|
h1: number,
|
|||
|
|
min1: number,
|
|||
|
|
s1: number,
|
|||
|
|
ms1: number,
|
|||
|
|
µs1: number,
|
|||
|
|
ns1: number,
|
|||
|
|
y2: number,
|
|||
|
|
mon2: number,
|
|||
|
|
d2: number,
|
|||
|
|
h2: number,
|
|||
|
|
min2: number,
|
|||
|
|
s2: number,
|
|||
|
|
ms2: number,
|
|||
|
|
µs2: number,
|
|||
|
|
ns2: number,
|
|||
|
|
calendar: CalendarSlot,
|
|||
|
|
largestUnit: Temporal.DateTimeUnit,
|
|||
|
|
options: Temporal.DifferenceOptions<Temporal.DateTimeUnit> | undefined
|
|||
|
|
) {
|
|||
|
|
let y1 = y1Param;
|
|||
|
|
let mon1 = mon1Param;
|
|||
|
|
let d1 = d1Param;
|
|||
|
|
|
|||
|
|
let { hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = DifferenceTime(
|
|||
|
|
h1,
|
|||
|
|
min1,
|
|||
|
|
s1,
|
|||
|
|
ms1,
|
|||
|
|
µs1,
|
|||
|
|
ns1,
|
|||
|
|
h2,
|
|||
|
|
min2,
|
|||
|
|
s2,
|
|||
|
|
ms2,
|
|||
|
|
µs2,
|
|||
|
|
ns2
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
const timeSign = DurationSign(0, 0, 0, 0, hours, minutes, seconds, milliseconds, microseconds, nanoseconds);
|
|||
|
|
const dateSign = CompareISODate(y2, mon2, d2, y1, mon1, d1);
|
|||
|
|
if (dateSign === -timeSign) {
|
|||
|
|
({ year: y1, month: mon1, day: d1 } = BalanceISODate(y1, mon1, d1 - timeSign));
|
|||
|
|
({ hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceDuration(
|
|||
|
|
-timeSign,
|
|||
|
|
hours,
|
|||
|
|
minutes,
|
|||
|
|
seconds,
|
|||
|
|
milliseconds,
|
|||
|
|
microseconds,
|
|||
|
|
nanoseconds,
|
|||
|
|
largestUnit
|
|||
|
|
));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const date1 = CreateTemporalDate(y1, mon1, d1, calendar);
|
|||
|
|
const date2 = CreateTemporalDate(y2, mon2, d2, calendar);
|
|||
|
|
const dateLargestUnit = LargerOfTwoTemporalUnits('day', largestUnit);
|
|||
|
|
const untilOptions = CopyOptions(options);
|
|||
|
|
untilOptions.largestUnit = dateLargestUnit;
|
|||
|
|
// TODO untilOptions doesn't want to compile as it seems that smallestUnit is not clamped?
|
|||
|
|
// Type 'SmallestUnit<DateTimeUnit> | undefined' is not assignable to type
|
|||
|
|
// 'SmallestUnit<"year" | "month" | "day" | "week"> | undefined'.
|
|||
|
|
// Type '"hour"' is not assignable to type
|
|||
|
|
// 'SmallestUnit<"year" | "month" | "day" | "week"> | undefined'.ts(2345)
|
|||
|
|
let { years, months, weeks, days } = CalendarDateUntil(calendar, date1, date2, untilOptions as any);
|
|||
|
|
// Signs of date part and time part may not agree; balance them together
|
|||
|
|
({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceDuration(
|
|||
|
|
days,
|
|||
|
|
hours,
|
|||
|
|
minutes,
|
|||
|
|
seconds,
|
|||
|
|
milliseconds,
|
|||
|
|
microseconds,
|
|||
|
|
nanoseconds,
|
|||
|
|
largestUnit
|
|||
|
|
));
|
|||
|
|
return { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function DifferenceZonedDateTime(
|
|||
|
|
ns1: JSBI,
|
|||
|
|
ns2: JSBI,
|
|||
|
|
timeZone: string | Temporal.TimeZoneProtocol,
|
|||
|
|
calendar: CalendarSlot,
|
|||
|
|
largestUnit: Temporal.DateTimeUnit,
|
|||
|
|
options: Temporal.DifferenceOptions<Temporal.DateTimeUnit>
|
|||
|
|
) {
|
|||
|
|
const nsDiff = JSBI.subtract(ns2, ns1);
|
|||
|
|
if (JSBI.equal(nsDiff, ZERO)) {
|
|||
|
|
return {
|
|||
|
|
years: 0,
|
|||
|
|
months: 0,
|
|||
|
|
weeks: 0,
|
|||
|
|
days: 0,
|
|||
|
|
hours: 0,
|
|||
|
|
minutes: 0,
|
|||
|
|
seconds: 0,
|
|||
|
|
milliseconds: 0,
|
|||
|
|
microseconds: 0,
|
|||
|
|
nanoseconds: 0
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Find the difference in dates only.
|
|||
|
|
const TemporalInstant = GetIntrinsic('%Temporal.Instant%');
|
|||
|
|
const start = new TemporalInstant(ns1);
|
|||
|
|
const end = new TemporalInstant(ns2);
|
|||
|
|
const dtStart = GetPlainDateTimeFor(timeZone, start, calendar);
|
|||
|
|
const dtEnd = GetPlainDateTimeFor(timeZone, end, calendar);
|
|||
|
|
let { years, months, weeks, days } = DifferenceISODateTime(
|
|||
|
|
GetSlot(dtStart, ISO_YEAR),
|
|||
|
|
GetSlot(dtStart, ISO_MONTH),
|
|||
|
|
GetSlot(dtStart, ISO_DAY),
|
|||
|
|
GetSlot(dtStart, ISO_HOUR),
|
|||
|
|
GetSlot(dtStart, ISO_MINUTE),
|
|||
|
|
GetSlot(dtStart, ISO_SECOND),
|
|||
|
|
GetSlot(dtStart, ISO_MILLISECOND),
|
|||
|
|
GetSlot(dtStart, ISO_MICROSECOND),
|
|||
|
|
GetSlot(dtStart, ISO_NANOSECOND),
|
|||
|
|
GetSlot(dtEnd, ISO_YEAR),
|
|||
|
|
GetSlot(dtEnd, ISO_MONTH),
|
|||
|
|
GetSlot(dtEnd, ISO_DAY),
|
|||
|
|
GetSlot(dtEnd, ISO_HOUR),
|
|||
|
|
GetSlot(dtEnd, ISO_MINUTE),
|
|||
|
|
GetSlot(dtEnd, ISO_SECOND),
|
|||
|
|
GetSlot(dtEnd, ISO_MILLISECOND),
|
|||
|
|
GetSlot(dtEnd, ISO_MICROSECOND),
|
|||
|
|
GetSlot(dtEnd, ISO_NANOSECOND),
|
|||
|
|
calendar,
|
|||
|
|
largestUnit,
|
|||
|
|
options
|
|||
|
|
);
|
|||
|
|
const intermediateNs = AddZonedDateTime(start, timeZone, calendar, years, months, weeks, 0, 0, 0, 0, 0, 0, 0);
|
|||
|
|
// may disambiguate
|
|||
|
|
let timeRemainderNs = JSBI.subtract(ns2, intermediateNs);
|
|||
|
|
const intermediate = CreateTemporalZonedDateTime(intermediateNs, timeZone, calendar);
|
|||
|
|
({ nanoseconds: timeRemainderNs, days } = NanosecondsToDays(timeRemainderNs, intermediate));
|
|||
|
|
|
|||
|
|
// Finally, merge the date and time durations and return the merged result.
|
|||
|
|
const { hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceDuration(
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
JSBI.toNumber(timeRemainderNs),
|
|||
|
|
'hour'
|
|||
|
|
);
|
|||
|
|
return { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type DifferenceOperation = 'since' | 'until';
|
|||
|
|
|
|||
|
|
function GetDifferenceSettings<T extends Temporal.DateTimeUnit>(
|
|||
|
|
op: DifferenceOperation,
|
|||
|
|
options: Temporal.DifferenceOptions<T>,
|
|||
|
|
group: 'datetime' | 'date' | 'time',
|
|||
|
|
disallowed: (Temporal.DateTimeUnit | 'auto')[],
|
|||
|
|
fallbackSmallest: T,
|
|||
|
|
smallestLargestDefaultUnit: T
|
|||
|
|
) {
|
|||
|
|
const ALLOWED_UNITS = SINGULAR_PLURAL_UNITS.reduce((allowed, unitInfo) => {
|
|||
|
|
const p = unitInfo[0];
|
|||
|
|
const s = unitInfo[1];
|
|||
|
|
const c = unitInfo[2];
|
|||
|
|
if ((group === 'datetime' || c === group) && !disallowed.includes(s)) {
|
|||
|
|
allowed.push(s, p);
|
|||
|
|
}
|
|||
|
|
return allowed;
|
|||
|
|
}, [] as (Temporal.DateTimeUnit | Temporal.PluralUnit<Temporal.DateTimeUnit>)[]);
|
|||
|
|
|
|||
|
|
let largestUnit = GetTemporalUnit(options, 'largestUnit', group, 'auto');
|
|||
|
|
if (disallowed.includes(largestUnit)) {
|
|||
|
|
throw new RangeError(`largestUnit must be one of ${ALLOWED_UNITS.join(', ')}, not ${largestUnit}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const roundingIncrement = ToTemporalRoundingIncrement(options);
|
|||
|
|
|
|||
|
|
let roundingMode = ToTemporalRoundingMode(options, 'trunc');
|
|||
|
|
if (op === 'since') roundingMode = NegateTemporalRoundingMode(roundingMode);
|
|||
|
|
|
|||
|
|
const smallestUnit = GetTemporalUnit(options, 'smallestUnit', group, fallbackSmallest);
|
|||
|
|
if (disallowed.includes(smallestUnit)) {
|
|||
|
|
throw new RangeError(`smallestUnit must be one of ${ALLOWED_UNITS.join(', ')}, not ${smallestUnit}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const defaultLargestUnit = LargerOfTwoTemporalUnits(smallestLargestDefaultUnit, smallestUnit);
|
|||
|
|
if (largestUnit === 'auto') largestUnit = defaultLargestUnit;
|
|||
|
|
if (LargerOfTwoTemporalUnits(largestUnit, smallestUnit) !== largestUnit) {
|
|||
|
|
throw new RangeError(`largestUnit ${largestUnit} cannot be smaller than smallestUnit ${smallestUnit}`);
|
|||
|
|
}
|
|||
|
|
const MAX_DIFFERENCE_INCREMENTS: { [k in Temporal.DateTimeUnit]?: number } = {
|
|||
|
|
hour: 24,
|
|||
|
|
minute: 60,
|
|||
|
|
second: 60,
|
|||
|
|
millisecond: 1000,
|
|||
|
|
microsecond: 1000,
|
|||
|
|
nanosecond: 1000
|
|||
|
|
};
|
|||
|
|
const maximum = MAX_DIFFERENCE_INCREMENTS[smallestUnit];
|
|||
|
|
if (maximum !== undefined) ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false);
|
|||
|
|
|
|||
|
|
return { largestUnit: largestUnit as T, roundingIncrement, roundingMode, smallestUnit: smallestUnit as T };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function DifferenceTemporalInstant(
|
|||
|
|
operation: DifferenceOperation,
|
|||
|
|
instant: Temporal.Instant,
|
|||
|
|
otherParam: InstantParams['until'][0],
|
|||
|
|
options: InstantParams['until'][1] | undefined
|
|||
|
|
): Temporal.Duration {
|
|||
|
|
const sign = operation === 'since' ? -1 : 1;
|
|||
|
|
const other = ToTemporalInstant(otherParam);
|
|||
|
|
|
|||
|
|
const resolvedOptions = CopyOptions(options);
|
|||
|
|
const settings = GetDifferenceSettings(operation, resolvedOptions, 'time', [], 'nanosecond', 'second');
|
|||
|
|
|
|||
|
|
const onens = GetSlot(instant, EPOCHNANOSECONDS);
|
|||
|
|
const twons = GetSlot(other, EPOCHNANOSECONDS);
|
|||
|
|
let { hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = DifferenceInstant(
|
|||
|
|
onens,
|
|||
|
|
twons,
|
|||
|
|
settings.roundingIncrement,
|
|||
|
|
settings.smallestUnit,
|
|||
|
|
settings.largestUnit,
|
|||
|
|
settings.roundingMode
|
|||
|
|
);
|
|||
|
|
const Duration = GetIntrinsic('%Temporal.Duration%');
|
|||
|
|
return new Duration(
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
sign * hours,
|
|||
|
|
sign * minutes,
|
|||
|
|
sign * seconds,
|
|||
|
|
sign * milliseconds,
|
|||
|
|
sign * microseconds,
|
|||
|
|
sign * nanoseconds
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function DifferenceTemporalPlainDate(
|
|||
|
|
operation: DifferenceOperation,
|
|||
|
|
plainDate: Temporal.PlainDate,
|
|||
|
|
otherParam: PlainDateParams['until'][0],
|
|||
|
|
options: PlainDateParams['until'][1]
|
|||
|
|
): Temporal.Duration {
|
|||
|
|
const sign = operation === 'since' ? -1 : 1;
|
|||
|
|
const other = ToTemporalDate(otherParam);
|
|||
|
|
const calendar = GetSlot(plainDate, CALENDAR);
|
|||
|
|
const otherCalendar = GetSlot(other, CALENDAR);
|
|||
|
|
ThrowIfCalendarsNotEqual(calendar, otherCalendar, 'compute difference between dates');
|
|||
|
|
|
|||
|
|
const resolvedOptions = CopyOptions(options);
|
|||
|
|
const settings = GetDifferenceSettings(operation, resolvedOptions, 'date', [], 'day', 'day');
|
|||
|
|
resolvedOptions.largestUnit = settings.largestUnit;
|
|||
|
|
|
|||
|
|
let { years, months, weeks, days } = CalendarDateUntil(calendar, plainDate, other, resolvedOptions);
|
|||
|
|
|
|||
|
|
if (settings.smallestUnit !== 'day' || settings.roundingIncrement !== 1) {
|
|||
|
|
({ years, months, weeks, days } = RoundDuration(
|
|||
|
|
years,
|
|||
|
|
months,
|
|||
|
|
weeks,
|
|||
|
|
days,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
settings.roundingIncrement,
|
|||
|
|
settings.smallestUnit,
|
|||
|
|
settings.roundingMode,
|
|||
|
|
plainDate
|
|||
|
|
));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const Duration = GetIntrinsic('%Temporal.Duration%');
|
|||
|
|
return new Duration(sign * years, sign * months, sign * weeks, sign * days, 0, 0, 0, 0, 0, 0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function DifferenceTemporalPlainDateTime(
|
|||
|
|
operation: DifferenceOperation,
|
|||
|
|
plainDateTime: Temporal.PlainDateTime,
|
|||
|
|
otherParam: PlainDateTimeParams['until'][0],
|
|||
|
|
options: PlainDateTimeParams['until'][1]
|
|||
|
|
): Temporal.Duration {
|
|||
|
|
const sign = operation === 'since' ? -1 : 1;
|
|||
|
|
const other = ToTemporalDateTime(otherParam);
|
|||
|
|
const calendar = GetSlot(plainDateTime, CALENDAR);
|
|||
|
|
const otherCalendar = GetSlot(other, CALENDAR);
|
|||
|
|
ThrowIfCalendarsNotEqual(calendar, otherCalendar, 'compute difference between dates');
|
|||
|
|
|
|||
|
|
const resolvedOptions = CopyOptions(options);
|
|||
|
|
const settings = GetDifferenceSettings(operation, resolvedOptions, 'datetime', [], 'nanosecond', 'day');
|
|||
|
|
|
|||
|
|
let { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } =
|
|||
|
|
DifferenceISODateTime(
|
|||
|
|
GetSlot(plainDateTime, ISO_YEAR),
|
|||
|
|
GetSlot(plainDateTime, ISO_MONTH),
|
|||
|
|
GetSlot(plainDateTime, ISO_DAY),
|
|||
|
|
GetSlot(plainDateTime, ISO_HOUR),
|
|||
|
|
GetSlot(plainDateTime, ISO_MINUTE),
|
|||
|
|
GetSlot(plainDateTime, ISO_SECOND),
|
|||
|
|
GetSlot(plainDateTime, ISO_MILLISECOND),
|
|||
|
|
GetSlot(plainDateTime, ISO_MICROSECOND),
|
|||
|
|
GetSlot(plainDateTime, ISO_NANOSECOND),
|
|||
|
|
GetSlot(other, ISO_YEAR),
|
|||
|
|
GetSlot(other, ISO_MONTH),
|
|||
|
|
GetSlot(other, ISO_DAY),
|
|||
|
|
GetSlot(other, ISO_HOUR),
|
|||
|
|
GetSlot(other, ISO_MINUTE),
|
|||
|
|
GetSlot(other, ISO_SECOND),
|
|||
|
|
GetSlot(other, ISO_MILLISECOND),
|
|||
|
|
GetSlot(other, ISO_MICROSECOND),
|
|||
|
|
GetSlot(other, ISO_NANOSECOND),
|
|||
|
|
calendar,
|
|||
|
|
settings.largestUnit,
|
|||
|
|
resolvedOptions
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
const relativeTo = TemporalDateTimeToDate(plainDateTime);
|
|||
|
|
({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = RoundDuration(
|
|||
|
|
years,
|
|||
|
|
months,
|
|||
|
|
weeks,
|
|||
|
|
days,
|
|||
|
|
hours,
|
|||
|
|
minutes,
|
|||
|
|
seconds,
|
|||
|
|
milliseconds,
|
|||
|
|
microseconds,
|
|||
|
|
nanoseconds,
|
|||
|
|
settings.roundingIncrement,
|
|||
|
|
settings.smallestUnit,
|
|||
|
|
settings.roundingMode,
|
|||
|
|
relativeTo
|
|||
|
|
));
|
|||
|
|
({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceDuration(
|
|||
|
|
days,
|
|||
|
|
hours,
|
|||
|
|
minutes,
|
|||
|
|
seconds,
|
|||
|
|
milliseconds,
|
|||
|
|
microseconds,
|
|||
|
|
nanoseconds,
|
|||
|
|
settings.largestUnit
|
|||
|
|
));
|
|||
|
|
|
|||
|
|
const Duration = GetIntrinsic('%Temporal.Duration%');
|
|||
|
|
return new Duration(
|
|||
|
|
sign * years,
|
|||
|
|
sign * months,
|
|||
|
|
sign * weeks,
|
|||
|
|
sign * days,
|
|||
|
|
sign * hours,
|
|||
|
|
sign * minutes,
|
|||
|
|
sign * seconds,
|
|||
|
|
sign * milliseconds,
|
|||
|
|
sign * microseconds,
|
|||
|
|
sign * nanoseconds
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function DifferenceTemporalPlainTime(
|
|||
|
|
operation: DifferenceOperation,
|
|||
|
|
plainTime: Temporal.PlainTime,
|
|||
|
|
otherParam: PlainTimeParams['until'][0],
|
|||
|
|
options: PlainTimeParams['until'][1]
|
|||
|
|
): Temporal.Duration {
|
|||
|
|
const sign = operation === 'since' ? -1 : 1;
|
|||
|
|
const other = ToTemporalTime(otherParam);
|
|||
|
|
|
|||
|
|
const resolvedOptions = CopyOptions(options);
|
|||
|
|
const settings = GetDifferenceSettings(operation, resolvedOptions, 'time', [], 'nanosecond', 'hour');
|
|||
|
|
|
|||
|
|
let { hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = DifferenceTime(
|
|||
|
|
GetSlot(plainTime, ISO_HOUR),
|
|||
|
|
GetSlot(plainTime, ISO_MINUTE),
|
|||
|
|
GetSlot(plainTime, ISO_SECOND),
|
|||
|
|
GetSlot(plainTime, ISO_MILLISECOND),
|
|||
|
|
GetSlot(plainTime, ISO_MICROSECOND),
|
|||
|
|
GetSlot(plainTime, ISO_NANOSECOND),
|
|||
|
|
GetSlot(other, ISO_HOUR),
|
|||
|
|
GetSlot(other, ISO_MINUTE),
|
|||
|
|
GetSlot(other, ISO_SECOND),
|
|||
|
|
GetSlot(other, ISO_MILLISECOND),
|
|||
|
|
GetSlot(other, ISO_MICROSECOND),
|
|||
|
|
GetSlot(other, ISO_NANOSECOND)
|
|||
|
|
);
|
|||
|
|
({ hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = RoundDuration(
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
hours,
|
|||
|
|
minutes,
|
|||
|
|
seconds,
|
|||
|
|
milliseconds,
|
|||
|
|
microseconds,
|
|||
|
|
nanoseconds,
|
|||
|
|
settings.roundingIncrement,
|
|||
|
|
settings.smallestUnit,
|
|||
|
|
settings.roundingMode
|
|||
|
|
));
|
|||
|
|
({ hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceDuration(
|
|||
|
|
0,
|
|||
|
|
hours,
|
|||
|
|
minutes,
|
|||
|
|
seconds,
|
|||
|
|
milliseconds,
|
|||
|
|
microseconds,
|
|||
|
|
nanoseconds,
|
|||
|
|
settings.largestUnit
|
|||
|
|
));
|
|||
|
|
const Duration = GetIntrinsic('%Temporal.Duration%');
|
|||
|
|
return new Duration(
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
sign * hours,
|
|||
|
|
sign * minutes,
|
|||
|
|
sign * seconds,
|
|||
|
|
sign * milliseconds,
|
|||
|
|
sign * microseconds,
|
|||
|
|
sign * nanoseconds
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function DifferenceTemporalPlainYearMonth(
|
|||
|
|
operation: DifferenceOperation,
|
|||
|
|
yearMonth: Temporal.PlainYearMonth,
|
|||
|
|
otherParam: PlainYearMonthParams['until'][0],
|
|||
|
|
options: PlainYearMonthParams['until'][1]
|
|||
|
|
): Temporal.Duration {
|
|||
|
|
const sign = operation === 'since' ? -1 : 1;
|
|||
|
|
const other = ToTemporalYearMonth(otherParam);
|
|||
|
|
const calendar = GetSlot(yearMonth, CALENDAR);
|
|||
|
|
const otherCalendar = GetSlot(other, CALENDAR);
|
|||
|
|
ThrowIfCalendarsNotEqual(calendar, otherCalendar, 'compute difference between months');
|
|||
|
|
|
|||
|
|
const resolvedOptions = CopyOptions(options);
|
|||
|
|
const settings = GetDifferenceSettings(operation, resolvedOptions, 'date', ['week', 'day'], 'month', 'year');
|
|||
|
|
resolvedOptions.largestUnit = settings.largestUnit;
|
|||
|
|
|
|||
|
|
const fieldNames = CalendarFields(calendar, ['monthCode', 'year']) as AnyTemporalKey[];
|
|||
|
|
const thisFields = PrepareTemporalFields(yearMonth, fieldNames, []);
|
|||
|
|
thisFields.day = 1;
|
|||
|
|
const thisDate = CalendarDateFromFields(calendar, thisFields);
|
|||
|
|
const otherFields = PrepareTemporalFields(other, fieldNames, []);
|
|||
|
|
otherFields.day = 1;
|
|||
|
|
const otherDate = CalendarDateFromFields(calendar, otherFields);
|
|||
|
|
|
|||
|
|
let { years, months } = CalendarDateUntil(calendar, thisDate, otherDate, resolvedOptions);
|
|||
|
|
|
|||
|
|
if (settings.smallestUnit !== 'month' || settings.roundingIncrement !== 1) {
|
|||
|
|
({ years, months } = RoundDuration(
|
|||
|
|
years,
|
|||
|
|
months,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
settings.roundingIncrement,
|
|||
|
|
settings.smallestUnit,
|
|||
|
|
settings.roundingMode,
|
|||
|
|
thisDate
|
|||
|
|
));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const Duration = GetIntrinsic('%Temporal.Duration%');
|
|||
|
|
return new Duration(sign * years, sign * months, 0, 0, 0, 0, 0, 0, 0, 0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function DifferenceTemporalZonedDateTime(
|
|||
|
|
operation: DifferenceOperation,
|
|||
|
|
zonedDateTime: Temporal.ZonedDateTime,
|
|||
|
|
otherParam: ZonedDateTimeParams['until'][0],
|
|||
|
|
options: ZonedDateTimeParams['until'][1]
|
|||
|
|
): Temporal.Duration {
|
|||
|
|
const sign = operation === 'since' ? -1 : 1;
|
|||
|
|
const other = ToTemporalZonedDateTime(otherParam);
|
|||
|
|
const calendar = GetSlot(zonedDateTime, CALENDAR);
|
|||
|
|
const otherCalendar = GetSlot(other, CALENDAR);
|
|||
|
|
ThrowIfCalendarsNotEqual(calendar, otherCalendar, 'compute difference between dates');
|
|||
|
|
|
|||
|
|
const resolvedOptions = CopyOptions(options);
|
|||
|
|
const settings = GetDifferenceSettings(operation, resolvedOptions, 'datetime', [], 'nanosecond', 'hour');
|
|||
|
|
resolvedOptions.largestUnit = settings.largestUnit;
|
|||
|
|
|
|||
|
|
const ns1 = GetSlot(zonedDateTime, EPOCHNANOSECONDS);
|
|||
|
|
const ns2 = GetSlot(other, EPOCHNANOSECONDS);
|
|||
|
|
let years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds;
|
|||
|
|
if (
|
|||
|
|
settings.largestUnit !== 'year' &&
|
|||
|
|
settings.largestUnit !== 'month' &&
|
|||
|
|
settings.largestUnit !== 'week' &&
|
|||
|
|
settings.largestUnit !== 'day'
|
|||
|
|
) {
|
|||
|
|
// The user is only asking for a time difference, so return difference of instants.
|
|||
|
|
years = 0;
|
|||
|
|
months = 0;
|
|||
|
|
weeks = 0;
|
|||
|
|
days = 0;
|
|||
|
|
({ hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = DifferenceInstant(
|
|||
|
|
ns1,
|
|||
|
|
ns2,
|
|||
|
|
settings.roundingIncrement,
|
|||
|
|
settings.smallestUnit as Temporal.TimeUnit,
|
|||
|
|
settings.largestUnit as Temporal.TimeUnit,
|
|||
|
|
settings.roundingMode
|
|||
|
|
));
|
|||
|
|
} else {
|
|||
|
|
const timeZone = GetSlot(zonedDateTime, TIME_ZONE);
|
|||
|
|
if (!TimeZoneEquals(timeZone, GetSlot(other, TIME_ZONE))) {
|
|||
|
|
throw new RangeError(
|
|||
|
|
"When calculating difference between time zones, largestUnit must be 'hours' " +
|
|||
|
|
'or smaller because day lengths can vary between time zones due to DST or time zone offset changes.'
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } =
|
|||
|
|
DifferenceZonedDateTime(ns1, ns2, timeZone, calendar, settings.largestUnit, resolvedOptions));
|
|||
|
|
({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = RoundDuration(
|
|||
|
|
years,
|
|||
|
|
months,
|
|||
|
|
weeks,
|
|||
|
|
days,
|
|||
|
|
hours,
|
|||
|
|
minutes,
|
|||
|
|
seconds,
|
|||
|
|
milliseconds,
|
|||
|
|
microseconds,
|
|||
|
|
nanoseconds,
|
|||
|
|
settings.roundingIncrement,
|
|||
|
|
settings.smallestUnit,
|
|||
|
|
settings.roundingMode,
|
|||
|
|
zonedDateTime
|
|||
|
|
));
|
|||
|
|
({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } =
|
|||
|
|
AdjustRoundedDurationDays(
|
|||
|
|
years,
|
|||
|
|
months,
|
|||
|
|
weeks,
|
|||
|
|
days,
|
|||
|
|
hours,
|
|||
|
|
minutes,
|
|||
|
|
seconds,
|
|||
|
|
milliseconds,
|
|||
|
|
microseconds,
|
|||
|
|
nanoseconds,
|
|||
|
|
settings.roundingIncrement,
|
|||
|
|
settings.smallestUnit,
|
|||
|
|
settings.roundingMode,
|
|||
|
|
zonedDateTime
|
|||
|
|
));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const Duration = GetIntrinsic('%Temporal.Duration%');
|
|||
|
|
return new Duration(
|
|||
|
|
sign * years,
|
|||
|
|
sign * months,
|
|||
|
|
sign * weeks,
|
|||
|
|
sign * days,
|
|||
|
|
sign * hours,
|
|||
|
|
sign * minutes,
|
|||
|
|
sign * seconds,
|
|||
|
|
sign * milliseconds,
|
|||
|
|
sign * microseconds,
|
|||
|
|
sign * nanoseconds
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function AddISODate(
|
|||
|
|
yearParam: number,
|
|||
|
|
monthParam: number,
|
|||
|
|
dayParam: number,
|
|||
|
|
yearsParam: number,
|
|||
|
|
monthsParam: number,
|
|||
|
|
weeksParam: number,
|
|||
|
|
daysParam: number,
|
|||
|
|
overflow: Temporal.ArithmeticOptions['overflow']
|
|||
|
|
) {
|
|||
|
|
let year = yearParam;
|
|||
|
|
let month = monthParam;
|
|||
|
|
let day = dayParam;
|
|||
|
|
let years = yearsParam;
|
|||
|
|
let months = monthsParam;
|
|||
|
|
let weeks = weeksParam;
|
|||
|
|
let days = daysParam;
|
|||
|
|
|
|||
|
|
year += years;
|
|||
|
|
month += months;
|
|||
|
|
({ year, month } = BalanceISOYearMonth(year, month));
|
|||
|
|
({ year, month, day } = RegulateISODate(year, month, day, overflow));
|
|||
|
|
days += 7 * weeks;
|
|||
|
|
day += days;
|
|||
|
|
({ year, month, day } = BalanceISODate(year, month, day));
|
|||
|
|
return { year, month, day };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function AddTime(
|
|||
|
|
hourParam: number,
|
|||
|
|
minuteParam: number,
|
|||
|
|
secondParam: number,
|
|||
|
|
millisecondParam: number,
|
|||
|
|
microsecondParam: number,
|
|||
|
|
nanosecondParam: number,
|
|||
|
|
hours: number,
|
|||
|
|
minutes: number,
|
|||
|
|
seconds: number,
|
|||
|
|
milliseconds: number,
|
|||
|
|
microseconds: number,
|
|||
|
|
nanoseconds: number
|
|||
|
|
) {
|
|||
|
|
let hour = hourParam;
|
|||
|
|
let minute = minuteParam;
|
|||
|
|
let second = secondParam;
|
|||
|
|
let millisecond = millisecondParam;
|
|||
|
|
let microsecond = microsecondParam;
|
|||
|
|
let nanosecond = nanosecondParam;
|
|||
|
|
|
|||
|
|
hour += hours;
|
|||
|
|
minute += minutes;
|
|||
|
|
second += seconds;
|
|||
|
|
millisecond += milliseconds;
|
|||
|
|
microsecond += microseconds;
|
|||
|
|
nanosecond += nanoseconds;
|
|||
|
|
let deltaDays = 0;
|
|||
|
|
({ deltaDays, hour, minute, second, millisecond, microsecond, nanosecond } = BalanceTime(
|
|||
|
|
hour,
|
|||
|
|
minute,
|
|||
|
|
second,
|
|||
|
|
millisecond,
|
|||
|
|
microsecond,
|
|||
|
|
nanosecond
|
|||
|
|
));
|
|||
|
|
return { deltaDays, hour, minute, second, millisecond, microsecond, nanosecond };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function AddDuration(
|
|||
|
|
y1: number,
|
|||
|
|
mon1: number,
|
|||
|
|
w1: number,
|
|||
|
|
d1: number,
|
|||
|
|
h1: number,
|
|||
|
|
min1: number,
|
|||
|
|
s1: number,
|
|||
|
|
ms1: number,
|
|||
|
|
µs1: number,
|
|||
|
|
ns1: number,
|
|||
|
|
y2: number,
|
|||
|
|
mon2: number,
|
|||
|
|
w2: number,
|
|||
|
|
d2: number,
|
|||
|
|
h2: number,
|
|||
|
|
min2: number,
|
|||
|
|
s2: number,
|
|||
|
|
ms2: number,
|
|||
|
|
µs2: number,
|
|||
|
|
ns2: number,
|
|||
|
|
relativeTo: ReturnType<typeof ToRelativeTemporalObject>
|
|||
|
|
) {
|
|||
|
|
const largestUnit1 = DefaultTemporalLargestUnit(y1, mon1, w1, d1, h1, min1, s1, ms1, µs1, ns1);
|
|||
|
|
const largestUnit2 = DefaultTemporalLargestUnit(y2, mon2, w2, d2, h2, min2, s2, ms2, µs2, ns2);
|
|||
|
|
const largestUnit = LargerOfTwoTemporalUnits(largestUnit1, largestUnit2);
|
|||
|
|
|
|||
|
|
let years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds;
|
|||
|
|
if (!relativeTo) {
|
|||
|
|
if (largestUnit === 'year' || largestUnit === 'month' || largestUnit === 'week') {
|
|||
|
|
throw new RangeError('relativeTo is required for years, months, or weeks arithmetic');
|
|||
|
|
}
|
|||
|
|
years = months = weeks = 0;
|
|||
|
|
({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceDuration(
|
|||
|
|
d1 + d2,
|
|||
|
|
JSBI.add(JSBI.BigInt(h1), JSBI.BigInt(h2)),
|
|||
|
|
JSBI.add(JSBI.BigInt(min1), JSBI.BigInt(min2)),
|
|||
|
|
JSBI.add(JSBI.BigInt(s1), JSBI.BigInt(s2)),
|
|||
|
|
JSBI.add(JSBI.BigInt(ms1), JSBI.BigInt(ms2)),
|
|||
|
|
JSBI.add(JSBI.BigInt(µs1), JSBI.BigInt(µs2)),
|
|||
|
|
JSBI.add(JSBI.BigInt(ns1), JSBI.BigInt(ns2)),
|
|||
|
|
largestUnit
|
|||
|
|
));
|
|||
|
|
} else if (IsTemporalDate(relativeTo)) {
|
|||
|
|
const TemporalDuration = GetIntrinsic('%Temporal.Duration%');
|
|||
|
|
const calendar = GetSlot(relativeTo, CALENDAR);
|
|||
|
|
|
|||
|
|
const dateDuration1 = new TemporalDuration(y1, mon1, w1, d1, 0, 0, 0, 0, 0, 0);
|
|||
|
|
const dateDuration2 = new TemporalDuration(y2, mon2, w2, d2, 0, 0, 0, 0, 0, 0);
|
|||
|
|
const dateAdd = typeof calendar !== 'string' ? GetMethod(calendar, 'dateAdd') : undefined;
|
|||
|
|
const intermediate = CalendarDateAdd(calendar, relativeTo, dateDuration1, undefined, dateAdd);
|
|||
|
|
const end = CalendarDateAdd(calendar, intermediate, dateDuration2, undefined, dateAdd);
|
|||
|
|
|
|||
|
|
const dateLargestUnit = LargerOfTwoTemporalUnits('day', largestUnit) as Temporal.DateUnit;
|
|||
|
|
const differenceOptions = ObjectCreate(null) as Temporal.DifferenceOptions<Temporal.DateUnit>;
|
|||
|
|
differenceOptions.largestUnit = dateLargestUnit;
|
|||
|
|
({ years, months, weeks, days } = CalendarDateUntil(calendar, relativeTo, end, differenceOptions));
|
|||
|
|
// Signs of date part and time part may not agree; balance them together
|
|||
|
|
({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceDuration(
|
|||
|
|
days,
|
|||
|
|
JSBI.add(JSBI.BigInt(h1), JSBI.BigInt(h2)),
|
|||
|
|
JSBI.add(JSBI.BigInt(min1), JSBI.BigInt(min2)),
|
|||
|
|
JSBI.add(JSBI.BigInt(s1), JSBI.BigInt(s2)),
|
|||
|
|
JSBI.add(JSBI.BigInt(ms1), JSBI.BigInt(ms2)),
|
|||
|
|
JSBI.add(JSBI.BigInt(µs1), JSBI.BigInt(µs2)),
|
|||
|
|
JSBI.add(JSBI.BigInt(ns1), JSBI.BigInt(ns2)),
|
|||
|
|
largestUnit
|
|||
|
|
));
|
|||
|
|
} else {
|
|||
|
|
// relativeTo is a ZonedDateTime
|
|||
|
|
const TemporalInstant = GetIntrinsic('%Temporal.Instant%');
|
|||
|
|
const timeZone = GetSlot(relativeTo, TIME_ZONE);
|
|||
|
|
const calendar = GetSlot(relativeTo, CALENDAR);
|
|||
|
|
const intermediateNs = AddZonedDateTime(
|
|||
|
|
GetSlot(relativeTo, INSTANT),
|
|||
|
|
timeZone,
|
|||
|
|
calendar,
|
|||
|
|
y1,
|
|||
|
|
mon1,
|
|||
|
|
w1,
|
|||
|
|
d1,
|
|||
|
|
h1,
|
|||
|
|
min1,
|
|||
|
|
s1,
|
|||
|
|
ms1,
|
|||
|
|
µs1,
|
|||
|
|
ns1
|
|||
|
|
);
|
|||
|
|
const endNs = AddZonedDateTime(
|
|||
|
|
new TemporalInstant(intermediateNs),
|
|||
|
|
timeZone,
|
|||
|
|
calendar,
|
|||
|
|
y2,
|
|||
|
|
mon2,
|
|||
|
|
w2,
|
|||
|
|
d2,
|
|||
|
|
h2,
|
|||
|
|
min2,
|
|||
|
|
s2,
|
|||
|
|
ms2,
|
|||
|
|
µs2,
|
|||
|
|
ns2
|
|||
|
|
);
|
|||
|
|
if (largestUnit !== 'year' && largestUnit !== 'month' && largestUnit !== 'week' && largestUnit !== 'day') {
|
|||
|
|
// The user is only asking for a time difference, so return difference of instants.
|
|||
|
|
years = 0;
|
|||
|
|
months = 0;
|
|||
|
|
weeks = 0;
|
|||
|
|
days = 0;
|
|||
|
|
({ hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = DifferenceInstant(
|
|||
|
|
GetSlot(relativeTo, EPOCHNANOSECONDS),
|
|||
|
|
endNs,
|
|||
|
|
1,
|
|||
|
|
'nanosecond',
|
|||
|
|
largestUnit,
|
|||
|
|
'halfExpand'
|
|||
|
|
));
|
|||
|
|
} else {
|
|||
|
|
({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } =
|
|||
|
|
DifferenceZonedDateTime(
|
|||
|
|
GetSlot(relativeTo, EPOCHNANOSECONDS),
|
|||
|
|
endNs,
|
|||
|
|
timeZone,
|
|||
|
|
calendar,
|
|||
|
|
largestUnit,
|
|||
|
|
ObjectCreate(null) as Temporal.DifferenceOptions<Temporal.DateTimeUnit>
|
|||
|
|
));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
RejectDuration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds);
|
|||
|
|
return { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function AddInstant(
|
|||
|
|
epochNanoseconds: JSBI,
|
|||
|
|
h: number | JSBI,
|
|||
|
|
min: number | JSBI,
|
|||
|
|
s: number | JSBI,
|
|||
|
|
ms: number | JSBI,
|
|||
|
|
µs: number | JSBI,
|
|||
|
|
ns: number | JSBI
|
|||
|
|
) {
|
|||
|
|
let sum = ZERO;
|
|||
|
|
sum = JSBI.add(sum, JSBI.BigInt(ns));
|
|||
|
|
sum = JSBI.add(sum, JSBI.multiply(JSBI.BigInt(µs), THOUSAND));
|
|||
|
|
sum = JSBI.add(sum, JSBI.multiply(JSBI.BigInt(ms), MILLION));
|
|||
|
|
sum = JSBI.add(sum, JSBI.multiply(JSBI.BigInt(s), BILLION));
|
|||
|
|
sum = JSBI.add(sum, JSBI.multiply(JSBI.BigInt(min), JSBI.BigInt(60 * 1e9)));
|
|||
|
|
sum = JSBI.add(sum, JSBI.multiply(JSBI.BigInt(h), JSBI.BigInt(60 * 60 * 1e9)));
|
|||
|
|
|
|||
|
|
const result = JSBI.add(epochNanoseconds, sum);
|
|||
|
|
ValidateEpochNanoseconds(result);
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function AddDateTime(
|
|||
|
|
year: number,
|
|||
|
|
month: number,
|
|||
|
|
day: number,
|
|||
|
|
hourParam: number,
|
|||
|
|
minuteParam: number,
|
|||
|
|
secondParam: number,
|
|||
|
|
millisecondParam: number,
|
|||
|
|
microsecondParam: number,
|
|||
|
|
nanosecondParam: number,
|
|||
|
|
calendar: CalendarSlot,
|
|||
|
|
years: number,
|
|||
|
|
months: number,
|
|||
|
|
weeks: number,
|
|||
|
|
daysParam: number,
|
|||
|
|
hours: number,
|
|||
|
|
minutes: number,
|
|||
|
|
seconds: number,
|
|||
|
|
milliseconds: number,
|
|||
|
|
microseconds: number,
|
|||
|
|
nanoseconds: number,
|
|||
|
|
options?: Temporal.ArithmeticOptions
|
|||
|
|
) {
|
|||
|
|
let days = daysParam;
|
|||
|
|
// Add the time part
|
|||
|
|
let { deltaDays, hour, minute, second, millisecond, microsecond, nanosecond } = AddTime(
|
|||
|
|
hourParam,
|
|||
|
|
minuteParam,
|
|||
|
|
secondParam,
|
|||
|
|
millisecondParam,
|
|||
|
|
microsecondParam,
|
|||
|
|
nanosecondParam,
|
|||
|
|
hours,
|
|||
|
|
minutes,
|
|||
|
|
seconds,
|
|||
|
|
milliseconds,
|
|||
|
|
microseconds,
|
|||
|
|
nanoseconds
|
|||
|
|
);
|
|||
|
|
days += deltaDays;
|
|||
|
|
|
|||
|
|
// Delegate the date part addition to the calendar
|
|||
|
|
const TemporalDuration = GetIntrinsic('%Temporal.Duration%');
|
|||
|
|
const datePart = CreateTemporalDate(year, month, day, calendar);
|
|||
|
|
const dateDuration = new TemporalDuration(years, months, weeks, days, 0, 0, 0, 0, 0, 0);
|
|||
|
|
const addedDate = CalendarDateAdd(calendar, datePart, dateDuration, options);
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
year: GetSlot(addedDate, ISO_YEAR),
|
|||
|
|
month: GetSlot(addedDate, ISO_MONTH),
|
|||
|
|
day: GetSlot(addedDate, ISO_DAY),
|
|||
|
|
hour,
|
|||
|
|
minute,
|
|||
|
|
second,
|
|||
|
|
millisecond,
|
|||
|
|
microsecond,
|
|||
|
|
nanosecond
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function AddZonedDateTime(
|
|||
|
|
instant: Temporal.Instant,
|
|||
|
|
timeZone: string | Temporal.TimeZoneProtocol,
|
|||
|
|
calendar: CalendarSlot,
|
|||
|
|
years: number,
|
|||
|
|
months: number,
|
|||
|
|
weeks: number,
|
|||
|
|
days: number,
|
|||
|
|
h: number | JSBI,
|
|||
|
|
min: number | JSBI,
|
|||
|
|
s: number | JSBI,
|
|||
|
|
ms: number | JSBI,
|
|||
|
|
µs: number | JSBI,
|
|||
|
|
ns: number | JSBI,
|
|||
|
|
options?: Temporal.ArithmeticOptions
|
|||
|
|
) {
|
|||
|
|
// If only time is to be added, then use Instant math. It's not OK to fall
|
|||
|
|
// through to the date/time code below because compatible disambiguation in
|
|||
|
|
// the PlainDateTime=>Instant conversion will change the offset of any
|
|||
|
|
// ZonedDateTime in the repeated clock time after a backwards transition.
|
|||
|
|
// When adding/subtracting time units and not dates, this disambiguation is
|
|||
|
|
// not expected and so is avoided below via a fast path for time-only
|
|||
|
|
// arithmetic.
|
|||
|
|
// BTW, this behavior is similar in spirit to offset: 'prefer' in `with`.
|
|||
|
|
const TemporalDuration = GetIntrinsic('%Temporal.Duration%');
|
|||
|
|
if (DurationSign(years, months, weeks, days, 0, 0, 0, 0, 0, 0) === 0) {
|
|||
|
|
return AddInstant(GetSlot(instant, EPOCHNANOSECONDS), h, min, s, ms, µs, ns);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// RFC 5545 requires the date portion to be added in calendar days and the
|
|||
|
|
// time portion to be added in exact time.
|
|||
|
|
const dt = GetPlainDateTimeFor(timeZone, instant, calendar);
|
|||
|
|
const datePart = CreateTemporalDate(GetSlot(dt, ISO_YEAR), GetSlot(dt, ISO_MONTH), GetSlot(dt, ISO_DAY), calendar);
|
|||
|
|
const dateDuration = new TemporalDuration(years, months, weeks, days, 0, 0, 0, 0, 0, 0);
|
|||
|
|
const addedDate = CalendarDateAdd(calendar, datePart, dateDuration, options);
|
|||
|
|
const dtIntermediate = CreateTemporalDateTime(
|
|||
|
|
GetSlot(addedDate, ISO_YEAR),
|
|||
|
|
GetSlot(addedDate, ISO_MONTH),
|
|||
|
|
GetSlot(addedDate, ISO_DAY),
|
|||
|
|
GetSlot(dt, ISO_HOUR),
|
|||
|
|
GetSlot(dt, ISO_MINUTE),
|
|||
|
|
GetSlot(dt, ISO_SECOND),
|
|||
|
|
GetSlot(dt, ISO_MILLISECOND),
|
|||
|
|
GetSlot(dt, ISO_MICROSECOND),
|
|||
|
|
GetSlot(dt, ISO_NANOSECOND),
|
|||
|
|
calendar
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// Note that 'compatible' is used below because this disambiguation behavior
|
|||
|
|
// is required by RFC 5545.
|
|||
|
|
const instantIntermediate = GetInstantFor(timeZone, dtIntermediate, 'compatible');
|
|||
|
|
return AddInstant(GetSlot(instantIntermediate, EPOCHNANOSECONDS), h, min, s, ms, µs, ns);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type AddSubtractOperation = 'add' | 'subtract';
|
|||
|
|
|
|||
|
|
export function AddDurationToOrSubtractDurationFromDuration(
|
|||
|
|
operation: AddSubtractOperation,
|
|||
|
|
duration: Temporal.Duration,
|
|||
|
|
other: DurationParams['add'][0],
|
|||
|
|
optionsParam: DurationParams['add'][1]
|
|||
|
|
): Temporal.Duration {
|
|||
|
|
const sign = operation === 'subtract' ? -1 : 1;
|
|||
|
|
let { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } =
|
|||
|
|
ToTemporalDurationRecord(other);
|
|||
|
|
const options = GetOptionsObject(optionsParam);
|
|||
|
|
const relativeTo = ToRelativeTemporalObject(options);
|
|||
|
|
({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = AddDuration(
|
|||
|
|
GetSlot(duration, YEARS),
|
|||
|
|
GetSlot(duration, MONTHS),
|
|||
|
|
GetSlot(duration, WEEKS),
|
|||
|
|
GetSlot(duration, DAYS),
|
|||
|
|
GetSlot(duration, HOURS),
|
|||
|
|
GetSlot(duration, MINUTES),
|
|||
|
|
GetSlot(duration, SECONDS),
|
|||
|
|
GetSlot(duration, MILLISECONDS),
|
|||
|
|
GetSlot(duration, MICROSECONDS),
|
|||
|
|
GetSlot(duration, NANOSECONDS),
|
|||
|
|
sign * years,
|
|||
|
|
sign * months,
|
|||
|
|
sign * weeks,
|
|||
|
|
sign * days,
|
|||
|
|
sign * hours,
|
|||
|
|
sign * minutes,
|
|||
|
|
sign * seconds,
|
|||
|
|
sign * milliseconds,
|
|||
|
|
sign * microseconds,
|
|||
|
|
sign * nanoseconds,
|
|||
|
|
relativeTo
|
|||
|
|
));
|
|||
|
|
const Duration = GetIntrinsic('%Temporal.Duration%');
|
|||
|
|
return new Duration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function AddDurationToOrSubtractDurationFromInstant(
|
|||
|
|
operation: AddSubtractOperation,
|
|||
|
|
instant: Temporal.Instant,
|
|||
|
|
durationLike: InstantParams['add'][0]
|
|||
|
|
): Temporal.Instant {
|
|||
|
|
const sign = operation === 'subtract' ? -1 : 1;
|
|||
|
|
const { hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ToLimitedTemporalDuration(durationLike, [
|
|||
|
|
'years',
|
|||
|
|
'months',
|
|||
|
|
'weeks',
|
|||
|
|
'days'
|
|||
|
|
]);
|
|||
|
|
const ns = AddInstant(
|
|||
|
|
GetSlot(instant, EPOCHNANOSECONDS),
|
|||
|
|
sign * hours,
|
|||
|
|
sign * minutes,
|
|||
|
|
sign * seconds,
|
|||
|
|
sign * milliseconds,
|
|||
|
|
sign * microseconds,
|
|||
|
|
sign * nanoseconds
|
|||
|
|
);
|
|||
|
|
const Instant = GetIntrinsic('%Temporal.Instant%');
|
|||
|
|
return new Instant(ns);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function AddDurationToOrSubtractDurationFromPlainDateTime(
|
|||
|
|
operation: AddSubtractOperation,
|
|||
|
|
dateTime: Temporal.PlainDateTime,
|
|||
|
|
durationLike: PlainDateTimeParams['add'][0],
|
|||
|
|
optionsParam: PlainDateTimeParams['add'][1]
|
|||
|
|
): Temporal.PlainDateTime {
|
|||
|
|
const sign = operation === 'subtract' ? -1 : 1;
|
|||
|
|
const { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } =
|
|||
|
|
ToTemporalDurationRecord(durationLike);
|
|||
|
|
const options = GetOptionsObject(optionsParam);
|
|||
|
|
const calendar = GetSlot(dateTime, CALENDAR);
|
|||
|
|
const { year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = AddDateTime(
|
|||
|
|
GetSlot(dateTime, ISO_YEAR),
|
|||
|
|
GetSlot(dateTime, ISO_MONTH),
|
|||
|
|
GetSlot(dateTime, ISO_DAY),
|
|||
|
|
GetSlot(dateTime, ISO_HOUR),
|
|||
|
|
GetSlot(dateTime, ISO_MINUTE),
|
|||
|
|
GetSlot(dateTime, ISO_SECOND),
|
|||
|
|
GetSlot(dateTime, ISO_MILLISECOND),
|
|||
|
|
GetSlot(dateTime, ISO_MICROSECOND),
|
|||
|
|
GetSlot(dateTime, ISO_NANOSECOND),
|
|||
|
|
calendar,
|
|||
|
|
sign * years,
|
|||
|
|
sign * months,
|
|||
|
|
sign * weeks,
|
|||
|
|
sign * days,
|
|||
|
|
sign * hours,
|
|||
|
|
sign * minutes,
|
|||
|
|
sign * seconds,
|
|||
|
|
sign * milliseconds,
|
|||
|
|
sign * microseconds,
|
|||
|
|
sign * nanoseconds,
|
|||
|
|
options
|
|||
|
|
);
|
|||
|
|
return CreateTemporalDateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, calendar);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function AddDurationToOrSubtractDurationFromPlainTime(
|
|||
|
|
operation: AddSubtractOperation,
|
|||
|
|
temporalTime: Temporal.PlainTime,
|
|||
|
|
durationLike: PlainTimeParams['add'][0]
|
|||
|
|
): Temporal.PlainTime {
|
|||
|
|
const sign = operation === 'subtract' ? -1 : 1;
|
|||
|
|
const { hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ToTemporalDurationRecord(durationLike);
|
|||
|
|
let { hour, minute, second, millisecond, microsecond, nanosecond } = AddTime(
|
|||
|
|
GetSlot(temporalTime, ISO_HOUR),
|
|||
|
|
GetSlot(temporalTime, ISO_MINUTE),
|
|||
|
|
GetSlot(temporalTime, ISO_SECOND),
|
|||
|
|
GetSlot(temporalTime, ISO_MILLISECOND),
|
|||
|
|
GetSlot(temporalTime, ISO_MICROSECOND),
|
|||
|
|
GetSlot(temporalTime, ISO_NANOSECOND),
|
|||
|
|
sign * hours,
|
|||
|
|
sign * minutes,
|
|||
|
|
sign * seconds,
|
|||
|
|
sign * milliseconds,
|
|||
|
|
sign * microseconds,
|
|||
|
|
sign * nanoseconds
|
|||
|
|
);
|
|||
|
|
({ hour, minute, second, millisecond, microsecond, nanosecond } = RegulateTime(
|
|||
|
|
hour,
|
|||
|
|
minute,
|
|||
|
|
second,
|
|||
|
|
millisecond,
|
|||
|
|
microsecond,
|
|||
|
|
nanosecond,
|
|||
|
|
'reject'
|
|||
|
|
));
|
|||
|
|
const PlainTime = GetIntrinsic('%Temporal.PlainTime%');
|
|||
|
|
return new PlainTime(hour, minute, second, millisecond, microsecond, nanosecond);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function AddDurationToOrSubtractDurationFromPlainYearMonth(
|
|||
|
|
operation: AddSubtractOperation,
|
|||
|
|
yearMonth: Temporal.PlainYearMonth,
|
|||
|
|
durationLike: PlainYearMonthParams['add'][0],
|
|||
|
|
optionsParam: PlainYearMonthParams['add'][1]
|
|||
|
|
): Temporal.PlainYearMonth {
|
|||
|
|
let duration = ToTemporalDurationRecord(durationLike);
|
|||
|
|
if (operation === 'subtract') {
|
|||
|
|
duration = {
|
|||
|
|
years: -duration.years,
|
|||
|
|
months: -duration.months,
|
|||
|
|
weeks: -duration.weeks,
|
|||
|
|
days: -duration.days,
|
|||
|
|
hours: -duration.hours,
|
|||
|
|
minutes: -duration.minutes,
|
|||
|
|
seconds: -duration.seconds,
|
|||
|
|
milliseconds: -duration.milliseconds,
|
|||
|
|
microseconds: -duration.microseconds,
|
|||
|
|
nanoseconds: -duration.nanoseconds
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
let { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = duration;
|
|||
|
|
({ days } = BalanceDuration(days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, 'day'));
|
|||
|
|
|
|||
|
|
const options = GetOptionsObject(optionsParam);
|
|||
|
|
|
|||
|
|
const calendar = GetSlot(yearMonth, CALENDAR);
|
|||
|
|
const fieldNames = CalendarFields(calendar, ['monthCode', 'year'] as const);
|
|||
|
|
const fields = PrepareTemporalFields(yearMonth, fieldNames, []);
|
|||
|
|
const fieldsCopy = ObjectCreate(null);
|
|||
|
|
CopyDataProperties(fieldsCopy, fields, []);
|
|||
|
|
fields.day = 1;
|
|||
|
|
// PrepareTemporalFields returns a type where 'day' is potentially undefined,
|
|||
|
|
// but TS doesn't narrow the type as a result of the assignment above.
|
|||
|
|
uncheckedAssertNarrowedType<typeof fields & { day: number }>(fields, '`day` is guaranteed to be non-undefined');
|
|||
|
|
let startDate = CalendarDateFromFields(calendar, fields);
|
|||
|
|
const sign = DurationSign(years, months, weeks, days, 0, 0, 0, 0, 0, 0);
|
|||
|
|
const dateAdd = GetMethod(calendar, 'dateAdd');
|
|||
|
|
const Duration = GetIntrinsic('%Temporal.Duration%');
|
|||
|
|
if (sign < 0) {
|
|||
|
|
const oneMonthDuration = new Duration(0, 1, 0, 0, 0, 0, 0, 0, 0, 0);
|
|||
|
|
const nextMonth = CalendarDateAdd(calendar, startDate, oneMonthDuration, undefined, dateAdd);
|
|||
|
|
const minusDayDuration = new Duration(0, 0, 0, -1, 0, 0, 0, 0, 0, 0);
|
|||
|
|
const endOfMonth = CalendarDateAdd(calendar, nextMonth, minusDayDuration, undefined, dateAdd);
|
|||
|
|
fieldsCopy.day = CalendarDay(calendar, endOfMonth);
|
|||
|
|
startDate = CalendarDateFromFields(calendar, fieldsCopy);
|
|||
|
|
}
|
|||
|
|
const durationToAdd = new Duration(years, months, weeks, days, 0, 0, 0, 0, 0, 0);
|
|||
|
|
const optionsCopy = CopyOptions(options);
|
|||
|
|
const addedDate = CalendarDateAdd(calendar, startDate, durationToAdd, options, dateAdd);
|
|||
|
|
const addedDateFields = PrepareTemporalFields(addedDate, fieldNames, []);
|
|||
|
|
|
|||
|
|
return CalendarYearMonthFromFields(calendar, addedDateFields, optionsCopy);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function AddDurationToOrSubtractDurationFromZonedDateTime(
|
|||
|
|
operation: AddSubtractOperation,
|
|||
|
|
zonedDateTime: Temporal.ZonedDateTime,
|
|||
|
|
durationLike: ZonedDateTimeParams['add'][0],
|
|||
|
|
optionsParam: ZonedDateTimeParams['add'][1]
|
|||
|
|
): Temporal.ZonedDateTime {
|
|||
|
|
const sign = operation === 'subtract' ? -1 : 1;
|
|||
|
|
const { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } =
|
|||
|
|
ToTemporalDurationRecord(durationLike);
|
|||
|
|
const options = GetOptionsObject(optionsParam);
|
|||
|
|
const timeZone = GetSlot(zonedDateTime, TIME_ZONE);
|
|||
|
|
const calendar = GetSlot(zonedDateTime, CALENDAR);
|
|||
|
|
const epochNanoseconds = AddZonedDateTime(
|
|||
|
|
GetSlot(zonedDateTime, INSTANT),
|
|||
|
|
timeZone,
|
|||
|
|
calendar,
|
|||
|
|
sign * years,
|
|||
|
|
sign * months,
|
|||
|
|
sign * weeks,
|
|||
|
|
sign * days,
|
|||
|
|
sign * hours,
|
|||
|
|
sign * minutes,
|
|||
|
|
sign * seconds,
|
|||
|
|
sign * milliseconds,
|
|||
|
|
sign * microseconds,
|
|||
|
|
sign * nanoseconds,
|
|||
|
|
options
|
|||
|
|
);
|
|||
|
|
return CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function RoundNumberToIncrement(quantity: JSBI, increment: JSBI, mode: Temporal.RoundingMode) {
|
|||
|
|
if (JSBI.equal(increment, ONE)) return quantity;
|
|||
|
|
let { quotient, remainder } = divmod(quantity, increment);
|
|||
|
|
if (JSBI.equal(remainder, ZERO)) return quantity;
|
|||
|
|
const sign = JSBI.lessThan(remainder, ZERO) ? -1 : 1;
|
|||
|
|
const tiebreaker = abs(JSBI.multiply(remainder, JSBI.BigInt(2)));
|
|||
|
|
const tie = JSBI.equal(tiebreaker, increment);
|
|||
|
|
const expandIsNearer = JSBI.greaterThan(tiebreaker, increment);
|
|||
|
|
switch (mode) {
|
|||
|
|
case 'ceil':
|
|||
|
|
if (sign > 0) quotient = JSBI.add(quotient, JSBI.BigInt(sign));
|
|||
|
|
break;
|
|||
|
|
case 'floor':
|
|||
|
|
if (sign < 0) quotient = JSBI.add(quotient, JSBI.BigInt(sign));
|
|||
|
|
break;
|
|||
|
|
case 'expand':
|
|||
|
|
// always expand if there is a remainder
|
|||
|
|
quotient = JSBI.add(quotient, JSBI.BigInt(sign));
|
|||
|
|
break;
|
|||
|
|
case 'trunc':
|
|||
|
|
// no change needed, because divmod is a truncation
|
|||
|
|
break;
|
|||
|
|
case 'halfCeil':
|
|||
|
|
if (expandIsNearer || (tie && sign > 0)) {
|
|||
|
|
quotient = JSBI.add(quotient, JSBI.BigInt(sign));
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
case 'halfFloor':
|
|||
|
|
if (expandIsNearer || (tie && sign < 0)) {
|
|||
|
|
quotient = JSBI.add(quotient, JSBI.BigInt(sign));
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
case 'halfExpand':
|
|||
|
|
// "half up away from zero"
|
|||
|
|
if (expandIsNearer || tie) {
|
|||
|
|
quotient = JSBI.add(quotient, JSBI.BigInt(sign));
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
case 'halfTrunc':
|
|||
|
|
if (expandIsNearer) {
|
|||
|
|
quotient = JSBI.add(quotient, JSBI.BigInt(sign));
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
case 'halfEven':
|
|||
|
|
if (expandIsNearer || (tie && JSBI.toNumber(JSBI.remainder(abs(quotient), JSBI.BigInt(2))) === 1)) {
|
|||
|
|
quotient = JSBI.add(quotient, JSBI.BigInt(sign));
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
return JSBI.multiply(quotient, increment);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function RoundInstant(
|
|||
|
|
epochNs: JSBI,
|
|||
|
|
increment: number,
|
|||
|
|
unit: keyof typeof nsPerTimeUnit,
|
|||
|
|
roundingMode: Temporal.RoundingMode
|
|||
|
|
) {
|
|||
|
|
let { remainder } = NonNegativeBigIntDivmod(epochNs, DAY_NANOS);
|
|||
|
|
const wholeDays = JSBI.subtract(epochNs, remainder);
|
|||
|
|
const roundedRemainder = RoundNumberToIncrement(
|
|||
|
|
remainder,
|
|||
|
|
JSBI.BigInt(nsPerTimeUnit[unit] * increment),
|
|||
|
|
roundingMode
|
|||
|
|
);
|
|||
|
|
return JSBI.add(wholeDays, roundedRemainder);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function RoundISODateTime(
|
|||
|
|
yearParam: number,
|
|||
|
|
monthParam: number,
|
|||
|
|
dayParam: number,
|
|||
|
|
hourParam: number,
|
|||
|
|
minuteParam: number,
|
|||
|
|
secondParam: number,
|
|||
|
|
millisecondParam: number,
|
|||
|
|
microsecondParam: number,
|
|||
|
|
nanosecondParam: number,
|
|||
|
|
increment: number,
|
|||
|
|
unit: UnitSmallerThanOrEqualTo<'day'>,
|
|||
|
|
roundingMode: Temporal.RoundingMode,
|
|||
|
|
dayLengthNs = 86400e9
|
|||
|
|
) {
|
|||
|
|
const { deltaDays, hour, minute, second, millisecond, microsecond, nanosecond } = RoundTime(
|
|||
|
|
hourParam,
|
|||
|
|
minuteParam,
|
|||
|
|
secondParam,
|
|||
|
|
millisecondParam,
|
|||
|
|
microsecondParam,
|
|||
|
|
nanosecondParam,
|
|||
|
|
increment,
|
|||
|
|
unit,
|
|||
|
|
roundingMode,
|
|||
|
|
dayLengthNs
|
|||
|
|
);
|
|||
|
|
const { year, month, day } = BalanceISODate(yearParam, monthParam, dayParam + deltaDays);
|
|||
|
|
return { year, month, day, hour, minute, second, millisecond, microsecond, nanosecond };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function RoundTime(
|
|||
|
|
hour: number,
|
|||
|
|
minute: number,
|
|||
|
|
second: number,
|
|||
|
|
millisecond: number,
|
|||
|
|
microsecond: number,
|
|||
|
|
nanosecond: number,
|
|||
|
|
increment: number,
|
|||
|
|
unit: keyof typeof nsPerTimeUnit | 'day',
|
|||
|
|
roundingMode: Temporal.RoundingMode,
|
|||
|
|
dayLengthNs = 86400e9
|
|||
|
|
) {
|
|||
|
|
let quantity = ZERO;
|
|||
|
|
switch (unit) {
|
|||
|
|
case 'day':
|
|||
|
|
case 'hour':
|
|||
|
|
quantity = JSBI.BigInt(hour);
|
|||
|
|
// fall through
|
|||
|
|
case 'minute':
|
|||
|
|
quantity = JSBI.add(JSBI.multiply(quantity, SIXTY), JSBI.BigInt(minute));
|
|||
|
|
// fall through
|
|||
|
|
case 'second':
|
|||
|
|
quantity = JSBI.add(JSBI.multiply(quantity, SIXTY), JSBI.BigInt(second));
|
|||
|
|
// fall through
|
|||
|
|
case 'millisecond':
|
|||
|
|
quantity = JSBI.add(JSBI.multiply(quantity, THOUSAND), JSBI.BigInt(millisecond));
|
|||
|
|
// fall through
|
|||
|
|
case 'microsecond':
|
|||
|
|
quantity = JSBI.add(JSBI.multiply(quantity, THOUSAND), JSBI.BigInt(microsecond));
|
|||
|
|
// fall through
|
|||
|
|
case 'nanosecond':
|
|||
|
|
quantity = JSBI.add(JSBI.multiply(quantity, THOUSAND), JSBI.BigInt(nanosecond));
|
|||
|
|
}
|
|||
|
|
const nsPerUnit = unit === 'day' ? dayLengthNs : nsPerTimeUnit[unit];
|
|||
|
|
const rounded = RoundNumberToIncrement(quantity, JSBI.BigInt(nsPerUnit * increment), roundingMode);
|
|||
|
|
const result = JSBI.toNumber(JSBI.divide(rounded, JSBI.BigInt(nsPerUnit)));
|
|||
|
|
switch (unit) {
|
|||
|
|
case 'day':
|
|||
|
|
return { deltaDays: result, hour: 0, minute: 0, second: 0, millisecond: 0, microsecond: 0, nanosecond: 0 };
|
|||
|
|
case 'hour':
|
|||
|
|
return BalanceTime(result, 0, 0, 0, 0, 0);
|
|||
|
|
case 'minute':
|
|||
|
|
return BalanceTime(hour, result, 0, 0, 0, 0);
|
|||
|
|
case 'second':
|
|||
|
|
return BalanceTime(hour, minute, result, 0, 0, 0);
|
|||
|
|
case 'millisecond':
|
|||
|
|
return BalanceTime(hour, minute, second, result, 0, 0);
|
|||
|
|
case 'microsecond':
|
|||
|
|
return BalanceTime(hour, minute, second, millisecond, result, 0);
|
|||
|
|
case 'nanosecond':
|
|||
|
|
return BalanceTime(hour, minute, second, millisecond, microsecond, result);
|
|||
|
|
default:
|
|||
|
|
throw new Error(`Invalid unit ${unit}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function DaysUntil(
|
|||
|
|
earlier: Temporal.PlainDate | Temporal.PlainDateTime | Temporal.ZonedDateTime,
|
|||
|
|
later: Temporal.PlainDate | Temporal.PlainDateTime | Temporal.ZonedDateTime
|
|||
|
|
) {
|
|||
|
|
return DifferenceISODate(
|
|||
|
|
GetSlot(earlier, ISO_YEAR),
|
|||
|
|
GetSlot(earlier, ISO_MONTH),
|
|||
|
|
GetSlot(earlier, ISO_DAY),
|
|||
|
|
GetSlot(later, ISO_YEAR),
|
|||
|
|
GetSlot(later, ISO_MONTH),
|
|||
|
|
GetSlot(later, ISO_DAY),
|
|||
|
|
'day'
|
|||
|
|
).days;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function MoveRelativeDate(
|
|||
|
|
calendar: CalendarSlot,
|
|||
|
|
relativeToParam: NonNullable<ReturnType<typeof ToRelativeTemporalObject>>,
|
|||
|
|
duration: Temporal.Duration,
|
|||
|
|
dateAdd: Temporal.CalendarProtocol['dateAdd'] | undefined
|
|||
|
|
) {
|
|||
|
|
const later = CalendarDateAdd(calendar, relativeToParam, duration, undefined, dateAdd);
|
|||
|
|
const days = DaysUntil(relativeToParam, later);
|
|||
|
|
return { relativeTo: later, days };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function MoveRelativeZonedDateTime(
|
|||
|
|
relativeTo: Temporal.ZonedDateTime,
|
|||
|
|
years: number,
|
|||
|
|
months: number,
|
|||
|
|
weeks: number,
|
|||
|
|
days: number
|
|||
|
|
) {
|
|||
|
|
const timeZone = GetSlot(relativeTo, TIME_ZONE);
|
|||
|
|
const calendar = GetSlot(relativeTo, CALENDAR);
|
|||
|
|
const intermediateNs = AddZonedDateTime(
|
|||
|
|
GetSlot(relativeTo, INSTANT),
|
|||
|
|
timeZone,
|
|||
|
|
calendar,
|
|||
|
|
years,
|
|||
|
|
months,
|
|||
|
|
weeks,
|
|||
|
|
days,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0
|
|||
|
|
);
|
|||
|
|
return CreateTemporalZonedDateTime(intermediateNs, timeZone, calendar);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function AdjustRoundedDurationDays(
|
|||
|
|
yearsParam: number,
|
|||
|
|
monthsParam: number,
|
|||
|
|
weeksParam: number,
|
|||
|
|
daysParam: number,
|
|||
|
|
hoursParam: number,
|
|||
|
|
minutesParam: number,
|
|||
|
|
secondsParam: number,
|
|||
|
|
millisecondsParam: number,
|
|||
|
|
microsecondsParam: number,
|
|||
|
|
nanosecondsParam: number,
|
|||
|
|
increment: number,
|
|||
|
|
unit: Temporal.DateTimeUnit,
|
|||
|
|
roundingMode: Temporal.RoundingMode,
|
|||
|
|
relativeTo: ReturnType<typeof ToRelativeTemporalObject>
|
|||
|
|
) {
|
|||
|
|
let years = yearsParam;
|
|||
|
|
let months = monthsParam;
|
|||
|
|
let weeks = weeksParam;
|
|||
|
|
let days = daysParam;
|
|||
|
|
let hours = hoursParam;
|
|||
|
|
let minutes = minutesParam;
|
|||
|
|
let seconds = secondsParam;
|
|||
|
|
let milliseconds = millisecondsParam;
|
|||
|
|
let microseconds = microsecondsParam;
|
|||
|
|
let nanoseconds = nanosecondsParam;
|
|||
|
|
if (
|
|||
|
|
!IsTemporalZonedDateTime(relativeTo) ||
|
|||
|
|
unit === 'year' ||
|
|||
|
|
unit === 'month' ||
|
|||
|
|
unit === 'week' ||
|
|||
|
|
unit === 'day' ||
|
|||
|
|
(unit === 'nanosecond' && increment === 1)
|
|||
|
|
) {
|
|||
|
|
return { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// There's one more round of rounding possible: if relativeTo is a
|
|||
|
|
// ZonedDateTime, the time units could have rounded up into enough hours
|
|||
|
|
// to exceed the day length. If this happens, grow the date part by a
|
|||
|
|
// single day and re-run exact time rounding on the smaller remainder. DO
|
|||
|
|
// NOT RECURSE, because once the extra hours are sucked up into the date
|
|||
|
|
// duration, there's no way for another full day to come from the next
|
|||
|
|
// round of rounding. And if it were possible (e.g. contrived calendar
|
|||
|
|
// with 30-minute-long "days") then it'd risk an infinite loop.
|
|||
|
|
let timeRemainderNs = TotalDurationNanoseconds(
|
|||
|
|
0,
|
|||
|
|
hours,
|
|||
|
|
minutes,
|
|||
|
|
seconds,
|
|||
|
|
milliseconds,
|
|||
|
|
microseconds,
|
|||
|
|
nanoseconds,
|
|||
|
|
0
|
|||
|
|
);
|
|||
|
|
const direction = MathSign(JSBI.toNumber(timeRemainderNs));
|
|||
|
|
|
|||
|
|
const timeZone = GetSlot(relativeTo, TIME_ZONE);
|
|||
|
|
const calendar = GetSlot(relativeTo, CALENDAR);
|
|||
|
|
const dayStart = AddZonedDateTime(
|
|||
|
|
GetSlot(relativeTo, INSTANT),
|
|||
|
|
timeZone,
|
|||
|
|
calendar,
|
|||
|
|
years,
|
|||
|
|
months,
|
|||
|
|
weeks,
|
|||
|
|
days,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0
|
|||
|
|
);
|
|||
|
|
const TemporalInstant = GetIntrinsic('%Temporal.Instant%');
|
|||
|
|
const dayEnd = AddZonedDateTime(
|
|||
|
|
new TemporalInstant(dayStart),
|
|||
|
|
timeZone,
|
|||
|
|
calendar,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
direction,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0
|
|||
|
|
);
|
|||
|
|
const dayLengthNs = JSBI.subtract(dayEnd, dayStart);
|
|||
|
|
|
|||
|
|
if (
|
|||
|
|
JSBI.greaterThanOrEqual(JSBI.multiply(JSBI.subtract(timeRemainderNs, dayLengthNs), JSBI.BigInt(direction)), ZERO)
|
|||
|
|
) {
|
|||
|
|
({ years, months, weeks, days } = AddDuration(
|
|||
|
|
years,
|
|||
|
|
months,
|
|||
|
|
weeks,
|
|||
|
|
days,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
direction,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
relativeTo
|
|||
|
|
));
|
|||
|
|
timeRemainderNs = RoundInstant(JSBI.subtract(timeRemainderNs, dayLengthNs), increment, unit, roundingMode);
|
|||
|
|
({ hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceDuration(
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
JSBI.toNumber(timeRemainderNs),
|
|||
|
|
'hour'
|
|||
|
|
));
|
|||
|
|
}
|
|||
|
|
return { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function RoundDuration(
|
|||
|
|
yearsParam: number,
|
|||
|
|
monthsParam: number,
|
|||
|
|
weeksParam: number,
|
|||
|
|
daysParam: number,
|
|||
|
|
hoursParam: number,
|
|||
|
|
minutesParam: number,
|
|||
|
|
secondsParam: number,
|
|||
|
|
millisecondsParam: number,
|
|||
|
|
microsecondsParam: number,
|
|||
|
|
nanosecondsParam: number,
|
|||
|
|
increment: number,
|
|||
|
|
unit: Temporal.DateTimeUnit,
|
|||
|
|
roundingMode: Temporal.RoundingMode,
|
|||
|
|
relativeToParam: ReturnType<typeof ToRelativeTemporalObject> = undefined
|
|||
|
|
) {
|
|||
|
|
let years = yearsParam;
|
|||
|
|
let months = monthsParam;
|
|||
|
|
let weeks = weeksParam;
|
|||
|
|
let days = daysParam;
|
|||
|
|
let hours = hoursParam;
|
|||
|
|
let minutes = minutesParam;
|
|||
|
|
let seconds = secondsParam;
|
|||
|
|
let milliseconds = millisecondsParam;
|
|||
|
|
let microseconds = microsecondsParam;
|
|||
|
|
let nanoseconds = JSBI.BigInt(nanosecondsParam);
|
|||
|
|
const TemporalDuration = GetIntrinsic('%Temporal.Duration%');
|
|||
|
|
let calendar, zdtRelative;
|
|||
|
|
// A cast is used below because relativeTo will be either PlainDate or
|
|||
|
|
// undefined for the rest of this long method (after any ZDT=>PlainDate
|
|||
|
|
// conversion below), and TS isn't smart enough to know that the type has
|
|||
|
|
// changed. See https://github.com/microsoft/TypeScript/issues/27706.
|
|||
|
|
let relativeTo = relativeToParam as Temporal.PlainDate | undefined;
|
|||
|
|
if (relativeTo) {
|
|||
|
|
if (IsTemporalZonedDateTime(relativeTo)) {
|
|||
|
|
zdtRelative = relativeTo;
|
|||
|
|
relativeTo = ToTemporalDate(relativeTo);
|
|||
|
|
} else if (!IsTemporalDate(relativeTo)) {
|
|||
|
|
throw new TypeError('starting point must be PlainDate or ZonedDateTime');
|
|||
|
|
}
|
|||
|
|
calendar = GetSlot(relativeTo, CALENDAR);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// First convert time units up to days, if rounding to days or higher units.
|
|||
|
|
// If rounding relative to a ZonedDateTime, then some days may not be 24h.
|
|||
|
|
// TS doesn't know that `dayLengthNs` is only used if the unit is day or
|
|||
|
|
// larger. We'll cast away `undefined` when it's used lower down below.
|
|||
|
|
let dayLengthNs: JSBI | undefined;
|
|||
|
|
if (unit === 'year' || unit === 'month' || unit === 'week' || unit === 'day') {
|
|||
|
|
nanoseconds = TotalDurationNanoseconds(0, hours, minutes, seconds, milliseconds, microseconds, nanosecondsParam, 0);
|
|||
|
|
let intermediate;
|
|||
|
|
if (zdtRelative) {
|
|||
|
|
intermediate = MoveRelativeZonedDateTime(zdtRelative, years, months, weeks, days);
|
|||
|
|
}
|
|||
|
|
let deltaDays;
|
|||
|
|
let dayLength: number;
|
|||
|
|
({ days: deltaDays, nanoseconds, dayLengthNs: dayLength } = NanosecondsToDays(nanoseconds, intermediate));
|
|||
|
|
dayLengthNs = JSBI.BigInt(dayLength);
|
|||
|
|
days += deltaDays;
|
|||
|
|
hours = minutes = seconds = milliseconds = microseconds = 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let total: number;
|
|||
|
|
switch (unit) {
|
|||
|
|
case 'year': {
|
|||
|
|
if (!calendar) throw new RangeError('A starting point is required for years rounding');
|
|||
|
|
assertExists(relativeTo);
|
|||
|
|
|
|||
|
|
// convert months and weeks to days by calculating difference(
|
|||
|
|
// relativeTo + years, relativeTo + { years, months, weeks })
|
|||
|
|
const yearsDuration = new TemporalDuration(years);
|
|||
|
|
const dateAdd = typeof calendar !== 'string' ? GetMethod(calendar, 'dateAdd') : undefined;
|
|||
|
|
const yearsLater = CalendarDateAdd(calendar, relativeTo, yearsDuration, undefined, dateAdd);
|
|||
|
|
const yearsMonthsWeeks = new TemporalDuration(years, months, weeks);
|
|||
|
|
const yearsMonthsWeeksLater = CalendarDateAdd(calendar, relativeTo, yearsMonthsWeeks, undefined, dateAdd);
|
|||
|
|
const monthsWeeksInDays = DaysUntil(yearsLater, yearsMonthsWeeksLater);
|
|||
|
|
relativeTo = yearsLater;
|
|||
|
|
days += monthsWeeksInDays;
|
|||
|
|
|
|||
|
|
const wholeDays = new TemporalDuration(0, 0, 0, days);
|
|||
|
|
const wholeDaysLater = CalendarDateAdd(calendar, relativeTo, wholeDays, undefined, dateAdd);
|
|||
|
|
const untilOptions = ObjectCreate(null) as Temporal.DifferenceOptions<typeof unit>;
|
|||
|
|
untilOptions.largestUnit = 'year';
|
|||
|
|
const yearsPassed = CalendarDateUntil(calendar, relativeTo, wholeDaysLater, untilOptions).years;
|
|||
|
|
years += yearsPassed;
|
|||
|
|
const oldRelativeTo = relativeTo;
|
|||
|
|
const yearsPassedDuration = new TemporalDuration(yearsPassed);
|
|||
|
|
relativeTo = CalendarDateAdd(calendar, relativeTo, yearsPassedDuration, undefined, dateAdd);
|
|||
|
|
const daysPassed = DaysUntil(oldRelativeTo, relativeTo);
|
|||
|
|
days -= daysPassed;
|
|||
|
|
const oneYear = new TemporalDuration(days < 0 ? -1 : 1);
|
|||
|
|
let { days: oneYearDays } = MoveRelativeDate(calendar, relativeTo, oneYear, dateAdd);
|
|||
|
|
|
|||
|
|
// Note that `nanoseconds` below (here and in similar code for months,
|
|||
|
|
// weeks, and days further below) isn't actually nanoseconds for the
|
|||
|
|
// full date range. Instead, it's a BigInt representation of total
|
|||
|
|
// days multiplied by the number of nanoseconds in the last day of
|
|||
|
|
// the duration. This lets us do days-or-larger rounding using BigInt
|
|||
|
|
// math which reduces precision loss.
|
|||
|
|
oneYearDays = MathAbs(oneYearDays);
|
|||
|
|
// dayLengthNs is never undefined if unit is `day` or larger.
|
|||
|
|
assertExists(dayLengthNs);
|
|||
|
|
const divisor = JSBI.multiply(JSBI.BigInt(oneYearDays), dayLengthNs);
|
|||
|
|
nanoseconds = JSBI.add(
|
|||
|
|
JSBI.add(JSBI.multiply(divisor, JSBI.BigInt(years)), JSBI.multiply(JSBI.BigInt(days), dayLengthNs)),
|
|||
|
|
nanoseconds
|
|||
|
|
);
|
|||
|
|
const rounded = RoundNumberToIncrement(nanoseconds, JSBI.multiply(divisor, JSBI.BigInt(increment)), roundingMode);
|
|||
|
|
total = BigIntDivideToNumber(nanoseconds, divisor);
|
|||
|
|
years = JSBI.toNumber(JSBI.divide(rounded, divisor));
|
|||
|
|
nanoseconds = ZERO;
|
|||
|
|
months = weeks = days = 0;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
case 'month': {
|
|||
|
|
if (!calendar) throw new RangeError('A starting point is required for months rounding');
|
|||
|
|
assertExists(relativeTo);
|
|||
|
|
|
|||
|
|
// convert weeks to days by calculating difference(relativeTo +
|
|||
|
|
// { years, months }, relativeTo + { years, months, weeks })
|
|||
|
|
const yearsMonths = new TemporalDuration(years, months);
|
|||
|
|
const dateAdd = typeof calendar !== 'string' ? GetMethod(calendar, 'dateAdd') : undefined;
|
|||
|
|
const yearsMonthsLater = CalendarDateAdd(calendar, relativeTo, yearsMonths, undefined, dateAdd);
|
|||
|
|
const yearsMonthsWeeks = new TemporalDuration(years, months, weeks);
|
|||
|
|
const yearsMonthsWeeksLater = CalendarDateAdd(calendar, relativeTo, yearsMonthsWeeks, undefined, dateAdd);
|
|||
|
|
const weeksInDays = DaysUntil(yearsMonthsLater, yearsMonthsWeeksLater);
|
|||
|
|
relativeTo = yearsMonthsLater;
|
|||
|
|
days += weeksInDays;
|
|||
|
|
|
|||
|
|
// Months may be different lengths of days depending on the calendar,
|
|||
|
|
// convert days to months in a loop as described above under 'years'.
|
|||
|
|
const sign = MathSign(days);
|
|||
|
|
const oneMonth = new TemporalDuration(0, days < 0 ? -1 : 1);
|
|||
|
|
let oneMonthDays: number;
|
|||
|
|
({ relativeTo, days: oneMonthDays } = MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd));
|
|||
|
|
while (MathAbs(days) >= MathAbs(oneMonthDays)) {
|
|||
|
|
months += sign;
|
|||
|
|
days -= oneMonthDays;
|
|||
|
|
({ relativeTo, days: oneMonthDays } = MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd));
|
|||
|
|
}
|
|||
|
|
oneMonthDays = MathAbs(oneMonthDays);
|
|||
|
|
// dayLengthNs is never undefined if unit is `day` or larger.
|
|||
|
|
assertExists(dayLengthNs);
|
|||
|
|
const divisor = JSBI.multiply(JSBI.BigInt(oneMonthDays), dayLengthNs);
|
|||
|
|
nanoseconds = JSBI.add(
|
|||
|
|
JSBI.add(JSBI.multiply(divisor, JSBI.BigInt(months)), JSBI.multiply(JSBI.BigInt(days), dayLengthNs)),
|
|||
|
|
nanoseconds
|
|||
|
|
);
|
|||
|
|
const rounded = RoundNumberToIncrement(nanoseconds, JSBI.multiply(divisor, JSBI.BigInt(increment)), roundingMode);
|
|||
|
|
total = BigIntDivideToNumber(nanoseconds, divisor);
|
|||
|
|
months = JSBI.toNumber(JSBI.divide(rounded, divisor));
|
|||
|
|
nanoseconds = ZERO;
|
|||
|
|
weeks = days = 0;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
case 'week': {
|
|||
|
|
if (!calendar) throw new RangeError('A starting point is required for weeks rounding');
|
|||
|
|
assertExists(relativeTo);
|
|||
|
|
|
|||
|
|
// Weeks may be different lengths of days depending on the calendar,
|
|||
|
|
// convert days to weeks in a loop as described above under 'years'.
|
|||
|
|
const sign = MathSign(days);
|
|||
|
|
const oneWeek = new TemporalDuration(0, 0, days < 0 ? -1 : 1);
|
|||
|
|
const dateAdd = typeof calendar !== 'string' ? GetMethod(calendar, 'dateAdd') : undefined;
|
|||
|
|
let oneWeekDays;
|
|||
|
|
({ relativeTo, days: oneWeekDays } = MoveRelativeDate(calendar, relativeTo, oneWeek, dateAdd));
|
|||
|
|
while (MathAbs(days) >= MathAbs(oneWeekDays)) {
|
|||
|
|
weeks += sign;
|
|||
|
|
days -= oneWeekDays;
|
|||
|
|
({ relativeTo, days: oneWeekDays } = MoveRelativeDate(calendar, relativeTo, oneWeek, dateAdd));
|
|||
|
|
}
|
|||
|
|
oneWeekDays = MathAbs(oneWeekDays);
|
|||
|
|
// dayLengthNs is never undefined if unit is `day` or larger.
|
|||
|
|
assertExists(dayLengthNs);
|
|||
|
|
const divisor = JSBI.multiply(JSBI.BigInt(oneWeekDays), dayLengthNs);
|
|||
|
|
nanoseconds = JSBI.add(
|
|||
|
|
JSBI.add(JSBI.multiply(divisor, JSBI.BigInt(weeks)), JSBI.multiply(JSBI.BigInt(days), dayLengthNs)),
|
|||
|
|
nanoseconds
|
|||
|
|
);
|
|||
|
|
const rounded = RoundNumberToIncrement(nanoseconds, JSBI.multiply(divisor, JSBI.BigInt(increment)), roundingMode);
|
|||
|
|
total = BigIntDivideToNumber(nanoseconds, divisor);
|
|||
|
|
weeks = JSBI.toNumber(JSBI.divide(rounded, divisor));
|
|||
|
|
nanoseconds = ZERO;
|
|||
|
|
days = 0;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
case 'day': {
|
|||
|
|
// dayLengthNs is never undefined if unit is `day` or larger.
|
|||
|
|
assertExists(dayLengthNs);
|
|||
|
|
const divisor = dayLengthNs;
|
|||
|
|
nanoseconds = JSBI.add(JSBI.multiply(divisor, JSBI.BigInt(days)), nanoseconds);
|
|||
|
|
const rounded = RoundNumberToIncrement(nanoseconds, JSBI.multiply(divisor, JSBI.BigInt(increment)), roundingMode);
|
|||
|
|
total = BigIntDivideToNumber(nanoseconds, divisor);
|
|||
|
|
days = JSBI.toNumber(JSBI.divide(rounded, divisor));
|
|||
|
|
nanoseconds = ZERO;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
case 'hour': {
|
|||
|
|
const divisor = 3600e9;
|
|||
|
|
let allNanoseconds = JSBI.multiply(JSBI.BigInt(hours), JSBI.BigInt(3600e9));
|
|||
|
|
allNanoseconds = JSBI.add(allNanoseconds, JSBI.multiply(JSBI.BigInt(minutes), JSBI.BigInt(60e9)));
|
|||
|
|
allNanoseconds = JSBI.add(allNanoseconds, JSBI.multiply(JSBI.BigInt(seconds), BILLION));
|
|||
|
|
allNanoseconds = JSBI.add(allNanoseconds, JSBI.multiply(JSBI.BigInt(milliseconds), MILLION));
|
|||
|
|
allNanoseconds = JSBI.add(allNanoseconds, JSBI.multiply(JSBI.BigInt(microseconds), THOUSAND));
|
|||
|
|
allNanoseconds = JSBI.add(allNanoseconds, nanoseconds);
|
|||
|
|
total = BigIntDivideToNumber(allNanoseconds, JSBI.BigInt(divisor));
|
|||
|
|
const rounded = RoundNumberToIncrement(allNanoseconds, JSBI.BigInt(divisor * increment), roundingMode);
|
|||
|
|
hours = JSBI.toNumber(JSBI.divide(rounded, JSBI.BigInt(divisor)));
|
|||
|
|
nanoseconds = ZERO;
|
|||
|
|
minutes = seconds = milliseconds = microseconds = 0;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
case 'minute': {
|
|||
|
|
const divisor = 60e9;
|
|||
|
|
let allNanoseconds = JSBI.multiply(JSBI.BigInt(minutes), JSBI.BigInt(60e9));
|
|||
|
|
allNanoseconds = JSBI.add(allNanoseconds, JSBI.multiply(JSBI.BigInt(seconds), BILLION));
|
|||
|
|
allNanoseconds = JSBI.add(allNanoseconds, JSBI.multiply(JSBI.BigInt(milliseconds), MILLION));
|
|||
|
|
allNanoseconds = JSBI.add(allNanoseconds, JSBI.multiply(JSBI.BigInt(microseconds), THOUSAND));
|
|||
|
|
allNanoseconds = JSBI.add(allNanoseconds, nanoseconds);
|
|||
|
|
total = BigIntDivideToNumber(allNanoseconds, JSBI.BigInt(divisor));
|
|||
|
|
const rounded = RoundNumberToIncrement(allNanoseconds, JSBI.BigInt(divisor * increment), roundingMode);
|
|||
|
|
minutes = JSBI.toNumber(JSBI.divide(rounded, JSBI.BigInt(divisor)));
|
|||
|
|
nanoseconds = ZERO;
|
|||
|
|
seconds = milliseconds = microseconds = 0;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
case 'second': {
|
|||
|
|
const divisor = 1e9;
|
|||
|
|
let allNanoseconds = JSBI.multiply(JSBI.BigInt(seconds), BILLION);
|
|||
|
|
allNanoseconds = JSBI.add(allNanoseconds, JSBI.multiply(JSBI.BigInt(milliseconds), MILLION));
|
|||
|
|
allNanoseconds = JSBI.add(allNanoseconds, JSBI.multiply(JSBI.BigInt(microseconds), THOUSAND));
|
|||
|
|
allNanoseconds = JSBI.add(allNanoseconds, nanoseconds);
|
|||
|
|
total = BigIntDivideToNumber(allNanoseconds, JSBI.BigInt(divisor));
|
|||
|
|
const rounded = RoundNumberToIncrement(allNanoseconds, JSBI.BigInt(divisor * increment), roundingMode);
|
|||
|
|
seconds = JSBI.toNumber(JSBI.divide(rounded, JSBI.BigInt(divisor)));
|
|||
|
|
nanoseconds = ZERO;
|
|||
|
|
milliseconds = microseconds = 0;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
case 'millisecond': {
|
|||
|
|
const divisor = 1e6;
|
|||
|
|
let allNanoseconds = JSBI.multiply(JSBI.BigInt(milliseconds), MILLION);
|
|||
|
|
allNanoseconds = JSBI.add(allNanoseconds, JSBI.multiply(JSBI.BigInt(microseconds), THOUSAND));
|
|||
|
|
allNanoseconds = JSBI.add(allNanoseconds, nanoseconds);
|
|||
|
|
total = BigIntDivideToNumber(allNanoseconds, JSBI.BigInt(divisor));
|
|||
|
|
const rounded = RoundNumberToIncrement(allNanoseconds, JSBI.BigInt(divisor * increment), roundingMode);
|
|||
|
|
milliseconds = JSBI.toNumber(JSBI.divide(rounded, JSBI.BigInt(divisor)));
|
|||
|
|
nanoseconds = ZERO;
|
|||
|
|
microseconds = 0;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
case 'microsecond': {
|
|||
|
|
const divisor = 1e3;
|
|||
|
|
let allNanoseconds = JSBI.multiply(JSBI.BigInt(microseconds), THOUSAND);
|
|||
|
|
allNanoseconds = JSBI.add(allNanoseconds, nanoseconds);
|
|||
|
|
total = BigIntDivideToNumber(allNanoseconds, JSBI.BigInt(divisor));
|
|||
|
|
const rounded = RoundNumberToIncrement(allNanoseconds, JSBI.BigInt(divisor * increment), roundingMode);
|
|||
|
|
microseconds = JSBI.toNumber(JSBI.divide(rounded, JSBI.BigInt(divisor)));
|
|||
|
|
nanoseconds = ZERO;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
case 'nanosecond': {
|
|||
|
|
total = JSBI.toNumber(nanoseconds);
|
|||
|
|
nanoseconds = RoundNumberToIncrement(JSBI.BigInt(nanoseconds), JSBI.BigInt(increment), roundingMode);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return {
|
|||
|
|
years,
|
|||
|
|
months,
|
|||
|
|
weeks,
|
|||
|
|
days,
|
|||
|
|
hours,
|
|||
|
|
minutes,
|
|||
|
|
seconds,
|
|||
|
|
milliseconds,
|
|||
|
|
microseconds,
|
|||
|
|
nanoseconds: JSBI.toNumber(nanoseconds),
|
|||
|
|
total
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CompareISODate(y1: number, m1: number, d1: number, y2: number, m2: number, d2: number) {
|
|||
|
|
for (const [x, y] of [
|
|||
|
|
[y1, y2],
|
|||
|
|
[m1, m2],
|
|||
|
|
[d1, d2]
|
|||
|
|
]) {
|
|||
|
|
if (x !== y) return ComparisonResult(x - y);
|
|||
|
|
}
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Not abstract operations from the spec
|
|||
|
|
|
|||
|
|
function NonNegativeBigIntDivmod(x: JSBI, y: JSBI) {
|
|||
|
|
let { quotient, remainder } = divmod(x, y);
|
|||
|
|
if (JSBI.lessThan(remainder, ZERO)) {
|
|||
|
|
quotient = JSBI.subtract(quotient, ONE);
|
|||
|
|
remainder = JSBI.add(remainder, y);
|
|||
|
|
}
|
|||
|
|
return { quotient, remainder };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function BigIntFloorDiv(left: JSBI, right: JSBI) {
|
|||
|
|
const { quotient, remainder } = divmod(left, right);
|
|||
|
|
if (!isZero(remainder) && !isNegativeJSBI(left) != !isNegativeJSBI(right)) {
|
|||
|
|
return JSBI.subtract(quotient, ONE);
|
|||
|
|
}
|
|||
|
|
return quotient;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** Divide two JSBIs, and return the result as a Number, including the remainder. */
|
|||
|
|
export function BigIntDivideToNumber(dividend: JSBI, divisor: JSBI) {
|
|||
|
|
const { quotient, remainder } = divmod(dividend, divisor);
|
|||
|
|
const result = JSBI.toNumber(quotient) + JSBI.toNumber(remainder) / JSBI.toNumber(divisor);
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Defaults to native bigint, or something "native bigint-like".
|
|||
|
|
// For users of Temporal that are running in environments without native BigInt,
|
|||
|
|
// the only guarantee we should give is that the returned object's toString will
|
|||
|
|
// return a string containing an accurate base 10 value of this bigint. This
|
|||
|
|
// form factor should correctly interop with other bigint compat libraries
|
|||
|
|
// easily.
|
|||
|
|
type ExternalBigInt = bigint;
|
|||
|
|
|
|||
|
|
export function ToBigIntExternal(arg: unknown): ExternalBigInt {
|
|||
|
|
const jsbiBI = ToBigInt(arg);
|
|||
|
|
if (typeof (globalThis as any).BigInt !== 'undefined') return (globalThis as any).BigInt(jsbiBI.toString(10));
|
|||
|
|
return jsbiBI as unknown as ExternalBigInt;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function ToBigInt(arg: unknown): JSBI {
|
|||
|
|
let prim = arg;
|
|||
|
|
if (typeof arg === 'object') {
|
|||
|
|
const toPrimFn = (arg as { [Symbol.toPrimitive]: unknown })[Symbol.toPrimitive];
|
|||
|
|
if (toPrimFn && typeof toPrimFn === 'function') {
|
|||
|
|
prim = ReflectApply(toPrimFn, arg, ['number']);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// The AO ToBigInt throws on numbers because it does not allow implicit
|
|||
|
|
// conversion between number and bigint (unlike the bigint constructor).
|
|||
|
|
if (typeof prim === 'number') {
|
|||
|
|
throw new TypeError('cannot convert number to bigint');
|
|||
|
|
}
|
|||
|
|
if (typeof prim === 'bigint') {
|
|||
|
|
// JSBI doesn't know anything about the bigint type, and intentionally
|
|||
|
|
// assumes it doesn't exist. Passing one to the BigInt function will throw
|
|||
|
|
// an error.
|
|||
|
|
return JSBI.BigInt(prim.toString(10));
|
|||
|
|
}
|
|||
|
|
// JSBI will properly coerce types into a BigInt the same as the native BigInt
|
|||
|
|
// constructor will, with the exception of native bigint which is handled
|
|||
|
|
// above.
|
|||
|
|
// As of 2023-04-07, the only runtime type that neither of those can handle is
|
|||
|
|
// 'symbol', and both native bigint and the JSBI.BigInt function will throw an
|
|||
|
|
// error if they are given a Symbol.
|
|||
|
|
return JSBI.BigInt(prim as string | boolean | object);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Note: This method returns values with bogus nanoseconds based on the previous iteration's
|
|||
|
|
// milliseconds. That way there is a guarantee that the full nanoseconds are always going to be
|
|||
|
|
// increasing at least and that the microsecond and nanosecond fields are likely to be non-zero.
|
|||
|
|
export const SystemUTCEpochNanoSeconds: () => JSBI = (() => {
|
|||
|
|
let ns = JSBI.BigInt(Date.now() % 1e6);
|
|||
|
|
return () => {
|
|||
|
|
const ms = JSBI.BigInt(Date.now());
|
|||
|
|
const result = JSBI.add(JSBI.multiply(ms, MILLION), ns);
|
|||
|
|
ns = JSBI.remainder(ms, MILLION);
|
|||
|
|
if (JSBI.greaterThan(result, NS_MAX)) return NS_MAX;
|
|||
|
|
if (JSBI.lessThan(result, NS_MIN)) return NS_MIN;
|
|||
|
|
return result;
|
|||
|
|
};
|
|||
|
|
})();
|
|||
|
|
|
|||
|
|
export function DefaultTimeZone() {
|
|||
|
|
return new IntlDateTimeFormat().resolvedOptions().timeZone;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function ComparisonResult(value: number) {
|
|||
|
|
return value < 0 ? -1 : value > 0 ? 1 : (value as 0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function GetOptionsObject<T>(options: T) {
|
|||
|
|
if (options === undefined) return ObjectCreate(null) as NonNullable<T>;
|
|||
|
|
if (IsObject(options) && options !== null) return options;
|
|||
|
|
throw new TypeError(`Options parameter must be an object, not ${options === null ? 'null' : `${typeof options}`}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CreateOnePropObject<K extends string, V>(propName: K, propValue: V): { [k in K]: V } {
|
|||
|
|
const o = ObjectCreate(null);
|
|||
|
|
o[propName] = propValue;
|
|||
|
|
return o;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function CopyOptions<T extends { [s in K]?: unknown }, K extends string & keyof T>(options: T | undefined) {
|
|||
|
|
const optionsCopy = ObjectCreate(null) as T;
|
|||
|
|
CopyDataProperties(optionsCopy, GetOptionsObject(options), []);
|
|||
|
|
return optionsCopy;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type StringlyTypedKeys<T> = Exclude<keyof T, symbol | number>;
|
|||
|
|
function GetOption<P extends StringlyTypedKeys<O>, O extends Partial<Record<P, unknown>>>(
|
|||
|
|
options: O,
|
|||
|
|
property: P,
|
|||
|
|
allowedValues: ReadonlyArray<O[P]>,
|
|||
|
|
fallback: undefined
|
|||
|
|
): O[P];
|
|||
|
|
function GetOption<
|
|||
|
|
P extends StringlyTypedKeys<O>,
|
|||
|
|
O extends Partial<Record<P, unknown>>,
|
|||
|
|
Fallback extends Required<O>[P] | undefined
|
|||
|
|
>(
|
|||
|
|
options: O,
|
|||
|
|
property: P,
|
|||
|
|
allowedValues: ReadonlyArray<O[P]>,
|
|||
|
|
fallback: Fallback
|
|||
|
|
): Fallback extends undefined ? O[P] | undefined : Required<O>[P];
|
|||
|
|
function GetOption<
|
|||
|
|
P extends StringlyTypedKeys<O>,
|
|||
|
|
O extends Partial<Record<P, unknown>>,
|
|||
|
|
Fallback extends Required<O>[P] | undefined
|
|||
|
|
>(
|
|||
|
|
options: O,
|
|||
|
|
property: P,
|
|||
|
|
allowedValues: ReadonlyArray<O[P]>,
|
|||
|
|
fallback: O[P]
|
|||
|
|
): Fallback extends undefined ? O[P] | undefined : Required<O>[P] {
|
|||
|
|
let value = options[property];
|
|||
|
|
if (value !== undefined) {
|
|||
|
|
value = ToString(value) as O[P];
|
|||
|
|
if (!allowedValues.includes(value)) {
|
|||
|
|
throw new RangeError(`${property} must be one of ${allowedValues.join(', ')}, not ${value}`);
|
|||
|
|
}
|
|||
|
|
return value;
|
|||
|
|
}
|
|||
|
|
return fallback;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function IsBuiltinCalendar(id: string): id is BuiltinCalendarId {
|
|||
|
|
return BUILTIN_CALENDAR_IDS.includes(ASCIILowercase(id));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function ASCIILowercase<T extends string>(str: T): T {
|
|||
|
|
// The spec defines this operation distinct from String.prototype.lowercase,
|
|||
|
|
// so we'll follow the spec here. Note that nasty security issues that can
|
|||
|
|
// happen for some use cases if you're comparing case-modified non-ASCII
|
|||
|
|
// values. For example, Turkish's "I" character was the source of a security
|
|||
|
|
// issue involving "file://" URLs. See
|
|||
|
|
// https://haacked.com/archive/2012/07/05/turkish-i-problem-and-why-you-should-care.aspx/.
|
|||
|
|
return str.replace(/[A-Z]/g, (l) => {
|
|||
|
|
const code = l.charCodeAt(0);
|
|||
|
|
return String.fromCharCode(code + 0x20);
|
|||
|
|
}) as T;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const OFFSET = new RegExp(`^${PARSE.offset.source}$`);
|
|||
|
|
|
|||
|
|
function bisect(
|
|||
|
|
getState: (epochNs: JSBI) => number,
|
|||
|
|
leftParam: JSBI,
|
|||
|
|
rightParam: JSBI,
|
|||
|
|
lstateParam: number = getState(leftParam),
|
|||
|
|
rstateParam: number = getState(rightParam)
|
|||
|
|
) {
|
|||
|
|
// This doesn't make much sense - why do these get converted unnecessarily?
|
|||
|
|
let left = JSBI.BigInt(leftParam);
|
|||
|
|
let right = JSBI.BigInt(rightParam);
|
|||
|
|
let lstate = lstateParam;
|
|||
|
|
let rstate = rstateParam;
|
|||
|
|
while (JSBI.greaterThan(JSBI.subtract(right, left), ONE)) {
|
|||
|
|
const middle = JSBI.divide(JSBI.add(left, right), JSBI.BigInt(2));
|
|||
|
|
const mstate = getState(middle);
|
|||
|
|
if (mstate === lstate) {
|
|||
|
|
left = middle;
|
|||
|
|
lstate = mstate;
|
|||
|
|
} else if (mstate === rstate) {
|
|||
|
|
right = middle;
|
|||
|
|
rstate = mstate;
|
|||
|
|
} else {
|
|||
|
|
throw new Error(`invalid state in bisection ${lstate} - ${mstate} - ${rstate}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return right;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const nsPerTimeUnit = {
|
|||
|
|
hour: 3600e9,
|
|||
|
|
minute: 60e9,
|
|||
|
|
second: 1e9,
|
|||
|
|
millisecond: 1e6,
|
|||
|
|
microsecond: 1e3,
|
|||
|
|
nanosecond: 1
|
|||
|
|
};
|