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

Rebase up to September 2023 #292

Merged
merged 22 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
3b36c3c
Polyfill: Extend transition-finding lookahead
justingrant Aug 19, 2023
d17fc21
tests: No need to save offsetSign in validStrings test
ptomato Aug 11, 2023
22afc83
Polyfill: Add missing return to ToTemporalDate
ptomato Aug 18, 2023
85d1062
Polyfill: Avoid observable array iteration in a few more places
ptomato Aug 18, 2023
2d068cb
Polyfill: Use original Map objects
ptomato Aug 22, 2023
caa80ee
Editorial: Simplify date/time range validation
justingrant Aug 19, 2023
cc65db0
Polyfill: Align DisambiguatePossibleInstants 'reject' path with spec
ptomato Aug 23, 2023
c9d50f0
Polyfill: Align HandleDateTimeTemporalTime with spec
ptomato Aug 23, 2023
c9738fc
Polyfill: Align HandleDateTimeTemporalDateTime with spec
ptomato Aug 24, 2023
9116653
Polyfill: Replace 'new PlainX' with 'CreateTemporalX' in internal code
ptomato Aug 24, 2023
8b91537
Polyfill: clarify `valueOf` error message
justingrant Aug 26, 2023
5f6acc7
Remove spurious calls to GetOptionsObject
ptomato Sep 7, 2023
a67280d
Polyfill: Make debug printing side-effect-free
ptomato Sep 8, 2023
b357116
Normative: Avoid calling user code in no-op round operations
ptomato Mar 3, 2023
216030d
Normative: Call user code on relativeTo only when necessary in RoundD…
ptomato Mar 3, 2023
26168a2
Normative: Don't convert relativeTo to PlainDate twice in Duration.co…
ptomato Mar 3, 2023
5e18d71
Normative: Separate zoned and plain operations in RoundDuration
ptomato Mar 7, 2023
86715a4
Normative: Don't observably iterate array in built-in calendar's fiel…
ptomato Mar 7, 2023
63d30ff
Normative: Avoid reconverting Zoned- to PlainDateTime in AddZonedDate…
ptomato Mar 8, 2023
e465a2b
Normative: Return early from Duration.compare if internal slots equal
ptomato Mar 10, 2023
b6d5a17
Normative: Do away with CalculateOffsetShift in Duration.compare
ptomato Mar 8, 2023
53b6822
Update test262
Ms2ger Sep 13, 2023
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
39 changes: 26 additions & 13 deletions lib/calendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ import type {
BuiltinCalendarId,
CalendarParams as Params,
CalendarReturn as Return,
AnyTemporalKey,
CalendarSlot
CalendarSlot,
FieldKey
} from './internaltypes';
import type { CalendarFieldDescriptor } from './ecmascript';

Expand Down Expand Up @@ -102,7 +102,7 @@ interface CalendarImpl {
two: Temporal.PlainDate,
largestUnit: 'year' | 'month' | 'week' | 'day'
): { years: number; months: number; weeks: number; days: number };
fields(fields: string[]): string[];
fields(fields: FieldKey[]): FieldKey[];
fieldKeysToIgnore(keys: string[]): string[];
}

Expand Down Expand Up @@ -132,15 +132,19 @@ const impl: CalendarImplementations = {} as unknown as CalendarImplementations;
*/
export class Calendar implements Temporal.Calendar {
constructor(id: Params['constructor'][0]) {
const stringId = ES.RequireString(id);

let stringId = ES.RequireString(id);
if (!ES.IsBuiltinCalendar(stringId)) throw new RangeError(`invalid calendar identifier ${stringId}`);
CreateSlots(this);
SetSlot(this, CALENDAR_ID, ES.ASCIILowercase(stringId));
stringId = ES.ASCIILowercase(stringId);
ES.uncheckedAssertNarrowedType<BuiltinCalendarId>(
stringId,
'ES.IsBuiltinCalendar may allow mixed-case IDs, they are only guaranteed to be built-in after being lowercased'
);
SetSlot(this, CALENDAR_ID, stringId);

if (DEBUG) {
Object.defineProperty(this, '_repr_', {
value: `${this[Symbol.toStringTag]} <${stringId}>`,
value: `Temporal.Calendar <${stringId}>`,
writable: false,
enumerable: false,
configurable: false
Expand Down Expand Up @@ -183,7 +187,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 fieldsArray = [] as FieldKey[];
const allowed = new OriginalSet<string>(['year', 'month', 'monthCode', 'day']);
for (const name of fields) {
if (typeof name !== 'string') throw new TypeError('invalid fields');
Expand All @@ -202,7 +206,8 @@ export class Calendar implements Temporal.Calendar {
const merged = ObjectCreate(null);
const fieldsKeys = ReflectOwnKeys(fieldsCopy);
ES.uncheckedAssertNarrowedType<string[]>(fieldsKeys, 'Reflect.ownKeys does not respect the type of its input');
for (const key of fieldsKeys) {
for (let ix = 0; ix < fieldsKeys.length; ix++) {
const key = fieldsKeys[ix];
let propValue = undefined;
if (ES.Call(ArrayIncludes, overriddenKeys, [key])) propValue = additionalFieldsCopy[key];
else propValue = fieldsCopy[key];
Expand Down Expand Up @@ -2331,7 +2336,7 @@ class NonIsoCalendar implements CalendarImpl {
calendarSlotValue: string
): Temporal.PlainDate {
const cache = new OneObjectCache();
const fieldNames = ['day', 'month', 'monthCode', 'year'] as AnyTemporalKey[];
const fieldNames = ['day', 'month', 'monthCode', 'year'] as FieldKey[];
const extraFieldDescriptors = this.CalendarFieldDescriptors('date');
const fields = ES.PrepareTemporalFields(fieldsParam, fieldNames, [], extraFieldDescriptors);
const overflow = ES.ToTemporalOverflow(options);
Expand All @@ -2346,7 +2351,7 @@ class NonIsoCalendar implements CalendarImpl {
calendarSlotValue: CalendarSlot
): Temporal.PlainYearMonth {
const cache = new OneObjectCache();
const fieldNames = ['month', 'monthCode', 'year'] as AnyTemporalKey[];
const fieldNames = ['month', 'monthCode', 'year'] as FieldKey[];
const extraFieldDescriptors = this.CalendarFieldDescriptors('year-month');
const fields = ES.PrepareTemporalFields(fieldsParam, fieldNames, [], extraFieldDescriptors);
const overflow = ES.ToTemporalOverflow(options);
Expand All @@ -2363,7 +2368,7 @@ 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 = ['day', 'month', 'monthCode', 'year'] as AnyTemporalKey[];
const fieldNames = ['day', 'month', 'monthCode', 'year'] as FieldKey[];
const extraFieldDescriptors = this.CalendarFieldDescriptors('date');
const fields = ES.PrepareTemporalFields(fieldsParam, fieldNames, [], extraFieldDescriptors);
const overflow = ES.ToTemporalOverflow(options);
Expand All @@ -2373,7 +2378,7 @@ class NonIsoCalendar implements CalendarImpl {
cache.setObject(result);
return result;
}
fields(fields: string[]): string[] {
fields(fields: FieldKey[]): FieldKey[] {
// 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.
Expand Down Expand Up @@ -2574,3 +2579,11 @@ for (const Helper of [
// per-calendar logic.
impl[helper.id] = new NonIsoCalendar(helper);
}

function calendarFieldsImpl(calendar: BuiltinCalendarId, fieldNames: FieldKey[]): FieldKey[] {
return impl[calendar].fields(fieldNames);
}
export type CalendarFieldsImplType = typeof calendarFieldsImpl;
// Probably not what the intrinsics mechanism was intended for, but view this as
// an export of calendarFieldsImpl while avoiding circular dependencies
DefineIntrinsic('calendarFieldsImpl', calendarFieldsImpl);
162 changes: 136 additions & 26 deletions lib/duration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import {
MILLISECONDS,
MICROSECONDS,
NANOSECONDS,
CALENDAR,
INSTANT,
TIME_ZONE,
CreateSlots,
GetSlot,
SetSlot
Expand Down Expand Up @@ -60,7 +63,7 @@ export class Duration implements Temporal.Duration {

if (DEBUG) {
Object.defineProperty(this, '_repr_', {
value: `${this[Symbol.toStringTag]} <${ES.TemporalDurationToString(
value: `Temporal.Duration <${ES.TemporalDurationToString(
years,
months,
weeks,
Expand Down Expand Up @@ -224,7 +227,7 @@ export class Duration implements Temporal.Duration {
let microseconds = GetSlot(this, MICROSECONDS);
let nanoseconds = GetSlot(this, NANOSECONDS);

let defaultLargestUnit = ES.DefaultTemporalLargestUnit(
const existingLargestUnit = ES.DefaultTemporalLargestUnit(
years,
months,
weeks,
Expand All @@ -242,7 +245,7 @@ export class Duration implements Temporal.Duration {
: ES.GetOptionsObject(roundToParam);

let largestUnit = ES.GetTemporalUnit(roundTo, 'largestUnit', 'datetime', undefined, ['auto']);
let relativeTo = ES.ToRelativeTemporalObject(roundTo);
let { plainRelativeTo, zonedRelativeTo } = ES.ToRelativeTemporalObject(roundTo);
const roundingIncrement = ES.ToTemporalRoundingIncrement(roundTo);
const roundingMode = ES.ToTemporalRoundingMode(roundTo, 'halfExpand');
let smallestUnit = ES.GetTemporalUnit(roundTo, 'smallestUnit', 'datetime', undefined);
Expand All @@ -252,7 +255,7 @@ export class Duration implements Temporal.Duration {
smallestUnitPresent = false;
smallestUnit = 'nanosecond';
}
defaultLargestUnit = ES.LargerOfTwoTemporalUnits(defaultLargestUnit, smallestUnit);
const defaultLargestUnit = ES.LargerOfTwoTemporalUnits(existingLargestUnit, smallestUnit);
let largestUnitPresent = true;
if (!largestUnit) {
largestUnitPresent = false;
Expand All @@ -277,13 +280,43 @@ export class Duration implements Temporal.Duration {
const maximum = maximumIncrements[smallestUnit];
if (maximum !== undefined) ES.ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false);

const roundingGranularityIsNoop = smallestUnit === 'nanosecond' && roundingIncrement === 1;
const balancingRequested = largestUnit !== existingLargestUnit;
const calendarUnitsPresent = years !== 0 || months !== 0 || weeks !== 0;
const timeUnitsOverflowWillOccur =
minutes >= 60 || seconds >= 60 || milliseconds >= 1000 || microseconds >= 1000 || nanoseconds >= 1000;
const hoursToDaysConversionMayOccur = (days !== 0 && zonedRelativeTo) || hours >= 24;
if (
roundingGranularityIsNoop &&
!balancingRequested &&
!calendarUnitsPresent &&
!timeUnitsOverflowWillOccur &&
!hoursToDaysConversionMayOccur
) {
return new Duration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds);
}

const plainRelativeToWillBeUsed =
smallestUnit === 'year' ||
smallestUnit === 'month' ||
smallestUnit === 'week' ||
years !== 0 ||
months !== 0 ||
weeks !== 0 ||
days !== 0;
if (zonedRelativeTo && plainRelativeToWillBeUsed) {
// Convert a ZonedDateTime relativeTo to PlainDate only if needed in one
// of the operations below, because the conversion is user visible
plainRelativeTo = ES.ToTemporalDate(zonedRelativeTo);
}

({ years, months, weeks, days } = ES.UnbalanceDateDurationRelative(
years,
months,
weeks,
days,
largestUnit,
relativeTo
plainRelativeTo
));
({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } =
ES.RoundDuration(
Expand All @@ -300,9 +333,10 @@ export class Duration implements Temporal.Duration {
roundingIncrement,
smallestUnit,
roundingMode,
relativeTo
plainRelativeTo,
zonedRelativeTo
));
if (ES.IsTemporalZonedDateTime(relativeTo)) {
if (zonedRelativeTo) {
({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } =
ES.AdjustRoundedDurationDays(
years,
Expand All @@ -318,7 +352,7 @@ export class Duration implements Temporal.Duration {
roundingIncrement,
smallestUnit,
roundingMode,
relativeTo
zonedRelativeTo
));
({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ES.BalanceTimeDurationRelative(
days,
Expand All @@ -329,7 +363,7 @@ export class Duration implements Temporal.Duration {
microseconds,
nanoseconds,
largestUnit,
relativeTo
zonedRelativeTo
));
} else {
({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ES.BalanceTimeDuration(
Expand All @@ -349,7 +383,7 @@ export class Duration implements Temporal.Duration {
weeks,
days,
largestUnit,
relativeTo
plainRelativeTo
));

return new Duration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds);
Expand All @@ -372,15 +406,30 @@ export class Duration implements Temporal.Duration {
typeof optionsParam === 'string'
? (ES.CreateOnePropObject('unit', optionsParam) as Exclude<typeof optionsParam, string>)
: ES.GetOptionsObject(optionsParam);
const relativeTo = ES.ToRelativeTemporalObject(options);
let { plainRelativeTo, zonedRelativeTo } = ES.ToRelativeTemporalObject(options);
const unit = ES.GetTemporalUnit(options, 'unit', 'datetime', ES.REQUIRED);

const plainRelativeToWillBeUsed =
unit === 'year' || unit === 'month' || unit === 'week' || years !== 0 || months !== 0 || weeks !== 0;
if (zonedRelativeTo !== undefined && plainRelativeToWillBeUsed) {
// Convert a ZonedDateTime relativeTo to PlainDate only if needed in one
// of the operations below, because the conversion is user visible
plainRelativeTo = ES.ToTemporalDate(zonedRelativeTo);
}

// Convert larger units down to days
({ years, months, weeks, days } = ES.UnbalanceDateDurationRelative(years, months, weeks, days, unit, relativeTo));
({ years, months, weeks, days } = ES.UnbalanceDateDurationRelative(
years,
months,
weeks,
days,
unit,
plainRelativeTo
));
// If the unit we're totalling is smaller than `days`, convert days down to that unit.
let balanceResult;
if (ES.IsTemporalZonedDateTime(relativeTo)) {
const intermediate = ES.MoveRelativeZonedDateTime(relativeTo, years, months, weeks, 0);
if (zonedRelativeTo) {
const intermediate = ES.MoveRelativeZonedDateTime(zonedRelativeTo, years, months, weeks, 0);
balanceResult = ES.BalancePossiblyInfiniteTimeDurationRelative(
days,
hours,
Expand Down Expand Up @@ -425,7 +474,8 @@ export class Duration implements Temporal.Duration {
1,
unit,
'trunc',
relativeTo
plainRelativeTo,
zonedRelativeTo
);
return total;
}
Expand Down Expand Up @@ -510,7 +560,7 @@ export class Duration implements Temporal.Duration {
);
}
valueOf(): never {
throw new TypeError('use compare() to compare Temporal.Duration');
ES.ValueOfThrows('Duration');
}
static from(item: Params['from'][0]): Return['from'] {
if (ES.IsTemporalDuration(item)) {
Expand All @@ -537,12 +587,11 @@ export class Duration implements Temporal.Duration {
const one = ES.ToTemporalDuration(oneParam);
const two = ES.ToTemporalDuration(twoParam);
const options = ES.GetOptionsObject(optionsParam);
const relativeTo = ES.ToRelativeTemporalObject(options);
const y1 = GetSlot(one, YEARS);
const mon1 = GetSlot(one, MONTHS);
const w1 = GetSlot(one, WEEKS);
let d1 = GetSlot(one, DAYS);
const h1 = GetSlot(one, HOURS);
let h1 = GetSlot(one, HOURS);
const min1 = GetSlot(one, MINUTES);
const s1 = GetSlot(one, SECONDS);
const ms1 = GetSlot(one, MILLISECONDS);
Expand All @@ -552,20 +601,81 @@ export class Duration implements Temporal.Duration {
const mon2 = GetSlot(two, MONTHS);
const w2 = GetSlot(two, WEEKS);
let d2 = GetSlot(two, DAYS);
const h2 = GetSlot(two, HOURS);
let h2 = GetSlot(two, HOURS);
const min2 = GetSlot(two, MINUTES);
const s2 = GetSlot(two, SECONDS);
const ms2 = GetSlot(two, MILLISECONDS);
const µs2 = GetSlot(two, MICROSECONDS);
let ns2 = GetSlot(two, NANOSECONDS);
const shift1 = ES.CalculateOffsetShift(relativeTo, y1, mon1, w1, d1);
const shift2 = ES.CalculateOffsetShift(relativeTo, y2, mon2, w2, d2);
if (y1 !== 0 || y2 !== 0 || mon1 !== 0 || mon2 !== 0 || w1 !== 0 || w2 !== 0) {
({ days: d1 } = ES.UnbalanceDateDurationRelative(y1, mon1, w1, d1, 'day', relativeTo));
({ days: d2 } = ES.UnbalanceDateDurationRelative(y2, mon2, w2, d2, 'day', relativeTo));

if (
y1 === y2 &&
mon1 === mon2 &&
w1 === w2 &&
d1 === d2 &&
h1 === h2 &&
min1 === min2 &&
s1 === s2 &&
ms1 === ms2 &&
µs1 === µs2 &&
ns1 === ns2
) {
return 0;
}
const { plainRelativeTo, zonedRelativeTo } = ES.ToRelativeTemporalObject(options);

const calendarUnitsPresent = y1 !== 0 || y2 !== 0 || mon1 !== 0 || mon2 !== 0 || w1 !== 0 || w2 !== 0;

if (zonedRelativeTo && (calendarUnitsPresent || d1 != 0 || d2 !== 0)) {
const instant = GetSlot(zonedRelativeTo, INSTANT);
const timeZone = GetSlot(zonedRelativeTo, TIME_ZONE);
const calendar = GetSlot(zonedRelativeTo, CALENDAR);
const precalculatedPlainDateTime = ES.GetPlainDateTimeFor(timeZone, instant, calendar);

const after1 = ES.AddZonedDateTime(
instant,
timeZone,
calendar,
y1,
mon1,
w1,
d1,
h1,
min1,
s1,
ms1,
µs1,
ns1,
precalculatedPlainDateTime
);
const after2 = ES.AddZonedDateTime(
instant,
timeZone,
calendar,
y2,
mon2,
w2,
d2,
h2,
min2,
s2,
ms2,
µs2,
ns2,
precalculatedPlainDateTime
);
return ES.ComparisonResult(JSBI.toNumber(JSBI.subtract(after1, after2)));
}

if (calendarUnitsPresent) {
// plainRelativeTo may be undefined, and if so Unbalance will throw
({ days: d1 } = ES.UnbalanceDateDurationRelative(y1, mon1, w1, d1, 'day', plainRelativeTo));
({ days: d2 } = ES.UnbalanceDateDurationRelative(y2, mon2, w2, d2, 'day', plainRelativeTo));
}
const totalNs1 = ES.TotalDurationNanoseconds(d1, h1, min1, s1, ms1, µs1, ns1, shift1);
const totalNs2 = ES.TotalDurationNanoseconds(d2, h2, min2, s2, ms2, µs2, ns2, shift2);
const h1Adjusted = JSBI.add(JSBI.BigInt(h1), JSBI.multiply(JSBI.BigInt(d1), ES.TWENTY_FOUR));
const h2Adjusted = JSBI.add(JSBI.BigInt(h2), JSBI.multiply(JSBI.BigInt(d2), ES.TWENTY_FOUR));
const totalNs1 = ES.TotalDurationNanoseconds(h1Adjusted, min1, s1, ms1, µs1, ns1);
const totalNs2 = ES.TotalDurationNanoseconds(h2Adjusted, min2, s2, ms2, µs2, ns2);
return ES.ComparisonResult(JSBI.toNumber(JSBI.subtract(totalNs1, totalNs2)));
}
[Symbol.toStringTag]!: 'Temporal.Duration';
Expand Down
Loading
Loading