Skip to content

Commit

Permalink
Add tests for previous commit
Browse files Browse the repository at this point in the history
These files test that calendar.dateAdd() is always called with undefined
as the options parameter from paths going through
BuiltinTimeZoneGetInstantFor.

Some tests must be skipped as they depend on tc39#1685 which will make
calendar.dateAdd() be called with undefined as the options parameter from
other operations as well. (If tc39#1685 does not go through, then these tests
will need to be rewritten.)

Includes fixes to the testing polyfill, fixing a few places where a
Temporal object was created internally using the ISO calendar instead of
the calendar passed to it.
  • Loading branch information
ptomato committed Aug 27, 2021
1 parent b39609c commit 659a919
Show file tree
Hide file tree
Showing 21 changed files with 631 additions and 40 deletions.
119 changes: 87 additions & 32 deletions polyfill/lib/ecmascript.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1778,34 +1778,104 @@ export const ES = ObjectAssign({}, ES2020, {
}
}

const utcns = ES.GetEpochFromISOParts(
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)
);
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 = ES.GetEpochFromISOParts(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond);
if (utcns === null) throw new RangeError('DateTime outside of supported range');
const dayBefore = new Instant(utcns.minus(86400e9));
const dayAfter = new Instant(utcns.plus(86400e9));
const offsetBefore = ES.GetOffsetNanosecondsFor(timeZone, dayBefore);
const offsetAfter = ES.GetOffsetNanosecondsFor(timeZone, dayAfter);
const nanoseconds = offsetAfter - offsetBefore;
const diff = ES.ToTemporalDurationRecord({ nanoseconds }, 'reject');
switch (disambiguation) {
case 'earlier': {
const earlier = dateTime.subtract(diff);
return ES.GetPossibleInstantsFor(timeZone, earlier)[0];
const calendar = GetSlot(dateTime, CALENDAR);
const PlainDateTime = GetIntrinsic('%Temporal.PlainDateTime%');
const earlier = ES.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 ES.GetPossibleInstantsFor(timeZone, earlierPlainDateTime)[0];
}
case 'compatible':
// fall through because 'compatible' means 'later' for "spring forward" transitions
case 'later': {
const later = dateTime.add(diff);
const possible = ES.GetPossibleInstantsFor(timeZone, later);
const calendar = GetSlot(dateTime, CALENDAR);
const PlainDateTime = GetIntrinsic('%Temporal.PlainDateTime%');
const later = ES.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 = ES.GetPossibleInstantsFor(timeZone, laterPlainDateTime);
return possible[possible.length - 1];
}
case 'reject': {
Expand Down Expand Up @@ -3429,22 +3499,7 @@ export const ES = ObjectAssign({}, ES2020, {
nanosecond
};
},
AddZonedDateTime: (
instant,
timeZone,
calendar,
years,
months,
weeks,
days,
h,
min,
s,
ms,
µs,
ns,
options = ObjectCreate(null)
) => {
AddZonedDateTime: (instant, timeZone, calendar, years, months, weeks, days, h, min, s, ms, µs, ns, options) => {
// 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
Expand Down
13 changes: 12 additions & 1 deletion polyfill/lib/plaintime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,18 @@ export class PlainTime {
const nanosecond = GetSlot(this, ISO_NANOSECOND);

const PlainDateTime = GetIntrinsic('%Temporal.PlainDateTime%');
const dt = new PlainDateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond);
const dt = new PlainDateTime(
year,
month,
day,
hour,
minute,
second,
millisecond,
microsecond,
nanosecond,
calendar
);
const instant = ES.BuiltinTimeZoneGetInstantFor(timeZone, dt, 'compatible');
return ES.CreateTemporalZonedDateTime(GetSlot(instant, EPOCHNANOSECONDS), timeZone, calendar);
}
Expand Down
42 changes: 38 additions & 4 deletions polyfill/lib/zoneddatetime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,18 @@ export class ZonedDateTime {
calendar = ES.ConsolidateCalendars(GetSlot(this, CALENDAR), calendar);
const timeZone = GetSlot(this, TIME_ZONE);
const PlainDateTime = GetIntrinsic('%Temporal.PlainDateTime%');
const dt = new PlainDateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond);
const dt = new PlainDateTime(
year,
month,
day,
hour,
minute,
second,
millisecond,
microsecond,
nanosecond,
calendar
);
const instant = ES.BuiltinTimeZoneGetInstantFor(timeZone, dt, 'compatible');
return ES.CreateTemporalZonedDateTime(GetSlot(instant, EPOCHNANOSECONDS), timeZone, calendar);
}
Expand All @@ -274,7 +285,18 @@ export class ZonedDateTime {

const timeZone = GetSlot(this, TIME_ZONE);
const PlainDateTime = GetIntrinsic('%Temporal.PlainDateTime%');
const dt = new PlainDateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond);
const dt = new PlainDateTime(
year,
month,
day,
hour,
minute,
second,
millisecond,
microsecond,
nanosecond,
calendar
);
const instant = ES.BuiltinTimeZoneGetInstantFor(timeZone, dt, 'compatible');
return ES.CreateTemporalZonedDateTime(GetSlot(instant, EPOCHNANOSECONDS), timeZone, calendar);
}
Expand Down Expand Up @@ -656,10 +678,22 @@ export class ZonedDateTime {
if (!ES.IsTemporalZonedDateTime(this)) throw new TypeError('invalid receiver');
const dt = dateTime(this);
const DateTime = GetIntrinsic('%Temporal.PlainDateTime%');
const dtStart = new DateTime(GetSlot(dt, ISO_YEAR), GetSlot(dt, ISO_MONTH), GetSlot(dt, ISO_DAY), 0, 0, 0, 0, 0, 0);
const calendar = GetSlot(this, CALENDAR);
const dtStart = new DateTime(
GetSlot(dt, ISO_YEAR),
GetSlot(dt, ISO_MONTH),
GetSlot(dt, ISO_DAY),
0,
0,
0,
0,
0,
0,
calendar
);
const timeZone = GetSlot(this, TIME_ZONE);
const instant = ES.BuiltinTimeZoneGetInstantFor(timeZone, dtStart, 'compatible');
return ES.CreateTemporalZonedDateTime(GetSlot(instant, EPOCHNANOSECONDS), timeZone, GetSlot(this, CALENDAR));
return ES.CreateTemporalZonedDateTime(GetSlot(instant, EPOCHNANOSECONDS), timeZone, calendar);
}
toInstant() {
if (!ES.IsTemporalZonedDateTime(this)) throw new TypeError('invalid receiver');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (C) 2021 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-temporal.duration.compare
description: >
BuiltinTimeZoneGetInstantFor calls Calendar.dateAdd with undefined as the
options value
includes: [temporalHelpers.js]
features: [Temporal]
---*/

const calendar = TemporalHelpers.calendarDateAddUndefinedOptions();
const timeZone = TemporalHelpers.oneShiftTimeZone(new Temporal.Instant(0n), 3600e9);
const relativeTo = new Temporal.ZonedDateTime(0n, timeZone, calendar);

const duration1 = new Temporal.Duration(0, 0, 1);
const duration2 = new Temporal.Duration(0, 0, 1);
Temporal.Duration.compare(duration1, duration2, { relativeTo });
assert.sameValue(calendar.dateAddCallCount, 4);
// one call in CalculateOffsetShift for each duration argument, plus one in
// UnbalanceDurationRelative for each duration argument
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (C) 2021 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-temporal.duration.prototype.add
description: >
BuiltinTimeZoneGetInstantFor calls Calendar.dateAdd with undefined as the
options value
includes: [temporalHelpers.js]
features: [Temporal]
---*/

const calendar = TemporalHelpers.calendarDateAddUndefinedOptions();
const timeZone = TemporalHelpers.oneShiftTimeZone(new Temporal.Instant(0n), 3600e9);
const instance = new Temporal.Duration(1, 1, 1, 1);
instance.add(instance, { relativeTo: new Temporal.ZonedDateTime(0n, timeZone, calendar) });
assert.sameValue(calendar.dateAddCallCount, 5);
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright (C) 2021 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-temporal.duration.prototype.round
description: >
BuiltinTimeZoneGetInstantFor calls Calendar.dateAdd with undefined as the
options value
includes: [temporalHelpers.js]
features: [Temporal]
---*/

const calendar = TemporalHelpers.calendarDateAddUndefinedOptions();
const timeZone = TemporalHelpers.oneShiftTimeZone(new Temporal.Instant(0n), 3600e9);
const relativeTo = new Temporal.ZonedDateTime(0n, timeZone, calendar);

// Rounding with smallestUnit a calendar unit.
// The calls come from these paths:
// Duration.round() ->
// RoundDuration ->
// MoveRelativeZonedDateTime -> AddZonedDateTime -> BuiltinTimeZoneGetInstantFor -> calendar.dateAdd()
// NanosecondsToDays -> AddZonedDateTime -> BuiltinTimeZoneGetInstantFor -> calendar.dateAdd()
// BalanceDurationRelative ->
// MoveRelativeDate -> calendar.dateAdd() (2x)
// calendar.dateAdd()
// MoveRelativeZonedDateTime -> AddZonedDateTime -> BuiltinTimeZoneGetInstantFor -> calendar.dateAdd()
// BalanceDuration ->
// AddZonedDateTime -> BuiltinTimeZoneGetInstantFor -> calendar.dateAdd()
// NanosecondsToDays -> AddZonedDateTime -> BuiltinTimeZoneGetInstantFor -> calendar.dateAdd() (2x)

const instance1 = new Temporal.Duration(1, 1, 1, 1, 1);
instance1.round({ smallestUnit: "days", relativeTo });
assert.sameValue(calendar.dateAddCallCount, 9, "rounding with calendar smallestUnit");

// Rounding with a non-default largestUnit to cover the path in
// UnbalanceDurationRelative where larger units are converted into smaller
// units; and with a smallestUnit larger than days to cover the path in
// RoundDuration where days are converted into larger units.
// The calls come from these paths:
// Duration.round() ->
// UnbalanceDurationRelative -> MoveRelativeDate -> calendar.dateAdd()
// RoundDuration ->
// MoveRelativeZonedDateTime -> AddZonedDateTime -> BuiltinTimeZoneGetInstantFor -> calendar.dateAdd()
// MoveRelativeDate -> calendar.dateAdd() (5x)
// BalanceDurationRelative
// MoveRelativeDate -> calendar.dateAdd()
// MoveRelativeZonedDateTime -> AddZonedDateTime -> BuiltinTimeZoneGetInstantFor -> calendar.dateAdd()

calendar.dateAddCallCount = 0;

const instance2 = new Temporal.Duration(0, 1, 1, 1);
instance2.round({ largestUnit: "weeks", smallestUnit: "weeks", relativeTo });
assert.sameValue(calendar.dateAddCallCount, 9, "rounding with non-default largestUnit and calendar smallestUnit");

// Rounding with smallestUnit a non-calendar unit, and having the resulting time
// difference be longer than a calendar day, covering the paths that go through
// AdjustRoundedDurationDays.
// The calls come from these paths:
// Duration.round() ->
// AdjustRoundedDurationDays ->
// AddZonedDateTime -> BuiltinTimeZoneGetInstantFor -> calendar.dateAdd()
// AddDuration ->
// AddZonedDateTime -> BuiltinTimeZoneGetInstantFor -> calendar.dateAdd()
// NanosecondsToDays -> AddZonedDateTime -> BuiltinTimeZoneGetInstantFor -> calendar.dateAdd() (2x)
// BalanceDuration ->
// AddZonedDateTime -> BuiltinTimeZoneGetInstantFor -> calendar.dateAdd()
// NanosecondsToDays -> AddZonedDateTime -> BuiltinTimeZoneGetInstantFor -> calendar.dateAdd() (2x)

calendar.dateAddCallCount = 0;

const instance3 = new Temporal.Duration(0, 0, 0, 0, 23, 59, 59, 999, 999, 999);
instance3.round({ largestUnit: "days", smallestUnit: "hours", roundingMode: "ceil", relativeTo });
assert.sameValue(calendar.dateAddCallCount, 7, "rounding with time difference exceeding calendar day");
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (C) 2021 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-temporal.duration.prototype.subtract
description: >
BuiltinTimeZoneGetInstantFor calls Calendar.dateAdd with undefined as the
options value
includes: [temporalHelpers.js]
features: [Temporal]
---*/

const calendar = TemporalHelpers.calendarDateAddUndefinedOptions();
const timeZone = TemporalHelpers.oneShiftTimeZone(new Temporal.Instant(0n), 3600e9);
const instance = new Temporal.Duration(1, 1, 1, 1);
instance.subtract(new Temporal.Duration(-1, -1, -1, -1), { relativeTo: new Temporal.ZonedDateTime(0n, timeZone, calendar) });
assert.sameValue(calendar.dateAddCallCount, 5);
Loading

0 comments on commit 659a919

Please sign in to comment.