Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port commits from proposal repo (2024-02-25 tranche) #275

Merged
merged 39 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
090262f
chore: Ignore Test262 in ESLint and Prettier
justingrant Jun 23, 2023
1b0d50b
Normative: Throw on duplicates in PrepareTemporalFields
guijemont May 3, 2023
b4fbcb4
Update test262
ptomato Jul 17, 2023
ca43c8c
Editorial: DRY refactor of time formatting
justingrant Jul 17, 2023
f4c7a9c
Don't coerce non-String inputs to strings
justingrant Jul 17, 2023
366fce3
Update test262
ptomato Jul 18, 2023
54ac3b1
Normative: Limit offset time zones to minutes
justingrant Jul 15, 2023
bfb5df9
Update Test262
justingrant Jul 17, 2023
8286a61
Editorial: rename TimeZone nanoseconds slot/AOs
justingrant Jul 19, 2023
d715ac0
Editorial: refactor offset string formatting
justingrant Jul 19, 2023
4f3a7bb
Polyfill: Implement proposal-canonical-tz Stage 3
justingrant Jul 19, 2023
81fdfd3
Update Test262 for proposal-canonical-tz Stage 3
justingrant Jul 19, 2023
8f29523
Polyfill: clean up ISO parser & align to spec
justingrant Jul 21, 2023
8550f78
Editorial: Reintroduce an AO that formats UTC offset with ns precision
ptomato Aug 10, 2023
847f45e
Polyfill: reject sub-minute ISO string annotations
justingrant Aug 10, 2023
356d3cf
Polyfill: Simplify time zone parsing
justingrant Aug 10, 2023
31b1f18
Update Test262
justingrant Aug 10, 2023
a3f78a0
Polyfill: Test parsing of Z designation
justingrant Aug 11, 2023
50b1618
Polyfill: Remove throw in ParseTemporalInstant
justingrant Aug 14, 2023
a3bf70f
Polyfill: RoundDuration TypeError=>assertion
justingrant Aug 14, 2023
72fd4a3
Normative: Avoid recalculating offset ns after GetPlainDateTimeFor
ptomato Mar 2, 2023
0486100
Normative: Limit allowed values for Calendar.p.fields to date units
ptomato Mar 2, 2023
31f8b39
Normative: Get receiver's time units from internal slots in with()
ptomato Mar 2, 2023
339afdf
Normative: Get receiver fields from a PlainDateTime in ZonedDateTime.…
ptomato Mar 2, 2023
d6147f6
Normative: Copy options object in {Plain,Zoned}DateTime.{from,p.with}
ptomato Mar 3, 2023
776a3a4
Normative: Copy options object in Plain{Date,MonthDay,YearMonth}.{fro…
ptomato Mar 3, 2023
bad2a5e
Update test262
ptomato Aug 16, 2023
d943b2a
Polyfill: Align time zone regular expressions with spec
gibson042 Aug 17, 2023
42ead26
Remove unreachable steps in DifferenceISODate
justingrant Aug 20, 2023
94318fc
Polyfill: Align with spec
Aditi-1400 May 17, 2023
2e637d1
Polyfill: Be more robust against late-run primordial manipulation
gibson042 Aug 15, 2023
a121504
Update test262
ptomato Aug 21, 2023
27164a7
Prune unused exports
justingrant Feb 26, 2024
a0f8e92
Fix validStrings.mjs tests
justingrant Feb 26, 2024
5ae5771
Add expected failures for ES5 and Node 14/16
justingrant Feb 26, 2024
585a096
fixup! Editorial: DRY refactor of time formatting
justingrant Mar 4, 2024
364ec9e
fixup! chore: Ignore Test262 in ESLint and Prettier
justingrant Mar 4, 2024
9260ee1
fixup! Polyfill: Be more robust against late-run primordial manipulation
justingrant Mar 5, 2024
100ac63
Minor updates from code reviews
justingrant Mar 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .eslintrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ globals:
globalThis: readonly
ignorePatterns:
- node_modules/
- /dist/
- /tsc-out/
- /test262/
- dist/
- tsc-out/
- test262/
rules:
array-element-newline:
- error
Expand Down
4 changes: 4 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules/
dist/
tsc-out/
test262/
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,7 @@ export namespace Temporal {
static from(timeZone: TimeZoneLike): Temporal.TimeZone | TimeZoneProtocol;
constructor(timeZoneIdentifier: string);
readonly id: string;
equals(timeZone: TimeZoneLike): boolean;
getOffsetNanosecondsFor(instant: Temporal.Instant | string): number;
getOffsetStringFor(instant: Temporal.Instant | string): string;
getPlainDateTimeFor(instant: Temporal.Instant | string, calendar?: CalendarLike): Temporal.PlainDateTime;
Expand Down
96 changes: 51 additions & 45 deletions lib/calendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,23 @@ import type {
AnyTemporalKey,
CalendarSlot
} from './internaltypes';
import type { CalendarFieldDescriptor } from './ecmascript';

const ArrayIncludes = Array.prototype.includes;
const ArrayPrototypePush = Array.prototype.push;
const ArrayPrototypeSort = Array.prototype.sort;
const IntlDateTimeFormat = globalThis.Intl.DateTimeFormat;
const ArraySort = Array.prototype.sort;
const MathAbs = Math.abs;
const MathFloor = Math.floor;
const ObjectCreate = Object.create;
const ObjectEntries = Object.entries;
const OriginalSet = Set;
const ReflectOwnKeys = Reflect.ownKeys;
const SetPrototypeAdd = Set.prototype.add;
const SetPrototypeValues = Set.prototype.values;

function arrayFromSet<T>(src: Set<T>): T[] {
return [...src];
}

/**
* Shape of internal implementation of each built-in calendar. Note that
Expand Down Expand Up @@ -127,21 +131,16 @@ const impl: CalendarImplementations = {} as unknown as CalendarImplementations;
* 6. Call the corresponding method in the implementation object.
*/
export class Calendar implements Temporal.Calendar {
constructor(idParam: Params['constructor'][0]) {
// Note: if the argument is not passed, IsBuiltinCalendar("undefined") will fail. This check
// exists only to improve the error message.
if (arguments.length < 1) {
throw new RangeError('missing argument: id is required');
}
constructor(id: Params['constructor'][0]) {
const stringId = ES.RequireString(id);

const id = ES.ToString(idParam);
if (!ES.IsBuiltinCalendar(id)) throw new RangeError(`invalid calendar identifier ${id}`);
if (!ES.IsBuiltinCalendar(stringId)) throw new RangeError(`invalid calendar identifier ${stringId}`);
CreateSlots(this);
SetSlot(this, CALENDAR_ID, ES.ASCIILowercase(id));
SetSlot(this, CALENDAR_ID, ES.ASCIILowercase(stringId));

if (DEBUG) {
Object.defineProperty(this, '_repr_', {
value: `${this[Symbol.toStringTag]} <${id}>`,
value: `${this[Symbol.toStringTag]} <${stringId}>`,
writable: false,
enumerable: false,
configurable: false
Expand Down Expand Up @@ -185,18 +184,7 @@ export class Calendar implements Temporal.Calendar {
fields(fields: Params['fields'][0]): Return['fields'] {
if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver');
const fieldsArray = [] as string[];
const allowed = new Set([
'year',
'month',
'monthCode',
'day',
'hour',
'minute',
'second',
'millisecond',
'microsecond',
'nanosecond'
]);
const allowed = new OriginalSet<string>(['year', 'month', 'monthCode', 'day']);
for (const name of fields) {
if (typeof name !== 'string') throw new TypeError('invalid fields');
if (!allowed.has(name)) throw new RangeError(`invalid field name ${name}`);
Expand Down Expand Up @@ -425,18 +413,17 @@ impl['iso8601'] = {
if (fields.month !== undefined && fields.year === undefined && fields.monthCode === undefined) {
throw new TypeError('either year or monthCode required with month');
}
const useYear = fields.monthCode === undefined;
const referenceISOYear = 1972;
fields = resolveNonLunisolarMonth(fields);
let { month, day, year } = fields;
({ month, day } = ES.RegulateISODate(useYear ? year : referenceISOYear, month, day, overflow));
({ month, day } = ES.RegulateISODate(year !== undefined ? year : referenceISOYear, month, day, overflow));
return ES.CreateTemporalMonthDay(month, day, calendarSlotValue, referenceISOYear);
},
fields(fields) {
return fields;
},
fieldKeysToIgnore(keys) {
const result = new OriginalSet();
const result = new OriginalSet<string>();
for (let ix = 0; ix < keys.length; ix++) {
const key = keys[ix];
ES.Call(SetPrototypeAdd, result, [key]);
Expand All @@ -446,7 +433,7 @@ impl['iso8601'] = {
ES.Call(SetPrototypeAdd, result, ['month']);
}
}
return [...ES.Call(SetPrototypeValues, result, [])];
return arrayFromSet(result);
},
dateAdd(date, years, months, weeks, days, overflow, calendarSlotValue) {
let year = GetSlot(date, ISO_YEAR);
Expand Down Expand Up @@ -1799,12 +1786,14 @@ function adjustEras(erasParam: InputEra[]): { eras: Era[]; anchorEra: Era } {
// Ensure that the latest epoch is first in the array. This lets us try to
// match eras in index order, with the last era getting the remaining older
// years. Any reverse-signed era must be at the end.
ArraySort.call(eras, (e1, e2) => {
if (e1.reverseOf) return 1;
if (e2.reverseOf) return -1;
if (!e1.isoEpoch || !e2.isoEpoch) throw new RangeError('Invalid era data: missing ISO epoch');
return e2.isoEpoch.year - e1.isoEpoch.year;
});
ES.Call(ArrayPrototypeSort, eras, [
(e1, e2) => {
if (e1.reverseOf) return 1;
if (e2.reverseOf) return -1;
if (!e1.isoEpoch || !e2.isoEpoch) throw new RangeError('Invalid era data: missing ISO epoch');
return e2.isoEpoch.year - e1.isoEpoch.year;
}
]);

// If there's a reversed era, then the one before it must be the era that's
// being reversed.
Expand Down Expand Up @@ -2326,14 +2315,25 @@ class DangiHelper extends ChineseBaseHelper {
*/
class NonIsoCalendar implements CalendarImpl {
constructor(private readonly helper: HelperBase) {}
CalendarFieldDescriptors(type: 'date' | 'month-day' | 'year-month'): CalendarFieldDescriptor[] {
let fieldDescriptors = [] as CalendarFieldDescriptor[];
if (type !== 'month-day') {
fieldDescriptors = [
{ property: 'era', conversion: ES.ToString, required: false },
{ property: 'eraYear', conversion: ES.ToIntegerOrInfinity, required: false }
];
}
return fieldDescriptors;
}
dateFromFields(
fieldsParam: Params['dateFromFields'][0],
options: NonNullable<Params['dateFromFields'][1]>,
calendarSlotValue: string
): Temporal.PlainDate {
const cache = new OneObjectCache();
const fieldNames = this.fields(['day', 'month', 'monthCode', 'year']) as AnyTemporalKey[];
const fields = ES.PrepareTemporalFields(fieldsParam, fieldNames, []);
const fieldNames = ['day', 'month', 'monthCode', 'year'] as AnyTemporalKey[];
const extraFieldDescriptors = this.CalendarFieldDescriptors('date');
const fields = ES.PrepareTemporalFields(fieldsParam, fieldNames, [], extraFieldDescriptors);
const overflow = ES.ToTemporalOverflow(options);
const { year, month, day } = this.helper.calendarToIsoDate(fields, overflow, cache);
const result = ES.CreateTemporalDate(year, month, day, calendarSlotValue);
Expand All @@ -2346,8 +2346,9 @@ class NonIsoCalendar implements CalendarImpl {
calendarSlotValue: CalendarSlot
): Temporal.PlainYearMonth {
const cache = new OneObjectCache();
const fieldNames = this.fields(['month', 'monthCode', 'year']) as AnyTemporalKey[];
const fields = ES.PrepareTemporalFields(fieldsParam, fieldNames, []);
const fieldNames = ['month', 'monthCode', 'year'] as AnyTemporalKey[];
const extraFieldDescriptors = this.CalendarFieldDescriptors('year-month');
const fields = ES.PrepareTemporalFields(fieldsParam, fieldNames, [], extraFieldDescriptors);
const overflow = ES.ToTemporalOverflow(options);
const { year, month, day } = this.helper.calendarToIsoDate({ ...fields, day: 1 }, overflow, cache);
const result = ES.CreateTemporalYearMonth(year, month, calendarSlotValue, /* referenceISODay = */ day);
Expand All @@ -2362,24 +2363,29 @@ class NonIsoCalendar implements CalendarImpl {
const cache = new OneObjectCache();
// For lunisolar calendars, either `monthCode` or `year` must be provided
// because `month` is ambiguous without a year or a code.
const fieldNames = this.fields(['day', 'month', 'monthCode', 'year']) as AnyTemporalKey[];
const fields = ES.PrepareTemporalFields(fieldsParam, fieldNames, []);
const fieldNames = ['day', 'month', 'monthCode', 'year'] as AnyTemporalKey[];
const extraFieldDescriptors = this.CalendarFieldDescriptors('date');
const fields = ES.PrepareTemporalFields(fieldsParam, fieldNames, [], extraFieldDescriptors);
const overflow = ES.ToTemporalOverflow(options);
const { year, month, day } = this.helper.monthDayFromFields(fields, overflow, cache);
// `year` is a reference year where this month/day exists in this calendar
const result = ES.CreateTemporalMonthDay(month, day, calendarSlotValue, /* referenceISOYear = */ year);
cache.setObject(result);
return result;
}
fields(fieldsParam: string[]): string[] {
let fields = fieldsParam;
if (ArrayIncludes.call(fields, 'year')) fields = [...fields, 'era', 'eraYear'];
fields(fields: string[]): string[] {
// Note that `fields` is a new array created by the caller of this method,
// not the original input passed by the original caller. So it's safe to
// mutate it here because the mutation is not observable.
if (ES.Call(ArrayIncludes, fields, ['year'])) {
ES.Call(ArrayPrototypePush, fields, ['era', 'eraYear']);
}
return fields;
}
fieldKeysToIgnore(
keys: Exclude<keyof Temporal.PlainDateLike, 'calendar'>[]
): Exclude<keyof Temporal.PlainDateLike, 'calendar'>[] {
const result = new OriginalSet();
const result = new OriginalSet<(typeof keys)[number]>();
for (let ix = 0; ix < keys.length; ix++) {
const key = keys[ix];
ES.Call(SetPrototypeAdd, result, [key]);
Expand Down Expand Up @@ -2419,7 +2425,7 @@ class NonIsoCalendar implements CalendarImpl {
break;
}
}
return [...ES.Call(SetPrototypeValues, result, [])];
return arrayFromSet(result);
}
dateAdd(
date: Temporal.PlainDate,
Expand Down
Loading
Loading