Skip to content

Commit 14af4d5

Browse files
committed
Add new calendar types
Declare an improved set of types for non-ISO calendars. They'll get used in later commits.
1 parent fac8f4a commit 14af4d5

File tree

1 file changed

+248
-2
lines changed

1 file changed

+248
-2
lines changed

lib/calendar.ts

Lines changed: 248 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -455,9 +455,213 @@ impl['iso8601'] = {
455455
}
456456
};
457457

458-
// Note: other built-in calendars than iso8601 are not part of the Temporal
458+
// Note: Built-in calendars other than iso8601 are not part of the Temporal
459459
// proposal for ECMA-262. These calendars will be standardized as part of
460-
// ECMA-402.
460+
// ECMA-402. Code below here includes an implementation of these calendars order
461+
// to validate the Temporal API and to get feedback. However, non-ISO calendar
462+
// implementation is subject to change because these calendars are
463+
// implementation-defined.
464+
//
465+
// Some ES implementations don't include ECMA 402. For this reason, it's helpful
466+
// to ensure a clean separation between the ISO calendar implementation which is
467+
// a part of ECMA 262 and the non-ISO calendar implementation which requires
468+
// ECMA 402.
469+
//
470+
// To ensure this separation, the implementation is split. The `NonIsoImpl`
471+
// interface is the top-level implementation for all non-ISO calendars. This
472+
// type has the same shape as the ECMA 262-only ISO calendar implementation so
473+
// can use the same callers, tests, etc.
474+
//
475+
// A derived interface `NonIsoImplWithHelper` adds a `helper` property that
476+
// includes the remaining non-ISO implementation properties and methods beyond
477+
// the ISO implementation above. The `helper` property's shape is a base
478+
// singleton object common to all calendars (`HelperSharedImpl`) that's extended
479+
// (interface `HelperPerCalendarImpl`) with implementation that varies for each
480+
// calendar.
481+
//
482+
// Typing of individual methods in the interfaces below uses the `this`
483+
// "parameter" declaration definition, which is a fake parameter (stripped by TS
484+
// during compilation and not visible at runtime) that tells TS what type `this`
485+
// is for a method. For historical reasons, the initial implementation of
486+
// non-ISO calendars mirrored the code style of the previous ISO-only
487+
// implementation which didn't use ES6 classes. Using the `this` parameter is a
488+
// hack to delay converting this file to use ES6 classes until the code was
489+
// fully typed to make a `class` refactoring easier and safer. We'll probably do
490+
// this conversion in the future. (PRs welcome!)
491+
492+
/**
493+
* `NonIsoImpl` - The generic top-level implementation for all non-ISO
494+
* calendars. This type has the same shape as the 262-only ISO calendar
495+
* implementation, which means the `Calendar` class implementation can swap out
496+
* the ISO for non-ISO implementations without changing any `Calendar` code.
497+
*/
498+
interface NonIsoImpl {
499+
dateFromFields(
500+
this: NonIsoImplWithHelper,
501+
fieldsParam: Params['dateFromFields'][0],
502+
options: NonNullable<Params['dateFromFields'][1]>,
503+
calendar: Temporal.Calendar
504+
): Temporal.PlainDate;
505+
yearMonthFromFields(
506+
this: NonIsoImplWithHelper,
507+
fieldsParam: Params['yearMonthFromFields'][0],
508+
options: NonNullable<Params['yearMonthFromFields'][1]>,
509+
calendar: Temporal.Calendar
510+
): Temporal.PlainYearMonth;
511+
monthDayFromFields(
512+
this: NonIsoImplWithHelper,
513+
fieldsParam: Params['monthDayFromFields'][0],
514+
options: NonNullable<Params['monthDayFromFields'][1]>,
515+
calendar: Temporal.Calendar
516+
): Temporal.PlainMonthDay;
517+
fields(fieldsParam: string[]): Return['fields'];
518+
mergeFields(fields: Params['mergeFields'][0], additionalFields: Params['mergeFields'][1]): Return['mergeFields'];
519+
dateAdd(
520+
this: NonIsoImplWithHelper,
521+
date: Temporal.PlainDate,
522+
years: number,
523+
months: number,
524+
weeks: number,
525+
days: number,
526+
overflow: Overflow,
527+
calendar: Temporal.Calendar
528+
): Temporal.PlainDate;
529+
dateUntil(
530+
this: NonIsoImplWithHelper,
531+
one: Temporal.PlainDate,
532+
two: Temporal.PlainDate,
533+
largestUnit: Temporal.DateUnit
534+
): {
535+
years: number;
536+
months: number;
537+
weeks: number;
538+
days: number;
539+
};
540+
year(this: NonIsoImplWithHelper, date: Temporal.PlainDate): number;
541+
month(this: NonIsoImplWithHelper, date: Temporal.PlainDate): number;
542+
day(this: NonIsoImplWithHelper, date: Temporal.PlainDate): number;
543+
era(this: NonIsoImplWithHelper, date: Temporal.PlainDate): string | undefined;
544+
eraYear(this: NonIsoImplWithHelper, date: Temporal.PlainDate): number | undefined;
545+
monthCode(this: NonIsoImplWithHelper, date: Temporal.PlainDate): string;
546+
dayOfWeek(date: Temporal.PlainDate): number;
547+
dayOfYear(this: NonIsoImplWithHelper, date: Temporal.PlainDate): number;
548+
weekOfYear(date: Temporal.PlainDate): number;
549+
daysInWeek(date: Temporal.PlainDate): number;
550+
daysInMonth(this: NonIsoImplWithHelper, date: Temporal.PlainDate | Temporal.PlainYearMonth): number;
551+
daysInYear(this: NonIsoImplWithHelper, dateParam: Temporal.PlainDate | Temporal.PlainYearMonth): number;
552+
monthsInYear(this: NonIsoImplWithHelper, date: Temporal.PlainDate | Temporal.PlainYearMonth): number;
553+
inLeapYear(this: NonIsoImplWithHelper, dateParam: Temporal.PlainDate | Temporal.PlainYearMonth): boolean;
554+
}
555+
556+
/**
557+
* This type exists solely to ensure a compiler error is shown if a per-calendar
558+
* implementation object doesn't declare a `helper` property. It will go away
559+
* if we migrate to ES6 classes.
560+
*
561+
* The methods of NonIsoImpl all set their `this` to NonIsoImplWithHelper in
562+
* order to avoid having to cast every use of `helper` to exclude `undefined`.
563+
* */
564+
interface NonIsoImplWithHelper extends NonIsoImpl {
565+
helper: HelperPerCalendarImpl;
566+
}
567+
568+
/** Shape of shared implementation code that applies to all calendars */
569+
interface HelperSharedImpl {
570+
isoToCalendarDate(isoDate: IsoYMD, cache: OneObjectCache): FullCalendarDate;
571+
validateCalendarDate(calendarDate: Partial<FullCalendarDate>): void;
572+
adjustCalendarDate(
573+
calendarDate: Partial<FullCalendarDate>,
574+
cache?: OneObjectCache,
575+
overflow?: Overflow,
576+
fromLegacyDate?: boolean
577+
): FullCalendarDate;
578+
regulateMonthDayNaive(calendarDate: FullCalendarDate, overflow: Overflow, cache: OneObjectCache): FullCalendarDate;
579+
calendarToIsoDate(date: CalendarDateFields, overflow: Overflow, cache: OneObjectCache): IsoYMD;
580+
temporalToCalendarDate(
581+
date: Temporal.PlainDate | Temporal.PlainMonthDay | Temporal.PlainYearMonth,
582+
cache: OneObjectCache
583+
): FullCalendarDate;
584+
compareCalendarDates(date1: Partial<CalendarYMD>, date2: Partial<CalendarYMD>): 0 | 1 | -1;
585+
regulateDate(calendarDate: CalendarYMD, overflow: Overflow, cache: OneObjectCache): FullCalendarDate;
586+
addDaysIso(isoDate: IsoYMD, days: number, cache?: OneObjectCache): IsoYMD;
587+
addDaysCalendar(calendarDate: CalendarYMD, days: number, cache: OneObjectCache): FullCalendarDate;
588+
addMonthsCalendar(calendarDate: CalendarYMD, months: number, overflow: Overflow, cache: OneObjectCache): CalendarYMD;
589+
addCalendar(
590+
calendarDate: CalendarYMD,
591+
{ years, months, weeks, days }: { years?: number; months?: number; weeks?: number; days?: number },
592+
overflow: Overflow,
593+
cache: OneObjectCache
594+
): FullCalendarDate;
595+
untilCalendar(
596+
calendarOne: FullCalendarDate,
597+
calendarTwo: FullCalendarDate,
598+
largestUnit: Temporal.DateUnit,
599+
cache: OneObjectCache
600+
): { years: number; months: number; weeks: number; days: number };
601+
daysInMonth(calendarDate: CalendarYMD, cache: OneObjectCache): number;
602+
daysInPreviousMonth(calendarDate: CalendarYMD, cache: OneObjectCache): number;
603+
startOfCalendarYear(calendarDate: CalendarYearOnly): CalendarYMD;
604+
startOfCalendarMonth(calendarDate: { year: number; month: number }): CalendarYMD;
605+
calendarDaysUntil(calendarOne: CalendarYMD, calendarTwo: CalendarYMD, cache: OneObjectCache): number;
606+
isoDaysUntil(oneIso: IsoYMD, twoIso: IsoYMD): number;
607+
eraLength: 'long' | 'short' | 'narrow';
608+
getFormatter(): globalThis.Intl.DateTimeFormat;
609+
formatter?: globalThis.Intl.DateTimeFormat;
610+
hasEra: boolean;
611+
monthDayFromFields(fields: Partial<FullCalendarDate>, overflow: Overflow, cache: OneObjectCache): IsoYMD;
612+
}
613+
614+
/** Calendar-specific implementation */
615+
interface HelperPerCalendarImpl extends HelperSharedImpl {
616+
id: string;
617+
reviseIntlEra?<T extends Partial<EraAndEraYear>>(calendarDate: T, isoDate: IsoYMD): T;
618+
constantEra?: string;
619+
checkIcuBugs?(isoDate: IsoYMD): void;
620+
calendarType?: string;
621+
monthsInYear(calendarDate: CalendarYearOnly, cache?: OneObjectCache): number;
622+
maximumMonthLength(calendarDate?: CalendarYM): number;
623+
minimumMonthLength(calendarDate?: CalendarYM): number;
624+
estimateIsoDate(calendarDate: CalendarYMD): IsoYMD;
625+
inLeapYear(calendarDate: CalendarYearOnly, cache?: OneObjectCache): boolean;
626+
627+
// Fields below here are only present in some subclasses but not others.
628+
eras?: Era[];
629+
anchorEra?: Era;
630+
calendarIsVulnerableToJulianBug?: boolean;
631+
v8IsVulnerableToJulianBug?: boolean;
632+
}
633+
634+
/**
635+
* This type is passed through from Calendar#dateFromFields().
636+
* `monthExtra` is additional information used internally to identify lunisolar leap months.
637+
*/
638+
type CalendarDateFields = Params['dateFromFields'][0] & { monthExtra?: string };
639+
640+
/**
641+
* This is a "fully populated" calendar date record. It's only lacking
642+
* `era`/`eraYear` (which may not be present in all calendars) and `monthExtra`
643+
* which is only used in some cases.
644+
*/
645+
type FullCalendarDate = {
646+
era?: string;
647+
eraYear?: number;
648+
year: number;
649+
month: number;
650+
monthCode: string;
651+
day: number;
652+
monthExtra?: string;
653+
};
654+
655+
// The types below are various subsets of calendar dates
656+
type CalendarYMD = { year: number; month: number; day: number };
657+
type CalendarYM = { year: number; month: number };
658+
type CalendarYearOnly = { year: number };
659+
type EraAndEraYear = { era: string; eraYear: number };
660+
661+
/** Record representing YMD of an ISO calendar date */
662+
type IsoYMD = { year: number; month: number; day: number };
663+
664+
type Overflow = Temporal.AssignmentOptions['overflow'];
461665

462666
function monthCodeNumberPart(monthCode: string) {
463667
if (!monthCode.startsWith('M')) {
@@ -1232,6 +1436,27 @@ const nonIsoHelperBase: NonIsoHelperBase = {
12321436
}
12331437
};
12341438

1439+
interface HebrewMonthInfo {
1440+
[m: string]: (
1441+
| {
1442+
leap: undefined;
1443+
regular: number;
1444+
}
1445+
| {
1446+
leap: number;
1447+
regular: undefined;
1448+
}
1449+
) & {
1450+
monthCode: string;
1451+
days:
1452+
| number
1453+
| {
1454+
min: number;
1455+
max: number;
1456+
};
1457+
};
1458+
}
1459+
12351460
const helperHebrew: NonIsoHelperBase = ObjectAssign({}, nonIsoHelperBase, {
12361461
id: 'hebrew',
12371462
calendarType: 'lunisolar',
@@ -1428,6 +1653,20 @@ const helperPersian: NonIsoHelperBase = ObjectAssign({}, nonIsoHelperBase, {
14281653
}
14291654
} as Partial<NonIsoHelperBase>);
14301655

1656+
interface IndianMonthInfo {
1657+
[month: number]: {
1658+
length: number;
1659+
month: number;
1660+
day: number;
1661+
leap?: {
1662+
length: number;
1663+
month: number;
1664+
day: number;
1665+
};
1666+
nextYear?: true | undefined;
1667+
};
1668+
}
1669+
14311670
const helperIndian: NonIsoHelperBase = ObjectAssign({}, nonIsoHelperBase, {
14321671
id: 'indian',
14331672
calendarType: 'solar',
@@ -1958,6 +2197,13 @@ const helperJapanese: NonIsoHelperBase = ObjectAssign(
19582197
} as Partial<NonIsoHelperBase>
19592198
);
19602199

2200+
interface ChineseMonthInfo {
2201+
[key: string]: { monthIndex: number; daysInMonth: number };
2202+
}
2203+
interface ChineseDraftMonthInfo {
2204+
[key: string]: { monthIndex: number; daysInMonth?: number };
2205+
}
2206+
19612207
const helperChinese: NonIsoHelperBase = ObjectAssign({}, nonIsoHelperBase, {
19622208
id: 'chinese',
19632209
calendarType: 'lunisolar',

0 commit comments

Comments
 (0)