Files
ask262/engine262/src/abstract-ops/temporal/calendar.mts
T

725 lines
28 KiB
TypeScript

import { CanonicalizeUValue } from '../../ecma402/not-implemented.mts';
import { __ts_cast__, isArray, type Mutable } from '../../helpers.mts';
import { ParseMonthCode, ParseTemporalCalendarString } from '../../parser/TemporalParser.mts';
import { isTemporalPlainDateTimeObject } from '../../intrinsics/Temporal/PlainDateTime.mts';
import { isTemporalPlainMonthDayObject } from '../../intrinsics/Temporal/PlainMonthDay.mts';
import { isTemporalZonedDateTimeObject } from '../../intrinsics/Temporal/ZonedDateTime.mts';
import { isTemporalPlainDateObject, type ISODateRecord } from '../../intrinsics/Temporal/PlainDate.mts';
import { isTemporalPlainYearMonthObject } from '../../intrinsics/Temporal/PlainYearMonth.mts';
import { ToZeroPaddedDecimalString } from './addition.mts';
import type { YearWeekRecord } from './addition.mts';
import {
EpochDaysToEpochMs,
EpochTimeForYear,
EpochTimeToDayInYear,
EpochTimeToWeekDay,
ISODateToEpochDays,
MathematicalDaysInYear,
MathematicalInLeapYear,
TemporalUnit,
ToIntegerWithTruncation, ToOffsetString, ToPositiveIntegerWithTruncation, type DateUnit,
} from './temporal.mts';
import { ToTemporalTimeZoneIdentifier } from './time-zone.mts';
import { mark_OtherCalendarNotImplemented, unreachable_OtherCalendarNotImplemented } from './not-implemented.mts';
import {
AddDaysToISODate,
Assert,
BalanceISOYearMonth,
CompareISODate,
CreateDateDurationRecord,
CreateISODateRecord,
F,
Get,
ISODateSurpasses,
ISODateWithinLimits,
JSStringValue,
NumberValue,
ObjectValue,
Q,
R,
RegulateISODate,
Throw,
ToString,
Value,
X,
ZeroDateDuration,
type DateDurationRecord,
type PlainCompletion, type PlainEvaluator,
} from '#self';
/** https://tc39.es/proposal-temporal/#sec-calendar-types */
export type CalendarType = 'iso8601';
/** https://tc39.es/proposal-temporal/#sec-temporal-canonicalizecalendar */
export function CanonicalizeCalendar(id: string): PlainCompletion<CalendarType> {
const calendars = AvailableCalendars();
if (!calendars.includes(id.toLowerCase() as CalendarType)) {
return Throw.RangeError('$1 is not a supported calendar', id);
}
return CanonicalizeUValue('ca', id) as CalendarType;
}
/** https://tc39.es/proposal-temporal/#sec-temporal-availablecalendars */
export function AvailableCalendars(): CalendarType[] {
mark_OtherCalendarNotImplemented();
return ['iso8601'];
}
export type MonthCode = string & { __brand: 'MonthCode' };
/** https://tc39.es/proposal-temporal/#sec-temporal-createmonthcode */
export function CreateMonthCode(monthNumber: number, isLeapMonth: boolean): MonthCode {
if (!isLeapMonth) Assert(monthNumber > 0);
const numberPart = ToZeroPaddedDecimalString(monthNumber, 2);
if (isLeapMonth) {
return `M${numberPart}L` as MonthCode;
}
return `M${numberPart}` as MonthCode;
}
/** https://tc39.es/proposal-temporal/#sec-temporal-calendar-date-records */
export interface CalendarDateRecord {
readonly Era: string | undefined;
readonly EraYear: number | undefined;
readonly Year: number;
readonly Month: number;
readonly MonthCode: string;
readonly Day: number;
readonly DayOfWeek: number;
readonly DayOfYear: number;
readonly WeekOfYear: YearWeekRecord;
readonly DaysInWeek: number;
readonly DaysInMonth: number;
readonly DaysInYear: number;
readonly MonthsInYear: number;
readonly InLeapYear: boolean;
}
/** https://tc39.es/proposal-temporal/#table-temporal-calendar-fields-record-fields */
export interface CalendarFieldsRecord {
readonly Era: string | undefined;
readonly EraYear: number | undefined;
Year: number | undefined;
Month: number | undefined;
MonthCode: string | undefined;
Day: number | undefined;
Hour: number | undefined;
Minute: number | undefined;
Second: number | undefined;
Millisecond: number | undefined;
Microsecond: number | undefined;
Nanosecond: number | undefined;
OffsetString: string | undefined;
readonly TimeZone: string | undefined;
}
export enum Table19_Conversion {
ToString = 'to-string',
ToIntegerWithTruncation = 'to-integer-with-truncation',
ToPositiveIntegerWithTruncation = 'to-positive-integer-with-truncation',
ToTemporalTimeZoneIdentifier = 'to-temporal-time-zone-identifier',
ToMonthCode = 'to-month-code',
ToOffsetString = 'to-offset-string',
}
export type CalendarFieldsRecordEnumerationKey = 'era' | 'era-year' | 'year' | 'month' | 'month-code' | 'day' | 'hour' | 'minute' | 'second' | 'millisecond' | 'microsecond' | 'nanosecond' | 'offset' | 'time-zone';
export const Table19_CalendarFieldsRecordFields = [
/* eslint-disable object-curly-newline */
{ FieldName: 'Era', DefaultValue: undefined, PropertyKey: 'era', EnumerationKey: 'era', Conversion: Table19_Conversion.ToString },
{ FieldName: 'EraYear', DefaultValue: undefined, PropertyKey: 'eraYear', EnumerationKey: 'era-year', Conversion: Table19_Conversion.ToIntegerWithTruncation },
{ FieldName: 'Year', DefaultValue: undefined, PropertyKey: 'year', EnumerationKey: 'year', Conversion: Table19_Conversion.ToIntegerWithTruncation },
{ FieldName: 'Month', DefaultValue: undefined, PropertyKey: 'month', EnumerationKey: 'month', Conversion: Table19_Conversion.ToPositiveIntegerWithTruncation },
{ FieldName: 'MonthCode', DefaultValue: undefined, PropertyKey: 'monthCode', EnumerationKey: 'month-code', Conversion: Table19_Conversion.ToMonthCode },
{ FieldName: 'Day', DefaultValue: undefined, PropertyKey: 'day', EnumerationKey: 'day', Conversion: Table19_Conversion.ToPositiveIntegerWithTruncation },
{ FieldName: 'Hour', DefaultValue: 0, PropertyKey: 'hour', EnumerationKey: 'hour', Conversion: Table19_Conversion.ToIntegerWithTruncation },
{ FieldName: 'Minute', DefaultValue: 0, PropertyKey: 'minute', EnumerationKey: 'minute', Conversion: Table19_Conversion.ToIntegerWithTruncation },
{ FieldName: 'Second', DefaultValue: 0, PropertyKey: 'second', EnumerationKey: 'second', Conversion: Table19_Conversion.ToIntegerWithTruncation },
{ FieldName: 'Millisecond', DefaultValue: 0, PropertyKey: 'millisecond', EnumerationKey: 'millisecond', Conversion: Table19_Conversion.ToIntegerWithTruncation },
{ FieldName: 'Microsecond', DefaultValue: 0, PropertyKey: 'microsecond', EnumerationKey: 'microsecond', Conversion: Table19_Conversion.ToIntegerWithTruncation },
{ FieldName: 'Nanosecond', DefaultValue: 0, PropertyKey: 'nanosecond', EnumerationKey: 'nanosecond', Conversion: Table19_Conversion.ToIntegerWithTruncation },
{ FieldName: 'OffsetString', DefaultValue: undefined, PropertyKey: 'offsetString', EnumerationKey: 'offset', Conversion: Table19_Conversion.ToOffsetString },
{ FieldName: 'TimeZone', DefaultValue: undefined, PropertyKey: 'timeZone', EnumerationKey: 'time-zone', Conversion: Table19_Conversion.ToTemporalTimeZoneIdentifier },
/* eslint-enable object-curly-newline */
] as const satisfies {
FieldName: keyof CalendarFieldsRecord;
DefaultValue: string | number | undefined;
PropertyKey: string;
EnumerationKey: CalendarFieldsRecordEnumerationKey;
Conversion: Table19_Conversion;
}[];
/** https://tc39.es/proposal-temporal/#sec-temporal-preparecalendarfields */
export function* PrepareCalendarFields(
calendar: CalendarType,
fields: ObjectValue,
calendarFieldNames: readonly CalendarFieldsRecordEnumerationKey[],
nonCalendarFieldNames: readonly CalendarFieldsRecordEnumerationKey[],
requiredFieldNames: 'partial' | readonly CalendarFieldsRecordEnumerationKey[],
): PlainEvaluator<CalendarFieldsRecord> {
// Assert: If requiredFieldNames is a List, requiredFieldNames contains zero or one of each of the elements of calendarFieldNames and nonCalendarFieldNames.
if (isArray(requiredFieldNames)) {
Assert(calendarFieldNames.every((name) => requiredFieldNames.filter((requiredName) => name === requiredName).length <= 1));
Assert(nonCalendarFieldNames.every((name) => requiredFieldNames.filter((requiredName) => name === requiredName).length <= 1));
}
let fieldNames: CalendarFieldsRecordEnumerationKey[] = [...calendarFieldNames, ...nonCalendarFieldNames];
const extraFieldNames = CalendarExtraFields(calendar, calendarFieldNames);
fieldNames = [...fieldNames, ...extraFieldNames];
// Assert: fieldNames contains no duplicate elements.
Assert(fieldNames.length === new Set(fieldNames).size);
const result: Mutable<CalendarFieldsRecord> = {
Era: undefined,
EraYear: undefined,
Year: undefined,
Month: undefined,
MonthCode: undefined,
Day: undefined,
Hour: undefined,
Minute: undefined,
Second: undefined,
Millisecond: undefined,
Microsecond: undefined,
Nanosecond: undefined,
OffsetString: undefined,
TimeZone: undefined,
};
let any = false;
// Let sortedPropertyNames be a List whose elements are the values in the Property Key column of Table 19 corresponding to the elements of fieldNames, sorted according to lexicographic code unit order.
const sortedPropertyNames = [...Table19_CalendarFieldsRecordFields].sort((a, b) => (a.PropertyKey < b.PropertyKey ? -1 : 1));
for (const {
FieldName, PropertyKey, Conversion, DefaultValue, EnumerationKey,
} of sortedPropertyNames) {
__ts_cast__<keyof CalendarFieldsRecord>(FieldName);
// Let key be the value in the Enumeration Key column of Table 19 corresponding to the row whose Property Key value is property.
const key = EnumerationKey;
let value = Q(yield* Get(fields, Value(PropertyKey)));
if (value !== Value.undefined) {
any = true;
if (Conversion === Table19_Conversion.ToIntegerWithTruncation) {
value = F(Q(yield* ToIntegerWithTruncation(value)));
} else if (Conversion === Table19_Conversion.ToPositiveIntegerWithTruncation) {
value = F(Q(yield* ToPositiveIntegerWithTruncation(value)));
} else if (Conversion === Table19_Conversion.ToString) {
value = Q(yield* ToString(value));
} else if (Conversion === Table19_Conversion.ToTemporalTimeZoneIdentifier) {
value = Value(Q(ToTemporalTimeZoneIdentifier(value)));
} else if (Conversion === Table19_Conversion.ToMonthCode) {
const parsed = Q(yield* ParseMonthCode(value));
value = Value(CreateMonthCode(parsed.MonthNumber, parsed.IsLeapMonth));
} else {
Assert(Conversion === Table19_Conversion.ToOffsetString);
value = Value(Q(yield* ToOffsetString(value)));
}
let assignValue;
if (value instanceof NumberValue) {
assignValue = R(value);
} else if (value instanceof JSStringValue) {
assignValue = value.stringValue();
}
if (assignValue === undefined) {
throw new Error('invalid type');
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
result[FieldName] = assignValue as any;
} else if (isArray(requiredFieldNames)) {
if (requiredFieldNames.includes(key)) {
return Throw.TypeError('$1 is a required on object $2', key, fields);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
result[FieldName] = DefaultValue as any;
}
}
if (requiredFieldNames === 'partial' && !any) {
return Throw.TypeError('$1 is not a TemporalTimeLike object', fields);
}
return result;
}
/** https://tc39.es/proposal-temporal/#sec-temporal-calendarfieldkeyspresent */
export function CalendarFieldKeysPresent(fields: CalendarFieldsRecord): CalendarFieldsRecordEnumerationKey[] {
const list: CalendarFieldsRecordEnumerationKey[] = [];
for (const { FieldName, EnumerationKey } of Table19_CalendarFieldsRecordFields) {
const value = fields[FieldName];
const enumerationKey = EnumerationKey;
if (value !== undefined) {
list.push(enumerationKey);
}
}
return list;
}
/** https://tc39.es/proposal-temporal/#sec-temporal-calendarmergefields */
export function CalendarMergeFields(calendar: CalendarType, fields: CalendarFieldsRecord, additionalFields: CalendarFieldsRecord): CalendarFieldsRecord {
const additionalKeys = CalendarFieldKeysPresent(additionalFields);
const overriddenKeys = CalendarFieldKeysToIgnore(calendar, additionalKeys);
const merged: Mutable<CalendarFieldsRecord> = {
Era: undefined,
EraYear: undefined,
Year: undefined,
Month: undefined,
MonthCode: undefined,
Day: undefined,
Hour: undefined,
Minute: undefined,
Second: undefined,
Millisecond: undefined,
Microsecond: undefined,
Nanosecond: undefined,
OffsetString: undefined,
TimeZone: undefined,
};
const fieldsKeys = CalendarFieldKeysPresent(fields);
for (const { EnumerationKey, FieldName } of Table19_CalendarFieldsRecordFields) {
const key = EnumerationKey;
if (fieldsKeys.includes(key) && !overriddenKeys.includes(key)) {
const propValue = fields[FieldName];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
merged[FieldName] = propValue as any;
}
if (additionalKeys.includes(key)) {
const propValue = additionalFields[FieldName];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
merged[FieldName] = propValue as any;
}
}
return merged;
}
/** https://tc39.es/proposal-temporal/#sec-temporal-nonisodateadd */
export function NonISODateAdd(
_calendar: CalendarType,
_isoDate: ISODateRecord,
_duration: DateDurationRecord,
_overflow: 'constrain' | 'reject',
): never {
mark_OtherCalendarNotImplemented();
unreachable_OtherCalendarNotImplemented();
}
/** https://tc39.es/proposal-temporal/#sec-temporal-calendardateadd */
export function CalendarDateAdd(
calendar: CalendarType,
isoDate: ISODateRecord,
duration: DateDurationRecord,
overflow: 'constrain' | 'reject',
): PlainCompletion<ISODateRecord> {
let result: ISODateRecord;
if (calendar === 'iso8601') {
const intermediate = Q(BalanceISOYearMonth(isoDate.Year + duration.Years, isoDate.Month + duration.Months));
const regulated = Q(RegulateISODate(intermediate.Year, intermediate.Month, isoDate.Day, overflow));
const days = regulated.Day + duration.Days + 7 * duration.Weeks;
result = Q(AddDaysToISODate(regulated, days));
} else {
result = Q(NonISODateAdd(calendar, isoDate, duration, overflow));
}
if (!ISODateWithinLimits(result)) {
return Throw.RangeError('Resulting ISODate is out of range');
}
return result;
}
/** https://tc39.es/proposal-temporal/#sec-temporal-nonisodateuntil */
export function NonISODateUntil(
_calendar: CalendarType,
_one: ISODateRecord,
_two: ISODateRecord,
_largestUnit: DateUnit,
): never {
mark_OtherCalendarNotImplemented();
unreachable_OtherCalendarNotImplemented();
}
/** https://tc39.es/proposal-temporal/#sec-temporal-calendardateuntil */
export function CalendarDateUntil(
calendar: CalendarType,
one: ISODateRecord,
two: ISODateRecord,
largestUnit: DateUnit,
): DateDurationRecord {
if (calendar === 'iso8601') {
const sign = -CompareISODate(one, two) as 1 | -1 | 0;
if (sign === 0) {
return ZeroDateDuration();
}
let years = 0;
if (largestUnit === TemporalUnit.Year) {
let candidateYears = sign;
while (!ISODateSurpasses(sign, one, two, candidateYears, 0, 0, 0)) {
years = candidateYears;
candidateYears += sign;
}
}
let months = 0;
if (largestUnit === TemporalUnit.Month) {
let candidateMonths = sign;
while (!ISODateSurpasses(sign, one, two, years, candidateMonths, 0, 0)) {
months = candidateMonths;
candidateMonths += sign;
}
}
let weeks = 0;
if (largestUnit === TemporalUnit.Week) {
let candidateWeeks = sign;
while (!ISODateSurpasses(sign, one, two, years, months, candidateWeeks, 0)) {
weeks = candidateWeeks;
candidateWeeks += sign;
}
}
let days = 0;
let candidateDays = sign;
while (!ISODateSurpasses(sign, one, two, years, months, weeks, candidateDays)) {
days = candidateDays;
candidateDays += sign;
}
return X(CreateDateDurationRecord(years, months, weeks, days));
}
return NonISODateUntil(calendar, one, two, largestUnit);
}
/** https://tc39.es/proposal-temporal/#sec-temporal-totemporalcalendaridentifier */
export function ToTemporalCalendarIdentifier(temporalCalendarLike: Value): PlainCompletion<CalendarType> {
if (temporalCalendarLike instanceof ObjectValue) {
if (
isTemporalPlainDateObject(temporalCalendarLike)
|| isTemporalPlainDateTimeObject(temporalCalendarLike)
|| isTemporalPlainMonthDayObject(temporalCalendarLike)
|| isTemporalPlainYearMonthObject(temporalCalendarLike)
|| isTemporalZonedDateTimeObject(temporalCalendarLike)) {
return temporalCalendarLike.Calendar;
}
}
if (!(temporalCalendarLike instanceof JSStringValue)) {
return Throw.TypeError('temporalCalendarLike must be a string or a Temporal object, but got $1', temporalCalendarLike);
}
const identifier = Q(ParseTemporalCalendarString(temporalCalendarLike.stringValue()));
return Q(CanonicalizeCalendar(identifier));
}
/** https://tc39.es/proposal-temporal/#sec-temporal-gettemporalcalendaridentifierwithisodefault */
export function* GetTemporalCalendarIdentifierWithISODefault(item: ObjectValue): PlainEvaluator<CalendarType> {
if (isTemporalPlainDateObject(item)
|| isTemporalPlainDateTimeObject(item)
|| isTemporalPlainMonthDayObject(item)
|| isTemporalPlainYearMonthObject(item)
|| isTemporalZonedDateTimeObject(item)) {
return item.Calendar;
}
const calendarLike = Q(yield* Get(item, Value('calendar')));
if (calendarLike === Value.undefined) {
return 'iso8601';
}
return Q(ToTemporalCalendarIdentifier(calendarLike));
}
/** https://tc39.es/proposal-temporal/#sec-temporal-calendardatefromfields */
export function* CalendarDateFromFields(
calendar: CalendarType,
fields: CalendarFieldsRecord,
overflow: 'constrain' | 'reject',
): PlainEvaluator<ISODateRecord> {
Q(yield* CalendarResolveFields(calendar, fields, 'date'));
const result = Q(CalendarDateToISO(calendar, fields, overflow));
if (!ISODateWithinLimits(result)) {
return Throw.RangeError('Resulting ISODate is out of range');
}
return result;
}
/** https://tc39.es/proposal-temporal/#sec-temporal-calendaryearmonthfromfields */
export function* CalendarYearMonthFromFields(
calendar: CalendarType,
fields: CalendarFieldsRecord,
overflow: 'constrain' | 'reject',
): PlainEvaluator<ISODateRecord> {
Q(yield* CalendarResolveFields(calendar, fields, 'year-month'));
// Let firstDayIndex be the 1-based index of the first day of the month described by fields (i.e., 1 unless the month's first day is skipped by this calendar.)
const firstDayIndex = 1;
fields.Day = firstDayIndex;
const result = Q(CalendarDateToISO(calendar, fields, overflow));
if (!ISODateWithinLimits(result)) {
return Throw.RangeError('Resulting ISODate is out of range');
}
return result;
}
/** https://tc39.es/proposal-temporal/#sec-temporal-calendarmonthdayfromfields */
export function* CalendarMonthDayFromFields(
calendar: CalendarType,
fields: CalendarFieldsRecord,
overflow: 'constrain' | 'reject',
): PlainEvaluator<ISODateRecord> {
Q(yield* CalendarResolveFields(calendar, fields, 'month-day'));
const result = Q(CalendarMonthDayToISOReferenceDate(calendar, fields, overflow));
if (!ISODateWithinLimits(result)) {
return Throw.RangeError('Resulting ISODate is out of range');
}
return result;
}
/** https://tc39.es/proposal-temporal/#sec-temporal-formatcalendarannotation */
export function FormatCalendarAnnotation(
id: CalendarType,
showCalendar: 'auto' | 'always' | 'never' | 'critical',
): string {
if (showCalendar === 'never') {
return '';
}
if (showCalendar === 'auto' && id === 'iso8601') {
return '';
}
const flag = showCalendar === 'critical' ? '!' : '';
return `[${flag}u-ca=${id}]`;
}
/** https://tc39.es/proposal-temporal/#sec-temporal-calendarequals */
export function CalendarEquals(one: CalendarType, two: CalendarType): boolean {
if (CanonicalizeUValue('ca', one) === CanonicalizeUValue('ca', two)) {
return true;
}
return false;
}
/** https://tc39.es/proposal-temporal/#sec-temporal-isodaysinmonth */
export function ISODaysInMonth(year: number, month: number): number {
if (month === 1 || month === 3 || month === 5 || month === 7 || month === 8 || month === 10 || month === 12) {
return 31;
}
if (month === 4 || month === 6 || month === 9 || month === 11) {
return 30;
}
Assert(month === 2);
return 28 + MathematicalInLeapYear(EpochTimeForYear(year));
}
/** https://tc39.es/proposal-temporal/#sec-temporal-isoweekofyear */
export function ISOWeekOfYear(isoDate: ISODateRecord): YearWeekRecord {
const year = isoDate.Year;
const wednesday = 3;
const thursday = 4;
const friday = 5;
const saturday = 6;
const daysInWeek = 7;
const maxWeekNumber = 53;
const dayOfYear = ISODayOfYear(isoDate);
const dayOfWeek = ISODayOfWeek(isoDate);
const week = Math.floor((dayOfYear + daysInWeek - dayOfWeek + wednesday) / daysInWeek);
if (week < 1) {
// NOTE: This is the last week of the previous year.
const jan1st = CreateISODateRecord(year, 1, 1);
const dayOfJan1st = ISODayOfWeek(jan1st);
if (dayOfJan1st === friday) {
return { Week: maxWeekNumber, Year: year - 1 };
}
if (dayOfJan1st === saturday && MathematicalInLeapYear(EpochTimeForYear(year - 1)) === 1) {
return { Week: maxWeekNumber, Year: year - 1 };
}
return { Week: maxWeekNumber - 1, Year: year - 1 };
}
if (week === maxWeekNumber) {
const daysInYear = MathematicalDaysInYear(year);
const daysLaterInYear = daysInYear - dayOfYear;
const daysAfterThursday = thursday - dayOfWeek;
if (daysLaterInYear < daysAfterThursday) {
return { Week: 1, Year: year + 1 };
}
}
return { Week: week, Year: year };
}
/** https://tc39.es/proposal-temporal/#sec-temporal-isodayofyear */
export function ISODayOfYear(isoDate: ISODateRecord): number {
const epochDays = ISODateToEpochDays(isoDate.Year, isoDate.Month - 1, isoDate.Day);
return EpochTimeToDayInYear(EpochDaysToEpochMs(epochDays, 0)) + 1;
}
/** https://tc39.es/proposal-temporal/#sec-temporal-isodayofweek */
export function ISODayOfWeek(isoDate: ISODateRecord): number {
const epochDays = ISODateToEpochDays(isoDate.Year, isoDate.Month - 1, isoDate.Day);
const dayOfWeek = EpochTimeToWeekDay(EpochDaysToEpochMs(epochDays, 0));
if (dayOfWeek === 0) {
return 7;
}
return dayOfWeek;
}
/** https://tc39.es/proposal-temporal/#sec-temporal-nonisocalendardatetoiso */
export function NonISOCalendarDateToISO(
_calendar: CalendarType,
_fields: CalendarFieldsRecord,
_overflow: 'constrain' | 'reject',
): PlainCompletion<ISODateRecord> {
mark_OtherCalendarNotImplemented();
unreachable_OtherCalendarNotImplemented();
}
/** https://tc39.es/proposal-temporal/#sec-temporal-calendardatetoiso */
export function CalendarDateToISO(
calendar: CalendarType,
fields: CalendarFieldsRecord,
overflow: 'constrain' | 'reject',
): PlainCompletion<ISODateRecord> {
if (calendar === 'iso8601') {
Assert(fields.Year !== undefined && fields.Month !== undefined && fields.Day !== undefined);
return Q(RegulateISODate(fields.Year, fields.Month, fields.Day, overflow));
}
return Q(NonISOCalendarDateToISO(calendar, fields, overflow));
}
/** https://tc39.es/proposal-temporal/#sec-temporal-nonisomonthdaytoisoreferencedate */
export function NonISOMonthDayToISOReferenceDate(
_calendar: CalendarType,
_fields: CalendarFieldsRecord,
_overflow: 'constrain' | 'reject',
): never {
mark_OtherCalendarNotImplemented();
unreachable_OtherCalendarNotImplemented();
}
/** https://tc39.es/proposal-temporal/#sec-temporal-calendarmonthdaytoisoreferencedate */
export function CalendarMonthDayToISOReferenceDate(
calendar: CalendarType,
fields: CalendarFieldsRecord,
overflow: 'constrain' | 'reject',
): PlainCompletion<ISODateRecord> {
if (calendar === 'iso8601') {
Assert(fields.Month !== undefined && fields.Day !== undefined);
const referenceISOYear = 1972;
const year = fields.Year === undefined ? referenceISOYear : fields.Year;
const result = Q(RegulateISODate(year, fields.Month, fields.Day, overflow));
return CreateISODateRecord(referenceISOYear, result.Month, result.Day);
}
return Q(NonISOMonthDayToISOReferenceDate(calendar, fields, overflow));
}
// NonISOCalendarISOToDate
/** https://tc39.es/proposal-temporal/#sec-temporal-nonisocalendarisotodate */
export function NonISOCalendarISOToDate(
_calendar: CalendarType,
_isoDate: ISODateRecord,
): CalendarDateRecord {
mark_OtherCalendarNotImplemented();
unreachable_OtherCalendarNotImplemented();
}
/** https://tc39.es/proposal-temporal/#sec-temporal-calendarisotodate */
export function CalendarISOToDate(
calendar: CalendarType,
isoDate: ISODateRecord,
): CalendarDateRecord {
if (calendar === 'iso8601') {
const inLeapYear = MathematicalInLeapYear(EpochTimeForYear(isoDate.Year)) === 1;
return {
Era: undefined,
EraYear: undefined,
Year: isoDate.Year,
Month: isoDate.Month,
MonthCode: CreateMonthCode(isoDate.Month, false),
Day: isoDate.Day,
DayOfWeek: ISODayOfWeek(isoDate),
DayOfYear: ISODayOfYear(isoDate),
WeekOfYear: ISOWeekOfYear(isoDate),
DaysInWeek: 7,
DaysInMonth: ISODaysInMonth(isoDate.Year, isoDate.Month),
DaysInYear: MathematicalDaysInYear(isoDate.Year),
MonthsInYear: 12,
InLeapYear: inLeapYear,
};
}
return NonISOCalendarISOToDate(calendar, isoDate);
}
/** https://tc39.es/proposal-temporal/#sec-temporal-calendarextrafields */
export function CalendarExtraFields(
calendar: CalendarType,
_fields: readonly CalendarFieldsRecordEnumerationKey[],
): CalendarFieldsRecordEnumerationKey[] {
if (calendar === 'iso8601') {
return [];
}
mark_OtherCalendarNotImplemented();
unreachable_OtherCalendarNotImplemented();
}
/** https://tc39.es/proposal-temporal/#sec-temporal-nonisofieldkeystoignore */
export function NonISOFieldKeysToIgnore(
_calendar: CalendarType,
_keys: readonly CalendarFieldsRecordEnumerationKey[],
): CalendarFieldsRecordEnumerationKey[] {
mark_OtherCalendarNotImplemented();
unreachable_OtherCalendarNotImplemented();
}
/** https://tc39.es/proposal-temporal/#sec-temporal-calendarfieldkeystoignore */
export function CalendarFieldKeysToIgnore(
calendar: CalendarType,
keys: readonly CalendarFieldsRecordEnumerationKey[],
): CalendarFieldsRecordEnumerationKey[] {
if (calendar === 'iso8601') {
const ignoredKeys: CalendarFieldsRecordEnumerationKey[] = [];
for (const key of keys) {
ignoredKeys.push(key);
if (key === 'month') {
ignoredKeys.push('month-code');
} else if (key === 'month-code') {
ignoredKeys.push('month');
}
}
return ignoredKeys;
}
return NonISOFieldKeysToIgnore(calendar, keys);
}
/** https://tc39.es/proposal-temporal/#sec-temporal-nonisoresolvefields */
export function NonISOResolveFields(
_calendar: CalendarType,
_fields: CalendarFieldsRecord,
_type: 'date' | 'year-month' | 'month-day',
): CalendarFieldsRecord {
mark_OtherCalendarNotImplemented();
unreachable_OtherCalendarNotImplemented();
}
/** https://tc39.es/proposal-temporal/#sec-temporal-calendarresolvefields */
export function* CalendarResolveFields(
calendar: CalendarType,
fields: CalendarFieldsRecord,
type: 'date' | 'year-month' | 'month-day',
): PlainEvaluator<void> {
if (calendar === 'iso8601') {
if ((type === 'date' || type === 'year-month') && fields.Year === undefined) {
return Throw.TypeError('"year" is required');
}
if ((type === 'date' || type === 'month-day') && fields.Day === undefined) {
return Throw.TypeError('"day" is required');
}
const month = fields.Month;
const monthCode = fields.MonthCode;
if (monthCode === undefined) {
if (month === undefined) {
return Throw.TypeError('"month-code" or "month" is required');
}
}
Assert(typeof monthCode === 'string');
const parsedMonthCode = Q(yield* ParseMonthCode(monthCode));
if (parsedMonthCode.IsLeapMonth) {
return Throw.RangeError('Invalid leap month');
}
if (parsedMonthCode.MonthNumber > 12) {
return Throw.RangeError('Invalid month');
}
if (month !== undefined && month !== parsedMonthCode.MonthNumber) {
return Throw.RangeError('Invalid month');
}
fields.Month = parsedMonthCode.MonthNumber;
} else {
Q(NonISOResolveFields(calendar, fields, type));
}
}