mirror of https://github.com/tc39/test262.git
Update tests for "Limit valid values for DurationFormats to match upcoming limits in Temporal"
Update tests for <https://github.com/tc39/proposal-intl-duration-format/pull/173>.
This commit is contained in:
parent
0596ff6981
commit
ab809f8f0c
|
@ -0,0 +1,84 @@
|
|||
// Copyright (C) 2024 André Bargull. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
esid: sec-Intl.DurationFormat.prototype.format
|
||||
description: >
|
||||
IsValidDurationRecord rejects too large "years", "months", and "weeks" values.
|
||||
info: |
|
||||
Intl.DurationFormat.prototype.format ( duration )
|
||||
...
|
||||
3. Let record be ? ToDurationRecord(duration).
|
||||
...
|
||||
|
||||
ToDurationRecord ( input )
|
||||
...
|
||||
24. If IsValidDurationRecord(result) is false, throw a RangeError exception.
|
||||
...
|
||||
|
||||
IsValidDurationRecord ( record )
|
||||
...
|
||||
6. If abs(years) ≥ 2^32, return false.
|
||||
7. If abs(months) ≥ 2^32, return false.
|
||||
8. If abs(weeks) ≥ 2^32, return false.
|
||||
...
|
||||
|
||||
features: [Intl.DurationFormat]
|
||||
---*/
|
||||
|
||||
const df = new Intl.DurationFormat();
|
||||
|
||||
const units = [
|
||||
"years",
|
||||
"months",
|
||||
"weeks",
|
||||
];
|
||||
|
||||
const invalidValues = [
|
||||
2**32,
|
||||
2**32 + 1,
|
||||
Number.MAX_SAFE_INTEGER,
|
||||
Number.MAX_VALUE,
|
||||
];
|
||||
|
||||
const validValues = [
|
||||
2**32 - 1,
|
||||
];
|
||||
|
||||
for (let unit of units) {
|
||||
for (let value of invalidValues) {
|
||||
let positive = {[unit]: value};
|
||||
assert.throws(
|
||||
RangeError,
|
||||
() => df.format(positive),
|
||||
`Duration "${unit}" throws when value is ${value}`
|
||||
);
|
||||
|
||||
// Also test with flipped sign.
|
||||
let negative = {[unit]: -value};
|
||||
assert.throws(
|
||||
RangeError,
|
||||
() => df.format(negative),
|
||||
`Duration "${unit}" throws when value is ${-value}`
|
||||
);
|
||||
}
|
||||
|
||||
for (let value of validValues) {
|
||||
// We don't care about the exact contents of the returned string, the call
|
||||
// just shouldn't throw an exception.
|
||||
let positive = {[unit]: value};
|
||||
assert.sameValue(
|
||||
typeof df.format(positive),
|
||||
"string",
|
||||
`Duration "${unit}" doesn't throw when value is ${value}`
|
||||
);
|
||||
|
||||
// Also test with flipped sign.
|
||||
let negative = {[unit]: -value};
|
||||
assert.sameValue(
|
||||
typeof df.format(negative),
|
||||
"string",
|
||||
`Duration "${unit}" doesn't throw when value is ${-value}`
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
// Copyright (C) 2024 André Bargull. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
esid: sec-Intl.DurationFormat.prototype.format
|
||||
description: >
|
||||
IsValidDurationRecord rejects too large "days", "hours", ... values.
|
||||
info: |
|
||||
Intl.DurationFormat.prototype.format ( duration )
|
||||
...
|
||||
3. Let record be ? ToDurationRecord(duration).
|
||||
...
|
||||
|
||||
ToDurationRecord ( input )
|
||||
...
|
||||
24. If IsValidDurationRecord(result) is false, throw a RangeError exception.
|
||||
...
|
||||
|
||||
IsValidDurationRecord ( record )
|
||||
...
|
||||
16. Let normalizedSeconds be days × 86,400 + hours × 3600 + minutes × 60 + seconds +
|
||||
milliseconds × 10^-3 + microseconds × 10^-6 + nanoseconds × 10^-9.
|
||||
17. If abs(normalizedSeconds) ≥ 2^53, return false.
|
||||
...
|
||||
|
||||
features: [Intl.DurationFormat]
|
||||
---*/
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
let f64 = new Float64Array([num]);
|
||||
let u64 = new BigUint64Array(f64.buffer);
|
||||
u64[0] += (num < 0 ? 1n : -1n);
|
||||
return f64[0];
|
||||
}
|
||||
|
||||
const df = new Intl.DurationFormat();
|
||||
|
||||
const invalidValues = {
|
||||
days: [
|
||||
Math.ceil((Number.MAX_SAFE_INTEGER + 1) / 86400),
|
||||
],
|
||||
hours: [
|
||||
Math.ceil((Number.MAX_SAFE_INTEGER + 1) / 3600),
|
||||
],
|
||||
minutes: [
|
||||
Math.ceil((Number.MAX_SAFE_INTEGER + 1) / 60),
|
||||
],
|
||||
seconds: [
|
||||
Number.MAX_SAFE_INTEGER + 1,
|
||||
],
|
||||
milliseconds: [
|
||||
(Number.MAX_SAFE_INTEGER + 1) * 1e3,
|
||||
9007199254740992_000,
|
||||
],
|
||||
microseconds: [
|
||||
(Number.MAX_SAFE_INTEGER + 1) * 1e6,
|
||||
9007199254740992_000_000,
|
||||
],
|
||||
nanoseconds: [
|
||||
(Number.MAX_SAFE_INTEGER + 1) * 1e9,
|
||||
9007199254740992_000_000_000,
|
||||
],
|
||||
};
|
||||
|
||||
const validValues = {
|
||||
days: [
|
||||
Math.floor(Number.MAX_SAFE_INTEGER / 86400),
|
||||
],
|
||||
hours: [
|
||||
Math.floor(Number.MAX_SAFE_INTEGER / 3600),
|
||||
],
|
||||
minutes: [
|
||||
Math.floor(Number.MAX_SAFE_INTEGER / 60),
|
||||
],
|
||||
seconds: [
|
||||
Number.MAX_SAFE_INTEGER,
|
||||
],
|
||||
milliseconds: [
|
||||
Number.MAX_SAFE_INTEGER * 1e3,
|
||||
nextDown(9007199254740992_000),
|
||||
],
|
||||
microseconds: [
|
||||
Number.MAX_SAFE_INTEGER * 1e6,
|
||||
nextDown(9007199254740992_000_000),
|
||||
],
|
||||
nanoseconds: [
|
||||
Number.MAX_SAFE_INTEGER * 1e9,
|
||||
nextDown(9007199254740992_000_000_000),
|
||||
],
|
||||
};
|
||||
|
||||
for (let [unit, values] of Object.entries(invalidValues)) {
|
||||
for (let value of values) {
|
||||
let positive = {[unit]: value};
|
||||
assert.throws(
|
||||
RangeError,
|
||||
() => df.format(positive),
|
||||
`Duration "${unit}" throws when value is ${value}`
|
||||
);
|
||||
|
||||
// Also test with flipped sign.
|
||||
let negative = {[unit]: -value};
|
||||
assert.throws(
|
||||
RangeError,
|
||||
() => df.format(negative),
|
||||
`Duration "${unit}" throws when value is ${-value}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (let [unit, values] of Object.entries(validValues)) {
|
||||
for (let value of values) {
|
||||
// We don't care about the exact contents of the returned string, the call
|
||||
// just shouldn't throw an exception.
|
||||
let positive = {[unit]: value};
|
||||
assert.sameValue(
|
||||
typeof df.format(positive),
|
||||
"string",
|
||||
`Duration "${unit}" doesn't throw when value is ${value}`
|
||||
);
|
||||
|
||||
// Also test with flipped sign.
|
||||
let negative = {[unit]: -value};
|
||||
assert.sameValue(
|
||||
typeof df.format(negative),
|
||||
"string",
|
||||
`Duration "${unit}" doesn't throw when value is ${-value}`
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
// Copyright (C) 2024 André Bargull. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
esid: sec-Intl.DurationFormat.prototype.format
|
||||
description: >
|
||||
IsValidDurationRecord rejects too large time duration units.
|
||||
info: |
|
||||
Intl.DurationFormat.prototype.format ( duration )
|
||||
...
|
||||
3. Let record be ? ToDurationRecord(duration).
|
||||
...
|
||||
|
||||
ToDurationRecord ( input )
|
||||
...
|
||||
24. If IsValidDurationRecord(result) is false, throw a RangeError exception.
|
||||
...
|
||||
|
||||
IsValidDurationRecord ( record )
|
||||
...
|
||||
16. Let normalizedSeconds be days × 86,400 + hours × 3600 + minutes × 60 + seconds +
|
||||
milliseconds × 10^-3 + microseconds × 10^-6 + nanoseconds × 10^-9.
|
||||
17. If abs(normalizedSeconds) ≥ 2^53, return false.
|
||||
...
|
||||
|
||||
features: [Intl.DurationFormat]
|
||||
---*/
|
||||
|
||||
// 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];
|
||||
}
|
||||
|
||||
// Negate |duration| similar to Temporal.Duration.prototype.negated.
|
||||
function negatedDuration(duration) {
|
||||
let result = {...duration};
|
||||
for (let key of Object.keys(result)) {
|
||||
// Add +0 to normalize -0 to +0.
|
||||
result[key] = -result[key] + 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function fromNanoseconds(unit, value) {
|
||||
switch (unit) {
|
||||
case "days":
|
||||
return value / (86400n * 1_000_000_000n);
|
||||
case "hours":
|
||||
return value / (3600n * 1_000_000_000n);
|
||||
case "minutes":
|
||||
return value / (60n * 1_000_000_000n);
|
||||
case "seconds":
|
||||
return value / 1_000_000_000n;
|
||||
case "milliseconds":
|
||||
return value / 1_000_000n;
|
||||
case "microseconds":
|
||||
return value / 1_000n;
|
||||
case "nanoseconds":
|
||||
return value;
|
||||
}
|
||||
throw new Error("invalid unit:" + unit);
|
||||
}
|
||||
|
||||
function toNanoseconds(unit, value) {
|
||||
switch (unit) {
|
||||
case "days":
|
||||
return value * 86400n * 1_000_000_000n;
|
||||
case "hours":
|
||||
return value * 3600n * 1_000_000_000n;
|
||||
case "minutes":
|
||||
return value * 60n * 1_000_000_000n;
|
||||
case "seconds":
|
||||
return value * 1_000_000_000n;
|
||||
case "milliseconds":
|
||||
return value * 1_000_000n;
|
||||
case "microseconds":
|
||||
return value * 1_000n;
|
||||
case "nanoseconds":
|
||||
return value;
|
||||
}
|
||||
throw new Error("invalid unit:" + unit);
|
||||
}
|
||||
|
||||
const df = new Intl.DurationFormat();
|
||||
|
||||
const units = [
|
||||
"days",
|
||||
"hours",
|
||||
"minutes",
|
||||
"seconds",
|
||||
"milliseconds",
|
||||
"microseconds",
|
||||
"nanoseconds",
|
||||
];
|
||||
|
||||
const zeroDuration = {
|
||||
days: 0,
|
||||
hours: 0,
|
||||
minutes: 0,
|
||||
seconds: 0,
|
||||
milliseconds: 0,
|
||||
microseconds: 0,
|
||||
nanoseconds: 0,
|
||||
};
|
||||
|
||||
const maxTimeDuration = BigInt(Number.MAX_SAFE_INTEGER) * 1_000_000_000n + 999_999_999n;
|
||||
|
||||
// Iterate over all time duration units and create the largest possible duration.
|
||||
for (let i = 0; i < units.length; ++i) {
|
||||
let unit = units[i];
|
||||
|
||||
// Test not only the next smallest unit, but all smaller units.
|
||||
for (let j = i + 1; j < units.length; ++j) {
|
||||
// Maximum duration value for |unit|.
|
||||
let maxUnit = fromNanoseconds(unit, maxTimeDuration);
|
||||
|
||||
// Adjust |maxUnit| when the value is too large for Number.
|
||||
let adjusted = BigInt(Number(maxUnit));
|
||||
if (adjusted <= maxUnit) {
|
||||
maxUnit = adjusted;
|
||||
} else {
|
||||
maxUnit = BigInt(nextDown(Number(maxUnit)));
|
||||
}
|
||||
|
||||
// Remaining number of nanoseconds.
|
||||
let remaining = maxTimeDuration - toNanoseconds(unit, maxUnit);
|
||||
|
||||
// Create the maximum valid duration.
|
||||
let maxDuration = {
|
||||
...zeroDuration,
|
||||
[unit]: Number(maxUnit),
|
||||
};
|
||||
for (let k = j; k < units.length; ++k) {
|
||||
let smallerUnit = units[k];
|
||||
|
||||
// Remaining number of nanoseconds in |smallerUnit|.
|
||||
let remainingSmallerUnit = fromNanoseconds(smallerUnit, remaining);
|
||||
maxDuration[smallerUnit] = Number(remainingSmallerUnit);
|
||||
|
||||
remaining -= toNanoseconds(smallerUnit, remainingSmallerUnit);
|
||||
}
|
||||
assert.sameValue(remaining, 0n, "zero remaining nanoseconds");
|
||||
|
||||
// We don't care about the exact contents of the returned string, the call
|
||||
// just shouldn't throw an exception.
|
||||
assert.sameValue(
|
||||
typeof df.format(maxDuration),
|
||||
"string",
|
||||
`Duration "${JSON.stringify(maxDuration)}" doesn't throw`
|
||||
);
|
||||
|
||||
// Also test with flipped sign.
|
||||
let minDuration = negatedDuration(maxDuration);
|
||||
|
||||
// We don't care about the exact contents of the returned string, the call
|
||||
// just shouldn't throw an exception.
|
||||
assert.sameValue(
|
||||
typeof df.format(minDuration),
|
||||
"string",
|
||||
`Duration "${JSON.stringify(minDuration)}" doesn't throw`
|
||||
);
|
||||
|
||||
// Adding a single nanoseconds creates a too large duration.
|
||||
let tooLargeDuration = {
|
||||
...maxDuration,
|
||||
nanoseconds: maxDuration.nanoseconds + 1,
|
||||
};
|
||||
|
||||
assert.throws(
|
||||
RangeError,
|
||||
() => df.format(tooLargeDuration),
|
||||
`Duration "${JSON.stringify(tooLargeDuration)}" throws`
|
||||
);
|
||||
|
||||
// Also test with flipped sign.
|
||||
let tooSmallDuration = negatedDuration(tooLargeDuration);
|
||||
|
||||
assert.throws(
|
||||
RangeError,
|
||||
() => df.format(tooSmallDuration),
|
||||
`Duration "${JSON.stringify(tooSmallDuration)}" throws`
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
// Copyright (C) 2024 André Bargull. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
esid: sec-Intl.DurationFormat.prototype.format
|
||||
description: >
|
||||
IsValidDurationRecord rejects too large time duration units.
|
||||
info: |
|
||||
Intl.DurationFormat.prototype.format ( duration )
|
||||
...
|
||||
3. Let record be ? ToDurationRecord(duration).
|
||||
...
|
||||
|
||||
ToDurationRecord ( input )
|
||||
...
|
||||
24. If IsValidDurationRecord(result) is false, throw a RangeError exception.
|
||||
...
|
||||
|
||||
IsValidDurationRecord ( record )
|
||||
...
|
||||
16. Let normalizedSeconds be days × 86,400 + hours × 3600 + minutes × 60 + seconds +
|
||||
milliseconds × 10^-3 + microseconds × 10^-6 + nanoseconds × 10^-9.
|
||||
17. If abs(normalizedSeconds) ≥ 2^53, return false.
|
||||
...
|
||||
|
||||
features: [Intl.DurationFormat]
|
||||
---*/
|
||||
|
||||
const df = new Intl.DurationFormat();
|
||||
|
||||
const duration = {
|
||||
// Actual value is: 4503599627370497024
|
||||
milliseconds: 4503599627370497_000,
|
||||
|
||||
// Actual value is: 4503599627370494951424
|
||||
microseconds: 4503599627370495_000000,
|
||||
};
|
||||
|
||||
// The naive approach to compute the duration seconds leads to an incorrect result.
|
||||
let durationSecondsNaive = Math.trunc(duration.milliseconds / 1e3 + duration.microseconds / 1e6);
|
||||
assert.sameValue(
|
||||
Number.isSafeInteger(durationSecondsNaive),
|
||||
false,
|
||||
"Naive approach incorrectly computes duration seconds as out-of-range"
|
||||
);
|
||||
|
||||
// The exact approach to compute the duration seconds leads to the correct result.
|
||||
let durationSecondsExact = Number(BigInt(duration.milliseconds) / 1_000n) +
|
||||
Number(BigInt(duration.microseconds) / 1_000_000n) +
|
||||
Math.trunc(((duration.milliseconds % 1e3) * 1e3 + (duration.microseconds % 1e6)) / 1e6);
|
||||
assert.sameValue(
|
||||
Number.isSafeInteger(Number(durationSecondsExact)),
|
||||
true,
|
||||
"Exact approach correctly computes duration seconds as in-range"
|
||||
);
|
||||
|
||||
// We don't care about the exact contents of the returned string, the call
|
||||
// just shouldn't throw an exception.
|
||||
assert.sameValue(
|
||||
typeof df.format(duration),
|
||||
"string",
|
||||
`Duration "${JSON.stringify(duration)}" doesn't throw`
|
||||
);
|
|
@ -37,21 +37,6 @@ const durations = [
|
|||
nanoseconds: 1,
|
||||
},
|
||||
|
||||
// 9007199254740991 + (9007199254740991 / 10^3) + (9007199254740991 / 10^6) + (9007199254740991 / 10^9)
|
||||
// = 9.016215470202185986731991 × 10^15
|
||||
{
|
||||
seconds: Number.MAX_SAFE_INTEGER,
|
||||
milliseconds: Number.MAX_SAFE_INTEGER,
|
||||
microseconds: Number.MAX_SAFE_INTEGER,
|
||||
nanoseconds: Number.MAX_SAFE_INTEGER,
|
||||
},
|
||||
{
|
||||
seconds: Number.MIN_SAFE_INTEGER,
|
||||
milliseconds: Number.MIN_SAFE_INTEGER,
|
||||
microseconds: Number.MIN_SAFE_INTEGER,
|
||||
nanoseconds: Number.MIN_SAFE_INTEGER,
|
||||
},
|
||||
|
||||
// 1 + (2 / 10^3) + (3 / 10^6) + (9007199254740991 / 10^9)
|
||||
// = 9.007200256743991 × 10^6
|
||||
{
|
||||
|
@ -61,23 +46,15 @@ const durations = [
|
|||
nanoseconds: Number.MAX_SAFE_INTEGER,
|
||||
},
|
||||
|
||||
// 9007199254740991 + (10^3 / 10^3) + (10^6 / 10^6) + (10^9 / 10^9)
|
||||
// = 9007199254740991 + 3
|
||||
// = 9007199254740994
|
||||
// (4503599627370497024 / 10^3) + (4503599627370494951424 / 10^6)
|
||||
// = 4503599627370497.024 + 4503599627370494.951424
|
||||
// = 9007199254740991.975424
|
||||
{
|
||||
seconds: Number.MAX_SAFE_INTEGER,
|
||||
milliseconds: 10 ** 3,
|
||||
microseconds: 10 ** 6,
|
||||
nanoseconds: 10 ** 9,
|
||||
},
|
||||
// Actual value is: 4503599627370497024
|
||||
milliseconds: 4503599627370497_000,
|
||||
|
||||
// ~1.7976931348623157e+308 / 10^9
|
||||
// = ~1.7976931348623157 × 10^299
|
||||
{
|
||||
seconds: 0,
|
||||
milliseconds: 0,
|
||||
microseconds: 0,
|
||||
nanoseconds: Number.MAX_VALUE,
|
||||
// Actual value is: 4503599627370494951424
|
||||
microseconds: 4503599627370495_000000,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
Loading…
Reference in New Issue