diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:35:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:35:29 +0000 |
commit | 59203c63bb777a3bacec32fb8830fba33540e809 (patch) | |
tree | 58298e711c0ff0575818c30485b44a2f21bf28a0 /js/src/tests/test262/built-ins/Temporal/ZonedDateTime | |
parent | Adding upstream version 126.0.1. (diff) | |
download | firefox-59203c63bb777a3bacec32fb8830fba33540e809.tar.xz firefox-59203c63bb777a3bacec32fb8830fba33540e809.zip |
Adding upstream version 127.0.upstream/127.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/tests/test262/built-ins/Temporal/ZonedDateTime')
79 files changed, 2934 insertions, 286 deletions
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-calendar-iso-string.js new file mode 100644 index 0000000000..3a440b4994 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-calendar-iso-string.js @@ -0,0 +1,30 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.compare +description: An ISO 8601 string can be converted to a calendar ID in Calendar +features: [Temporal] +---*/ + +const datetime = new Temporal.ZonedDateTime(0n, "UTC"); + +for (const calendar of [ + "2020-01-01", + "2020-01-01[u-ca=iso8601]", + "2020-01-01T00:00:00.000000000", + "2020-01-01T00:00:00.000000000[u-ca=iso8601]", + "01-01", + "01-01[u-ca=iso8601]", + "2020-01", + "2020-01[u-ca=iso8601]", +]) { + const arg = { year: 1970, monthCode: "M01", day: 1, timeZone: "UTC", calendar }; + const result1 = Temporal.ZonedDateTime.compare(arg, datetime); + assert.sameValue(result1, 0, `Calendar created from string "${calendar}" (first argument)`); + const result2 = Temporal.ZonedDateTime.compare(datetime, arg); + assert.sameValue(result2, 0, `Calendar created from string "${calendar}" (second argument)`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-out-of-range-backward-offset-shift.js new file mode 100644 index 0000000000..9e09864074 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-out-of-range-backward-offset-shift.js @@ -0,0 +1,51 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.compare +description: > + UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours. +features: [Temporal] +info: | + GetPossibleInstantsFor: + 5.b.i. Let _numResults_ be _list_'s length. + ii. If _numResults_ > 1, then + 1. Let _epochNs_ be a new empty List. + 2. For each value _instant_ in list, do + a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_. + 3. Let _min_ be the least element of the List _epochNs_. + 4. Let _max_ be the greatest element of the List _epochNs_. + 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception. +---*/ + +class ShiftLonger24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + return 0; + } + + getPossibleInstantsFor(plainDateTime) { + const utc = new Temporal.TimeZone("UTC"); + const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime); + return [ + utcInstant.subtract({ hours: 12, nanoseconds: 1 }), + utcInstant.add({ hours: 12 }), + utcInstant, // add a third value in case the implementation doesn't sort + ]; + } +} + +const timeZone = new ShiftLonger24Hour(); +const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone }; +const datetime = new Temporal.ZonedDateTime(0n, timeZone); + +assert.throws(RangeError, () => Temporal.ZonedDateTime.compare(arg, datetime), "RangeError should be thrown (first argument)"); +assert.throws(RangeError, () => Temporal.ZonedDateTime.compare(datetime, arg), "RangeError should be thrown (second argument)"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-out-of-range-forward-offset-shift.js new file mode 100644 index 0000000000..d2fe4ab99f --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-out-of-range-forward-offset-shift.js @@ -0,0 +1,46 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.compare +description: > + UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor + in DisambiguatePossibleInstants cannot be greater than 24 hours. +features: [Temporal] +info: | + DisambiguatePossibleInstants: + 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception. +---*/ + +class ShiftLonger24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9; + return 12 * 3600e9 + 1; + } + + getPossibleInstantsFor(plainDateTime) { + const [utcInstant] = super.getPossibleInstantsFor(plainDateTime); + const { year, month, day } = plainDateTime; + + if (year < 1970) return [utcInstant.subtract({ hours: 12 })]; + if (year === 1970 && month === 1 && day === 1) return []; + return [utcInstant.add({ hours: 12, nanoseconds: 1 })]; + } +} + +const timeZone = new ShiftLonger24Hour(); +const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone }; +const datetime = new Temporal.ZonedDateTime(0n, timeZone); + +assert.throws(RangeError, () => Temporal.ZonedDateTime.compare(arg, datetime), "RangeError should be thrown (first argument)"); +assert.throws(RangeError, () => Temporal.ZonedDateTime.compare(datetime, arg), "RangeError should be thrown (second argument)"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/compare/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/compare/argument-string-calendar-annotation-invalid-key.js new file mode 100644 index 0000000000..a3f48aa25a --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/compare/argument-string-calendar-annotation-invalid-key.js @@ -0,0 +1,32 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.compare +description: Annotation keys are lowercase-only +features: [Temporal] +---*/ + +const datetime = new Temporal.ZonedDateTime(0n, "UTC"); + +const invalidStrings = [ + ["1970-01-01T00:00[UTC][U-CA=iso8601]", "invalid capitalized key"], + ["1970-01-01T00:00[UTC][u-CA=iso8601]", "invalid partially-capitalized key"], + ["1970-01-01T00:00[UTC][FOO=bar]", "invalid capitalized unrecognized key"], +]; + +invalidStrings.forEach(([arg, descr]) => { + assert.throws( + RangeError, + () => Temporal.ZonedDateTime.compare(arg, datetime), + `annotation keys must be lowercase: ${arg} - ${descr} (first argument)` + ); + assert.throws( + RangeError, + () => Temporal.ZonedDateTime.compare(datetime, arg), + `annotation keys must be lowercase: ${arg} - ${descr} (second argument)` + ); +}); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-calendar-iso-string.js new file mode 100644 index 0000000000..ea5074b440 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-calendar-iso-string.js @@ -0,0 +1,28 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.from +description: An ISO 8601 string can be converted to a calendar ID in Calendar +features: [Temporal] +---*/ + +for (const calendar of [ + "2020-01-01", + "2020-01-01[u-ca=iso8601]", + "2020-01-01T00:00:00.000000000", + "2020-01-01T00:00:00.000000000[u-ca=iso8601]", + "01-01", + "01-01[u-ca=iso8601]", + "2020-01", + "2020-01[u-ca=iso8601]", +]) { + const timeZone = new Temporal.TimeZone("UTC"); +const arg = { year: 1970, monthCode: "M01", day: 1, timeZone, calendar }; + const result = Temporal.ZonedDateTime.from(arg); + assert.sameValue(result.calendarId, "iso8601", `Calendar created from string "${calendar}"`); + assert.sameValue(result.getISOFields().calendar, "iso8601", "calendar slot stores a string"); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-calendar-year-zero.js index d736edeb21..2741d61844 100644 --- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-calendar-year-zero.js +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-calendar-year-zero.js @@ -16,7 +16,8 @@ const invalidStrings = [ "-000000-10-31T17:45+00:00[UTC]", ]; -invalidStrings.forEach((arg) => { +invalidStrings.forEach((str) => { + const arg = { year: 1976, month: 11, day: 18, calendar: str }; assert.throws( RangeError, () => Temporal.ZonedDateTime.from(arg), diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-out-of-range-backward-offset-shift.js new file mode 100644 index 0000000000..1c4da44c1b --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-out-of-range-backward-offset-shift.js @@ -0,0 +1,49 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.from +description: > + UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours. +features: [Temporal] +info: | + GetPossibleInstantsFor: + 5.b.i. Let _numResults_ be _list_'s length. + ii. If _numResults_ > 1, then + 1. Let _epochNs_ be a new empty List. + 2. For each value _instant_ in list, do + a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_. + 3. Let _min_ be the least element of the List _epochNs_. + 4. Let _max_ be the greatest element of the List _epochNs_. + 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception. +---*/ + +class ShiftLonger24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + return 0; + } + + getPossibleInstantsFor(plainDateTime) { + const utc = new Temporal.TimeZone("UTC"); + const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime); + return [ + utcInstant.subtract({ hours: 12, nanoseconds: 1 }), + utcInstant.add({ hours: 12 }), + utcInstant, // add a third value in case the implementation doesn't sort + ]; + } +} + +const timeZone = new ShiftLonger24Hour(); +const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone }; + +assert.throws(RangeError, () => Temporal.ZonedDateTime.from(arg), "RangeError should be thrown"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-out-of-range-forward-offset-shift.js new file mode 100644 index 0000000000..fad0037aae --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-out-of-range-forward-offset-shift.js @@ -0,0 +1,44 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.from +description: > + UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor + in DisambiguatePossibleInstants cannot be greater than 24 hours. +features: [Temporal] +info: | + DisambiguatePossibleInstants: + 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception. +---*/ + +class ShiftLonger24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9; + return 12 * 3600e9 + 1; + } + + getPossibleInstantsFor(plainDateTime) { + const [utcInstant] = super.getPossibleInstantsFor(plainDateTime); + const { year, month, day } = plainDateTime; + + if (year < 1970) return [utcInstant.subtract({ hours: 12 })]; + if (year === 1970 && month === 1 && day === 1) return []; + return [utcInstant.add({ hours: 12, nanoseconds: 1 })]; + } +} + +const timeZone = new ShiftLonger24Hour(); +const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone }; + +assert.throws(RangeError, () => Temporal.ZonedDateTime.from(arg), "RangeError should be thrown"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-string-calendar-annotation-invalid-key.js new file mode 100644 index 0000000000..1ea7b9aea4 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-string-calendar-annotation-invalid-key.js @@ -0,0 +1,32 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.from +description: Annotation keys are lowercase-only +features: [Temporal] +---*/ + +const invalidStrings = [ + ["1970-01-01T00:00[UTC][U-CA=iso8601]", "invalid capitalized key"], + ["1970-01-01T00:00[UTC][u-CA=iso8601]", "invalid partially-capitalized key"], + ["1970-01-01T00:00[UTC][FOO=bar]", "invalid capitalized unrecognized key"], +]; + +invalidStrings.forEach(([arg, descr]) => { + assert.throws( + RangeError, + () => Temporal.ZonedDateTime.from(arg), + `annotation keys must be lowercase: ${arg} - ${descr}` + ); + for (const offset of ["use", "prefer", "ignore", "reject"]) { + assert.throws( + RangeError, + () => Temporal.ZonedDateTime.from(arg, { offset }), + `annotation keys must be lowercase: ${arg} - ${descr} (offset ${offset})` + ); + } +}); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-calendar-iso-string.js new file mode 100644 index 0000000000..4ee800c7bc --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-calendar-iso-string.js @@ -0,0 +1,29 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.equals +description: An ISO 8601 string can be converted to a calendar ID in Calendar +features: [Temporal] +---*/ + +const timeZone = new Temporal.TimeZone("UTC"); +const instance = new Temporal.ZonedDateTime(0n, timeZone); + +for (const calendar of [ + "2020-01-01", + "2020-01-01[u-ca=iso8601]", + "2020-01-01T00:00:00.000000000", + "2020-01-01T00:00:00.000000000[u-ca=iso8601]", + "01-01", + "01-01[u-ca=iso8601]", + "2020-01", + "2020-01[u-ca=iso8601]", +]) { + const arg = { year: 1970, monthCode: "M01", day: 1, timeZone, calendar }; + const result = instance.equals(arg); + assert.sameValue(result, true, `Calendar created from string "${calendar}"`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-calendar-year-zero.js index c0230f2f3e..196e992b08 100644 --- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-calendar-year-zero.js +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-calendar-year-zero.js @@ -17,7 +17,8 @@ const invalidStrings = [ ]; const timeZone = new Temporal.TimeZone("UTC"); const instance = new Temporal.ZonedDateTime(0n, timeZone); -invalidStrings.forEach((arg) => { +invalidStrings.forEach((str) => { + const arg = { year: 1976, month: 11, day: 18, calendar: str }; assert.throws( RangeError, () => instance.equals(arg), diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-out-of-range-backward-offset-shift.js new file mode 100644 index 0000000000..bf311b856d --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-out-of-range-backward-offset-shift.js @@ -0,0 +1,50 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.equals +description: > + UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours. +features: [Temporal] +info: | + GetPossibleInstantsFor: + 5.b.i. Let _numResults_ be _list_'s length. + ii. If _numResults_ > 1, then + 1. Let _epochNs_ be a new empty List. + 2. For each value _instant_ in list, do + a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_. + 3. Let _min_ be the least element of the List _epochNs_. + 4. Let _max_ be the greatest element of the List _epochNs_. + 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception. +---*/ + +class ShiftLonger24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + return 0; + } + + getPossibleInstantsFor(plainDateTime) { + const utc = new Temporal.TimeZone("UTC"); + const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime); + return [ + utcInstant.subtract({ hours: 12, nanoseconds: 1 }), + utcInstant.add({ hours: 12 }), + utcInstant, // add a third value in case the implementation doesn't sort + ]; + } +} + +const timeZone = new ShiftLonger24Hour(); +const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone }; + +const instance = new Temporal.ZonedDateTime(0n, timeZone); +assert.throws(RangeError, () => instance.equals(arg), "RangeError should be thrown"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-out-of-range-forward-offset-shift.js new file mode 100644 index 0000000000..8b27e78eff --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-out-of-range-forward-offset-shift.js @@ -0,0 +1,45 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.equals +description: > + UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor + in DisambiguatePossibleInstants cannot be greater than 24 hours. +features: [Temporal] +info: | + DisambiguatePossibleInstants: + 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception. +---*/ + +class ShiftLonger24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9; + return 12 * 3600e9 + 1; + } + + getPossibleInstantsFor(plainDateTime) { + const [utcInstant] = super.getPossibleInstantsFor(plainDateTime); + const { year, month, day } = plainDateTime; + + if (year < 1970) return [utcInstant.subtract({ hours: 12 })]; + if (year === 1970 && month === 1 && day === 1) return []; + return [utcInstant.add({ hours: 12, nanoseconds: 1 })]; + } +} + +const timeZone = new ShiftLonger24Hour(); +const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone }; + +const instance = new Temporal.ZonedDateTime(0n, timeZone); +assert.throws(RangeError, () => instance.equals(arg), "RangeError should be thrown"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-string-calendar-annotation-invalid-key.js new file mode 100644 index 0000000000..89b0d4d509 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-string-calendar-annotation-invalid-key.js @@ -0,0 +1,25 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.equals +description: Annotation keys are lowercase-only +features: [Temporal] +---*/ + +const invalidStrings = [ + ["1970-01-01T00:00[UTC][U-CA=iso8601]", "invalid capitalized key"], + ["1970-01-01T00:00[UTC][u-CA=iso8601]", "invalid partially-capitalized key"], + ["1970-01-01T00:00[UTC][FOO=bar]", "invalid capitalized unrecognized key"], +]; +const instance = new Temporal.ZonedDateTime(0n, "UTC"); +invalidStrings.forEach(([arg, descr]) => { + assert.throws( + RangeError, + () => instance.equals(arg), + `annotation keys must be lowercase: ${arg} - ${descr}` + ); +}); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getoffsetnanosecondsfor-maximum-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getoffsetnanosecondsfor-maximum-forward-offset-shift.js new file mode 100644 index 0000000000..f41e0be158 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getoffsetnanosecondsfor-maximum-forward-offset-shift.js @@ -0,0 +1,49 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.hoursinday +description: > + UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor + in DisambiguatePossibleInstants can be at most 24 hours. +features: [Temporal] +info: | + DisambiguatePossibleInstants: + 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception. +---*/ + +let calls = 0; + +class Shift24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + calls++; + if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9; + return 12 * 3600e9; + } + + getPossibleInstantsFor(plainDateTime) { + const [utcInstant] = super.getPossibleInstantsFor(plainDateTime); + const { year, month, day } = plainDateTime; + + if (year < 1970) return [utcInstant.subtract({ hours: 12 })]; + if (year === 1970 && month === 1 && day === 1) return []; + return [utcInstant.add({ hours: 12 })]; + } +} + +const timeZone = new Shift24Hour(); + +const instance = new Temporal.ZonedDateTime(0n, timeZone); +instance.hoursInDay; + +assert(calls >= 2, "getOffsetNanosecondsFor should be called at least twice"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js new file mode 100644 index 0000000000..7a41b9456e --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js @@ -0,0 +1,44 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.hoursinday +description: > + UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor + in DisambiguatePossibleInstants cannot be greater than 24 hours. +features: [Temporal] +info: | + DisambiguatePossibleInstants: + 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception. +---*/ + +class ShiftLonger24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9; + return 12 * 3600e9 + 1; + } + + getPossibleInstantsFor(plainDateTime) { + const [utcInstant] = super.getPossibleInstantsFor(plainDateTime); + const { year, month, day } = plainDateTime; + + if (year < 1970) return [utcInstant.subtract({ hours: 12 })]; + if (year === 1970 && month === 1 && day === 1) return []; + return [utcInstant.add({ hours: 12, nanoseconds: 1 })]; + } +} + +const timeZone = new ShiftLonger24Hour(); + +const instance = new Temporal.ZonedDateTime(0n, timeZone); +assert.throws(RangeError, () => instance.hoursInDay, "RangeError should be thrown"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getpossibleinstantsfor-maximum-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getpossibleinstantsfor-maximum-backward-offset-shift.js new file mode 100644 index 0000000000..8312ef765b --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getpossibleinstantsfor-maximum-backward-offset-shift.js @@ -0,0 +1,53 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.hoursinday +description: > + UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours. +features: [Temporal] +info: | + GetPossibleInstantsFor: + 5.b.i. Let _numResults_ be _list_'s length. + ii. If _numResults_ > 1, then + 1. Let _epochNs_ be a new empty List. + 2. For each value _instant_ in list, do + a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_. + 3. Let _min_ be the least element of the List _epochNs_. + 4. Let _max_ be the greatest element of the List _epochNs_. + 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception. +---*/ + +let calls = 0; + +class Shift24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + return 0; + } + + getPossibleInstantsFor(plainDateTime) { + calls++; + const utc = new Temporal.TimeZone("UTC"); + const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime); + return [ + utcInstant.subtract({ hours: 12 }), + utcInstant.add({ hours: 12 }) + ]; + } +} + +const timeZone = new Shift24Hour(); + +const instance = new Temporal.ZonedDateTime(0n, timeZone); +instance.hoursInDay; + +assert(calls >= 1, "getPossibleInstantsFor should be called at least once"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getpossibleinstantsfor-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getpossibleinstantsfor-out-of-range-backward-offset-shift.js new file mode 100644 index 0000000000..566b61b2b0 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getpossibleinstantsfor-out-of-range-backward-offset-shift.js @@ -0,0 +1,49 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.hoursinday +description: > + UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours. +features: [Temporal] +info: | + GetPossibleInstantsFor: + 5.b.i. Let _numResults_ be _list_'s length. + ii. If _numResults_ > 1, then + 1. Let _epochNs_ be a new empty List. + 2. For each value _instant_ in list, do + a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_. + 3. Let _min_ be the least element of the List _epochNs_. + 4. Let _max_ be the greatest element of the List _epochNs_. + 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception. +---*/ + +class ShiftLonger24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + return 0; + } + + getPossibleInstantsFor(plainDateTime) { + const utc = new Temporal.TimeZone("UTC"); + const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime); + return [ + utcInstant.subtract({ hours: 12, nanoseconds: 1 }), + utcInstant.add({ hours: 12 }), + utcInstant, // add a third value in case the implementation doesn't sort + ]; + } +} + +const timeZone = new ShiftLonger24Hour(); + +const instance = new Temporal.ZonedDateTime(0n, timeZone); +assert.throws(RangeError, () => instance.hoursInDay, "RangeError should be thrown"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/div-zero.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/div-zero.js index 776cb7454f..dd912f46d4 100644 --- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/div-zero.js +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/div-zero.js @@ -18,10 +18,8 @@ class TimeZone extends Temporal.TimeZone { } } -const units = ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"]; -for (const smallestUnit of units) { - const zdt = new Temporal.ZonedDateTime(0n, new TimeZone("UTC")); - assert.throws(RangeError, () => zdt.round({ smallestUnit, roundingIncrement: 2 }), `zero day-length with smallestUnit ${smallestUnit}`); -} +const zdt = new Temporal.ZonedDateTime(0n, new TimeZone("UTC")); + +assert.throws(RangeError, () => zdt.round({ smallestUnit: "day" }), `zero day-length with smallestUnit 'day'`); reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getoffsetnanosecondsfor-maximum-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getoffsetnanosecondsfor-maximum-forward-offset-shift.js new file mode 100644 index 0000000000..1d3a5425d0 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getoffsetnanosecondsfor-maximum-forward-offset-shift.js @@ -0,0 +1,49 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.round +description: > + UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor + in DisambiguatePossibleInstants can be at most 24 hours. +features: [Temporal] +info: | + DisambiguatePossibleInstants: + 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception. +---*/ + +let calls = 0; + +class Shift24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + calls++; + if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9; + return 12 * 3600e9; + } + + getPossibleInstantsFor(plainDateTime) { + const [utcInstant] = super.getPossibleInstantsFor(plainDateTime); + const { year, month, day } = plainDateTime; + + if (year < 1970) return [utcInstant.subtract({ hours: 12 })]; + if (year === 1970 && month === 1 && day === 1) return []; + return [utcInstant.add({ hours: 12 })]; + } +} + +const timeZone = new Shift24Hour(); + +const instance = new Temporal.ZonedDateTime(0n, timeZone); +instance.round({ smallestUnit: "hours" }); + +assert(calls >= 2, "getOffsetNanosecondsFor should be called at least twice"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-called-with-iso8601-calendar.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-called-with-iso8601-calendar.js index 0c622df167..91c8ec7bc2 100644 --- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-called-with-iso8601-calendar.js +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-called-with-iso8601-calendar.js @@ -51,8 +51,8 @@ const nonBuiltinISOCalendar = new Temporal.Calendar("iso8601"); const timeZone = new SkippedDateTime(); const instance = new Temporal.ZonedDateTime(0n, timeZone, nonBuiltinISOCalendar); -instance.round({ smallestUnit: "hours" }); +instance.round({ smallestUnit: "day" }); -assert.sameValue(timeZone.calls, 6, "getPossibleInstantsFor should have been called 6 times"); +assert.sameValue(timeZone.calls, 4, "getPossibleInstantsFor should have been called 4 times"); reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-maximum-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-maximum-backward-offset-shift.js new file mode 100644 index 0000000000..0c60d44311 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-maximum-backward-offset-shift.js @@ -0,0 +1,53 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.round +description: > + UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours. +features: [Temporal] +info: | + GetPossibleInstantsFor: + 5.b.i. Let _numResults_ be _list_'s length. + ii. If _numResults_ > 1, then + 1. Let _epochNs_ be a new empty List. + 2. For each value _instant_ in list, do + a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_. + 3. Let _min_ be the least element of the List _epochNs_. + 4. Let _max_ be the greatest element of the List _epochNs_. + 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception. +---*/ + +let calls = 0; + +class Shift24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + return 0; + } + + getPossibleInstantsFor(plainDateTime) { + calls++; + const utc = new Temporal.TimeZone("UTC"); + const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime); + return [ + utcInstant.subtract({ hours: 12 }), + utcInstant.add({ hours: 12 }) + ]; + } +} + +const timeZone = new Shift24Hour(); + +const instance = new Temporal.ZonedDateTime(0n, timeZone); +instance.round({ smallestUnit: "hours" }); + +assert(calls >= 1, "getPossibleInstantsFor should be called at least once"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-out-of-range-backward-offset-shift.js new file mode 100644 index 0000000000..dabcfe8ed6 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-out-of-range-backward-offset-shift.js @@ -0,0 +1,49 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.round +description: > + UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours. +features: [Temporal] +info: | + GetPossibleInstantsFor: + 5.b.i. Let _numResults_ be _list_'s length. + ii. If _numResults_ > 1, then + 1. Let _epochNs_ be a new empty List. + 2. For each value _instant_ in list, do + a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_. + 3. Let _min_ be the least element of the List _epochNs_. + 4. Let _max_ be the greatest element of the List _epochNs_. + 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception. +---*/ + +class ShiftLonger24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + return 0; + } + + getPossibleInstantsFor(plainDateTime) { + const utc = new Temporal.TimeZone("UTC"); + const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime); + return [ + utcInstant.subtract({ hours: 12, nanoseconds: 1 }), + utcInstant.add({ hours: 12 }), + utcInstant, // add a third value in case the implementation doesn't sort + ]; + } +} + +const timeZone = new ShiftLonger24Hour(); + +const instance = new Temporal.ZonedDateTime(0n, timeZone); +assert.throws(RangeError, () => instance.round({ smallestUnit: "hours" }), "RangeError should be thrown"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/order-of-operations.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/order-of-operations.js index 48ca638b39..65fac9036c 100644 --- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/order-of-operations.js +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/order-of-operations.js @@ -24,10 +24,6 @@ const expected = [ "get this.timeZone.getPossibleInstantsFor", // GetPlainDateTimeFor on receiver's instant "call this.timeZone.getOffsetNanosecondsFor", - // GetInstantFor on preceding midnight - "call this.timeZone.getPossibleInstantsFor", - // AddDaysToZonedDateTime - "call this.timeZone.getPossibleInstantsFor", // InterpretISODateTimeOffset "call this.timeZone.getPossibleInstantsFor", "call this.timeZone.getOffsetNanosecondsFor", @@ -84,36 +80,8 @@ beforeFallBackInstance.round(nextHourOptions); assert.compareArray(actual, expected, "order of operations with rounding result at repeated wall-clock time"); actual.splice(0); // clear -const expectedSkippedDateTime = [ - "get options.roundingIncrement", - "get options.roundingIncrement.valueOf", - "call options.roundingIncrement.valueOf", - "get options.roundingMode", - "get options.roundingMode.toString", - "call options.roundingMode.toString", - "get options.smallestUnit", - "get options.smallestUnit.toString", - "call options.smallestUnit.toString", - // lookup - "get this.timeZone.getOffsetNanosecondsFor", - "get this.timeZone.getPossibleInstantsFor", - // GetPlainDateTimeFor on receiver's instant - "call this.timeZone.getOffsetNanosecondsFor", - // GetInstantFor on preceding midnight - "call this.timeZone.getPossibleInstantsFor", - // DisambiguatePossibleInstants - "call this.timeZone.getOffsetNanosecondsFor", - "call this.timeZone.getOffsetNanosecondsFor", - "call this.timeZone.getPossibleInstantsFor", - // AddZonedDateTime - "call this.timeZone.getPossibleInstantsFor", - // InterpretISODateTimeOffset - "call this.timeZone.getPossibleInstantsFor", - "call this.timeZone.getOffsetNanosecondsFor", -]; - springForwardInstance.round(options); -assert.compareArray(actual, expectedSkippedDateTime, "order of operations with preceding midnight at skipped wall-clock time"); +assert.compareArray(actual, expected, "order of operations with preceding midnight at skipped wall-clock time"); actual.splice(0); // clear const expectedSkippedResult = [ @@ -131,14 +99,6 @@ const expectedSkippedResult = [ "get this.timeZone.getPossibleInstantsFor", // GetPlainDateTimeFor on receiver's instant "call this.timeZone.getOffsetNanosecondsFor", - // GetInstantFor on preceding midnight - "call this.timeZone.getPossibleInstantsFor", - // AddDaysToZonedDateTime - "call this.timeZone.getPossibleInstantsFor", - // DisambiguatePossibleInstants - "call this.timeZone.getOffsetNanosecondsFor", - "call this.timeZone.getOffsetNanosecondsFor", - "call this.timeZone.getPossibleInstantsFor", // InterpretISODateTimeOffset "call this.timeZone.getPossibleInstantsFor", // DisambiguatePossibleInstants diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/smallest-unit-day-rounding-modes.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/smallest-unit-day-rounding-modes.js index f694712457..435bfa16d8 100644 --- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/smallest-unit-day-rounding-modes.js +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/smallest-unit-day-rounding-modes.js @@ -41,21 +41,22 @@ class TimeZone extends Temporal.TimeZone { #count = 0; #nanoseconds; - constructor(nanoseconds) { + constructor(todayEpochNanoseconds, tomorrowEpochNanoseconds) { super("UTC"); - this.#nanoseconds = nanoseconds; + this.#nanoseconds = [todayEpochNanoseconds, tomorrowEpochNanoseconds]; } getPossibleInstantsFor(dateTime) { - if (++this.#count === 2) { - return [new Temporal.Instant(this.#nanoseconds)]; + const nanoseconds = this.#nanoseconds[this.#count++]; + if (nanoseconds === undefined) { + return super.getPossibleInstantsFor(dateTime); } - return super.getPossibleInstantsFor(dateTime); + return [new Temporal.Instant(nanoseconds)]; } } -function test(epochNanoseconds, tomorrowEpochNanoseconds, testCases) { +function test(epochNanoseconds, todayEpochNanoseconds, tomorrowEpochNanoseconds, testCases) { for (let [roundingMode, expected] of Object.entries(testCases)) { - let timeZone = new TimeZone(tomorrowEpochNanoseconds); + let timeZone = new TimeZone(todayEpochNanoseconds, tomorrowEpochNanoseconds); let zoned = new Temporal.ZonedDateTime(epochNanoseconds, timeZone); let result = zoned.round({ smallestUnit: "days", roundingMode }); assert.sameValue(result.epochNanoseconds, expected); @@ -64,31 +65,35 @@ function test(epochNanoseconds, tomorrowEpochNanoseconds, testCases) { const oneDay = 24n * 60n * 60n * 1000n * 1000n * 1000n; -// Test positive divisor (dayLengthNs). -test(3n, 10n, { - ceil: oneDay, +test(3n, undefined, 10n, { + ceil: 10n, // end-of-day according to TimeZone protocol floor: 0n, trunc: 0n, halfExpand: 0n, }); -test(-3n, 10n, { - ceil: 0n, +test(-3n, undefined, 10n, { + ceil: 10n, // end-of-day according to TimeZone protocol floor: -oneDay, trunc: -oneDay, - halfExpand: 0n, + halfExpand: 10n, // end-of-day according to TimeZone protocol }); -test(-3n, -10n, { - ceil: oneDay, - floor: 0n, - trunc: 0n, - halfExpand: 0n, -}); +assert.throws(RangeError, () => { + test(-3n, 0n, 10n, { ceil: undefined }); +}, "instant is before TimeZone protocol's start-of-day"); + +assert.throws(RangeError, () => { + test(-3n, undefined, -10n, { ceil: undefined }); +}, "instant is after TimeZone protocol's end-of-day"); + +assert.throws(RangeError, () => { + test(0n, 0n, 0n, { ceil: undefined }); +}, "instant is within zero-duration day"); // Test values at int64 boundaries. -test(3n, /*INT64_MAX=*/ 9223372036854775807n, { - ceil: oneDay, +test(3n, undefined, /*INT64_MAX=*/ 9223372036854775807n, { + ceil: /*INT64_MAX=*/ 9223372036854775807n, // end-of-day according to TimeZone protocol floor: 0n, trunc: 0n, halfExpand: 0n, diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/timezone-getpossibleinstantsfor-iterable.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/timezone-getpossibleinstantsfor-iterable.js index 922b114e0b..28c064e24d 100644 --- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/timezone-getpossibleinstantsfor-iterable.js +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/timezone-getpossibleinstantsfor-iterable.js @@ -23,8 +23,6 @@ features: [Temporal] ---*/ const expected = [ - "2001-09-09T00:00:00", // called once on midnight of the input datetime - "2001-09-10T00:00:00", // called once on the previous value plus one calendar day "2001-09-09T02:00:00", // called once on the rounding result ]; diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-calendar-iso-string.js new file mode 100644 index 0000000000..6eeb874839 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-calendar-iso-string.js @@ -0,0 +1,30 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.since +description: An ISO 8601 string can be converted to a calendar ID in Calendar +includes: [temporalHelpers.js] +features: [Temporal] +---*/ + +const timeZone = new Temporal.TimeZone("UTC"); +const instance = new Temporal.ZonedDateTime(0n, timeZone); + +for (const calendar of [ + "2020-01-01", + "2020-01-01[u-ca=iso8601]", + "2020-01-01T00:00:00.000000000", + "2020-01-01T00:00:00.000000000[u-ca=iso8601]", + "01-01", + "01-01[u-ca=iso8601]", + "2020-01", + "2020-01[u-ca=iso8601]", +]) { + const arg = { year: 1970, monthCode: "M01", day: 1, timeZone, calendar }; + const result = instance.since(arg); + TemporalHelpers.assertDuration(result, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, `Calendar created from string "${calendar}"`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-calendar-year-zero.js index 52a3aee0f5..b5dc42f348 100644 --- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-calendar-year-zero.js +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-calendar-year-zero.js @@ -17,7 +17,8 @@ const invalidStrings = [ ]; const timeZone = new Temporal.TimeZone("UTC"); const instance = new Temporal.ZonedDateTime(0n, timeZone); -invalidStrings.forEach((arg) => { +invalidStrings.forEach((str) => { + const arg = { year: 1976, month: 11, day: 18, calendar: str }; assert.throws( RangeError, () => instance.since(arg), diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-backward-offset-shift.js new file mode 100644 index 0000000000..c5181e85e0 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-backward-offset-shift.js @@ -0,0 +1,50 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.since +description: > + UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours. +features: [Temporal] +info: | + GetPossibleInstantsFor: + 5.b.i. Let _numResults_ be _list_'s length. + ii. If _numResults_ > 1, then + 1. Let _epochNs_ be a new empty List. + 2. For each value _instant_ in list, do + a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_. + 3. Let _min_ be the least element of the List _epochNs_. + 4. Let _max_ be the greatest element of the List _epochNs_. + 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception. +---*/ + +class ShiftLonger24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + return 0; + } + + getPossibleInstantsFor(plainDateTime) { + const utc = new Temporal.TimeZone("UTC"); + const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime); + return [ + utcInstant.subtract({ hours: 12, nanoseconds: 1 }), + utcInstant.add({ hours: 12 }), + utcInstant, // add a third value in case the implementation doesn't sort + ]; + } +} + +const timeZone = new ShiftLonger24Hour(); +const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone }; + +const instance = new Temporal.ZonedDateTime(0n, timeZone); +assert.throws(RangeError, () => instance.since(arg), "RangeError should be thrown"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-forward-offset-shift.js new file mode 100644 index 0000000000..d6f7970e8a --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-forward-offset-shift.js @@ -0,0 +1,45 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.since +description: > + UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor + in DisambiguatePossibleInstants cannot be greater than 24 hours. +features: [Temporal] +info: | + DisambiguatePossibleInstants: + 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception. +---*/ + +class ShiftLonger24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9; + return 12 * 3600e9 + 1; + } + + getPossibleInstantsFor(plainDateTime) { + const [utcInstant] = super.getPossibleInstantsFor(plainDateTime); + const { year, month, day } = plainDateTime; + + if (year < 1970) return [utcInstant.subtract({ hours: 12 })]; + if (year === 1970 && month === 1 && day === 1) return []; + return [utcInstant.add({ hours: 12, nanoseconds: 1 })]; + } +} + +const timeZone = new ShiftLonger24Hour(); +const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone }; + +const instance = new Temporal.ZonedDateTime(0n, timeZone); +assert.throws(RangeError, () => instance.since(arg), "RangeError should be thrown"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-string-calendar-annotation-invalid-key.js new file mode 100644 index 0000000000..5d0053cad5 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-string-calendar-annotation-invalid-key.js @@ -0,0 +1,26 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.since +description: Annotation keys are lowercase-only +features: [Temporal] +---*/ + +const invalidStrings = [ + ["1970-01-01T00:00[UTC][U-CA=iso8601]", "invalid capitalized key"], + ["1970-01-01T00:00[UTC][u-CA=iso8601]", "invalid partially-capitalized key"], + ["1970-01-01T00:00[UTC][FOO=bar]", "invalid capitalized unrecognized key"], +]; +const timeZone = new Temporal.TimeZone("UTC"); +const instance = new Temporal.ZonedDateTime(0n, timeZone); +invalidStrings.forEach(([arg, descr]) => { + assert.throws( + RangeError, + () => instance.since(arg), + `annotation keys must be lowercase: ${arg} - ${descr}` + ); +}); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/calendar-dateadd-called-with-options-undefined.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/calendar-dateadd-called-with-options-undefined.js index 7a5367bf29..932847c329 100644 --- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/calendar-dateadd-called-with-options-undefined.js +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/calendar-dateadd-called-with-options-undefined.js @@ -14,27 +14,17 @@ features: [Temporal] const calendar = TemporalHelpers.calendarDateAddUndefinedOptions(); const timeZone = TemporalHelpers.oneShiftTimeZone(new Temporal.Instant(0n), 3600e9); const earlier = new Temporal.ZonedDateTime(0n, timeZone, calendar); - -// Basic difference with largestUnit larger than days. -// The call comes from this path: -// ZonedDateTime.since() -> DifferenceZonedDateTime -> AddZonedDateTime -> calendar.dateAdd() - -const later1 = new Temporal.ZonedDateTime(1_213_200_000_000_000n, timeZone, calendar); -later1.since(earlier, { largestUnit: "weeks" }); -assert.sameValue(calendar.dateAddCallCount, 1, "basic difference with largestUnit >days"); +const later = new Temporal.ZonedDateTime(1_213_200_000_000_000n, timeZone, calendar); // Difference with rounding, with smallestUnit a calendar unit. // The calls come from these paths: // ZonedDateTime.since() -> -// DifferenceZonedDateTime -> AddZonedDateTime -> calendar.dateAdd() // RoundDuration -> // MoveRelativeZonedDateTime -> AddZonedDateTime -> calendar.dateAdd() // MoveRelativeDate -> calendar.dateAdd() // BalanceDurationRelative -> MoveRelativeDate -> calendar.dateAdd() -calendar.dateAddCallCount = 0; - -later1.since(earlier, { smallestUnit: "weeks" }); -assert.sameValue(calendar.dateAddCallCount, 4, "rounding difference with calendar smallestUnit"); +later.since(earlier, { smallestUnit: "weeks" }); +assert.sameValue(calendar.dateAddCallCount, 3, "rounding difference with calendar smallestUnit"); reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/differencezoneddatetime-inconsistent-custom-calendar.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/differencezoneddatetime-inconsistent-custom-calendar.js new file mode 100644 index 0000000000..391f978f0d --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/differencezoneddatetime-inconsistent-custom-calendar.js @@ -0,0 +1,56 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.since +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 fiftyDays12Hours = 50n * 86400_000_000_000n + 12n * 3600_000_000_000n; + +{ + const tz = new (class extends Temporal.TimeZone { + getPossibleInstantsFor(dateTime) { + return super.getPossibleInstantsFor(dateTime.subtract({ days: 3 })); + } + })("UTC"); + + const zdt1 = new Temporal.ZonedDateTime(0n, tz); + const zdt2 = new Temporal.ZonedDateTime(fiftyDays12Hours, tz); + + assert.throws(RangeError, () => zdt2.since(zdt1, { largestUnit: "weeks" }), + "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 zdt1 = new Temporal.ZonedDateTime(0n, "UTC", cal); + const zdt2 = new Temporal.ZonedDateTime(fiftyDays12Hours, "UTC", cal); + + assert.throws(RangeError, () => zdt2.since(zdt1, { largestUnit: "weeks" }), + "Calendar calculation causing mixed-sign values should cause RangeError"); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/dst-month-day-boundary.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/dst-month-day-boundary.js new file mode 100644 index 0000000000..c45ebd5aa2 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/dst-month-day-boundary.js @@ -0,0 +1,29 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.since +description: > + Difference with the endpoint being the end of a skipped hour, chooses the + smaller of two possible durations +includes: [temporalHelpers.js] +features: [Temporal] +---*/ + +// Based on a test case by Adam Shaw + +const timeZone = TemporalHelpers.springForwardFallBackTimeZone(); + +const d1 = new Temporal.ZonedDateTime(957258000_000_000_000n /* = 2000-05-02T02:00-07:00 */, timeZone); +const d2 = new Temporal.ZonedDateTime(954669600_000_000_000n /* = 2000-04-02T03:00-07:00 */, timeZone); +// NOTE: nonexistent hour just before d2 + +const result = d1.since(d2, { largestUnit: "months" }); + +TemporalHelpers.assertDuration( + result, 0, 0, 0, 29, 23, 0, 0, 0, 0, 0, + "Result should not balance up to months, but pick the smaller of two possible durations" +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/dst-rounding-result.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/dst-rounding-result.js new file mode 100644 index 0000000000..c3a0f3f3a2 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/dst-rounding-result.js @@ -0,0 +1,48 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.since +description: > + Rounding the resulting duration takes the time zone's UTC offset shifts + into account +includes: [temporalHelpers.js] +features: [Temporal] +---*/ + +// Based on a test case by Adam Shaw + +const timeZone = TemporalHelpers.springForwardFallBackTimeZone(); + +{ + // Month-only part of duration lands on skipped DST hour, should not cause + // disambiguation + const start = new Temporal.ZonedDateTime( + 950868000_000_000_000n /* = 2000-02-18T10Z */, + timeZone); /* = 2000-02-18T02-08 in local time */ + const end = new Temporal.ZonedDateTime( + 954709200_000_000_000n /* = 2000-04-02T21Z */, + timeZone); /* = 2000-04-02T14-07 in local time */ + + const duration = start.since(end, { largestUnit: "months" }); + TemporalHelpers.assertDuration(duration, 0, -1, 0, -15, -11, 0, 0, 0, 0, 0, + "1-month rounding window is shortened by DST"); +} + + +{ + // Month-only part of duration lands on skipped DST hour, should not cause + // disambiguation + const start = new Temporal.ZonedDateTime( + 951991200_000_000_000n /* = 2000-03-02T10Z */, + timeZone); /* = 2000-03-02T02-08 in local time */ + const end = new Temporal.ZonedDateTime( + 956005200_000_000_000n /* = 2000-04-17T21Z */, + timeZone); /* = 2000-04-17T14-07 in local time */ + + const duration = start.since(end, { largestUnit: "months" }); + TemporalHelpers.assertDuration(duration, 0, -1, 0, -15, -12, 0, 0, 0, 0, 0, + "1-month rounding window is not shortened by DST"); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/find-intermediate-instant.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/find-intermediate-instant.js new file mode 100644 index 0000000000..7ef187ce3b --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/find-intermediate-instant.js @@ -0,0 +1,84 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.until +description: > + Up to 3 intermediate instants may be tried when calculating ZonedDateTime + difference +includes: [compareArray.js, temporalHelpers.js] +features: [BigInt, Temporal] +---*/ + +const calls = []; + +const springFallZone = TemporalHelpers.springForwardFallBackTimeZone(); +TemporalHelpers.observeMethod(calls, springFallZone, "getPossibleInstantsFor"); + +const dateLineZone = TemporalHelpers.crossDateLineTimeZone(); +TemporalHelpers.observeMethod(calls, dateLineZone, "getPossibleInstantsFor"); + +const zdt2 = new Temporal.ZonedDateTime(946722600_000_000_000n /* = 2000-01-01T02:30 local */, springFallZone); + +// Future -> past, without wall-clock overshoot +// Expects valid intermediate Instant WITHOUT day correction (computed once) +{ + const zdt1 = new Temporal.ZonedDateTime(949442400_000_000_000n /* = 2000-02-01T14:00 local */, springFallZone); + const result = zdt1.since(zdt2, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, 0, 1, 0, 0, 11, 30, 0, 0, 0, 0, "no wall-clock overshoot, no DST"); + assert.compareArray(calls, [ + "call getPossibleInstantsFor", // first intermediate in DifferenceZonedDateTime + ], "one intermediate should be tried"); +} + +calls.splice(0); // clear + +// Future -> past, WITH wall-clock overshoot +// Expects valid intermediate Instant with guaranteed 1-DAY correction (computed once) +{ + const zdt1 = new Temporal.ZonedDateTime(949395600_000_000_000n /* = 2000-02-01T01:00 local */, springFallZone); + const result = zdt1.since(zdt2, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, 0, 0, 0, 30, 22, 30, 0, 0, 0, 0, "wall-clock overshoot, no DST"); + assert.compareArray(calls, [ + "call getPossibleInstantsFor", // first intermediate in DifferenceZonedDateTime + ], "one intermediate should be tried"); +} + +calls.splice(0); // clear + +// Future -> past, WITH wall-clock overshoot +// Expects valid intermediate Instant with guaranteed 1-DAY correction (computed once) +// Intermediate Instant falls within spring DST gap and gets pushed forward, +// but since moving from future -> past, not possible to exacerbate overflow, +// so no other day corrections. +{ + const end = new Temporal.ZonedDateTime(957258000_000_000_000n /* = 2000-05-02T02:00 local */, springFallZone); + const start = new Temporal.ZonedDateTime(954671400_000_000_000n /* = 2000-04-02T03:30-07:00 local */, springFallZone); + const result = end.since(start, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, 0, 0, 0, 29, 22, 30, 0, 0, 0, 0, "wall-clock overshoot, inconsiquential DST"); + assert.compareArray(calls, [ + "call getPossibleInstantsFor", // first intermediate in DifferenceZonedDateTime + ], "one intermediate should be tried"); +} + +calls.splice(0); // clear + +// Past -> future, WITH wall-clock overshoot +// Tries intermediate Instant with 1-DAY correction (first compute) +// Then, ANOTHER day correction because updated intermediate Instant falls within dateline DST gap, +// pushing it forward, causing wall-clock overshoot again +// (Not possible when going backwards) +// (This test is just the same as the corresponding one in until(), but negative) +{ + const start = new Temporal.ZonedDateTime(1325102400_000_000_000n /* = 2011-12-28T10:00 local */, dateLineZone); + const end = new Temporal.ZonedDateTime(1325257200_000_000_000n /* = 2011-12-31T05:00 local */, dateLineZone); + const result = start.since(end, { largestUnit: "days" }); + TemporalHelpers.assertDuration(result, 0, 0, 0, -1, -19, 0, 0, 0, 0, 0, "wall-clock overshoot, consiquential DST"); + assert.compareArray(calls, [ + "call getPossibleInstantsFor", // first intermediate in DifferenceZonedDateTime + "call getPossibleInstantsFor", // second intermediate in DifferenceZonedDateTime + "call getPossibleInstantsFor", // DisambiguatePossibleInstants on second intermediate + ], "two intermediates should be tried, with disambiguation"); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-loop-arbitrarily.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-loop-arbitrarily.js index 23f993f25f..454d5d216a 100644 --- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-loop-arbitrarily.js +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-loop-arbitrarily.js @@ -5,74 +5,42 @@ /*--- esid: sec-temporal.zoneddatetime.prototype.since description: > - NormalizedTimeDurationToDays can loop arbitrarily up to max safe integer + NormalizedTimeDurationToDays should not be able to loop arbitrarily. info: | NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] ) ... - 21. Repeat, while done is false, - a. Let oneDayFarther be ? AddDaysToZonedDateTime(relativeResult.[[Instant]], - relativeResult.[[DateTime]], timeZoneRec, zonedRelativeTo.[[Calendar]], sign). - b. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(oneDayFarther.[[EpochNanoseconds]], - relativeResult.[[EpochNanoseconds]]). - c. Let oneDayLess be ? SubtractNormalizedTimeDuration(norm, dayLengthNs). - c. If NormalizedTimeDurationSign(oneDayLess) × sign ≥ 0, then - i. Set norm to oneDayLess. - ii. Set relativeResult to oneDayFarther. - iii. Set days to days + sign. - d. Else, - i. Set done to true. -includes: [temporalHelpers.js] + 22. If NormalizedTimeDurationSign(_oneDayLess_) × _sign_ ≥ 0, then + a. Set _norm_ to _oneDayLess_. + b. Set _relativeResult_ to _oneDayFarther_. + c. Set _days_ to _days_ + _sign_. + d. Set _oneDayFarther_ to ? AddDaysToZonedDateTime(_relativeResult_.[[Instant]], _relativeResult_.[[DateTime]], _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _sign_). + e. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(_oneDayFarther.[[EpochNanoseconds]], relativeResult.[[EpochNanoseconds]]). + f. If NormalizedTimeDurationSign(? SubtractNormalizedTimeDuration(_norm_, _dayLengthNs_)) × _sign_ ≥ 0, then + i. Throw a *RangeError* exception. features: [Temporal] ---*/ -const calls = []; const dayLengthNs = 86400000000000n; -const other = new Temporal.ZonedDateTime(dayLengthNs, "UTC", "iso8601"); - -function createRelativeTo(count) { - const dayInstant = new Temporal.Instant(dayLengthNs); - const substitutions = []; - const timeZone = new Temporal.TimeZone("UTC"); - // Return constant value for first _count_ calls - TemporalHelpers.substituteMethod( - timeZone, - "getPossibleInstantsFor", - substitutions - ); - substitutions.length = count; - let i = 0; - for (i = 0; i < substitutions.length; i++) { - // (this value) - substitutions[i] = [dayInstant]; +const dayInstant = new Temporal.Instant(dayLengthNs); +let calls = 0; +const timeZone = new class extends Temporal.TimeZone { + getPossibleInstantsFor() { + calls++; + return [dayInstant]; } - // Record calls in calls[] - TemporalHelpers.observeMethod(calls, timeZone, "getPossibleInstantsFor"); - return new Temporal.ZonedDateTime(0n, timeZone); -} - -let zdt = createRelativeTo(50); -calls.splice(0); // Reset calls list after ZonedDateTime construction -zdt.since(other, { - largestUnit: "day", -}); -assert.sameValue( - calls.length, - 50 + 1, - "Expected ZonedDateTime.since to call getPossibleInstantsFor correct number of times" -); +}("UTC"); -zdt = createRelativeTo(100); -calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction -zdt.since(other, { - largestUnit: "day", -}); -assert.sameValue( - calls.length, - 100 + 1, - "Expected ZonedDateTime.since to call getPossibleInstantsFor correct number of times" -); +const zdt = new Temporal.ZonedDateTime(0n, timeZone); +const other = new Temporal.ZonedDateTime(dayLengthNs * 2n, "UTC", "iso8601"); -zdt = createRelativeTo(105); -assert.throws(RangeError, () => zdt.since(other, { largestUnit: "day" }), "105 days > 2⁵³ ns"); +assert.throws(RangeError, () => zdt.since(other, { largestUnit: "day", smallestUnit: "second" }), "indefinite loop is prevented"); +assert.sameValue(calls, 4, "getPossibleInstantsFor is not called indefinitely"); + // Expected calls: + // DifferenceTemporalZonedDateTime -> + // DifferenceZonedDateTime -> GetInstantFor (1) + // NormalizedTimeDurationToDays -> + // AddDaysToZonedDateTime (2, step 12) + // AddDaysToZonedDateTime (3, step 15) + // AddDaysToZonedDateTime (4, step 18.d) reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-range-errors.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-range-errors.js index 0c0b07f5dc..a6af0a69bc 100644 --- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-range-errors.js +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-range-errors.js @@ -8,12 +8,12 @@ description: > RangeErrors. info: | NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] ) - 22. If days < 0 and sign = 1, throw a RangeError exception. - 23. If days > 0 and sign = -1, throw a RangeError exception. + 23. If days < 0 and sign = 1, throw a RangeError exception. + 24. If days > 0 and sign = -1, throw a RangeError exception. ... - 25. If NormalizedTimeDurationSign(_norm_) = 1 and sign = -1, throw a RangeError exception. + 26. If NormalizedTimeDurationSign(_norm_) = 1 and sign = -1, throw a RangeError exception. ... - 28. If dayLength ≥ 2⁵³, throw a RangeError exception. + 29. If dayLength ≥ 2⁵³, throw a RangeError exception. features: [Temporal, BigInt] includes: [temporalHelpers.js] ---*/ @@ -40,13 +40,16 @@ const dayNs = 86_400_000_000_000; const zeroZDT = new Temporal.ZonedDateTime(0n, "UTC"); const oneZDT = new Temporal.ZonedDateTime(1n, "UTC"); const epochInstant = new Temporal.Instant(0n); -const options = { largestUnit: "days" }; +const options = { largestUnit: "days", smallestUnit: "seconds", roundingMode: "expand" }; -// Step 22: days < 0 and sign = 1 +// Step 23: days < 0 and sign = 1 let start = new Temporal.ZonedDateTime( 0n, // Sets DifferenceZonedDateTime _ns1_ timeZoneSubstituteValues( - [[epochInstant]], // Returned in step 16, setting _relativeResult_ + [ + TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally in DifferenceZonedDateTime + [epochInstant], // Returned in step 16, setting _relativeResult_ + ], [ // Behave normally in 2 calls made prior to NormalizedTimeDurationToDays TemporalHelpers.SUBSTITUTE_SKIP, @@ -60,14 +63,18 @@ assert.throws(RangeError, () => start.since( oneZDT, // Sets DifferenceZonedDateTime _ns2_ options - ) + ), + "days < 0 and sign = 1" ); -// Step 23: days > 0 and sign = -1 +// Step 24: days > 0 and sign = -1 start = new Temporal.ZonedDateTime( 1n, // Sets DifferenceZonedDateTime _ns1_ timeZoneSubstituteValues( - [[epochInstant]], // Returned in step 16, setting _relativeResult_ + [ + TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally in DifferenceZonedDateTime + [epochInstant], // Returned in step 16, setting _relativeResult_ + ], [ // Behave normally in 2 calls made prior to NormalizedTimeDurationToDays TemporalHelpers.SUBSTITUTE_SKIP, @@ -81,14 +88,18 @@ assert.throws(RangeError, () => start.since( zeroZDT, // Sets DifferenceZonedDateTime _ns2_ options - ) + ), + "days > 0 and sign = -1" ); -// Step 25: nanoseconds > 0 and sign = -1 +// Step 26: nanoseconds > 0 and sign = -1 start = new Temporal.ZonedDateTime( 1n, // Sets DifferenceZonedDateTime _ns1_ timeZoneSubstituteValues( - [[new Temporal.Instant(-1n)]], // Returned in step 16, setting _relativeResult_ + [ + TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally in DifferenceZonedDateTime + [new Temporal.Instant(-2_000_000_000n)], // Returned in step 16, setting _relativeResult_ + ], [ // Behave normally in 2 calls made prior to NormalizedTimeDurationToDays TemporalHelpers.SUBSTITUTE_SKIP, @@ -102,16 +113,20 @@ assert.throws(RangeError, () => start.since( zeroZDT, // Sets DifferenceZonedDateTime _ns2_ options - ) + ), + "norm > 0 and sign = -1" ); -// Step 28: day length is an unsafe integer +// Step 29: day length is an unsafe integer start = new Temporal.ZonedDateTime( 0n, timeZoneSubstituteValues( - // Not called in step 16 because _days_ = 0 - // Returned in step 21.a, making _oneDayFarther_ 2^53 ns later than _relativeResult_ - [[new Temporal.Instant(2n ** 53n)]], + [ + TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally in DifferenceZonedDateTime + // Not called in step 16 because _days_ = 0 + // Returned in step 19, making _oneDayFarther_ 2^53 ns later than _relativeResult_ + [new Temporal.Instant(2n ** 53n)], + ], [] ) ); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/order-of-operations.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/order-of-operations.js index 566a1ce192..90fc1e0475 100644 --- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/order-of-operations.js +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/order-of-operations.js @@ -306,10 +306,6 @@ assert.compareArray(actual, [ // DifferenceZonedDateTime "call this.timeZone.getOffsetNanosecondsFor", "call this.timeZone.getOffsetNanosecondsFor", - // NanosecondsToDays - "call this.timeZone.getOffsetNanosecondsFor", - "call this.timeZone.getOffsetNanosecondsFor", - // NanosecondsToDays → AddDaysToZonedDateTime "call this.timeZone.getPossibleInstantsFor", ], "order of operations with identical wall-clock times and largestUnit a calendar unit"); actual.splice(0); // clear @@ -328,17 +324,8 @@ const expectedOpsForCalendarDifference = [ "call this.timeZone.getOffsetNanosecondsFor", // DifferenceZonedDateTime "call this.timeZone.getOffsetNanosecondsFor", - // DifferenceISODateTime - "call this.calendar.dateUntil", - // AddZonedDateTime - "call this.calendar.dateAdd", - "call this.timeZone.getPossibleInstantsFor", - // NanosecondsToDays - "call this.timeZone.getOffsetNanosecondsFor", - "call this.timeZone.getOffsetNanosecondsFor", - // NanosecondsToDays → AddDaysToZonedDateTime - "call this.timeZone.getPossibleInstantsFor", "call this.timeZone.getPossibleInstantsFor", + "call this.calendar.dateUntil", ]; const expectedOpsForCalendarRounding = [ diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/wrapping-at-end-of-month.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/wrapping-at-end-of-month.js new file mode 100644 index 0000000000..3fddf0d09b --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/wrapping-at-end-of-month.js @@ -0,0 +1,120 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.since +description: Tests balancing of days to months at end of month +includes: [temporalHelpers.js] +features: [Temporal] +---*/ + +// Difference between end of longer month to end of following shorter month +{ + const end = new Temporal.ZonedDateTime(5011200_000_000_000n /* = 1970-02-28T00Z */, "UTC"); + for (const largestUnit of ["years", "months"]) { + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(2332800_000_000_000n /* = 1970-01-28T00Z */, "UTC").since(end, { largestUnit }), + 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, + "Jan 28th to Feb 28th is one month" + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(2419200_000_000_000n /* = 1970-01-29T00Z */, "UTC").since(end, { largestUnit }), + 0, 0, 0, -30, 0, 0, 0, 0, 0, 0, + "Jan 29th to Feb 28th is 30 days, not one month" + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(2505600_000_000_000n /* = 1970-01-30T00Z */, "UTC").since(end, { largestUnit }), + 0, 0, 0, -29, 0, 0, 0, 0, 0, 0, + "Jan 30th to Feb 28th is 29 days, not one month" + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(2592000_000_000_000n /* = 1970-01-31T00Z */, "UTC").since(end, { largestUnit }), + 0, 0, 0, -28, 0, 0, 0, 0, 0, 0, + "Jan 31st to Feb 28th is 28 days, not one month" + ); + } +} + +// Difference between end of leap-year January to end of leap-year February +{ + const end = new Temporal.ZonedDateTime(68169600_000_000_000n /* = 1972-02-29T00Z */, "UTC"); + for (const largestUnit of ["years", "months"]) { + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(65491200_000_000_000n /* = 1972-01-29T00Z */, "UTC").since(end, { largestUnit }), + 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, + "Jan 29th to Feb 29th is one month" + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(65577600_000_000_000n /* = 1972-01-30T00Z */, "UTC").since(end, { largestUnit }), + 0, 0, 0, -30, 0, 0, 0, 0, 0, 0, + "Jan 30th to Feb 29th is 30 days, not one month" + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(65664000_000_000_000n /* = 1972-01-31T00Z */, "UTC").since(end, { largestUnit }), + 0, 0, 0, -29, 0, 0, 0, 0, 0, 0, + "Jan 31st to Feb 29th is 29 days, not one month" + ); + } +} + +// Difference between end of longer month to end of not-immediately-following +// shorter month +{ + const end = new Temporal.ZonedDateTime(28771200_000_000_000n /* = 1970-11-30T00Z */, "UTC"); + for (const largestUnit of ["years", "months"]) { + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(20822400_000_000_000n /* = 1970-08-30T00Z */, "UTC").since(end, { largestUnit }), + 0, -3, 0, 0, 0, 0, 0, 0, 0, 0, + "Aug 30th to Nov 30th is 3 months" + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(20908800_000_000_000n /* = 1970-08-31T00Z */, "UTC").since(end, { largestUnit }), + 0, -2, 0, -30, 0, 0, 0, 0, 0, 0, + "Aug 31st to Nov 30th is 2 months 30 days, not 3 months" + ); + } +} + +// Difference between end of longer month in one year to shorter month in +// later year +{ + const end = new Temporal.ZonedDateTime(104976000_000_000_000n /* = 1973-04-30T00Z */, "UTC"); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(31363200_000_000_000n /* = 1970-12-30T00Z */, "UTC").since(end, { largestUnit: "months" }), + 0, -28, 0, 0, 0, 0, 0, 0, 0, 0, + "Dec 30th 1970 to Apr 30th 1973 is 28 months" + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(31363200_000_000_000n /* = 1970-12-30T00Z */, "UTC").since(end, { largestUnit: "years" }), + -2, -4, 0, 0, 0, 0, 0, 0, 0, 0, + "Dec 30th 1970 to Apr 30th 1973 is 2 years, 4 months" + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(31449600_000_000_000n /* = 1970-12-31T00Z */, "UTC").since(end, { largestUnit: "months" }), + 0, -27, 0, -30, 0, 0, 0, 0, 0, 0, + "Dec 30th 1970 to Apr 30th 1973 is 27 months, 30 days, not 28 months" + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(31449600_000_000_000n /* = 1970-12-31T00Z */, "UTC").since(end, { largestUnit: "years" }), + -2, -3, 0, -30, 0, 0, 0, 0, 0, 0, + "Dec 30th 1970 to Apr 30th 1973 is 2 years, 3 months, 30 days, not 2 years 4 months" + ); +} + +// Difference where months passes through a month that's the same length or +// shorter than either the start or end month +{ + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(2419200_000_000_000n /* = 1970-01-29T00Z */, "UTC").since(new Temporal.ZonedDateTime(7430400_000_000_000n /* = 1970-03-28T00Z */, "UTC"), { largestUnit: "months" }), + 0, -1, 0, -28, 0, 0, 0, 0, 0, 0, + "Jan 29th to Mar 28th is 1 month 28 days, not 58 days" + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(2592000_000_000_000n /* = 1970-01-31T00Z */, "UTC").since(new Temporal.ZonedDateTime(44409600_000_000_000n /* = 1971-05-30T00Z */, "UTC"), { largestUnit: "years" }), + -1, -3, 0, -30, 0, 0, 0, 0, 0, 0, + "Jan 31st 1970 to May 30th 1971 is 1 year, 3 months, 30 days, not 1 year, 2 months, 60 days" + ); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getoffsetnanosecondsfor-maximum-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getoffsetnanosecondsfor-maximum-forward-offset-shift.js new file mode 100644 index 0000000000..d71f6be08e --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getoffsetnanosecondsfor-maximum-forward-offset-shift.js @@ -0,0 +1,49 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.startofday +description: > + UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor + in DisambiguatePossibleInstants can be at most 24 hours. +features: [Temporal] +info: | + DisambiguatePossibleInstants: + 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception. +---*/ + +let calls = 0; + +class Shift24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + _shiftEpochNs = 0n + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + calls++; + if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9; + return 12 * 3600e9; + } + + getPossibleInstantsFor(plainDateTime) { + const [utcInstant] = super.getPossibleInstantsFor(plainDateTime); + const { year, month, day } = plainDateTime; + + if (year < 1970) return [utcInstant.subtract({ hours: 12 })]; + if (year === 1970 && month === 1 && day === 1) return []; + return [utcInstant.add({ hours: 12 })]; + } +} + +const timeZone = new Shift24Hour(); + +const instance = new Temporal.ZonedDateTime(0n, timeZone); +instance.startOfDay(); + +assert(calls >= 2, "getOffsetNanosecondsFor should be called at least twice"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js new file mode 100644 index 0000000000..82e86c4ee5 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js @@ -0,0 +1,44 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.startofday +description: > + UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor + in DisambiguatePossibleInstants cannot be greater than 24 hours. +features: [Temporal] +info: | + DisambiguatePossibleInstants: + 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception. +---*/ + +class ShiftLonger24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + _shiftEpochNs = 0n + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9; + return 12 * 3600e9 + 1; + } + + getPossibleInstantsFor(plainDateTime) { + const [utcInstant] = super.getPossibleInstantsFor(plainDateTime); + const { year, month, day } = plainDateTime; + + if (year < 1970) return [utcInstant.subtract({ hours: 12 })]; + if (year === 1970 && month === 1 && day === 1) return []; + return [utcInstant.add({ hours: 12, nanoseconds: 1 })]; + } +} + +const timeZone = new ShiftLonger24Hour(); + +const instance = new Temporal.ZonedDateTime(0n, timeZone); +assert.throws(RangeError, () => instance.startOfDay(), "RangeError should be thrown"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getpossibleinstantsfor-maximum-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getpossibleinstantsfor-maximum-backward-offset-shift.js new file mode 100644 index 0000000000..f1342dfe2d --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getpossibleinstantsfor-maximum-backward-offset-shift.js @@ -0,0 +1,53 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.startofday +description: > + UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours. +features: [Temporal] +info: | + GetPossibleInstantsFor: + 5.b.i. Let _numResults_ be _list_'s length. + ii. If _numResults_ > 1, then + 1. Let _epochNs_ be a new empty List. + 2. For each value _instant_ in list, do + a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_. + 3. Let _min_ be the least element of the List _epochNs_. + 4. Let _max_ be the greatest element of the List _epochNs_. + 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception. +---*/ + +let calls = 0; + +class Shift24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + return 0; + } + + getPossibleInstantsFor(plainDateTime) { + calls++; + const utc = new Temporal.TimeZone("UTC"); + const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime); + return [ + utcInstant.subtract({ hours: 12 }), + utcInstant.add({ hours: 12 }) + ]; + } +} + +const timeZone = new Shift24Hour(); + +const instance = new Temporal.ZonedDateTime(0n, timeZone); +instance.startOfDay(); + +assert(calls >= 1, "getPossibleInstantsFor should be called at least once"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getpossibleinstantsfor-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getpossibleinstantsfor-out-of-range-backward-offset-shift.js new file mode 100644 index 0000000000..06055c287e --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getpossibleinstantsfor-out-of-range-backward-offset-shift.js @@ -0,0 +1,49 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.startofday +description: > + UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours. +features: [Temporal] +info: | + GetPossibleInstantsFor: + 5.b.i. Let _numResults_ be _list_'s length. + ii. If _numResults_ > 1, then + 1. Let _epochNs_ be a new empty List. + 2. For each value _instant_ in list, do + a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_. + 3. Let _min_ be the least element of the List _epochNs_. + 4. Let _max_ be the greatest element of the List _epochNs_. + 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception. +---*/ + +class ShiftLonger24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + return 0; + } + + getPossibleInstantsFor(plainDateTime) { + const utc = new Temporal.TimeZone("UTC"); + const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime); + return [ + utcInstant.subtract({ hours: 12, nanoseconds: 1 }), + utcInstant.add({ hours: 12 }), + utcInstant, // add a third value in case the implementation doesn't sort + ]; + } +} + +const timeZone = new ShiftLonger24Hour(); + +const instance = new Temporal.ZonedDateTime(0n, timeZone); +assert.throws(RangeError, () => instance.startOfDay(), "RangeError should be thrown"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-calendar-iso-string.js new file mode 100644 index 0000000000..b01b28a4bd --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-calendar-iso-string.js @@ -0,0 +1,30 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.until +description: An ISO 8601 string can be converted to a calendar ID in Calendar +includes: [temporalHelpers.js] +features: [Temporal] +---*/ + +const timeZone = new Temporal.TimeZone("UTC"); +const instance = new Temporal.ZonedDateTime(0n, timeZone); + +for (const calendar of [ + "2020-01-01", + "2020-01-01[u-ca=iso8601]", + "2020-01-01T00:00:00.000000000", + "2020-01-01T00:00:00.000000000[u-ca=iso8601]", + "01-01", + "01-01[u-ca=iso8601]", + "2020-01", + "2020-01[u-ca=iso8601]", +]) { + const arg = { year: 1970, monthCode: "M01", day: 1, timeZone, calendar }; + const result = instance.until(arg); + TemporalHelpers.assertDuration(result, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, `Calendar created from string "${calendar}"`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-calendar-year-zero.js index f0c915198e..f69f22d2b8 100644 --- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-calendar-year-zero.js +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-calendar-year-zero.js @@ -17,7 +17,8 @@ const invalidStrings = [ ]; const timeZone = new Temporal.TimeZone("UTC"); const instance = new Temporal.ZonedDateTime(0n, timeZone); -invalidStrings.forEach((arg) => { +invalidStrings.forEach((str) => { + const arg = { year: 1976, month: 11, day: 18, calendar: str }; assert.throws( RangeError, () => instance.until(arg), diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-out-of-range-backward-offset-shift.js new file mode 100644 index 0000000000..bf1b4089f0 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-out-of-range-backward-offset-shift.js @@ -0,0 +1,50 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.until +description: > + UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours. +features: [Temporal] +info: | + GetPossibleInstantsFor: + 5.b.i. Let _numResults_ be _list_'s length. + ii. If _numResults_ > 1, then + 1. Let _epochNs_ be a new empty List. + 2. For each value _instant_ in list, do + a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_. + 3. Let _min_ be the least element of the List _epochNs_. + 4. Let _max_ be the greatest element of the List _epochNs_. + 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception. +---*/ + +class ShiftLonger24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + return 0; + } + + getPossibleInstantsFor(plainDateTime) { + const utc = new Temporal.TimeZone("UTC"); + const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime); + return [ + utcInstant.subtract({ hours: 12, nanoseconds: 1 }), + utcInstant.add({ hours: 12 }), + utcInstant, // add a third value in case the implementation doesn't sort + ]; + } +} + +const timeZone = new ShiftLonger24Hour(); +const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone }; + +const instance = new Temporal.ZonedDateTime(0n, timeZone); +assert.throws(RangeError, () => instance.until(arg), "RangeError should be thrown"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-out-of-range-forward-offset-shift.js new file mode 100644 index 0000000000..891307c99d --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-out-of-range-forward-offset-shift.js @@ -0,0 +1,45 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.until +description: > + UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor + in DisambiguatePossibleInstants cannot be greater than 24 hours. +features: [Temporal] +info: | + DisambiguatePossibleInstants: + 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception. +---*/ + +class ShiftLonger24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9; + return 12 * 3600e9 + 1; + } + + getPossibleInstantsFor(plainDateTime) { + const [utcInstant] = super.getPossibleInstantsFor(plainDateTime); + const { year, month, day } = plainDateTime; + + if (year < 1970) return [utcInstant.subtract({ hours: 12 })]; + if (year === 1970 && month === 1 && day === 1) return []; + return [utcInstant.add({ hours: 12, nanoseconds: 1 })]; + } +} + +const timeZone = new ShiftLonger24Hour(); +const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone }; + +const instance = new Temporal.ZonedDateTime(0n, timeZone); +assert.throws(RangeError, () => instance.until(arg), "RangeError should be thrown"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-string-calendar-annotation-invalid-key.js new file mode 100644 index 0000000000..7226f0cb8c --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-string-calendar-annotation-invalid-key.js @@ -0,0 +1,26 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.until +description: Annotation keys are lowercase-only +features: [Temporal] +---*/ + +const invalidStrings = [ + ["1970-01-01T00:00[UTC][U-CA=iso8601]", "invalid capitalized key"], + ["1970-01-01T00:00[UTC][u-CA=iso8601]", "invalid partially-capitalized key"], + ["1970-01-01T00:00[UTC][FOO=bar]", "invalid capitalized unrecognized key"], +]; +const timeZone = new Temporal.TimeZone("UTC"); +const instance = new Temporal.ZonedDateTime(0n, timeZone); +invalidStrings.forEach(([arg, descr]) => { + assert.throws( + RangeError, + () => instance.until(arg), + `annotation keys must be lowercase: ${arg} - ${descr}` + ); +}); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/calendar-dateadd-called-with-options-undefined.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/calendar-dateadd-called-with-options-undefined.js index 5710b9c2b4..14a26aa8ef 100644 --- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/calendar-dateadd-called-with-options-undefined.js +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/calendar-dateadd-called-with-options-undefined.js @@ -14,27 +14,17 @@ features: [Temporal] const calendar = TemporalHelpers.calendarDateAddUndefinedOptions(); const timeZone = TemporalHelpers.oneShiftTimeZone(new Temporal.Instant(0n), 3600e9); const earlier = new Temporal.ZonedDateTime(0n, timeZone, calendar); - -// Basic difference with largestUnit larger than days. -// The call comes from this path: -// ZonedDateTime.until() -> DifferenceZonedDateTime -> AddZonedDateTime -> calendar.dateAdd() - -const later1 = new Temporal.ZonedDateTime(1_213_200_000_000_000n, timeZone, calendar); -earlier.until(later1, { largestUnit: "weeks" }); -assert.sameValue(calendar.dateAddCallCount, 1, "basic difference with largestUnit >days"); +const later = new Temporal.ZonedDateTime(1_213_200_000_000_000n, timeZone, calendar); // Difference with rounding, with smallestUnit a calendar unit. // The calls come from these paths: // ZonedDateTime.until() -> -// DifferenceZonedDateTime -> AddZonedDateTime -> calendar.dateAdd() // RoundDuration -> // MoveRelativeZonedDateTime -> AddZonedDateTime -> calendar.dateAdd() // MoveRelativeDate -> calendar.dateAdd() // BalanceDurationRelative -> MoveRelativeDate -> calendar.dateAdd() -calendar.dateAddCallCount = 0; - -earlier.until(later1, { smallestUnit: "weeks" }); -assert.sameValue(calendar.dateAddCallCount, 4, "rounding difference with calendar smallestUnit"); +earlier.until(later, { smallestUnit: "weeks" }); +assert.sameValue(calendar.dateAddCallCount, 3, "rounding difference with calendar smallestUnit"); reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/differencezoneddatetime-inconsistent-custom-calendar.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/differencezoneddatetime-inconsistent-custom-calendar.js new file mode 100644 index 0000000000..7b77608492 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/differencezoneddatetime-inconsistent-custom-calendar.js @@ -0,0 +1,56 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.until +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 fiftyDays12Hours = 50n * 86400_000_000_000n + 12n * 3600_000_000_000n; + +{ + const tz = new (class extends Temporal.TimeZone { + getPossibleInstantsFor(dateTime) { + return super.getPossibleInstantsFor(dateTime.add({ days: 3 })); + } + })("UTC"); + + const zdt1 = new Temporal.ZonedDateTime(0n, tz); + const zdt2 = new Temporal.ZonedDateTime(fiftyDays12Hours, tz); + + assert.throws(RangeError, () => zdt1.until(zdt2, { largestUnit: "weeks" }), + "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 zdt1 = new Temporal.ZonedDateTime(0n, "UTC", cal); + const zdt2 = new Temporal.ZonedDateTime(fiftyDays12Hours, "UTC", cal); + + assert.throws(RangeError, () => zdt1.until(zdt2, { largestUnit: "weeks" }), + "Calendar calculation causing mixed-sign values should cause RangeError"); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/dst-month-day-boundary.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/dst-month-day-boundary.js new file mode 100644 index 0000000000..73596e06c5 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/dst-month-day-boundary.js @@ -0,0 +1,29 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.until +description: > + Difference with the endpoint being the end of a skipped hour, chooses the + smaller of two possible durations +includes: [temporalHelpers.js] +features: [Temporal] +---*/ + +// Based on a test case by Adam Shaw + +const timeZone = TemporalHelpers.springForwardFallBackTimeZone(); + +const d1 = new Temporal.ZonedDateTime(957258000_000_000_000n /* = 2000-05-02T02:00-07:00 */, timeZone); +const d2 = new Temporal.ZonedDateTime(954669600_000_000_000n /* = 2000-04-02T03:00-07:00 */, timeZone); +// NOTE: nonexistent hour just before d2 + +const result = d1.until(d2, { largestUnit: "months" }); + +TemporalHelpers.assertDuration( + result, 0, 0, 0, -29, -23, 0, 0, 0, 0, 0, + "Result should not balance up to months, but pick the smaller of two possible durations" +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/dst-rounding-result.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/dst-rounding-result.js new file mode 100644 index 0000000000..e6faed63f3 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/dst-rounding-result.js @@ -0,0 +1,48 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.until +description: > + Rounding the resulting duration takes the time zone's UTC offset shifts + into account +includes: [temporalHelpers.js] +features: [Temporal] +---*/ + +// Based on a test case by Adam Shaw + +const timeZone = TemporalHelpers.springForwardFallBackTimeZone(); + +{ + // Month-only part of duration lands on skipped DST hour, should not cause + // disambiguation + const start = new Temporal.ZonedDateTime( + 950868000_000_000_000n /* = 2000-02-18T10Z */, + timeZone); /* = 2000-02-18T02-08 in local time */ + const end = new Temporal.ZonedDateTime( + 954709200_000_000_000n /* = 2000-04-02T21Z */, + timeZone); /* = 2000-04-02T14-07 in local time */ + + const duration = start.until(end, { largestUnit: "months" }); + TemporalHelpers.assertDuration(duration, 0, 1, 0, 15, 11, 0, 0, 0, 0, 0, + "1-month rounding window is shortened by DST"); +} + + +{ + // Month-only part of duration lands on skipped DST hour, should not cause + // disambiguation + const start = new Temporal.ZonedDateTime( + 951991200_000_000_000n /* = 2000-03-02T10Z */, + timeZone); /* = 2000-03-02T02-08 in local time */ + const end = new Temporal.ZonedDateTime( + 956005200_000_000_000n /* = 2000-04-17T21Z */, + timeZone); /* = 2000-04-17T14-07 in local time */ + + const duration = start.until(end, { largestUnit: "months" }); + TemporalHelpers.assertDuration(duration, 0, 1, 0, 15, 12, 0, 0, 0, 0, 0, + "1-month rounding window is not shortened by DST"); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/find-intermediate-instant.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/find-intermediate-instant.js new file mode 100644 index 0000000000..9440f950a6 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/find-intermediate-instant.js @@ -0,0 +1,85 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.until +description: > + Up to 3 intermediate instants may be tried when calculating ZonedDateTime + difference +includes: [compareArray.js, temporalHelpers.js] +features: [BigInt, Temporal] +---*/ + +const calls = []; + +const springFallZone = TemporalHelpers.springForwardFallBackTimeZone(); +TemporalHelpers.observeMethod(calls, springFallZone, "getPossibleInstantsFor"); + +const dateLineZone = TemporalHelpers.crossDateLineTimeZone(); +TemporalHelpers.observeMethod(calls, dateLineZone, "getPossibleInstantsFor"); + +const zdt1 = new Temporal.ZonedDateTime(946722600_000_000_000n /* = 2000-01-01T02:30 local */, springFallZone); + +// Past -> future, without wall-clock overshoot +// Expects valid intermediate Instant WITHOUT day correction (computed once) +{ + const zdt2 = new Temporal.ZonedDateTime(949442400_000_000_000n /* = 2000-02-01T14:00 local */, springFallZone); + const result = zdt1.until(zdt2, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, 0, 1, 0, 0, 11, 30, 0, 0, 0, 0, "no wall-clock overshoot, no DST"); + assert.compareArray(calls, [ + "call getPossibleInstantsFor", // first intermediate in DifferenceZonedDateTime + ], "one intermediate should be tried"); +} + +calls.splice(0); // clear + +// Past -> future, WITH wall-clock overshoot +// Expects valid intermediate Instant with guaranteed 1-DAY correction (computed once) +{ + const zdt2 = new Temporal.ZonedDateTime(949395600_000_000_000n /* = 2000-02-01T01:00 local */, springFallZone); + const result = zdt1.until(zdt2, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, 0, 0, 0, 30, 22, 30, 0, 0, 0, 0, "wall-clock overshoot, no DST"); + assert.compareArray(calls, [ + "call getPossibleInstantsFor", // first intermediate in DifferenceZonedDateTime + ], "one intermediate should be tried"); +} + +calls.splice(0); // clear + +// Past -> future, WITH wall-clock overshoot +// Tries intermediate Instant with 1-DAY correction (first compute) +// Then, ANOTHER correction because updated intermediate Instant falls within spring DST gap, +// pushing it forward, causing wall-clock overshoot again +// (Not possible when going backwards) +{ + const zdt2 = new Temporal.ZonedDateTime(954669600_000_000_000n /* = 2000-04-02T02:00 local */, springFallZone); + const result = zdt1.until(zdt2, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, 0, 3, 0, 0, 23, 30, 0, 0, 0, 0, "wall-clock overshoot, small consiquential DST"); + assert.compareArray(calls, [ + "call getPossibleInstantsFor", // first intermediate in DifferenceZonedDateTime + "call getPossibleInstantsFor", // DisambiguatePossibleInstants on first intermediate + "call getPossibleInstantsFor", // second intermediate in DifferenceZonedDateTime + ], "two intermediates should be tried, with disambiguation"); +} + +calls.splice(0); // clear + +// Past -> future, WITH wall-clock overshoot +// Tries intermediate Instant with 1-DAY correction (first compute) +// Then, ANOTHER correction because updated intermediate Instant falls within dateline DST gap, +// pushing it forward, causing wall-clock overshoot again +// (Not possible when going backwards) +// (This test is just the same as the corresponding one in since(), but negative) +{ + const start = new Temporal.ZonedDateTime(1325102400_000_000_000n /* = 2011-12-28T10:00 local */, dateLineZone); + const end = new Temporal.ZonedDateTime(1325257200_000_000_000n /* = 2011-12-31T05:00 local */, dateLineZone); + const result = start.until(end, { largestUnit: "days" }); + TemporalHelpers.assertDuration(result, 0, 0, 0, 1, 19, 0, 0, 0, 0, 0, "wall-clock overshoot, big consiquential DST"); + assert.compareArray(calls, [ + "call getPossibleInstantsFor", // first intermediate in DifferenceZonedDateTime + "call getPossibleInstantsFor", // second intermediate in DifferenceZonedDateTime + "call getPossibleInstantsFor", // DisambiguatePossibleInstants on second intermediate + ], "two intermediates should be tried, with disambiguation"); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-loop-arbitrarily.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-loop-arbitrarily.js index f057d733d9..77886a2042 100644 --- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-loop-arbitrarily.js +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-loop-arbitrarily.js @@ -5,74 +5,42 @@ /*--- esid: sec-temporal.zoneddatetime.prototype.until description: > - NormalizedTimeDurationToDays can loop arbitrarily up to max safe integer + NormalizedTimeDurationToDays should not be able to loop arbitrarily. info: | NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] ) ... - 21. Repeat, while done is false, - a. Let oneDayFarther be ? AddDaysToZonedDateTime(relativeResult.[[Instant]], - relativeResult.[[DateTime]], timeZoneRec, zonedRelativeTo.[[Calendar]], sign). - b. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(oneDayFarther.[[EpochNanoseconds]], - relativeResult.[[EpochNanoseconds]]). - c. Let oneDayLess be ? SubtractNormalizedTimeDuration(norm, dayLengthNs). - c. If NormalizedTimeDurationSign(oneDayLess) × sign ≥ 0, then - i. Set norm to oneDayLess. - ii. Set relativeResult to oneDayFarther. - iii. Set days to days + sign. - d. Else, - i. Set done to true. -includes: [temporalHelpers.js] + 22. If NormalizedTimeDurationSign(_oneDayLess_) × _sign_ ≥ 0, then + a. Set _norm_ to _oneDayLess_. + b. Set _relativeResult_ to _oneDayFarther_. + c. Set _days_ to _days_ + _sign_. + d. Set _oneDayFarther_ to ? AddDaysToZonedDateTime(_relativeResult_.[[Instant]], _relativeResult_.[[DateTime]], _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _sign_). + e. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(_oneDayFarther.[[EpochNanoseconds]], relativeResult.[[EpochNanoseconds]]). + f. If NormalizedTimeDurationSign(? SubtractNormalizedTimeDuration(_norm_, _dayLengthNs_)) × _sign_ ≥ 0, then + i. Throw a *RangeError* exception. features: [Temporal] ---*/ -const calls = []; const dayLengthNs = 86400000000000n; -const other = new Temporal.ZonedDateTime(dayLengthNs, "UTC", "iso8601"); - -function createRelativeTo(count) { - const dayInstant = new Temporal.Instant(dayLengthNs); - const substitutions = []; - const timeZone = new Temporal.TimeZone("UTC"); - // Return constant value for first _count_ calls - TemporalHelpers.substituteMethod( - timeZone, - "getPossibleInstantsFor", - substitutions - ); - substitutions.length = count; - let i = 0; - for (i = 0; i < substitutions.length; i++) { - // (this value) - substitutions[i] = [dayInstant]; +const dayInstant = new Temporal.Instant(dayLengthNs); +let calls = 0; +const timeZone = new class extends Temporal.TimeZone { + getPossibleInstantsFor() { + calls++; + return [dayInstant]; } - // Record calls in calls[] - TemporalHelpers.observeMethod(calls, timeZone, "getPossibleInstantsFor"); - return new Temporal.ZonedDateTime(0n, timeZone); -} - -let zdt = createRelativeTo(50); -calls.splice(0); // Reset calls list after ZonedDateTime construction -zdt.until(other, { - largestUnit: "day", -}); -assert.sameValue( - calls.length, - 50 + 1, - "Expected ZonedDateTime.until to call getPossibleInstantsFor correct number of times" -); +}("UTC"); -zdt = createRelativeTo(100); -calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction -zdt.until(other, { - largestUnit: "day", -}); -assert.sameValue( - calls.length, - 100 + 1, - "Expected ZonedDateTime.until to call getPossibleInstantsFor correct number of times" -); +const zdt = new Temporal.ZonedDateTime(0n, timeZone); +const other = new Temporal.ZonedDateTime(dayLengthNs * 2n, "UTC", "iso8601"); -zdt = createRelativeTo(105); -assert.throws(RangeError, () => zdt.until(other, { largestUnit: "day" }), "105 days > 2⁵³ ns"); +assert.throws(RangeError, () => zdt.until(other, { largestUnit: "day", smallestUnit: "second" }), "indefinite loop is prevented"); +assert.sameValue(calls, 4, "getPossibleInstantsFor is not called indefinitely"); + // Expected calls: + // DifferenceTemporalZonedDateTime -> + // DifferenceZonedDateTime -> GetInstantFor (1) + // NormalizedTimeDurationToDays -> + // AddDaysToZonedDateTime (2, step 12) + // AddDaysToZonedDateTime (3, step 15) + // AddDaysToZonedDateTime (4, step 18.d) reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-range-errors.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-range-errors.js index 178b1c981b..479dcf0b0c 100644 --- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-range-errors.js +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-range-errors.js @@ -8,12 +8,12 @@ description: > RangeErrors. info: | NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] ) - 22. If days < 0 and sign = 1, throw a RangeError exception. - 23. If days > 0 and sign = -1, throw a RangeError exception. + 23. If days < 0 and sign = 1, throw a RangeError exception. + 24. If days > 0 and sign = -1, throw a RangeError exception. ... - 25. If NormalizedTimeDurationSign(_norm_) = 1 and sign = -1, throw a RangeError exception. + 26. If NormalizedTimeDurationSign(_norm_) = 1 and sign = -1, throw a RangeError exception. ... - 28. If dayLength ≥ 2⁵³, throw a RangeError exception. + 29. If dayLength ≥ 2⁵³, throw a RangeError exception. features: [Temporal, BigInt] includes: [temporalHelpers.js] ---*/ @@ -40,13 +40,16 @@ const dayNs = 86_400_000_000_000; const zeroZDT = new Temporal.ZonedDateTime(0n, "UTC"); const oneZDT = new Temporal.ZonedDateTime(1n, "UTC"); const epochInstant = new Temporal.Instant(0n); -const options = { largestUnit: "days" }; +const options = { largestUnit: "days", smallestUnit: "seconds", roundingMode: "expand" }; -// Step 22: days < 0 and sign = 1 +// Step 23: days < 0 and sign = 1 let start = new Temporal.ZonedDateTime( 0n, // Sets DifferenceZonedDateTime _ns1_ timeZoneSubstituteValues( - [[epochInstant]], // Returned in step 16, setting _relativeResult_ + [ + TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally in DifferenceZonedDateTime + [epochInstant], // Returned in step 16, setting _relativeResult_ + ], [ // Behave normally in 2 calls made prior to NormalizedTimeDurationToDays TemporalHelpers.SUBSTITUTE_SKIP, @@ -60,14 +63,18 @@ assert.throws(RangeError, () => start.until( oneZDT, // Sets DifferenceZonedDateTime _ns2_ options - ) + ), + "days < 0 and sign = 1" ); -// Step 23: days > 0 and sign = -1 +// Step 24: days > 0 and sign = -1 start = new Temporal.ZonedDateTime( 1n, // Sets DifferenceZonedDateTime _ns1_ timeZoneSubstituteValues( - [[epochInstant]], // Returned in step 16, setting _relativeResult_ + [ + TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally in DifferenceZonedDateTime + [epochInstant], // Returned in step 16, setting _relativeResult_ + ], [ // Behave normally in 2 calls made prior to NormalizedTimeDurationToDays TemporalHelpers.SUBSTITUTE_SKIP, @@ -81,14 +88,18 @@ assert.throws(RangeError, () => start.until( zeroZDT, // Sets DifferenceZonedDateTime _ns2_ options - ) + ), + "days > 0 and sign = -1" ); -// Step 25: nanoseconds > 0 and sign = -1 +// Step 26: nanoseconds > 0 and sign = -1 start = new Temporal.ZonedDateTime( 1n, // Sets DifferenceZonedDateTime _ns1_ timeZoneSubstituteValues( - [[new Temporal.Instant(-1n)]], // Returned in step 16, setting _relativeResult_ + [ + TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally in DifferenceZonedDateTime + [new Temporal.Instant(-2_000_000_000n)], // Returned in step 16, setting _relativeResult_ + ], [ // Behave normally in 2 calls made prior to NormalizedTimeDurationToDays TemporalHelpers.SUBSTITUTE_SKIP, @@ -102,16 +113,20 @@ assert.throws(RangeError, () => start.until( zeroZDT, // Sets DifferenceZonedDateTime _ns2_ options - ) + ), + "norm > 0 and sign = -1" ); -// Step 28: day length is an unsafe integer +// Step 29: day length is an unsafe integer start = new Temporal.ZonedDateTime( 0n, timeZoneSubstituteValues( - // Not called in step 16 because _days_ = 0 - // Returned in step 21.a, making _oneDayFarther_ 2^53 ns later than _relativeResult_ - [[new Temporal.Instant(2n ** 53n)]], + [ + TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally in DifferenceZonedDateTime + // Not called in step 16 because _days_ = 0 + // Returned in step 19, making _oneDayFarther_ 2^53 ns later than _relativeResult_ + [new Temporal.Instant(2n ** 53n)], + ], [] ) ); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/order-of-operations.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/order-of-operations.js index 6a233dc989..c88ecfd0d6 100644 --- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/order-of-operations.js +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/order-of-operations.js @@ -306,10 +306,6 @@ assert.compareArray(actual, [ // DifferenceZonedDateTime "call this.timeZone.getOffsetNanosecondsFor", "call this.timeZone.getOffsetNanosecondsFor", - // NanosecondsToDays - "call this.timeZone.getOffsetNanosecondsFor", - "call this.timeZone.getOffsetNanosecondsFor", - // NanosecondsToDays → AddDaysToZonedDateTime "call this.timeZone.getPossibleInstantsFor", ], "order of operations with identical wall-clock times and largestUnit a calendar unit"); actual.splice(0); // clear @@ -328,17 +324,8 @@ const expectedOpsForCalendarDifference = [ "call this.timeZone.getOffsetNanosecondsFor", // DifferenceZonedDateTime "call this.timeZone.getOffsetNanosecondsFor", - // DifferenceISODateTime - "call this.calendar.dateUntil", - // AddZonedDateTime - "call this.calendar.dateAdd", - "call this.timeZone.getPossibleInstantsFor", - // NanosecondsToDays - "call this.timeZone.getOffsetNanosecondsFor", - "call this.timeZone.getOffsetNanosecondsFor", - // NanosecondsToDays → AddDaysToZonedDateTime - "call this.timeZone.getPossibleInstantsFor", "call this.timeZone.getPossibleInstantsFor", + "call this.calendar.dateUntil", ]; const expectedOpsForCalendarRounding = [ diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/wrapping-at-end-of-month.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/wrapping-at-end-of-month.js new file mode 100644 index 0000000000..50b40393f1 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/wrapping-at-end-of-month.js @@ -0,0 +1,120 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.until +description: Tests balancing of days to months at end of month +includes: [temporalHelpers.js] +features: [Temporal] +---*/ + +// Difference between end of longer month to end of following shorter month +{ + const end = new Temporal.ZonedDateTime(5011200_000_000_000n /* = 1970-02-28T00Z */, "UTC"); + for (const largestUnit of ["years", "months"]) { + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(2332800_000_000_000n /* = 1970-01-28T00Z */, "UTC").until(end, { largestUnit }), + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + "Jan 28th to Feb 28th is one month" + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(2419200_000_000_000n /* = 1970-01-29T00Z */, "UTC").until(end, { largestUnit }), + 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, + "Jan 29th to Feb 28th is 30 days, not one month" + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(2505600_000_000_000n /* = 1970-01-30T00Z */, "UTC").until(end, { largestUnit }), + 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, + "Jan 30th to Feb 28th is 29 days, not one month" + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(2592000_000_000_000n /* = 1970-01-31T00Z */, "UTC").until(end, { largestUnit }), + 0, 0, 0, 28, 0, 0, 0, 0, 0, 0, + "Jan 31st to Feb 28th is 28 days, not one month" + ); + } +} + +// Difference between end of leap-year January to end of leap-year February +{ + const end = new Temporal.ZonedDateTime(68169600_000_000_000n /* = 1972-02-29T00Z */, "UTC"); + for (const largestUnit of ["years", "months"]) { + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(65491200_000_000_000n /* = 1972-01-29T00Z */, "UTC").until(end, { largestUnit }), + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + "Jan 29th to Feb 29th is one month" + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(65577600_000_000_000n /* = 1972-01-30T00Z */, "UTC").until(end, { largestUnit }), + 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, + "Jan 30th to Feb 29th is 30 days, not one month" + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(65664000_000_000_000n /* = 1972-01-31T00Z */, "UTC").until(end, { largestUnit }), + 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, + "Jan 31st to Feb 29th is 29 days, not one month" + ); + } +} + +// Difference between end of longer month to end of not-immediately-following +// shorter month +{ + const end = new Temporal.ZonedDateTime(28771200_000_000_000n /* = 1970-11-30T00Z */, "UTC"); + for (const largestUnit of ["years", "months"]) { + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(20822400_000_000_000n /* = 1970-08-30T00Z */, "UTC").until(end, { largestUnit }), + 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, + "Aug 30th to Nov 30th is 3 months" + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(20908800_000_000_000n /* = 1970-08-31T00Z */, "UTC").until(end, { largestUnit }), + 0, 2, 0, 30, 0, 0, 0, 0, 0, 0, + "Aug 31st to Nov 30th is 2 months 30 days, not 3 months" + ); + } +} + +// Difference between end of longer month in one year to shorter month in +// later year +{ + const end = new Temporal.ZonedDateTime(104976000_000_000_000n /* = 1973-04-30T00Z */, "UTC"); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(31363200_000_000_000n /* = 1970-12-30T00Z */, "UTC").until(end, { largestUnit: "months" }), + 0, 28, 0, 0, 0, 0, 0, 0, 0, 0, + "Dec 30th 1970 to Apr 30th 1973 is 28 months" + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(31363200_000_000_000n /* = 1970-12-30T00Z */, "UTC").until(end, { largestUnit: "years" }), + 2, 4, 0, 0, 0, 0, 0, 0, 0, 0, + "Dec 30th 1970 to Apr 30th 1973 is 2 years, 4 months" + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(31449600_000_000_000n /* = 1970-12-31T00Z */, "UTC").until(end, { largestUnit: "months" }), + 0, 27, 0, 30, 0, 0, 0, 0, 0, 0, + "Dec 30th 1970 to Apr 30th 1973 is 27 months, 30 days, not 28 months" + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(31449600_000_000_000n /* = 1970-12-31T00Z */, "UTC").until(end, { largestUnit: "years" }), + 2, 3, 0, 30, 0, 0, 0, 0, 0, 0, + "Dec 30th 1970 to Apr 30th 1973 is 2 years, 3 months, 30 days, not 2 years 4 months" + ); +} + +// Difference where months passes through a month that's the same length or +// shorter than either the start or end month +{ + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(2419200_000_000_000n /* = 1970-01-29T00Z */, "UTC").until(new Temporal.ZonedDateTime(7430400_000_000_000n /* = 1970-03-28T00Z */, "UTC"), { largestUnit: "months" }), + 0, 1, 0, 28, 0, 0, 0, 0, 0, 0, + "Jan 29th to Mar 28th is 1 month 28 days, not 58 days" + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(2592000_000_000_000n /* = 1970-01-31T00Z */, "UTC").until(new Temporal.ZonedDateTime(44409600_000_000_000n /* = 1971-05-30T00Z */, "UTC"), { largestUnit: "years" }), + 1, 3, 0, 30, 0, 0, 0, 0, 0, 0, + "Jan 31st 1970 to May 30th 1971 is 1 year, 3 months, 30 days, not 1 year, 2 months, 60 days" + ); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/weekOfYear/custom-calendar-weekofyear.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/weekOfYear/custom-calendar-weekofyear.js new file mode 100644 index 0000000000..43a7d2ba14 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/weekOfYear/custom-calendar-weekofyear.js @@ -0,0 +1,27 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.weekofyear +description: > + Temporal.ZonedDateTime.prototype.weekOfYear returns undefined for all + custom calendars where weekOfYear() returns undefined. +features: [Temporal] +---*/ + +class CustomCalendar extends Temporal.Calendar { + constructor() { + super("iso8601"); + } + weekOfYear() { + return undefined; + } +} + +const calendar = new CustomCalendar(); +// Epoch Nanoseconds for new Temporal.PlainDateTime(2024, 1, 1, 12, 34, 56, 987, 654, 321, calendar); +const customCalendarDate = new Temporal.ZonedDateTime(1_704_112_496_987_654_321n, "UTC", calendar); +assert.sameValue(customCalendarDate.weekOfYear, undefined); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/weekOfYear/validate-calendar-value.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/weekOfYear/validate-calendar-value.js index 8b18332ce4..b2b83c1d41 100644 --- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/weekOfYear/validate-calendar-value.js +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/weekOfYear/validate-calendar-value.js @@ -9,7 +9,6 @@ features: [Temporal] ---*/ const badResults = [ - [undefined, TypeError], [null, TypeError], [false, TypeError], [Infinity, RangeError], diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/with/getoffsetnanosecondsfor-maximum-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/with/getoffsetnanosecondsfor-maximum-forward-offset-shift.js new file mode 100644 index 0000000000..a1c2cec71d --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/with/getoffsetnanosecondsfor-maximum-forward-offset-shift.js @@ -0,0 +1,52 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.with +description: > + UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor + in DisambiguatePossibleInstants can be at most 24 hours. +features: [Temporal] +info: | + DisambiguatePossibleInstants: + 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception. +---*/ + +let calls = 0; + +class Shift24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + _shiftEpochNs = 0n; + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + calls++; + if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9; + return 12 * 3600e9; + } + + getPossibleInstantsFor(plainDateTime) { + const [utcInstant] = super.getPossibleInstantsFor(plainDateTime); + const { year, month, day } = plainDateTime; + + if (year < 1970) return [utcInstant.subtract({ hours: 12 })]; + if (year === 1970 && month === 1 && day === 1) return []; + return [utcInstant.add({ hours: 12 })]; + } +} + +const timeZone = new Shift24Hour(); +const instance = new Temporal.ZonedDateTime(0n, timeZone); + +for (const disambiguation of ["earlier", "later", "compatible"]) { + instance.with({ day: 1 }, { disambiguation }); + + assert(calls >= 2, "getOffsetNanosecondsFor should be called at least twice"); + calls = 0; +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/with/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/with/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js new file mode 100644 index 0000000000..a93ee7ff32 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/with/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js @@ -0,0 +1,46 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.with +description: > + UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor + in DisambiguatePossibleInstants cannot be greater than 24 hours. +features: [Temporal] +info: | + DisambiguatePossibleInstants: + 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception. +---*/ + +class ShiftLonger24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + _shiftEpochNs = 0n; + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9; + return 12 * 3600e9 + 1; + } + + getPossibleInstantsFor(plainDateTime) { + const [utcInstant] = super.getPossibleInstantsFor(plainDateTime); + const { year, month, day } = plainDateTime; + + if (year < 1970) return [utcInstant.subtract({ hours: 12 })]; + if (year === 1970 && month === 1 && day === 1) return []; + return [utcInstant.add({ hours: 12, nanoseconds: 1 })]; + } +} + +const timeZone = new ShiftLonger24Hour(); + +const instance = new Temporal.ZonedDateTime(0n, timeZone); +for (const disambiguation of ["earlier", "later", "compatible"]) { + assert.throws(RangeError, () => instance.with({ day: 1 }, { disambiguation }), "RangeError should be thrown"); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/with/getpossibleinstantsfor-maximum-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/with/getpossibleinstantsfor-maximum-backward-offset-shift.js new file mode 100644 index 0000000000..b053782926 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/with/getpossibleinstantsfor-maximum-backward-offset-shift.js @@ -0,0 +1,56 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.with +description: > + UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours. +features: [Temporal] +info: | + GetPossibleInstantsFor: + 5.b.i. Let _numResults_ be _list_'s length. + ii. If _numResults_ > 1, then + 1. Let _epochNs_ be a new empty List. + 2. For each value _instant_ in list, do + a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_. + 3. Let _min_ be the least element of the List _epochNs_. + 4. Let _max_ be the greatest element of the List _epochNs_. + 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception. +---*/ + +let calls = 0; + +class Shift24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + return 0; + } + + getPossibleInstantsFor(plainDateTime) { + calls++; + const utc = new Temporal.TimeZone("UTC"); + const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime); + return [ + utcInstant.subtract({ hours: 12 }), + utcInstant.add({ hours: 12 }) + ]; + } +} + +const timeZone = new Shift24Hour(); + +const instance = new Temporal.ZonedDateTime(0n, timeZone); +for (const disambiguation of ["earlier", "later", "compatible"]) { + instance.with({ day: 1 }, { disambiguation }); + + assert(calls >= 1, "getPossibleInstantsFor should be called at least once"); + calls = 0; +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/with/getpossibleinstantsfor-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/with/getpossibleinstantsfor-out-of-range-backward-offset-shift.js new file mode 100644 index 0000000000..7db6925321 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/with/getpossibleinstantsfor-out-of-range-backward-offset-shift.js @@ -0,0 +1,51 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.with +description: > + UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours. +features: [Temporal] +info: | + GetPossibleInstantsFor: + 5.b.i. Let _numResults_ be _list_'s length. + ii. If _numResults_ > 1, then + 1. Let _epochNs_ be a new empty List. + 2. For each value _instant_ in list, do + a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_. + 3. Let _min_ be the least element of the List _epochNs_. + 4. Let _max_ be the greatest element of the List _epochNs_. + 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception. +---*/ + +class ShiftLonger24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + return 0; + } + + getPossibleInstantsFor(plainDateTime) { + const utc = new Temporal.TimeZone("UTC"); + const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime); + return [ + utcInstant.subtract({ hours: 12, nanoseconds: 1 }), + utcInstant.add({ hours: 12 }), + utcInstant, // add a third value in case the implementation doesn't sort + ]; + } +} + +const timeZone = new ShiftLonger24Hour(); + +const instance = new Temporal.ZonedDateTime(0n, timeZone); +for (const disambiguation of ["earlier", "later", "compatible"]) { + assert.throws(RangeError, () => instance.with({ day: 1 }, { disambiguation }), "RangeError should be thrown"); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withCalendar/calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withCalendar/calendar-iso-string.js new file mode 100644 index 0000000000..834be0ebb6 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withCalendar/calendar-iso-string.js @@ -0,0 +1,49 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.withcalendar +description: An ISO 8601 string can be converted to a calendar ID in Calendar +features: [Temporal] +---*/ + +const instance = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", { + dateAdd() {}, + dateFromFields() {}, + dateUntil() {}, + day() {}, + dayOfWeek() {}, + dayOfYear() {}, + daysInMonth() {}, + daysInWeek() {}, + daysInYear() {}, + fields() {}, + id: "replace-me", + inLeapYear() {}, + mergeFields() {}, + month() {}, + monthCode() {}, + monthDayFromFields() {}, + monthsInYear() {}, + weekOfYear() {}, + year() {}, + yearMonthFromFields() {}, + yearOfWeek() {}, +}); + +for (const arg of [ + "2020-01-01", + "2020-01-01[u-ca=iso8601]", + "2020-01-01T00:00:00.000000000", + "2020-01-01T00:00:00.000000000[u-ca=iso8601]", + "01-01", + "01-01[u-ca=iso8601]", + "2020-01", + "2020-01[u-ca=iso8601]", +]) { + const result = instance.withCalendar(arg); + assert.sameValue(result.getISOFields().calendar, "iso8601", `Calendar created from string "${arg}"`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-propertybag-calendar-iso-string.js new file mode 100644 index 0000000000..a0b1fa2928 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-propertybag-calendar-iso-string.js @@ -0,0 +1,29 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.withplaindate +description: An ISO 8601 string can be converted to a calendar ID in Calendar +features: [Temporal] +---*/ + +const timeZone = new Temporal.TimeZone("UTC"); +const instance = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, timeZone); + +for (const calendar of [ + "2020-01-01", + "2020-01-01[u-ca=iso8601]", + "2020-01-01T00:00:00.000000000", + "2020-01-01T00:00:00.000000000[u-ca=iso8601]", + "01-01", + "01-01[u-ca=iso8601]", + "2020-01", + "2020-01[u-ca=iso8601]", +]) { + const arg = { year: 1976, monthCode: "M11", day: 18, calendar }; + const result = instance.withPlainDate(arg); + assert.sameValue(result.epochNanoseconds, 217_129_600_000_000_000n, `Calendar created from string "${calendar}"`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-propertybag-calendar-year-zero.js index 3d7d95215e..52cefeffa3 100644 --- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-propertybag-calendar-year-zero.js +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-propertybag-calendar-year-zero.js @@ -17,7 +17,8 @@ const invalidStrings = [ ]; const timeZone = new Temporal.TimeZone("UTC"); const instance = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, timeZone); -invalidStrings.forEach((arg) => { +invalidStrings.forEach((str) => { + const arg = { year: 1976, month: 11, day: 18, calendar: str }; assert.throws( RangeError, () => instance.withPlainDate(arg), diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-string-calendar-annotation-invalid-key.js new file mode 100644 index 0000000000..c1f874813b --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-string-calendar-annotation-invalid-key.js @@ -0,0 +1,26 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.withplaindate +description: Annotation keys are lowercase-only +features: [Temporal] +---*/ + +const invalidStrings = [ + ["1970-01-01[U-CA=iso8601]", "invalid capitalized key"], + ["1970-01-01[u-CA=iso8601]", "invalid partially-capitalized key"], + ["1970-01-01[FOO=bar]", "invalid capitalized unrecognized key"], +]; +const timeZone = new Temporal.TimeZone("UTC"); +const instance = new Temporal.ZonedDateTime(0n, timeZone); +invalidStrings.forEach(([arg, descr]) => { + assert.throws( + RangeError, + () => instance.withPlainDate(arg), + `annotation keys must be lowercase: ${arg} - ${descr}` + ); +}); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getoffsetnanosecondsfor-maximum-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getoffsetnanosecondsfor-maximum-forward-offset-shift.js new file mode 100644 index 0000000000..d595cb1952 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getoffsetnanosecondsfor-maximum-forward-offset-shift.js @@ -0,0 +1,49 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.withplaindate +description: > + UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor + in DisambiguatePossibleInstants can be at most 24 hours. +features: [Temporal] +info: | + DisambiguatePossibleInstants: + 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception. +---*/ + +let calls = 0; + +class Shift24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + calls++; + if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9; + return 12 * 3600e9; + } + + getPossibleInstantsFor(plainDateTime) { + const [utcInstant] = super.getPossibleInstantsFor(plainDateTime); + const { year, month, day } = plainDateTime; + + if (year < 1970) return [utcInstant.subtract({ hours: 12 })]; + if (year === 1970 && month === 1 && day === 1) return []; + return [utcInstant.add({ hours: 12 })]; + } +} + +const timeZone = new Shift24Hour(); + +const instance = new Temporal.ZonedDateTime(0n, timeZone); +instance.withPlainDate(new Temporal.PlainDate(1970, 1, 1)); + +assert(calls >= 2, "getOffsetNanosecondsFor should be called at least twice"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js new file mode 100644 index 0000000000..7af0ddc2d1 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js @@ -0,0 +1,44 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.withplaindate +description: > + UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor + in DisambiguatePossibleInstants cannot be greater than 24 hours. +features: [Temporal] +info: | + DisambiguatePossibleInstants: + 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception. +---*/ + +class ShiftLonger24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9; + return 12 * 3600e9 + 1; + } + + getPossibleInstantsFor(plainDateTime) { + const [utcInstant] = super.getPossibleInstantsFor(plainDateTime); + const { year, month, day } = plainDateTime; + + if (year < 1970) return [utcInstant.subtract({ hours: 12 })]; + if (year === 1970 && month === 1 && day === 1) return []; + return [utcInstant.add({ hours: 12, nanoseconds: 1 })]; + } +} + +const timeZone = new ShiftLonger24Hour(); + +const instance = new Temporal.ZonedDateTime(0n, timeZone); +assert.throws(RangeError, () => instance.withPlainDate(new Temporal.PlainDate(1970, 1, 1)), "RangeError should be thrown"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getpossibleinstantsfor-maximum-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getpossibleinstantsfor-maximum-backward-offset-shift.js new file mode 100644 index 0000000000..2ed4c8f1d9 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getpossibleinstantsfor-maximum-backward-offset-shift.js @@ -0,0 +1,53 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.withplaindate +description: > + UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours. +features: [Temporal] +info: | + GetPossibleInstantsFor: + 5.b.i. Let _numResults_ be _list_'s length. + ii. If _numResults_ > 1, then + 1. Let _epochNs_ be a new empty List. + 2. For each value _instant_ in list, do + a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_. + 3. Let _min_ be the least element of the List _epochNs_. + 4. Let _max_ be the greatest element of the List _epochNs_. + 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception. +---*/ + +let calls = 0; + +class Shift24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + return 0; + } + + getPossibleInstantsFor(plainDateTime) { + calls++; + const utc = new Temporal.TimeZone("UTC"); + const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime); + return [ + utcInstant.subtract({ hours: 12 }), + utcInstant.add({ hours: 12 }) + ]; + } +} + +const timeZone = new Shift24Hour(); + +const instance = new Temporal.ZonedDateTime(0n, timeZone); +instance.withPlainDate(new Temporal.PlainDate(1970, 1, 1)); + +assert(calls >= 1, "getPossibleInstantsFor should be called at least once"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getpossibleinstantsfor-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getpossibleinstantsfor-out-of-range-backward-offset-shift.js new file mode 100644 index 0000000000..b43bdba300 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getpossibleinstantsfor-out-of-range-backward-offset-shift.js @@ -0,0 +1,49 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.withplaindate +description: > + UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours. +features: [Temporal] +info: | + GetPossibleInstantsFor: + 5.b.i. Let _numResults_ be _list_'s length. + ii. If _numResults_ > 1, then + 1. Let _epochNs_ be a new empty List. + 2. For each value _instant_ in list, do + a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_. + 3. Let _min_ be the least element of the List _epochNs_. + 4. Let _max_ be the greatest element of the List _epochNs_. + 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception. +---*/ + +class ShiftLonger24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + return 0; + } + + getPossibleInstantsFor(plainDateTime) { + const utc = new Temporal.TimeZone("UTC"); + const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime); + return [ + utcInstant.subtract({ hours: 12, nanoseconds: 1 }), + utcInstant.add({ hours: 12 }), + utcInstant, // add a third value in case the implementation doesn't sort + ]; + } +} + +const timeZone = new ShiftLonger24Hour(); + +const instance = new Temporal.ZonedDateTime(0n, timeZone); +assert.throws(RangeError, () => instance.withPlainDate(new Temporal.PlainDate(1970, 1, 1)), "RangeError should be thrown"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/argument-string-calendar-annotation-invalid-key.js new file mode 100644 index 0000000000..d07df63e1e --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/argument-string-calendar-annotation-invalid-key.js @@ -0,0 +1,32 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.withplaintime +description: Annotation keys are lowercase-only +features: [Temporal] +---*/ + +const invalidStrings = [ + ["00:00[U-CA=iso8601]", "invalid capitalized key, time-only"], + ["T00:00[U-CA=iso8601]", "invalid capitalized key, time designator"], + ["1970-01-01T00:00[U-CA=iso8601]", "invalid capitalized key"], + ["00:00[u-CA=iso8601]", "invalid partially-capitalized key, time-only"], + ["T00:00[u-CA=iso8601]", "invalid partially-capitalized key, time designator"], + ["1970-01-01T00:00[u-CA=iso8601]", "invalid partially-capitalized key"], + ["00:00[FOO=bar]", "invalid capitalized unrecognized key, time-only"], + ["T00:00[FOO=bar]", "invalid capitalized unrecognized key, time designator"], + ["1970-01-01T00:00[FOO=bar]", "invalid capitalized unrecognized key"], +]; +const timeZone = new Temporal.TimeZone("UTC"); +const instance = new Temporal.ZonedDateTime(0n, timeZone); +invalidStrings.forEach(([arg, descr]) => { + assert.throws( + RangeError, + () => instance.withPlainTime(arg), + `annotation keys must be lowercase: ${arg} - ${descr}` + ); +}); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js new file mode 100644 index 0000000000..0bf0721c54 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js @@ -0,0 +1,49 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.withplaintime +description: > + UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor + in DisambiguatePossibleInstants can be at most 24 hours. +features: [Temporal] +info: | + DisambiguatePossibleInstants: + 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception. +---*/ + +let calls = 0; + +class Shift24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + _shiftEpochNs = 0n + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + calls++; + if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9; + return 12 * 3600e9; + } + + getPossibleInstantsFor(plainDateTime) { + const [utcInstant] = super.getPossibleInstantsFor(plainDateTime); + const { year, month, day } = plainDateTime; + + if (year < 1970) return [utcInstant.subtract({ hours: 12 })]; + if (year === 1970 && month === 1 && day === 1) return []; + return [utcInstant.add({ hours: 12 })]; + } +} + +const timeZone = new Shift24Hour(); + +const instance = new Temporal.ZonedDateTime(0n, timeZone); +instance.withPlainTime(); + +assert(calls >= 2, "getOffsetNanosecondsFor should be called at least twice"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js new file mode 100644 index 0000000000..8f2615faae --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js @@ -0,0 +1,44 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.withplaintime +description: > + UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor + in DisambiguatePossibleInstants cannot be greater than 24 hours. +features: [Temporal] +info: | + DisambiguatePossibleInstants: + 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception. +---*/ + +class ShiftLonger24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + _shiftEpochNs = 0n + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9; + return 12 * 3600e9 + 1; + } + + getPossibleInstantsFor(plainDateTime) { + const [utcInstant] = super.getPossibleInstantsFor(plainDateTime); + const { year, month, day } = plainDateTime; + + if (year < 1970) return [utcInstant.subtract({ hours: 12 })]; + if (year === 1970 && month === 1 && day === 1) return []; + return [utcInstant.add({ hours: 12, nanoseconds: 1 })]; + } +} + +const timeZone = new ShiftLonger24Hour(); + +const instance = new Temporal.ZonedDateTime(0n, timeZone); +assert.throws(RangeError, () => instance.withPlainTime(), "RangeError should be thrown"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getpossibleinstantsfor-maximum-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getpossibleinstantsfor-maximum-backward-offset-shift.js new file mode 100644 index 0000000000..21dcd8b3ba --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getpossibleinstantsfor-maximum-backward-offset-shift.js @@ -0,0 +1,53 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.withplaintime +description: > + UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours. +features: [Temporal] +info: | + GetPossibleInstantsFor: + 5.b.i. Let _numResults_ be _list_'s length. + ii. If _numResults_ > 1, then + 1. Let _epochNs_ be a new empty List. + 2. For each value _instant_ in list, do + a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_. + 3. Let _min_ be the least element of the List _epochNs_. + 4. Let _max_ be the greatest element of the List _epochNs_. + 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception. +---*/ + +let calls = 0; + +class Shift24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + return 0; + } + + getPossibleInstantsFor(plainDateTime) { + calls++; + const utc = new Temporal.TimeZone("UTC"); + const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime); + return [ + utcInstant.subtract({ hours: 12 }), + utcInstant.add({ hours: 12 }) + ]; + } +} + +const timeZone = new Shift24Hour(); + +const instance = new Temporal.ZonedDateTime(0n, timeZone); +instance.withPlainTime(); + +assert(calls >= 1, "getPossibleInstantsFor should be called at least once"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js new file mode 100644 index 0000000000..f2856f0aea --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js @@ -0,0 +1,49 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.withplaintime +description: > + UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours. +features: [Temporal] +info: | + GetPossibleInstantsFor: + 5.b.i. Let _numResults_ be _list_'s length. + ii. If _numResults_ > 1, then + 1. Let _epochNs_ be a new empty List. + 2. For each value _instant_ in list, do + a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_. + 3. Let _min_ be the least element of the List _epochNs_. + 4. Let _max_ be the greatest element of the List _epochNs_. + 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception. +---*/ + +class ShiftLonger24Hour extends Temporal.TimeZone { + id = 'TestTimeZone'; + + constructor() { + super('UTC'); + } + + getOffsetNanosecondsFor(instant) { + return 0; + } + + getPossibleInstantsFor(plainDateTime) { + const utc = new Temporal.TimeZone("UTC"); + const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime); + return [ + utcInstant.subtract({ hours: 12, nanoseconds: 1 }), + utcInstant.add({ hours: 12 }), + utcInstant, // add a third value in case the implementation doesn't sort + ]; + } +} + +const timeZone = new ShiftLonger24Hour(); + +const instance = new Temporal.ZonedDateTime(0n, timeZone); +assert.throws(RangeError, () => instance.withPlainTime(), "RangeError should be thrown"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/yearOfWeek/custom-calendar-weekofyear.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/yearOfWeek/custom-calendar-weekofyear.js new file mode 100644 index 0000000000..331002f262 --- /dev/null +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/yearOfWeek/custom-calendar-weekofyear.js @@ -0,0 +1,27 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// 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.zoneddatetime.prototype.yearofweek +description: > + Temporal.ZonedDateTime.prototype.yearOfWeek returns undefined for all + custom calendars where yearOfWeek() returns undefined. +features: [Temporal] +---*/ + +class CustomCalendar extends Temporal.Calendar { + constructor() { + super("iso8601"); + } + yearOfWeek() { + return undefined; + } +} + +const calendar = new CustomCalendar(); +// Epoch Nanoseconds for new Temporal.PlainDateTime(2024, 1, 1, 12, 34, 56, 987, 654, 321, calendar); +const customCalendarDate = new Temporal.ZonedDateTime(1_704_112_496_987_654_321n, "UTC", calendar); +assert.sameValue(customCalendarDate.yearOfWeek, undefined); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/yearOfWeek/validate-calendar-value.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/yearOfWeek/validate-calendar-value.js index 9bd447f9a1..9eb067c70b 100644 --- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/yearOfWeek/validate-calendar-value.js +++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/yearOfWeek/validate-calendar-value.js @@ -9,7 +9,6 @@ features: [Temporal] ---*/ const badResults = [ - [undefined, TypeError], [Infinity, RangeError], [-Infinity, RangeError], [Symbol("foo"), TypeError], |