Import SpiderMonkey Temporal tests

Temporal tests written for the SpiderMonkey implementation. Mostly
covers edge cases around mathematical operations and regression tests
for reported spec bugs.
This commit is contained in:
André Bargull 2022-07-05 12:48:26 +02:00 committed by Philip Chimento
parent 46c3823117
commit e292fb80de
5 changed files with 446 additions and 0 deletions

View File

@ -0,0 +1,77 @@
// Copyright (C) 2022 André Bargull. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.round
description: >
NanosecondsToDays computes with precise mathematical integers.
info: |
NanosecondsToDays ( nanoseconds, relativeTo )
...
14. If sign is 1, then
a. Repeat, while days > 0 and intermediateNs > endNs,
i. Set days to days - 1.
ii. ...
Ensure |days = days - 1| is exact and doesn't loose precision.
features: [Temporal]
---*/
var expectedDurationDays = [
Number.MAX_SAFE_INTEGER + 4, // 9007199254740996
Number.MAX_SAFE_INTEGER + 3, // 9007199254740994
Number.MAX_SAFE_INTEGER + 2, // 9007199254740992
Number.MAX_SAFE_INTEGER + 1, // 9007199254740992
Number.MAX_SAFE_INTEGER + 0, // 9007199254740991
Number.MAX_SAFE_INTEGER - 1, // 9007199254740990
Number.MAX_SAFE_INTEGER - 2, // 9007199254740989
Number.MAX_SAFE_INTEGER - 3, // 9007199254740988
Number.MAX_SAFE_INTEGER - 4, // 9007199254740987
Number.MAX_SAFE_INTEGER - 5, // 9007199254740986
];
// Intentionally not Test262Error to ensure assertions errors are propagated.
class StopExecution extends Error {}
var cal = new class extends Temporal.Calendar {
#dateUntil = 0;
dateUntil(one, two, options) {
if (++this.#dateUntil === 1) {
return Temporal.Duration.from({days: Number.MAX_SAFE_INTEGER + 4});
}
return super.dateUntil(one, two, options);
}
#dateAdd = 0;
dateAdd(date, duration, options) {
// Ensure we don't add too many days which would lead to creating an invalid date.
if (++this.#dateAdd === 3) {
assert.sameValue(duration.days, Number.MAX_SAFE_INTEGER + 4);
// The added days must be larger than 5 for the |intermediateNs > endNs| condition.
return super.dateAdd(date, "P6D", options);
}
// Ensure the duration days are exact.
if (this.#dateAdd > 3) {
if (!expectedDurationDays.length) {
throw new StopExecution();
}
assert.sameValue(duration.days, expectedDurationDays.shift());
// Add more than 5 for the |intermediateNs > endNs| condition.
return super.dateAdd(date, "P6D", options);
}
// Otherwise call the default implementation.
return super.dateAdd(date, duration, options);
}
}("iso8601");
var zoned = new Temporal.ZonedDateTime(0n, "UTC", cal);
var duration = Temporal.Duration.from({days: 5});
var options = {smallestUnit: "days", relativeTo: zoned};
assert.throws(StopExecution, () => duration.round(options));

View File

@ -0,0 +1,45 @@
// Copyright (C) 2022 André Bargull. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.round
description: >
NanosecondsToDays computes with precise mathematical integers.
info: |
NanosecondsToDays ( nanoseconds, relativeTo )
...
17. Repeat, while done is false,
...
c. If (nanoseconds - dayLengthNs) × sign 0, then
...
iii. Set days to days + sign.
Ensure |days = days + sign| is exact and doesn't loose precision.
features: [Temporal]
---*/
var cal = new class extends Temporal.Calendar {
#dateUntil = 0;
dateUntil(one, two, options) {
if (++this.#dateUntil === 1) {
return Temporal.Duration.from({days: Number.MAX_SAFE_INTEGER + 10});
}
return super.dateUntil(one, two, options);
}
#dateAdd = 0;
dateAdd(date, duration, options) {
if (++this.#dateAdd === 3) {
return super.dateAdd(date, "P1D", options);
}
return super.dateAdd(date, duration, options);
}
}("iso8601");
var zoned = new Temporal.ZonedDateTime(0n, "UTC", cal);
var duration = Temporal.Duration.from({days: 5});
var result = duration.round({smallestUnit: "days", relativeTo: zoned});
assert.sameValue(result.days, Number(BigInt(Number.MAX_SAFE_INTEGER + 10) + 5n));

View File

@ -0,0 +1,96 @@
// Copyright (C) 2022 André Bargull. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.duration.prototype.total
description: >
RoundDuration computes on exact mathematical values.
features: [Temporal]
---*/
// Return the next Number value in direction to +Infinity.
function nextUp(num) {
if (!Number.isFinite(num)) {
return num;
}
if (num === 0) {
return Number.MIN_VALUE;
}
var f64 = new Float64Array([num]);
var u64 = new BigUint64Array(f64.buffer);
u64[0] += (num < 0 ? -1n : 1n);
return f64[0];
}
// Return the next Number value in direction to -Infinity.
function nextDown(num) {
if (!Number.isFinite(num)) {
return num;
}
if (num === 0) {
return -Number.MIN_VALUE;
}
var f64 = new Float64Array([num]);
var u64 = new BigUint64Array(f64.buffer);
u64[0] += (num < 0 ? 1n : -1n);
return f64[0];
}
let duration = Temporal.Duration.from({
hours: 4000,
nanoseconds: 1,
});
let total = duration.total({unit: "hours"});
// From RoundDuration():
//
// 7. Let fractionalSeconds be nanoseconds × 10^-9 + microseconds × 10^-6 + milliseconds × 10^-3 + seconds.
// = nanoseconds × 10^-9
// = 1 × 10^-9
// = 10^-9
// = 0.000000001
//
// 13.a. Let fractionalHours be (fractionalSeconds / 60 + minutes) / 60 + hours.
// = (fractionalSeconds / 60) / 60 + 4000
// = 0.000000001 / 3600 + 4000
//
// 13.b. Set hours to RoundNumberToIncrement(fractionalHours, increment, roundingMode).
// = trunc(fractionalHours)
// = trunc(0.000000001 / 3600 + 4000)
// = 4000
//
// 13.c. Set remainder to fractionalHours - hours.
// = fractionalHours - hours
// = 0.000000001 / 3600 + 4000 - 4000
// = 0.000000001 / 3600
//
// From Temporal.Duration.prototype.total ( options ):
//
// 18. If unit is "hours", then let whole be roundResult.[[Hours]].
// ...
// 24. Return whole + roundResult.[[Remainder]].
//
// |whole| is 4000 and the remainder is (0.000000001 / 3600).
//
// 0.000000001 / 3600
// = (1 / 10^9) / 3600
// = (1 / 36) / 10^11
// = 0.02777.... / 10^11
// = 0.0000000000002777...
//
// 4000.0000000000002777... can't be represented exactly, the next best approximation
// is 4000.0000000000005.
const expected = 4000.0000000000005;
assert.sameValue(expected, 4000.0000000000002777);
// The next Number in direction -Infinity is less precise.
assert.sameValue(nextDown(expected), 4000);
// The next Number in direction +Infinity is less precise.
assert.sameValue(nextUp(expected), 4000.000000000001);
assert.sameValue(total, expected);

View File

@ -0,0 +1,98 @@
// Copyright (C) 2022 André Bargull. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.duration.prototype.total
description: >
RoundDuration computes on exact mathematical values.
features: [Temporal]
---*/
// Return the next Number value in direction to +Infinity.
function nextUp(num) {
if (!Number.isFinite(num)) {
return num;
}
if (num === 0) {
return Number.MIN_VALUE;
}
var f64 = new Float64Array([num]);
var u64 = new BigUint64Array(f64.buffer);
u64[0] += (num < 0 ? -1n : 1n);
return f64[0];
}
// Return the next Number value in direction to -Infinity.
function nextDown(num) {
if (!Number.isFinite(num)) {
return num;
}
if (num === 0) {
return -Number.MIN_VALUE;
}
var f64 = new Float64Array([num]);
var u64 = new BigUint64Array(f64.buffer);
u64[0] += (num < 0 ? 1n : -1n);
return f64[0];
}
let duration = Temporal.Duration.from({
hours: 4000,
minutes: 59,
seconds: 59,
milliseconds: 999,
microseconds: 999,
nanoseconds: 999,
});
let total = duration.total({unit: "hours"});
// From RoundDuration():
//
// 7. Let fractionalSeconds be nanoseconds × 10^-9 + microseconds × 10^-6 + milliseconds × 10^-3 + seconds.
// = 999 × 10^-9 + 999 × 10^-6 + 999 × 10^-3 + 59
// = 59.999'999'999
//
// 13.a. Let fractionalHours be (fractionalSeconds / 60 + minutes) / 60 + hours.
// = (59.999'999'999 / 60 + 59) / 60 + 4000
// = 1 - 0.000000001 / 3600 + 4000
//
// 13.b. Set hours to RoundNumberToIncrement(fractionalHours, increment, roundingMode).
// = trunc(fractionalHours)
// = trunc(1 - 0.000000001 / 3600 + 4000)
// = 4000
//
// 13.c. Set remainder to fractionalHours - hours.
// = fractionalHours - hours
// = 1 - 0.000000001 / 3600 + 4000 - 4000
// = 1 - 0.000000001 / 3600
//
// From Temporal.Duration.prototype.total ( options ):
//
// 18. If unit is "hours", then let whole be roundResult.[[Hours]].
// ...
// 24. Return whole + roundResult.[[Remainder]].
//
// |whole| is 4000 and the remainder is (1 - 0.000000001 / 3600).
//
// 1 - 0.000000001 / 3600
// = 1 - (1 / 10^9) / 3600
// = 1 - (1 / 36) / 10^11
// = 1 - 0.02777.... / 10^11
// = 0.9999999999997222...
//
// 4000.9999999999997222... can't be represented exactly, the next best approximation
// is 4000.9999999999995.
const expected = 4000.9999999999995;
assert.sameValue(expected, 4000.9999999999997222);
// The next Number in direction -Infinity is less precise.
assert.sameValue(nextDown(expected), 4000.999999999999);
// The next Number in direction +Infinity is less precise.
assert.sameValue(nextUp(expected), 4001);
assert.sameValue(total, expected);

View File

@ -0,0 +1,130 @@
// Copyright (C) 2022 André Bargull. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-get-temporal.zoneddatetime.prototype.hoursinday
description: >
Hours in day is correctly rounded using precise mathematical values.
info: |
get Temporal.ZonedDateTime.prototype.hoursInDay
...
15. Let diffNs be tomorrowInstant.[[Nanoseconds]] - todayInstant.[[Nanoseconds]].
16. Return 𝔽(diffNs / (3.6 × 10^12)).
features: [Temporal]
---*/
const maxInstant = 86_40000_00000_00000_00000n;
function nextUp(num) {
if (!Number.isFinite(num)) {
return num;
}
if (num === 0) {
return Number.MIN_VALUE;
}
var f64 = new Float64Array([num]);
var u64 = new BigUint64Array(f64.buffer);
u64[0] += (num < 0 ? -1n : 1n);
return f64[0];
}
function nextDown(num) {
if (!Number.isFinite(num)) {
return num;
}
if (num === 0) {
return -Number.MIN_VALUE;
}
var f64 = new Float64Array([num]);
var u64 = new BigUint64Array(f64.buffer);
u64[0] += (num < 0 ? 1n : -1n);
return f64[0];
}
function BigIntAbs(n) {
return n >= 0 ? n : -n;
}
function test(todayInstant, tomorrowInstant, expected) {
let timeZone = new class extends Temporal.TimeZone {
#getPossibleInstantsFor = 0;
getPossibleInstantsFor() {
if (++this.#getPossibleInstantsFor === 1) {
return [new Temporal.Instant(todayInstant)];
}
assert.sameValue(this.#getPossibleInstantsFor, 2);
return [new Temporal.Instant(tomorrowInstant)];
}
}("UTC");
let zdt = new Temporal.ZonedDateTime(0n, timeZone);
let zdt_hoursInDay = zdt.hoursInDay;
assert.sameValue(zdt_hoursInDay, expected);
// Ensure the |expected| value is actually correctly rounded.
const nsPerSec = 1000n * 1000n * 1000n;
const secPerHour = 60n * 60n;
const nsPerHour = secPerHour * nsPerSec;
function toNanoseconds(hours) {
let wholeHours = BigInt(Math.trunc(hours)) * nsPerHour;
let fractionalHours = BigInt(Math.trunc(hours % 1 * Number(nsPerHour)));
return wholeHours + fractionalHours;
}
let diff = tomorrowInstant - todayInstant;
let nanosInDay = toNanoseconds(zdt_hoursInDay);
// The next number gives a less precise result.
let next = toNanoseconds(nextUp(zdt_hoursInDay));
assert(BigIntAbs(diff - nanosInDay) <= BigIntAbs(diff - next));
// The previous number gives a less precise result.
let prev = toNanoseconds(nextDown(zdt_hoursInDay));
assert(BigIntAbs(diff - nanosInDay) <= BigIntAbs(diff - prev));
// This computation can be inaccurate.
let inaccurate = Number(diff) / Number(nsPerHour);
// Doing it component-wise can produce more accurate results.
let hours = Number(diff / nsPerSec) / Number(secPerHour);
let fractionalHours = Number(diff % nsPerSec) / Number(nsPerHour);
assert.sameValue(hours + fractionalHours, expected);
// Ensure the result is more precise than the inaccurate result.
let inaccurateNanosInDay = toNanoseconds(inaccurate);
assert(BigIntAbs(diff - nanosInDay) <= BigIntAbs(diff - inaccurateNanosInDay));
}
test(-maxInstant, 0n, 2400000000);
test(-maxInstant, 1n, 2400000000);
test(-maxInstant, 10n, 2400000000);
test(-maxInstant, 100n, 2400000000);
test(-maxInstant, 1_000n, 2400000000);
test(-maxInstant, 10_000n, 2400000000);
test(-maxInstant, 100_000n, 2400000000);
test(-maxInstant, 1_000_000n, 2400000000.0000005);
test(-maxInstant, 10_000_000n, 2400000000.000003);
test(-maxInstant, 100_000_000n, 2400000000.0000277);
test(-maxInstant, 1_000_000_000n, 2400000000.000278);
test(-maxInstant, 10_000_000_000n, 2400000000.0027776);
test(-maxInstant, 100_000_000_000n, 2400000000.0277777);
test(-maxInstant, maxInstant, 4800000000);
test(-maxInstant, maxInstant - 10_000_000_000n, 4799999999.997222);
test(-maxInstant, maxInstant - 10_000_000_000n + 1n, 4799999999.997222);
test(-maxInstant, maxInstant - 10_000_000_000n - 1n, 4799999999.997222);
test(maxInstant, -maxInstant, -4800000000);
test(maxInstant, -(maxInstant - 10_000_000_000n), -4799999999.997222);
test(maxInstant, -(maxInstant - 10_000_000_000n + 1n), -4799999999.997222);
test(maxInstant, -(maxInstant - 10_000_000_000n - 1n), -4799999999.997222);