Skip to content

Commit

Permalink
Temporal: Tests for correct intermediate value in ZonedDateTime diffe…
Browse files Browse the repository at this point in the history
…rence/rounding

These test cases ensure that DST disambiguation does not take place on
intermediate values that are not the start or the end of the calculation.

Note that NormalizedTimeDurationToDays is no longer called inside
Temporal.Duration.prototype.add/subtract, so a few tests can be deleted.

Other tests need to be adjusted because NormalizedTimeDurationToDays is
no longer called inside Temporal.ZonedDateTime.prototype.since/until via
DifferenceZonedDateTime, although it is still called as part of rounding.

In addition, new tests for the now-fixed edge case are added, and for the
day corrections that can happen to intermediates.

See tc39/proposal-temporal#2760
  • Loading branch information
ptomato committed Apr 2, 2024
1 parent d19cb15 commit 984f3cc
Show file tree
Hide file tree
Showing 26 changed files with 592 additions and 482 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ 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, 3);
assert.sameValue(calendar.dateAddCallCount, 2);
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (C) 2024 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: >
Throws a RangeError when custom calendar method returns inconsistent result
info: |
DifferenceZonedDateTime ( ... )
8. Repeat 3 times:
...
g. If _sign_ = 0, or _timeSign_ = 0, or _sign_ = _timeSign_, then
...
viii. Return ? CreateNormalizedDurationRecord(_dateDifference_.[[Years]],
_dateDifference_.[[Months]], _dateDifference_.[[Weeks]],
_dateDifference_.[[Days]], _norm_).
h. Set _dayCorrection_ to _dayCorrection_ + 1.
9. NOTE: This step is only reached when custom calendar or time zone methods
return inconsistent values.
10. Throw a *RangeError* exception.
features: [Temporal]
---*/

// Based partly on a test case by André Bargull

const duration1 = new Temporal.Duration(0, 0, /* weeks = */ 7, 0, /* hours = */ 12);
const duration2 = new Temporal.Duration(0, 0, 0, /* days = */ 1);

{
const tz = new (class extends Temporal.TimeZone {
getPossibleInstantsFor(dateTime) {
return super.getPossibleInstantsFor(dateTime.add({ days: 3 }));
}
})("UTC");

const relativeTo = new Temporal.ZonedDateTime(0n, tz);

assert.throws(RangeError, () => duration1.add(duration2, { relativeTo }),
"Calendar calculation where more than 2 days correction is needed should cause RangeError");
}

{
const cal = new (class extends Temporal.Calendar {
dateUntil(one, two, options) {
return super.dateUntil(one, two, options).negated();
}
})("iso8601");

const relativeTo = new Temporal.ZonedDateTime(0n, "UTC", cal);

assert.throws(RangeError, () => duration1.add(duration2, { relativeTo }),
"Calendar calculation causing mixed-sign values should cause RangeError");
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -338,18 +338,8 @@ const expectedOpsForZonedRelativeTo = expected.concat([
"call options.relativeTo.timeZone.getPossibleInstantsFor",
// AddDuration → DifferenceZonedDateTime
"call options.relativeTo.timeZone.getOffsetNanosecondsFor",
// AddDuration → DifferenceZonedDateTime → DifferenceISODateTime
"call options.relativeTo.calendar.dateUntil",
// AddDuration → DifferenceZonedDateTime → AddZonedDateTime
"call options.relativeTo.calendar.dateAdd",
"call options.relativeTo.timeZone.getPossibleInstantsFor",
// AddDuration → DifferenceZonedDateTime → NanosecondsToDays
"call options.relativeTo.timeZone.getOffsetNanosecondsFor",
"call options.relativeTo.timeZone.getOffsetNanosecondsFor",
// AddDuration → DifferenceZonedDateTime → NanosecondsToDays → AddZonedDateTime 1
"call options.relativeTo.timeZone.getPossibleInstantsFor",
// AddDuration → DifferenceZonedDateTime → NanosecondsToDays → AddZonedDateTime 2
"call options.relativeTo.timeZone.getPossibleInstantsFor",
"call options.relativeTo.calendar.dateUntil",
]);

const zonedRelativeTo = TemporalHelpers.propertyBagObserver(actual, {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (C) 2024 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: >
Rounding the resulting duration takes the time zone's UTC offset shifts
into account
includes: [temporalHelpers.js]
features: [Temporal]
---*/

const timeZone = TemporalHelpers.springForwardFallBackTimeZone();

// Based on a test case by Adam Shaw

{
// Date part of duration lands on skipped DST hour, causing disambiguation
const duration = new Temporal.Duration(0, 1, 0, 15, 12);
const relativeTo = new Temporal.ZonedDateTime(
950868000_000_000_000n /* = 2000-02-18T10Z */,
timeZone); /* = 2000-02-18T02-08 in local time */

TemporalHelpers.assertDuration(duration.round({ smallestUnit: "months", relativeTo }),
0, 2, 0, 0, 0, 0, 0, 0, 0, 0,
"1 month 15 days 12 hours should be exactly 1.5 months, which rounds up to 2 months");
TemporalHelpers.assertDuration(duration.round({ smallestUnit: "months", roundingMode: 'halfTrunc', relativeTo }),
0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
"1 month 15 days 12 hours should be exactly 1.5 months, which rounds down to 1 month");
}

{
// Month-only part of duration lands on skipped DST hour, should not cause
// disambiguation
const duration = new Temporal.Duration(0, 1, 0, 15);
const relativeTo = new Temporal.ZonedDateTime(
951991200_000_000_000n /* = 2000-03-02T10Z */,
timeZone); /* = 2000-03-02T02-08 in local time */

TemporalHelpers.assertDuration(duration.round({ smallestUnit: "months", relativeTo }),
0, 2, 0, 0, 0, 0, 0, 0, 0, 0,
"1 month 15 days should be exactly 1.5 months, which rounds up to 2 months");
TemporalHelpers.assertDuration(duration.round({ smallestUnit: "months", roundingMode: 'halfTrunc', relativeTo }),
0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
"1 month 15 days should be exactly 1.5 months, which rounds down to 1 month");
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ 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, 3);
assert.sameValue(calendar.dateAddCallCount, 2);
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (C) 2024 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: >
Throws a RangeError when custom calendar method returns inconsistent result
info: |
DifferenceZonedDateTime ( ... )
8. Repeat 3 times:
...
g. If _sign_ = 0, or _timeSign_ = 0, or _sign_ = _timeSign_, then
...
viii. Return ? CreateNormalizedDurationRecord(_dateDifference_.[[Years]],
_dateDifference_.[[Months]], _dateDifference_.[[Weeks]],
_dateDifference_.[[Days]], _norm_).
h. Set _dayCorrection_ to _dayCorrection_ + 1.
9. NOTE: This step is only reached when custom calendar or time zone methods
return inconsistent values.
10. Throw a *RangeError* exception.
features: [Temporal]
---*/

// Based partly on a test case by André Bargull

const duration1 = new Temporal.Duration(0, 0, /* weeks = */ 7, 0, /* hours = */ 12);
const duration2 = new Temporal.Duration(0, 0, 0, /* days = */ -1);

{
const tz = new (class extends Temporal.TimeZone {
getPossibleInstantsFor(dateTime) {
return super.getPossibleInstantsFor(dateTime.add({ days: 3 }));
}
})("UTC");

const relativeTo = new Temporal.ZonedDateTime(0n, tz);

assert.throws(RangeError, () => duration1.subtract(duration2, { relativeTo }),
"Calendar calculation where more than 2 days correction is needed should cause RangeError");
}

{
const cal = new (class extends Temporal.Calendar {
dateUntil(one, two, options) {
return super.dateUntil(one, two, options).negated();
}
})("iso8601");

const relativeTo = new Temporal.ZonedDateTime(0n, "UTC", cal);

assert.throws(RangeError, () => duration1.subtract(duration2, { relativeTo }),
"Calendar calculation causing mixed-sign values should cause RangeError");
}
Loading

0 comments on commit 984f3cc

Please sign in to comment.