summaryrefslogtreecommitdiffstats
path: root/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until')
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-builtin-calendar-no-array-iteration.js24
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-calendar-datefromfields-called-with-null-prototype-fields.js19
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-constructor-in-calendar-fields.js17
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-duplicate-calendar-fields.js19
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-leap-second.js30
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-number.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-plaindatetime.js24
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-case-insensitive.js20
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-leap-second.js24
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-number.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-string.js20
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-wrong-type.js46
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-year-zero.js27
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-proto-in-calendar-fields.js17
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-calendar-annotation.js34
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-critical-unknown-annotation.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-date-with-utc-offset.js49
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-invalid.js64
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-multiple-calendar.js32
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-multiple-time-zone.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-time-separators.js30
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-time-zone-annotation.js39
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-unknown-annotation.js33
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-with-utc-designator.js24
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-wrong-type.js43
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-zoneddatetime-convert.js22
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-zoneddatetime-slots.js40
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-non-integer.js19
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-not-callable.js23
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-out-of-range.js19
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-wrong-type.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/basic.js23
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/branding.js27
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/browser.js0
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/builtin-calendar-no-observable-calls.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/builtin.js36
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-dateadd-called-with-plaindate-instance.js20
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-datefromfields-called-with-options-undefined.js19
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-dateuntil-called-with-null-prototype-options.js20
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-dateuntil-called-with-singular-largestunit.js30
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-fields-iterable.js36
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-id-match.js33
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-invalid-return.js42
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-mismatch.js62
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-temporal-object.js29
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/custom.js35
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/days-in-month.js22
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/days-in-year.js24
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/infinity-throws-rangeerror.js26
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-default.js22
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-higher-units.js29
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-invalid-string.js41
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-plurals-accepted.js22
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-smallestunit-mismatch.js22
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-undefined.js20
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-wrong-type.js19
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/length.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/name.js26
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/not-a-constructor.js24
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/options-object.js26
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/options-wrong-type.js26
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/order-of-operations.js214
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/prop-desc.js24
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/round-cross-unit-boundary.js17
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/rounding-relative.js37
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/rounding-zero-year-month-week-length.js34
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingincrement-nan.js22
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingincrement-non-integer.js27
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingincrement-out-of-range.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingincrement-undefined.js27
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingincrement-wrong-type.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingincrement.js31
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-ceil.js39
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-expand.js39
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-floor.js39
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-halfCeil.js39
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-halfEven.js39
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-halfExpand.js39
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-halfFloor.js39
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-halfTrunc.js39
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-invalid-string.js17
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-trunc.js39
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-undefined.js26
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-wrong-type.js19
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/shell.js24
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/smallestunit-higher-units.js27
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/smallestunit-invalid-string.js41
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/smallestunit-plurals-accepted.js22
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/smallestunit-undefined.js20
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/smallestunit-wrong-type.js19
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/weeks-months.js19
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/year-zero.js26
92 files changed, 2815 insertions, 0 deletions
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-builtin-calendar-no-array-iteration.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-builtin-calendar-no-array-iteration.js
new file mode 100644
index 0000000000..65a50eeeb2
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-builtin-calendar-no-array-iteration.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: >
+ Calling the method with a property bag argument with a builtin calendar causes
+ no observable array iteration when getting the calendar fields.
+features: [Temporal]
+---*/
+
+const arrayPrototypeSymbolIteratorOriginal = Array.prototype[Symbol.iterator];
+Array.prototype[Symbol.iterator] = function arrayIterator() {
+ throw new Test262Error("Array should not be iterated");
+}
+
+const instance = new Temporal.PlainDate(2000, 5, 2);
+const arg = { year: 2000, month: 5, day: 2, calendar: "iso8601" };
+instance.until(arg);
+
+Array.prototype[Symbol.iterator] = arrayPrototypeSymbolIteratorOriginal;
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-calendar-datefromfields-called-with-null-prototype-fields.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-calendar-datefromfields-called-with-null-prototype-fields.js
new file mode 100644
index 0000000000..81df965c93
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-calendar-datefromfields-called-with-null-prototype-fields.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: >
+ Calendar.dateFromFields method is called with a null-prototype fields object
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const calendar = TemporalHelpers.calendarCheckFieldsPrototypePollution();
+const instance = new Temporal.PlainDate(2000, 5, 2);
+const arg = { year: 2000, month: 5, day: 2, calendar };
+instance.until(arg);
+assert.sameValue(calendar.dateFromFieldsCallCount, 1, "dateFromFields should be called on the property bag's calendar");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-constructor-in-calendar-fields.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-constructor-in-calendar-fields.js
new file mode 100644
index 0000000000..11d0874d9f
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-constructor-in-calendar-fields.js
@@ -0,0 +1,17 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: If a calendar's fields() method returns a field named 'constructor', PrepareTemporalFields should throw a RangeError.
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const calendar = TemporalHelpers.calendarWithExtraFields(['constructor']);
+const arg = {year: 2023, month: 5, monthCode: 'M05', day: 1, calendar: calendar};
+const instance = new Temporal.PlainDate(2000, 5, 2);
+
+assert.throws(RangeError, () => instance.until(arg));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-duplicate-calendar-fields.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-duplicate-calendar-fields.js
new file mode 100644
index 0000000000..ef00260ae3
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-duplicate-calendar-fields.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: If a calendar's fields() method returns duplicate field names, PrepareTemporalFields should throw a RangeError.
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+for (const extra_fields of [['foo', 'foo'], ['day'], ['month'], ['monthCode'], ['year']]) {
+ const calendar = TemporalHelpers.calendarWithExtraFields(extra_fields);
+ const arg = { year: 2023, month: 5, monthCode: 'M05', day: 1, calendar: calendar };
+ const instance = new Temporal.PlainDate(2000, 5, 2);
+
+ assert.throws(RangeError, () => instance.until(arg));
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-leap-second.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-leap-second.js
new file mode 100644
index 0000000000..e62b69de21
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-leap-second.js
@@ -0,0 +1,30 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Leap second is a valid ISO string for PlainDate
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainDate(2016, 12, 31);
+
+let arg = "2016-12-31T23:59:60";
+const result1 = instance.until(arg);
+TemporalHelpers.assertDuration(
+ result1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ "leap second is a valid ISO string for PlainDate"
+);
+
+arg = { year: 2016, month: 12, day: 31, hour: 23, minute: 59, second: 60 };
+const result2 = instance.until(arg);
+TemporalHelpers.assertDuration(
+ result2,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ "second: 60 is ignored in property bag for PlainDate"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-number.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-number.js
new file mode 100644
index 0000000000..c181972581
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-number.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: A number cannot be used in place of a Temporal.PlainDate
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainDate(1976, 11, 18);
+
+const numbers = [
+ 1,
+ 19761118,
+ -19761118,
+ 1234567890,
+];
+
+for (const arg of numbers) {
+ assert.throws(
+ TypeError,
+ () => instance.until(arg),
+ 'Numbers cannot be used in place of an ISO string for PlainDate'
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-plaindatetime.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-plaindatetime.js
new file mode 100644
index 0000000000..8dfa1b88ad
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-plaindatetime.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.until
+description: Fast path for converting Temporal.PlainDateTime to Temporal.PlainDate by reading internal slots
+info: |
+ sec-temporal.plaindate.prototype.until step 3:
+ 3. Set _other_ to ? ToTemporalDate(_other_).
+ sec-temporal-totemporaldate step 2.b:
+ b. If _item_ has an [[InitializedTemporalDateTime]] internal slot, then
+ i. Return ! CreateTemporalDate(_item_.[[ISOYear]], _item_.[[ISOMonth]], _item_.[[ISODay]], _item_.[[Calendar]]).
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+TemporalHelpers.checkPlainDateTimeConversionFastPath((datetime) => {
+ const date = new Temporal.PlainDate(2000, 5, 2);
+ const result = date.until(datetime);
+ assert.sameValue(result.total({ unit: "nanoseconds" }), 0, "time part dropped");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-case-insensitive.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-case-insensitive.js
new file mode 100644
index 0000000000..c3a029272a
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-case-insensitive.js
@@ -0,0 +1,20 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: The calendar name is case-insensitive
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainDate(1976, 11, 18);
+
+const calendar = "IsO8601";
+
+const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+const result = instance.until(arg);
+TemporalHelpers.assertDuration(result, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "Calendar is case-insensitive");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-leap-second.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-leap-second.js
new file mode 100644
index 0000000000..a05da04f05
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-leap-second.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Leap second is a valid ISO string for a calendar in a property bag
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainDate(1976, 11, 18);
+
+const calendar = "2016-12-31T23:59:60";
+
+const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+const result = instance.until(arg);
+TemporalHelpers.assertDuration(
+ result,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ "leap second is a valid ISO string for calendar"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-number.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-number.js
new file mode 100644
index 0000000000..c46e6ff2ec
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-number.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: A number as calendar in a property bag is not accepted
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainDate(1976, 11, 18);
+
+const numbers = [
+ 1,
+ 19970327,
+ -19970327,
+ 1234567890,
+];
+for (const calendar of numbers) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ assert.throws(
+ TypeError,
+ () => instance.until(arg),
+ "Numbers cannot be used as a calendar"
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-string.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-string.js
new file mode 100644
index 0000000000..2dddc518d7
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-string.js
@@ -0,0 +1,20 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: A calendar ID is valid input for Calendar
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainDate(1976, 11, 18);
+
+const calendar = "iso8601";
+
+const arg = { year: 1976, monthCode: "M11", day: 18, 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/PlainDate/prototype/until/argument-propertybag-calendar-wrong-type.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-wrong-type.js
new file mode 100644
index 0000000000..65304590aa
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-wrong-type.js
@@ -0,0 +1,46 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: >
+ Appropriate error thrown when a calendar property from a property bag cannot
+ be converted to a calendar object or string
+features: [BigInt, Symbol, Temporal]
+---*/
+
+const timeZone = new Temporal.TimeZone("UTC");
+const instance = new Temporal.PlainDate(2000, 5, 2);
+
+const primitiveTests = [
+ [null, "null"],
+ [true, "boolean"],
+ ["", "empty string"],
+ [1, "number that doesn't convert to a valid ISO string"],
+ [1n, "bigint"],
+];
+
+for (const [calendar, description] of primitiveTests) {
+ const arg = { year: 2019, monthCode: "M11", day: 1, calendar };
+ assert.throws(
+ typeof calendar === 'string' ? RangeError : TypeError,
+ () => instance.until(arg),
+ `${description} does not convert to a valid ISO string`
+ );
+}
+
+const typeErrorTests = [
+ [Symbol(), "symbol"],
+ [{}, "plain object that doesn't implement the protocol"],
+ [new Temporal.TimeZone("UTC"), "time zone instance"],
+ [Temporal.Calendar, "Temporal.Calendar, object"],
+ [Temporal.Calendar.prototype, "Temporal.Calendar.prototype, object"], // fails brand check in dateFromFields()
+];
+
+for (const [calendar, description] of typeErrorTests) {
+ const arg = { year: 2019, monthCode: "M11", day: 1, calendar };
+ assert.throws(TypeError, () => instance.until(arg), `${description} is not a valid property bag and does not convert to a string`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-year-zero.js
new file mode 100644
index 0000000000..abe100487e
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-year-zero.js
@@ -0,0 +1,27 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Negative zero, as an extended year, is rejected
+features: [Temporal, arrow-function]
+---*/
+
+const invalidStrings = [
+ "-000000-10-31",
+ "-000000-10-31T17:45",
+ "-000000-10-31T17:45Z",
+ "-000000-10-31T17:45+01:00",
+ "-000000-10-31T17:45+00:00[UTC]",
+];
+const instance = new Temporal.PlainDate(2000, 5, 2);
+invalidStrings.forEach((arg) => {
+ assert.throws(
+ RangeError,
+ () => instance.until(arg),
+ "reject minus zero as extended year"
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-proto-in-calendar-fields.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-proto-in-calendar-fields.js
new file mode 100644
index 0000000000..01c0f901aa
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-proto-in-calendar-fields.js
@@ -0,0 +1,17 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: If a calendar's fields() method returns a field named '__proto__', PrepareTemporalFields should throw a RangeError.
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const calendar = TemporalHelpers.calendarWithExtraFields(['__proto__']);
+const arg = {year: 2023, month: 5, monthCode: 'M05', day: 1, calendar: calendar};
+const instance = new Temporal.PlainDate(2000, 5, 2);
+
+assert.throws(RangeError, () => instance.until(arg));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-calendar-annotation.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-calendar-annotation.js
new file mode 100644
index 0000000000..a19cb0caff
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-calendar-annotation.js
@@ -0,0 +1,34 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Various forms of calendar annotation; critical flag has no effect
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+const tests = [
+ ["2000-05-02[u-ca=iso8601]", "without time or time zone"],
+ ["2000-05-02[UTC][u-ca=iso8601]", "with time zone and no time"],
+ ["2000-05-02T15:23[u-ca=iso8601]", "without time zone"],
+ ["2000-05-02T15:23[UTC][u-ca=iso8601]", "with time zone"],
+ ["2000-05-02T15:23[!u-ca=iso8601]", "with ! and no time zone"],
+ ["2000-05-02T15:23[UTC][!u-ca=iso8601]", "with ! and time zone"],
+ ["2000-05-02T15:23[u-ca=iso8601][u-ca=discord]", "second annotation ignored"],
+];
+
+const instance = new Temporal.PlainDate(2000, 5, 2);
+
+tests.forEach(([arg, description]) => {
+ const result = instance.until(arg);
+
+ TemporalHelpers.assertDuration(
+ result,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ `calendar annotation (${description})`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-critical-unknown-annotation.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-critical-unknown-annotation.js
new file mode 100644
index 0000000000..ac10df4a67
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-critical-unknown-annotation.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Unknown annotations with critical flag are rejected
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ "1970-01-01[!foo=bar]",
+ "1970-01-01T00:00[!foo=bar]",
+ "1970-01-01T00:00[UTC][!foo=bar]",
+ "1970-01-01T00:00[u-ca=iso8601][!foo=bar]",
+ "1970-01-01T00:00[UTC][!foo=bar][u-ca=iso8601]",
+ "1970-01-01T00:00[foo=bar][!_foo-bar0=Dont-Ignore-This-99999999999]",
+];
+const instance = new Temporal.PlainDate(2000, 5, 2);
+invalidStrings.forEach((arg) => {
+ assert.throws(
+ RangeError,
+ () => instance.until(arg),
+ `reject unknown annotation with critical flag: ${arg}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-date-with-utc-offset.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-date-with-utc-offset.js
new file mode 100644
index 0000000000..06692bd6b3
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-date-with-utc-offset.js
@@ -0,0 +1,49 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: UTC offset not valid with format that does not include a time
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+const instance = new Temporal.PlainDate(2000, 5, 2);
+
+const validStrings = [
+ "2000-05-02T00+00:00",
+ "2000-05-02T00+00:00[UTC]",
+ "2000-05-02T00+00:00[!UTC]",
+ "2000-05-02T00-02:30[America/St_Johns]",
+];
+
+for (const arg of validStrings) {
+ const result = instance.until(arg);
+
+ TemporalHelpers.assertDuration(
+ result,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ `"${arg}" is a valid UTC offset with time for PlainDate`
+ );
+}
+
+const invalidStrings = [
+ "2022-09-15Z",
+ "2022-09-15Z[UTC]",
+ "2022-09-15Z[Europe/Vienna]",
+ "2022-09-15+00:00",
+ "2022-09-15+00:00[UTC]",
+ "2022-09-15-02:30",
+ "2022-09-15-02:30[America/St_Johns]",
+];
+
+for (const arg of invalidStrings) {
+ assert.throws(
+ RangeError,
+ () => instance.until(arg),
+ `"${arg}" UTC offset without time is not valid for PlainDate`
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-invalid.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-invalid.js
new file mode 100644
index 0000000000..47c10e5c58
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-invalid.js
@@ -0,0 +1,64 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: >
+ RangeError thrown if an invalid ISO string (or syntactically valid ISO string
+ that is not supported) is used as a PlainDate
+features: [Temporal, arrow-function]
+---*/
+
+const invalidStrings = [
+ // invalid ISO strings:
+ "",
+ "invalid iso8601",
+ "2020-01-00",
+ "2020-01-32",
+ "2020-02-30",
+ "2021-02-29",
+ "2020-00-01",
+ "2020-13-01",
+ "2020-01-01T",
+ "2020-01-01T25:00:00",
+ "2020-01-01T01:60:00",
+ "2020-01-01T01:60:61",
+ "2020-01-01junk",
+ "2020-01-01T00:00:00junk",
+ "2020-01-01T00:00:00+00:00junk",
+ "2020-01-01T00:00:00+00:00[UTC]junk",
+ "2020-01-01T00:00:00+00:00[UTC][u-ca=iso8601]junk",
+ "02020-01-01",
+ "2020-001-01",
+ "2020-01-001",
+ "2020-01-01T001",
+ "2020-01-01T01:001",
+ "2020-01-01T01:01:001",
+ // valid, but forms not supported in Temporal:
+ "2020-W01-1",
+ "2020-001",
+ "+0002020-01-01",
+ // valid, but this calendar must not exist:
+ "2020-01-01[u-ca=notexist]",
+ // may be valid in other contexts, but insufficient information for PlainDate:
+ "2020-01",
+ "+002020-01",
+ "01-01",
+ "2020-W01",
+ "P1Y",
+ "-P12Y",
+ // valid, but outside the supported range:
+ "-999999-01-01",
+ "+999999-01-01",
+];
+const instance = new Temporal.PlainDate(2000, 5, 2);
+for (const arg of invalidStrings) {
+ assert.throws(
+ RangeError,
+ () => instance.until(arg),
+ `"${arg}" should not be a valid ISO string for a PlainDate`
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-multiple-calendar.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-multiple-calendar.js
new file mode 100644
index 0000000000..db7f9127f1
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-multiple-calendar.js
@@ -0,0 +1,32 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: >
+ More than one calendar annotation is not syntactical if any have the criical
+ flag
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ "1970-01-01[u-ca=iso8601][!u-ca=iso8601]",
+ "1970-01-01[!u-ca=iso8601][u-ca=iso8601]",
+ "1970-01-01[UTC][u-ca=iso8601][!u-ca=iso8601]",
+ "1970-01-01[u-ca=iso8601][foo=bar][!u-ca=iso8601]",
+ "1970-01-01T00:00[u-ca=iso8601][!u-ca=iso8601]",
+ "1970-01-01T00:00[!u-ca=iso8601][u-ca=iso8601]",
+ "1970-01-01T00:00[UTC][u-ca=iso8601][!u-ca=iso8601]",
+ "1970-01-01T00:00[u-ca=iso8601][foo=bar][!u-ca=iso8601]",
+];
+const instance = new Temporal.PlainDate(2000, 5, 2);
+invalidStrings.forEach((arg) => {
+ assert.throws(
+ RangeError,
+ () => instance.until(arg),
+ `reject more than one calendar annotation if any critical: ${arg}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-multiple-time-zone.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-multiple-time-zone.js
new file mode 100644
index 0000000000..1ec82cadc5
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-multiple-time-zone.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: More than one time zone annotation is not syntactical
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ "1970-01-01[UTC][UTC]",
+ "1970-01-01T00:00[UTC][UTC]",
+ "1970-01-01T00:00[!UTC][UTC]",
+ "1970-01-01T00:00[UTC][!UTC]",
+ "1970-01-01T00:00[UTC][u-ca=iso8601][UTC]",
+ "1970-01-01T00:00[UTC][foo=bar][UTC]",
+];
+const instance = new Temporal.PlainDate(2000, 5, 2);
+invalidStrings.forEach((arg) => {
+ assert.throws(
+ RangeError,
+ () => instance.until(arg),
+ `reject more than one time zone annotation: ${arg}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-time-separators.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-time-separators.js
new file mode 100644
index 0000000000..e9c944cc64
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-time-separators.js
@@ -0,0 +1,30 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Time separator in string argument can vary
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+const tests = [
+ ["2000-05-02T15:23", "uppercase T"],
+ ["2000-05-02t15:23", "lowercase T"],
+ ["2000-05-02 15:23", "space between date and time"],
+];
+
+const instance = new Temporal.PlainDate(2000, 5, 2);
+
+tests.forEach(([arg, description]) => {
+ const result = instance.until(arg);
+
+ TemporalHelpers.assertDuration(
+ result,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ `variant time separators (${description})`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-time-zone-annotation.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-time-zone-annotation.js
new file mode 100644
index 0000000000..f8cdb4306e
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-time-zone-annotation.js
@@ -0,0 +1,39 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Various forms of time zone annotation; critical flag has no effect
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+const tests = [
+ ["2000-05-02[Asia/Kolkata]", "named, with no time"],
+ ["2000-05-02[!Europe/Vienna]", "named, with ! and no time"],
+ ["2000-05-02[+00:00]", "numeric, with no time"],
+ ["2000-05-02[!-02:30]", "numeric, with ! and no time"],
+ ["2000-05-02T15:23[America/Sao_Paulo]", "named, with no offset"],
+ ["2000-05-02T15:23[!Asia/Tokyo]", "named, with ! and no offset"],
+ ["2000-05-02T15:23[-02:30]", "numeric, with no offset"],
+ ["2000-05-02T15:23[!+00:00]", "numeric, with ! and no offset"],
+ ["2000-05-02T15:23+00:00[America/New_York]", "named, with offset"],
+ ["2000-05-02T15:23+00:00[!UTC]", "named, with offset and !"],
+ ["2000-05-02T15:23+00:00[+01:00]", "numeric, with offset"],
+ ["2000-05-02T15:23+00:00[!-08:00]", "numeric, with offset and !"],
+];
+
+const instance = new Temporal.PlainDate(2000, 5, 2);
+
+tests.forEach(([arg, description]) => {
+ const result = instance.until(arg);
+
+ TemporalHelpers.assertDuration(
+ result,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ `time zone annotation (${description})`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-unknown-annotation.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-unknown-annotation.js
new file mode 100644
index 0000000000..c8b5af5c6f
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-unknown-annotation.js
@@ -0,0 +1,33 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Various forms of unknown annotation
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+const tests = [
+ ["2000-05-02[foo=bar]", "without time"],
+ ["2000-05-02T15:23[foo=bar]", "alone"],
+ ["2000-05-02T15:23[UTC][foo=bar]", "with time zone"],
+ ["2000-05-02T15:23[u-ca=iso8601][foo=bar]", "with calendar"],
+ ["2000-05-02T15:23[UTC][foo=bar][u-ca=iso8601]", "with time zone and calendar"],
+ ["2000-05-02T15:23[foo=bar][_foo-bar0=Ignore-This-999999999999]", "with another unknown annotation"],
+];
+
+const instance = new Temporal.PlainDate(2000, 5, 2);
+
+tests.forEach(([arg, description]) => {
+ const result = instance.until(arg);
+
+ TemporalHelpers.assertDuration(
+ result,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ `unknown annotation (${description})`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-with-utc-designator.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-with-utc-designator.js
new file mode 100644
index 0000000000..95f193aed7
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-with-utc-designator.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: RangeError thrown if a string with UTC designator is used as a PlainDate
+features: [Temporal, arrow-function]
+---*/
+
+const invalidStrings = [
+ "2019-10-01T09:00:00Z",
+ "2019-10-01T09:00:00Z[UTC]",
+];
+const instance = new Temporal.PlainDate(2000, 5, 2);
+invalidStrings.forEach((arg) => {
+ assert.throws(
+ RangeError,
+ () => instance.until(arg),
+ "String with UTC designator should not be valid as a PlainDate"
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-wrong-type.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-wrong-type.js
new file mode 100644
index 0000000000..d6886c8c7d
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-wrong-type.js
@@ -0,0 +1,43 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: >
+ Appropriate error thrown when argument cannot be converted to a valid string
+ or property bag for PlainDate
+features: [BigInt, Symbol, Temporal]
+---*/
+
+const instance = new Temporal.PlainDate(2000, 5, 2);
+
+const primitiveTests = [
+ [undefined, "undefined"],
+ [null, "null"],
+ [true, "boolean"],
+ ["", "empty string"],
+ [1, "number that doesn't convert to a valid ISO string"],
+ [1n, "bigint"],
+];
+
+for (const [arg, description] of primitiveTests) {
+ assert.throws(
+ typeof arg === 'string' ? RangeError : TypeError,
+ () => instance.until(arg),
+ `${description} does not convert to a valid ISO string`
+ );
+}
+
+const typeErrorTests = [
+ [Symbol(), "symbol"],
+ [{}, "plain object"],
+ [Temporal.PlainDate, "Temporal.PlainDate, object"],
+ [Temporal.PlainDate.prototype, "Temporal.PlainDate.prototype, object"],
+];
+
+for (const [arg, description] of typeErrorTests) {
+ assert.throws(TypeError, () => instance.until(arg), `${description} is not a valid property bag and does not convert to a string`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-zoneddatetime-convert.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-zoneddatetime-convert.js
new file mode 100644
index 0000000000..807b83051b
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-zoneddatetime-convert.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: An exception from TimeZone#getOffsetNanosecondsFor() is propagated.
+features: [Temporal]
+---*/
+
+class TZ extends Temporal.TimeZone {
+ constructor() { super("UTC") }
+ getOffsetNanosecondsFor() { throw new Test262Error() }
+}
+
+const tz = new TZ();
+const arg = new Temporal.ZonedDateTime(0n, tz);
+const instance = new Temporal.PlainDate(1976, 11, 18);
+
+assert.throws(Test262Error, () => instance.until(arg));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-zoneddatetime-slots.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-zoneddatetime-slots.js
new file mode 100644
index 0000000000..7777ce9c05
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-zoneddatetime-slots.js
@@ -0,0 +1,40 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Getters are not called when converting a ZonedDateTime to a PlainDate.
+includes: [compareArray.js]
+features: [Temporal]
+---*/
+
+const actual = [];
+const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.ZonedDateTime.prototype);
+const getters = ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond", "calendar"];
+
+for (const property of getters) {
+ Object.defineProperty(Temporal.ZonedDateTime.prototype, property, {
+ get() {
+ actual.push(`get ${property}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${property}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${property}`);
+ return value;
+ },
+ };
+ },
+ });
+}
+
+const arg = new Temporal.ZonedDateTime(0n, "UTC");
+const instance = new Temporal.PlainDate(1976, 11, 18);
+instance.until(arg);
+assert.compareArray(actual, []);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-non-integer.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-non-integer.js
new file mode 100644
index 0000000000..cc57645903
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-non-integer.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: RangeError thrown if time zone reports an offset that is not an integer number of nanoseconds
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+[3600_000_000_000.5, NaN, -Infinity, Infinity].forEach((wrongOffset) => {
+ const timeZone = TemporalHelpers.specificOffsetTimeZone(wrongOffset);
+ const date = new Temporal.PlainDate(2000, 5, 2);
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, timeZone);
+ assert.throws(RangeError, () => date.until(datetime));
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-not-callable.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-not-callable.js
new file mode 100644
index 0000000000..12ee440503
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-not-callable.js
@@ -0,0 +1,23 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: TypeError thrown if timeZone.getOffsetNanosecondsFor is not callable
+features: [BigInt, Symbol, Temporal, arrow-function]
+---*/
+
+[undefined, null, true, Math.PI, 'string', Symbol('sym'), 42n, {}].forEach((notCallable) => {
+ const timeZone = new Temporal.TimeZone("UTC");
+ const date = new Temporal.PlainDate(2000, 5, 2);
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, timeZone);
+ timeZone.getOffsetNanosecondsFor = notCallable;
+ assert.throws(
+ TypeError,
+ () => date.until(datetime),
+ `Uncallable ${notCallable === null ? 'null' : typeof notCallable} getOffsetNanosecondsFor should throw TypeError`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-out-of-range.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-out-of-range.js
new file mode 100644
index 0000000000..31bf9a778f
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-out-of-range.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: RangeError thrown if time zone reports an offset that is out of range
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+[-86400_000_000_000, 86400_000_000_000].forEach((wrongOffset) => {
+ const timeZone = TemporalHelpers.specificOffsetTimeZone(wrongOffset);
+ const date = new Temporal.PlainDate(2000, 5, 2);
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, timeZone);
+ assert.throws(RangeError, () => date.until(datetime));
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-wrong-type.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-wrong-type.js
new file mode 100644
index 0000000000..5bdc156026
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-wrong-type.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: TypeError thrown if time zone reports an offset that is not a Number
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+[
+ undefined,
+ null,
+ true,
+ "+01:00",
+ Symbol(),
+ 3600_000_000_000n,
+ {},
+ { valueOf() { return 3600_000_000_000; } },
+].forEach((wrongOffset) => {
+ const timeZone = TemporalHelpers.specificOffsetTimeZone(wrongOffset);
+ const date = new Temporal.PlainDate(2000, 5, 2);
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, timeZone);
+ assert.throws(TypeError, () => date.until(datetime));
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/basic.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/basic.js
new file mode 100644
index 0000000000..d2166e9e3e
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/basic.js
@@ -0,0 +1,23 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Basic tests for until().
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const plainDate = new Temporal.PlainDate(1969, 7, 24);
+const plainDate2 = Temporal.PlainDate.from({ year: 1969, month: 10, day: 5 });
+TemporalHelpers.assertDuration(plainDate.until(plainDate2), 0, 0, 0, /* days = */ 73, 0, 0, 0, 0, 0, 0, "same year");
+
+const earlier = new Temporal.PlainDate(1969, 7, 24);
+const later = new Temporal.PlainDate(1996, 3, 3);
+TemporalHelpers.assertDuration(earlier.until(later), 0, 0, 0, /* days = */ 9719, 0, 0, 0, 0, 0, 0, "different year");
+
+TemporalHelpers.assertDuration(plainDate.until({ year: 2019, month: 7, day: 24 }), 0, 0, 0, /* days = */ 18262, 0, 0, 0, 0, 0, 0, "option bag");
+TemporalHelpers.assertDuration(plainDate.until("2019-07-24"), 0, 0, 0, /* days = */ 18262, 0, 0, 0, 0, 0, 0, "string");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/branding.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/branding.js
new file mode 100644
index 0000000000..2625e235f3
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/branding.js
@@ -0,0 +1,27 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Throw a TypeError if the receiver is invalid
+features: [Symbol, Temporal]
+---*/
+
+const until = Temporal.PlainDate.prototype.until;
+
+assert.sameValue(typeof until, "function");
+
+const args = [new Temporal.PlainDate(2022, 6, 22)];
+
+assert.throws(TypeError, () => until.apply(undefined, args), "undefined");
+assert.throws(TypeError, () => until.apply(null, args), "null");
+assert.throws(TypeError, () => until.apply(true, args), "true");
+assert.throws(TypeError, () => until.apply("", args), "empty string");
+assert.throws(TypeError, () => until.apply(Symbol(), args), "symbol");
+assert.throws(TypeError, () => until.apply(1, args), "1");
+assert.throws(TypeError, () => until.apply({}, args), "plain object");
+assert.throws(TypeError, () => until.apply(Temporal.PlainDate, args), "Temporal.PlainDate");
+assert.throws(TypeError, () => until.apply(Temporal.PlainDate.prototype, args), "Temporal.PlainDate.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/browser.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/browser.js
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/builtin-calendar-no-observable-calls.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/builtin-calendar-no-observable-calls.js
new file mode 100644
index 0000000000..400f425860
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/builtin-calendar-no-observable-calls.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: >
+ Calling the method on an instance constructed with a builtin calendar causes
+ no observable lookups or calls to calendar methods.
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const dateUntilOriginal = Object.getOwnPropertyDescriptor(Temporal.Calendar.prototype, "dateUntil");
+Object.defineProperty(Temporal.Calendar.prototype, "dateUntil", {
+ configurable: true,
+ enumerable: false,
+ get() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be looked up");
+ },
+});
+
+const instance = new Temporal.PlainDate(2000, 5, 2, "iso8601");
+instance.until(new Temporal.PlainDate(2001, 6, 13));
+
+Object.defineProperty(Temporal.Calendar.prototype, "dateUntil", dateUntilOriginal);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/builtin.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/builtin.js
new file mode 100644
index 0000000000..d65f2e5af7
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/builtin.js
@@ -0,0 +1,36 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: >
+ Tests that Temporal.PlainDate.prototype.until
+ meets the requirements for built-in objects defined by the
+ introduction of chapter 17 of the ECMAScript Language Specification.
+info: |
+ Built-in functions that are not constructors do not have a "prototype" property unless
+ otherwise specified in the description of a particular function.
+
+ Unless specified otherwise, a built-in object that is callable as a function is a built-in
+ function object with the characteristics described in 10.3. Unless specified otherwise, the
+ [[Extensible]] internal slot of a built-in object initially has the value true.
+
+ Unless otherwise specified every built-in function and every built-in constructor has the
+ Function prototype object [...] as the value of its [[Prototype]] internal slot.
+features: [Temporal]
+---*/
+
+assert.sameValue(Object.isExtensible(Temporal.PlainDate.prototype.until),
+ true, "Built-in objects must be extensible.");
+
+assert.sameValue(Object.prototype.toString.call(Temporal.PlainDate.prototype.until),
+ "[object Function]", "Object.prototype.toString");
+
+assert.sameValue(Object.getPrototypeOf(Temporal.PlainDate.prototype.until),
+ Function.prototype, "prototype");
+
+assert.sameValue(Temporal.PlainDate.prototype.until.hasOwnProperty("prototype"),
+ false, "prototype property");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-dateadd-called-with-plaindate-instance.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-dateadd-called-with-plaindate-instance.js
new file mode 100644
index 0000000000..6b72eabd28
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-dateadd-called-with-plaindate-instance.js
@@ -0,0 +1,20 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: >
+ relativeTo parameters that are not ZonedDateTime or undefined, are always
+ converted to PlainDate for observable calendar calls
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const calendar = TemporalHelpers.calendarDateAddPlainDateInstance();
+const instance = new Temporal.PlainDate(1970, 1, 1, calendar);
+calendar.specificPlainDate = instance;
+instance.until(new Temporal.PlainDate(2000, 5, 2, calendar), { smallestUnit: "month" });
+assert(calendar.dateAddCallCount > 0, "assertions in calendar.dateAdd() should have been tested");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-datefromfields-called-with-options-undefined.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-datefromfields-called-with-options-undefined.js
new file mode 100644
index 0000000000..c4455c2f1e
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-datefromfields-called-with-options-undefined.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: >
+ Calendar.dateFromFields method is called with undefined as the options value
+ when call originates internally
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const calendar = TemporalHelpers.calendarFromFieldsUndefinedOptions();
+const instance = new Temporal.PlainDate(2000, 5, 2, calendar);
+instance.until({ year: 2000, month: 5, day: 3, calendar });
+assert.sameValue(calendar.dateFromFieldsCallCount, 1);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-dateuntil-called-with-null-prototype-options.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-dateuntil-called-with-null-prototype-options.js
new file mode 100644
index 0000000000..2945cae16d
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-dateuntil-called-with-null-prototype-options.js
@@ -0,0 +1,20 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: >
+ Calendar.dateUntil method is called with a null-prototype object as the
+ options value when call originates internally
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const calendar = TemporalHelpers.calendarCheckOptionsPrototypePollution();
+const instance = new Temporal.PlainDate(2000, 5, 2, calendar);
+const argument = new Temporal.PlainDate(2022, 6, 14, calendar);
+instance.until(argument, { largestUnit: "months" });
+assert.sameValue(calendar.dateUntilCallCount, 1, "dateUntil should have been called on the calendar");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-dateuntil-called-with-singular-largestunit.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-dateuntil-called-with-singular-largestunit.js
new file mode 100644
index 0000000000..7a7f9d92f2
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-dateuntil-called-with-singular-largestunit.js
@@ -0,0 +1,30 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: The options object passed to calendar.dateUntil has a largestUnit property with its value in the singular form
+info: |
+ sec-temporal.plaindate.prototype.until steps 12–13:
+ 13. Let _untilOptions_ be ? MergeLargestUnitOption(_options_, _largestUnit_).
+ 14. Let _result_ be ? CalendarDateUntil(_temporalDate_.[[Calendar]], _temporalDate_, _other_, _untilOptions_).
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+TemporalHelpers.checkCalendarDateUntilLargestUnitSingular(
+ (calendar, largestUnit) => {
+ const earlier = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const later = new Temporal.PlainDate(2001, 6, 3, calendar);
+ earlier.until(later, { largestUnit });
+ },
+ {
+ years: ["year"],
+ months: ["month"],
+ weeks: ["week"],
+ days: []
+ }
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-fields-iterable.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-fields-iterable.js
new file mode 100644
index 0000000000..b77217dd0c
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-fields-iterable.js
@@ -0,0 +1,36 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Verify the result of calendar.fields() is treated correctly.
+info: |
+ sec-temporal.plaindate.prototype.until step 3:
+ 3. Set _other_ to ? ToTemporalDate(_other_).
+ sec-temporal-totemporaldate step 2.c:
+ c. Let _fieldNames_ be ? CalendarFields(_calendar_, « *"day"*, *"month"*, *"monthCode"*, *"year"* »).
+ sec-temporal-calendarfields step 4:
+ 4. Let _result_ be ? IterableToList(_fieldsArray_).
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const expected = [
+ "day",
+ "month",
+ "monthCode",
+ "year",
+];
+
+const calendar1 = TemporalHelpers.calendarFieldsIterable();
+const date = new Temporal.PlainDate(2000, 5, 2, calendar1);
+const calendar2 = TemporalHelpers.calendarFieldsIterable();
+date.until({ year: 2005, month: 6, day: 2, calendar: calendar2 });
+
+assert.sameValue(calendar1.fieldsCallCount, 0, "fields() method not called");
+assert.sameValue(calendar2.fieldsCallCount, 1, "fields() method called once");
+assert.compareArray(calendar2.fieldsCalledWith[0], expected, "fields() method called with correct args");
+assert(calendar2.iteratorExhausted[0], "iterated through the whole iterable");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-id-match.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-id-match.js
new file mode 100644
index 0000000000..ddff8dae6c
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-id-match.js
@@ -0,0 +1,33 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Calculation is performed if calendars' toString results match
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+class Calendar1 extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ return "A";
+ }
+}
+class Calendar2 extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ return "A";
+ }
+}
+
+const plainDate1 = new Temporal.PlainDate(2000, 1, 1, new Calendar1());
+const plainDate2 = new Temporal.PlainDate(2000, 1, 2, new Calendar2());
+TemporalHelpers.assertDuration(plainDate1.until(plainDate2), 0, 0, 0, /* days = */ 1, 0, 0, 0, 0, 0, 0);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-invalid-return.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-invalid-return.js
new file mode 100644
index 0000000000..632e9d69e3
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-invalid-return.js
@@ -0,0 +1,42 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Throw when the returned value from the calendar's dateUntil method is not a Duration.
+features: [Temporal]
+---*/
+
+class CustomCalendar extends Temporal.Calendar {
+ constructor(value) {
+ super("iso8601");
+ this.value = value;
+ }
+ dateUntil() {
+ return this.value;
+ }
+}
+
+const tests = [
+ [undefined],
+ [null, "null"],
+ [true],
+ ["2000-05"],
+ [Symbol()],
+ [200005],
+ [200005n],
+ [{}, "plain object"],
+ [() => {}, "lambda"],
+ [Temporal.Duration, "Temporal.Duration"],
+ [Temporal.Duration.prototype, "Temporal.Duration.prototype"],
+];
+for (const [test, description = typeof test] of tests) {
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, new CustomCalendar(test));
+ assert.throws(
+ TypeError, () => plainDate.until("2022-06-20", { largestUnit: "years" }),
+ `Expected error with ${description}`
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-mismatch.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-mismatch.js
new file mode 100644
index 0000000000..65be788ce3
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-mismatch.js
@@ -0,0 +1,62 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: RangeError thrown if calendars' id properties do not match
+features: [Temporal]
+---*/
+
+const calendar1 = {
+ dateAdd() {},
+ dateFromFields() {},
+ dateUntil() {},
+ day() {},
+ dayOfWeek() {},
+ dayOfYear() {},
+ daysInMonth() {},
+ daysInWeek() {},
+ daysInYear() {},
+ fields() {},
+ id: "A",
+ inLeapYear() {},
+ mergeFields() {},
+ month() {},
+ monthCode() {},
+ monthDayFromFields() {},
+ monthsInYear() {},
+ weekOfYear() {},
+ year() {},
+ yearMonthFromFields() {},
+ yearOfWeek() {},
+};
+const calendar2 = {
+ dateAdd() {},
+ dateFromFields() {},
+ dateUntil() {},
+ day() {},
+ dayOfWeek() {},
+ dayOfYear() {},
+ daysInMonth() {},
+ daysInWeek() {},
+ daysInYear() {},
+ fields() {},
+ id: "B",
+ inLeapYear() {},
+ mergeFields() {},
+ month() {},
+ monthCode() {},
+ monthDayFromFields() {},
+ monthsInYear() {},
+ weekOfYear() {},
+ year() {},
+ yearMonthFromFields() {},
+ yearOfWeek() {},
+};
+
+const plainDate1 = new Temporal.PlainDate(2000, 1, 1, calendar1);
+const plainDate2 = new Temporal.PlainDate(2000, 1, 1, calendar2);
+assert.throws(RangeError, () => plainDate1.until(plainDate2));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-temporal-object.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-temporal-object.js
new file mode 100644
index 0000000000..4f03a0b94d
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/calendar-temporal-object.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Fast path for converting other Temporal objects to Temporal.Calendar by reading internal slots
+info: |
+ sec-temporal.plaindate.prototype.until step 3:
+ 3. Set _other_ to ? ToTemporalDate(_other_).
+ sec-temporal-totemporaldate step 2.c:
+ c. Let _calendar_ be ? GetTemporalCalendarWithISODefault(_item_).
+ sec-temporal-gettemporalcalendarwithisodefault step 2:
+ 2. Return ? ToTemporalCalendarWithISODefault(_calendar_).
+ sec-temporal-totemporalcalendarwithisodefault step 2:
+ 3. Return ? ToTemporalCalendar(_temporalCalendarLike_).
+ sec-temporal-totemporalcalendar step 1.a:
+ a. If _temporalCalendarLike_ has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, then
+ i. Return _temporalCalendarLike_.[[Calendar]].
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+TemporalHelpers.checkToTemporalCalendarFastPath((temporalObject) => {
+ const date = new Temporal.PlainDate(2000, 5, 2, temporalObject);
+ date.until({ year: 2005, month: 6, day: 2, calendar: temporalObject });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/custom.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/custom.js
new file mode 100644
index 0000000000..c2d9d2cb82
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/custom.js
@@ -0,0 +1,35 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Basic tests with custom calendar
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const result = new Temporal.Duration(1, 3, 5, 7, 9);
+const options = { largestUnit: "years" };
+let calls = 0;
+class CustomCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ dateUntil(...args) {
+ ++calls;
+ assert.sameValue(args.length, 3, "Three arguments");
+ assert.sameValue(args[0], plainDate, "First argument");
+ assert.sameValue(args[1], other, "Second argument");
+ assert.sameValue(args[2].largestUnit, "year", "Third argument: largestUnit");
+ return result;
+ }
+}
+const calendar = new CustomCalendar();
+const plainDate = new Temporal.PlainDate(1976, 11, 18, calendar);
+const other = new Temporal.PlainDate(2022, 6, 20, calendar);
+TemporalHelpers.assertDuration(plainDate.until(other, options),
+ 1, 3, 5, 7, 0, 0, 0, 0, 0, 0, "result");
+assert.sameValue(calls, 1);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/days-in-month.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/days-in-month.js
new file mode 100644
index 0000000000..72ff38ca21
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/days-in-month.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: until() should take length of month into account.
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const plainDate1 = Temporal.PlainDate.from("2019-01-01");
+const plainDate2 = Temporal.PlainDate.from("2019-02-01");
+const plainDate3 = Temporal.PlainDate.from("2019-03-01");
+TemporalHelpers.assertDuration(plainDate1.until(plainDate2), 0, 0, 0, /* days = */ 31, 0, 0, 0, 0, 0, 0, "January 2019");
+TemporalHelpers.assertDuration(plainDate2.until(plainDate3), 0, 0, 0, /* days = */ 28, 0, 0, 0, 0, 0, 0, "February 2019");
+
+const plainDate4 = Temporal.PlainDate.from("2020-02-01");
+const plainDate5 = Temporal.PlainDate.from("2020-03-01");
+TemporalHelpers.assertDuration(plainDate4.until(plainDate5), 0, 0, 0, /* days = */ 29, 0, 0, 0, 0, 0, 0, "February 2020");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/days-in-year.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/days-in-year.js
new file mode 100644
index 0000000000..6ca8c59327
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/days-in-year.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: until() should take length of year into account.
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const plainDate1 = Temporal.PlainDate.from("2019-01-01");
+const plainDate2 = Temporal.PlainDate.from("2020-01-01");
+const plainDate3 = Temporal.PlainDate.from("2021-01-01");
+TemporalHelpers.assertDuration(plainDate1.until(plainDate2), 0, 0, 0, /* days = */ 365, 0, 0, 0, 0, 0, 0, "From January 2019");
+TemporalHelpers.assertDuration(plainDate2.until(plainDate3), 0, 0, 0, /* days = */ 366, 0, 0, 0, 0, 0, 0, "From January 2020");
+
+const plainDate4 = Temporal.PlainDate.from("2019-06-01");
+const plainDate5 = Temporal.PlainDate.from("2020-06-01");
+const plainDate6 = Temporal.PlainDate.from("2021-06-01");
+TemporalHelpers.assertDuration(plainDate4.until(plainDate5), 0, 0, 0, /* days = */ 366, 0, 0, 0, 0, 0, 0, "From June 2019");
+TemporalHelpers.assertDuration(plainDate5.until(plainDate6), 0, 0, 0, /* days = */ 365, 0, 0, 0, 0, 0, 0, "From June 2020");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/infinity-throws-rangeerror.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..6bd0c01f29
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/infinity-throws-rangeerror.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if any value in the property bag is Infinity or -Infinity
+esid: sec-temporal.plaindate.prototype.until
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainDate(2000, 5, 2);
+const base = { year: 2000, month: 5, day: 2 };
+
+[Infinity, -Infinity].forEach((inf) => {
+ ["year", "month", "day"].forEach((prop) => {
+ assert.throws(RangeError, () => instance.until({ ...base, [prop]: inf }), `${prop} property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, prop);
+ assert.throws(RangeError, () => instance.until({ ...base, [prop]: obj }));
+ assert.compareArray(calls, [`get ${prop}.valueOf`, `call ${prop}.valueOf`], "it fails after fetching the primitive value");
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-default.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-default.js
new file mode 100644
index 0000000000..907df1f977
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-default.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Default value for largestUnit option is days
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const feb20 = Temporal.PlainDate.from("2020-02-01");
+const feb21 = Temporal.PlainDate.from("2021-02-01");
+TemporalHelpers.assertDuration(feb20.until(feb21), 0, 0, 0, /* days = */ 366, 0, 0, 0, 0, 0, 0, "no options");
+TemporalHelpers.assertDuration(feb20.until(feb21, undefined), 0, 0, 0, /* days = */ 366, 0, 0, 0, 0, 0, 0, "undefined options");
+TemporalHelpers.assertDuration(feb20.until(feb21, {}), 0, 0, 0, /* days = */ 366, 0, 0, 0, 0, 0, 0, "no largestUnit");
+TemporalHelpers.assertDuration(feb20.until(feb21, { largestUnit: undefined }), 0, 0, 0, /* days = */ 366, 0, 0, 0, 0, 0, 0, "undefined largestUnit");
+TemporalHelpers.assertDuration(feb20.until(feb21, { largestUnit: "days" }), 0, 0, 0, /* days = */ 366, 0, 0, 0, 0, 0, 0, "days");
+TemporalHelpers.assertDuration(feb20.until(feb21, { largestUnit: "auto" }), 0, 0, 0, /* days = */ 366, 0, 0, 0, 0, 0, 0, "auto");
+TemporalHelpers.assertDuration(feb20.until(feb21, () => {}), 0, 0, 0, /* days = */ 366, 0, 0, 0, 0, 0, 0, "no largestUnit (function)");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-higher-units.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-higher-units.js
new file mode 100644
index 0000000000..a8f8812d9d
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-higher-units.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Tests calculations with higher largestUnit than the default of 'days'
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const date = new Temporal.PlainDate(1969, 7, 24);
+const later = Temporal.PlainDate.from({ year: 2019, month: 7, day: 24 });
+const duration = date.until(later, { largestUnit: "years" });
+TemporalHelpers.assertDuration(duration, /* years = */ 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, "crossing epoch");
+
+const feb20 = Temporal.PlainDate.from("2020-02-01");
+const feb21 = Temporal.PlainDate.from("2021-02-01");
+TemporalHelpers.assertDuration(feb20.until(feb21, { largestUnit: "years" }), /* years = */ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, "start of February, years");
+TemporalHelpers.assertDuration(feb20.until(feb21, { largestUnit: "months" }), 0, /* months = */ 12, 0, 0, 0, 0, 0, 0, 0, 0, "start of February, months");
+TemporalHelpers.assertDuration(feb20.until(feb21, { largestUnit: "weeks" }), 0, 0, /* weeks = */ 52, /* days = */ 2, 0, 0, 0, 0, 0, 0, "start of February, weeks");
+
+const lastFeb20 = Temporal.PlainDate.from("2020-02-29");
+const lastFeb21 = Temporal.PlainDate.from("2021-02-28");
+TemporalHelpers.assertDuration(lastFeb20.until(lastFeb21, { largestUnit: "years" }), /* years = */ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, "end of February, years");
+TemporalHelpers.assertDuration(lastFeb20.until(lastFeb21, { largestUnit: "months" }), 0, /* months = */ 12, 0, 0, 0, 0, 0, 0, 0, 0, "end of February, months");
+TemporalHelpers.assertDuration(lastFeb20.until(lastFeb21, { largestUnit: "weeks" }), 0, 0, /* weeks = */ 52, /* days = */ 1, 0, 0, 0, 0, 0, 0, "end of February, weeks");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-invalid-string.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-invalid-string.js
new file mode 100644
index 0000000000..145b8d3024
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-invalid-string.js
@@ -0,0 +1,41 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: RangeError thrown when largestUnit option not one of the allowed string values
+features: [Temporal]
+---*/
+
+const earlier = new Temporal.PlainDate(2000, 5, 2);
+const later = new Temporal.PlainDate(2001, 6, 3);
+const badValues = [
+ "era",
+ "eraYear",
+ "hour",
+ "minute",
+ "second",
+ "millisecond",
+ "microsecond",
+ "nanosecond",
+ "month\0",
+ "YEAR",
+ "eras",
+ "eraYears",
+ "hours",
+ "minutes",
+ "seconds",
+ "milliseconds",
+ "microseconds",
+ "nanoseconds",
+ "months\0",
+ "YEARS",
+ "other string"
+];
+for (const largestUnit of badValues) {
+ assert.throws(RangeError, () => earlier.until(later, { largestUnit }),
+ `"${largestUnit}" is not a valid value for largestUnit`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-plurals-accepted.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-plurals-accepted.js
new file mode 100644
index 0000000000..84769e1877
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-plurals-accepted.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Plural units are accepted as well for the largestUnit option
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const earlier = new Temporal.PlainDate(2000, 5, 2);
+const later = new Temporal.PlainDate(2001, 6, 12);
+const validUnits = [
+ "year",
+ "month",
+ "week",
+ "day",
+];
+TemporalHelpers.checkPluralUnitsAccepted((largestUnit) => earlier.until(later, { largestUnit }), validUnits);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-smallestunit-mismatch.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-smallestunit-mismatch.js
new file mode 100644
index 0000000000..91ba76b412
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-smallestunit-mismatch.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: RangeError thrown when smallestUnit is larger than largestUnit
+features: [Temporal]
+---*/
+
+const earlier = new Temporal.PlainDate(2000, 5, 2);
+const later = new Temporal.PlainDate(2001, 6, 3);
+const units = ["years", "months", "weeks", "days"];
+for (let largestIdx = 1; largestIdx < units.length; largestIdx++) {
+ for (let smallestIdx = 0; smallestIdx < largestIdx; smallestIdx++) {
+ const largestUnit = units[largestIdx];
+ const smallestUnit = units[smallestIdx];
+ assert.throws(RangeError, () => earlier.until(later, { largestUnit, smallestUnit }));
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-undefined.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-undefined.js
new file mode 100644
index 0000000000..0b24449663
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-undefined.js
@@ -0,0 +1,20 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Fallback value for largestUnit option
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const earlier = new Temporal.PlainDate(2000, 5, 2);
+const later = new Temporal.PlainDate(2001, 6, 3);
+
+const explicit = earlier.until(later, { largestUnit: undefined });
+TemporalHelpers.assertDuration(explicit, 0, 0, 0, 397, 0, 0, 0, 0, 0, 0, "default largestUnit is day");
+const implicit = earlier.until(later, {});
+TemporalHelpers.assertDuration(implicit, 0, 0, 0, 397, 0, 0, 0, 0, 0, 0, "default largestUnit is day");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-wrong-type.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-wrong-type.js
new file mode 100644
index 0000000000..42d56c641e
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-wrong-type.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Type conversions for largestUnit option
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const earlier = new Temporal.PlainDate(2000, 5, 2);
+const later = new Temporal.PlainDate(2001, 6, 3);
+TemporalHelpers.checkStringOptionWrongType("largestUnit", "year",
+ (largestUnit) => earlier.until(later, { largestUnit }),
+ (result, descr) => TemporalHelpers.assertDuration(result, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, descr),
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/length.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/length.js
new file mode 100644
index 0000000000..31c204731d
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/length.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2020 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Temporal.PlainDate.prototype.until.length is 1
+info: |
+ Every built-in function object, including constructors, has a "length" property whose value is
+ an integer. Unless otherwise specified, this value is equal to the largest number of named
+ arguments shown in the subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which are shown using the form
+ «...name») are not included in the default argument count.
+
+ Unless otherwise specified, the "length" property of a built-in function object has the
+ attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Temporal]
+---*/
+
+verifyProperty(Temporal.PlainDate.prototype.until, "length", {
+ value: 1,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/name.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/name.js
new file mode 100644
index 0000000000..99b7ae2be1
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/name.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Temporal.PlainDate.prototype.until.name is "until".
+info: |
+ Every built-in function object, including constructors, that is not identified as an anonymous
+ function has a "name" property whose value is a String. Unless otherwise specified, this value
+ is the name that is given to the function in this specification.
+
+ Unless otherwise specified, the "name" property of a built-in function object, if it exists,
+ has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Temporal]
+---*/
+
+verifyProperty(Temporal.PlainDate.prototype.until, "name", {
+ value: "until",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/not-a-constructor.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/not-a-constructor.js
new file mode 100644
index 0000000000..2d4a6df0d0
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/not-a-constructor.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: >
+ Temporal.PlainDate.prototype.until does not implement [[Construct]], is not new-able
+info: |
+ Built-in function objects that are not identified as constructors do not implement the
+ [[Construct]] internal method unless otherwise specified in the description of a particular
+ function.
+includes: [isConstructor.js]
+features: [Reflect.construct, Temporal]
+---*/
+
+assert.throws(TypeError, () => {
+ new Temporal.PlainDate.prototype.until();
+}, "Calling as constructor");
+
+assert.sameValue(isConstructor(Temporal.PlainDate.prototype.until), false,
+ "isConstructor(Temporal.PlainDate.prototype.until)");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/options-object.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/options-object.js
new file mode 100644
index 0000000000..978d5189f8
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/options-object.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Empty or a function object may be used as options
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainDate(2000, 5, 2);
+
+const result1 = instance.until(new Temporal.PlainDate(1976, 11, 18), {});
+TemporalHelpers.assertDuration(
+ result1, 0, 0, 0, -8566, 0, 0, 0, 0, 0, 0,
+ "options may be an empty plain object"
+);
+
+const result2 = instance.until(new Temporal.PlainDate(1976, 11, 18), () => {});
+TemporalHelpers.assertDuration(
+ result2, 0, 0, 0, -8566, 0, 0, 0, 0, 0, 0,
+ "options may be a function object"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/options-wrong-type.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/options-wrong-type.js
new file mode 100644
index 0000000000..dd67a3bd6b
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/options-wrong-type.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: TypeError thrown when options argument is a primitive
+features: [BigInt, Symbol, Temporal]
+---*/
+
+const badOptions = [
+ null,
+ true,
+ "some string",
+ Symbol(),
+ 1,
+ 2n,
+];
+
+const instance = new Temporal.PlainDate(2000, 5, 2);
+for (const value of badOptions) {
+ assert.throws(TypeError, () => instance.until(new Temporal.PlainDate(1976, 11, 18), value),
+ `TypeError on wrong options type ${typeof value}`);
+};
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/order-of-operations.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/order-of-operations.js
new file mode 100644
index 0000000000..427670784a
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/order-of-operations.js
@@ -0,0 +1,214 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Properties on objects passed to until() are accessed in the correct order
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const expected = [
+ // ToTemporalDate
+ "get other.calendar",
+ "has other.calendar.dateAdd",
+ "has other.calendar.dateFromFields",
+ "has other.calendar.dateUntil",
+ "has other.calendar.day",
+ "has other.calendar.dayOfWeek",
+ "has other.calendar.dayOfYear",
+ "has other.calendar.daysInMonth",
+ "has other.calendar.daysInWeek",
+ "has other.calendar.daysInYear",
+ "has other.calendar.fields",
+ "has other.calendar.id",
+ "has other.calendar.inLeapYear",
+ "has other.calendar.mergeFields",
+ "has other.calendar.month",
+ "has other.calendar.monthCode",
+ "has other.calendar.monthDayFromFields",
+ "has other.calendar.monthsInYear",
+ "has other.calendar.weekOfYear",
+ "has other.calendar.year",
+ "has other.calendar.yearMonthFromFields",
+ "has other.calendar.yearOfWeek",
+ "get other.calendar.dateFromFields",
+ "get other.calendar.fields",
+ "call other.calendar.fields",
+ "get other.day",
+ "get other.day.valueOf",
+ "call other.day.valueOf",
+ "get other.month",
+ "get other.month.valueOf",
+ "call other.month.valueOf",
+ "get other.monthCode",
+ "get other.monthCode.toString",
+ "call other.monthCode.toString",
+ "get other.year",
+ "get other.year.valueOf",
+ "call other.year.valueOf",
+ "call other.calendar.dateFromFields",
+ // CalendarEquals
+ "get this.calendar.id",
+ "get other.calendar.id",
+ // CopyDataProperties
+ "ownKeys options",
+ "getOwnPropertyDescriptor options.roundingIncrement",
+ "get options.roundingIncrement",
+ "getOwnPropertyDescriptor options.roundingMode",
+ "get options.roundingMode",
+ "getOwnPropertyDescriptor options.largestUnit",
+ "get options.largestUnit",
+ "getOwnPropertyDescriptor options.smallestUnit",
+ "get options.smallestUnit",
+ "getOwnPropertyDescriptor options.additional",
+ "get options.additional",
+ // GetDifferenceSettings
+ "get options.largestUnit.toString",
+ "call options.largestUnit.toString",
+ "get options.roundingIncrement.valueOf",
+ "call options.roundingIncrement.valueOf",
+ "get options.roundingMode.toString",
+ "call options.roundingMode.toString",
+ "get options.smallestUnit.toString",
+ "call options.smallestUnit.toString",
+];
+const actual = [];
+
+const ownCalendar = TemporalHelpers.calendarObserver(actual, "this.calendar");
+const instance = new Temporal.PlainDate(2000, 5, 2, ownCalendar);
+
+const otherDatePropertyBag = TemporalHelpers.propertyBagObserver(actual, {
+ year: 2001,
+ month: 6,
+ monthCode: "M06",
+ day: 2,
+ calendar: TemporalHelpers.calendarObserver(actual, "other.calendar"),
+}, "other");
+
+function createOptionsObserver({ smallestUnit = "days", largestUnit = "auto", roundingMode = "halfExpand", roundingIncrement = 1 } = {}) {
+ return TemporalHelpers.propertyBagObserver(actual, {
+ // order is significant, due to iterating through properties in order to
+ // copy them to an internal null-prototype object:
+ roundingIncrement,
+ roundingMode,
+ largestUnit,
+ smallestUnit,
+ additional: "property",
+ }, "options");
+}
+
+// clear any observable things that happened while constructing the objects
+actual.splice(0);
+
+// basic order of observable operations with calendar call, without rounding:
+instance.until(otherDatePropertyBag, createOptionsObserver({ largestUnit: "years" }));
+assert.compareArray(actual, expected.concat([
+ // lookup
+ "get this.calendar.dateAdd",
+ "get this.calendar.dateUntil",
+ // CalendarDateUntil
+ "call this.calendar.dateUntil",
+]), "order of operations");
+actual.splice(0); // clear
+
+// short-circuit for identical objects:
+
+const identicalPropertyBag = TemporalHelpers.propertyBagObserver(actual, {
+ year: 2000,
+ month: 5,
+ monthCode: "M05",
+ day: 2,
+ calendar: TemporalHelpers.calendarObserver(actual, "other.calendar"),
+}, "other");
+
+instance.since(identicalPropertyBag, createOptionsObserver());
+assert.compareArray(actual, expected, "order of operations with identical dates");
+actual.splice(0); // clear
+
+// code path through RoundDuration that rounds to the nearest year:
+const expectedOpsForYearRounding = expected.concat([
+ // lookup
+ "get this.calendar.dateAdd",
+ "get this.calendar.dateUntil",
+ // CalendarDateUntil
+ "call this.calendar.dateUntil",
+ // RoundDuration
+ "call this.calendar.dateAdd", // 12.d
+ "call this.calendar.dateAdd", // 12.f
+ "call this.calendar.dateUntil", // 12.n
+ "call this.calendar.dateAdd", // 12.x MoveRelativeDate
+ // (12.r not called because other units can't add up to >1 year at this point)
+ // BalanceDateDurationRelative
+ "call this.calendar.dateAdd", // 9.c
+ "call this.calendar.dateUntil" // 9.d
+]);
+instance.until(otherDatePropertyBag, createOptionsObserver({ smallestUnit: "years" }));
+assert.compareArray(actual, expectedOpsForYearRounding, "order of operations with smallestUnit = years");
+actual.splice(0); // clear
+
+// code path through RoundDuration that rounds to the nearest year and skips a DateUntil call:
+const otherDatePropertyBagSameMonth = TemporalHelpers.propertyBagObserver(actual, {
+ year: 2001,
+ month: 5,
+ monthCode: "M05",
+ day: 2,
+ calendar: TemporalHelpers.calendarObserver(actual, "other.calendar"),
+}, "other");
+const expectedOpsForYearRoundingSameMonth = expected.concat([
+ // lookup
+ "get this.calendar.dateAdd",
+ "get this.calendar.dateUntil",
+ // CalendarDateUntil
+ "call this.calendar.dateUntil",
+ // RoundDuration
+ "call this.calendar.dateAdd", // 12.d
+ "call this.calendar.dateAdd", // 12.f
+ "call this.calendar.dateAdd", // 12.x MoveRelativeDate
+ // (12.n not called because months and weeks == 0)
+ // BalanceDateDurationRelative
+ "call this.calendar.dateAdd", // 9.c
+ "call this.calendar.dateUntil" // 9.d
+]);
+instance.until(otherDatePropertyBagSameMonth, createOptionsObserver({ smallestUnit: "years" }));
+assert.compareArray(actual, expectedOpsForYearRoundingSameMonth, "order of operations with smallestUnit = years and no excess months/weeks");
+actual.splice(0); // clear
+
+// code path through RoundDuration that rounds to the nearest month:
+const expectedOpsForMonthRounding = expected.concat([
+ // lookup
+ "get this.calendar.dateAdd",
+ "get this.calendar.dateUntil",
+ // CalendarDateUntil
+ "call this.calendar.dateUntil",
+ // RoundDuration
+ "call this.calendar.dateAdd", // 13.c
+ "call this.calendar.dateAdd", // 13.e
+ "call this.calendar.dateAdd", // 13.w MoveRelativeDate
+ // BalanceDateDurationRelative
+ "call this.calendar.dateAdd", // 10.d
+ "call this.calendar.dateUntil" // 10.e
+]);
+instance.until(otherDatePropertyBag, createOptionsObserver({ smallestUnit: "months" }));
+assert.compareArray(actual, expectedOpsForMonthRounding, "order of operations with smallestUnit = months");
+actual.splice(0); // clear
+
+// code path through RoundDuration that rounds to the nearest week:
+const expectedOpsForWeekRounding = expected.concat([
+ // lookup
+ "get this.calendar.dateAdd",
+ "get this.calendar.dateUntil",
+ // CalendarDateUntil
+ "call this.calendar.dateUntil",
+ // RoundDuration
+ "call this.calendar.dateUntil", // 14.f
+ "call this.calendar.dateAdd", // 14.p MoveRelativeDate
+ // BalanceDateDurationRelative
+ "call this.calendar.dateAdd", // 16
+ "call this.calendar.dateUntil" // 17
+]);
+instance.until(otherDatePropertyBag, createOptionsObserver({ smallestUnit: "weeks" }));
+assert.compareArray(actual, expectedOpsForWeekRounding, "order of operations with smallestUnit = weeks");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/prop-desc.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/prop-desc.js
new file mode 100644
index 0000000000..0c230a999e
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/prop-desc.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: The "until" property of Temporal.PlainDate.prototype
+includes: [propertyHelper.js]
+features: [Temporal]
+---*/
+
+assert.sameValue(
+ typeof Temporal.PlainDate.prototype.until,
+ "function",
+ "`typeof PlainDate.prototype.until` is `function`"
+);
+
+verifyProperty(Temporal.PlainDate.prototype, "until", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/round-cross-unit-boundary.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/round-cross-unit-boundary.js
new file mode 100644
index 0000000000..6b15e24902
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/round-cross-unit-boundary.js
@@ -0,0 +1,17 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Rounding can cross unit boundaries up to largestUnit
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const earlier = new Temporal.PlainDate(2022, 1, 1);
+const later = new Temporal.PlainDate(2023, 12, 25);
+const duration = earlier.until(later, { largestUnit: "years", smallestUnit: "months", roundingMode: "expand" });
+TemporalHelpers.assertDuration(duration, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, "1 year 11 months balances to 2 years");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/rounding-relative.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/rounding-relative.js
new file mode 100644
index 0000000000..b7b28c9338
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/rounding-relative.js
@@ -0,0 +1,37 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Should round relative to the receiver.
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const date1 = Temporal.PlainDate.from("2019-01-01");
+const date2 = Temporal.PlainDate.from("2019-02-15");
+
+TemporalHelpers.assertDuration(
+ date1.until(date2, { smallestUnit: "months", roundingMode: "halfExpand" }),
+ 0, /* months = */ 2, 0, 0, 0, 0, 0, 0, 0, 0);
+TemporalHelpers.assertDuration(
+ date2.until(date1, { smallestUnit: "months", roundingMode: "halfExpand" }),
+ 0, /* months = */ -1, 0, 0, 0, 0, 0, 0, 0, 0);
+
+const cases = [
+ ["2019-03-01", "2019-01-29", 1, 1],
+ ["2019-01-29", "2019-03-01", -1, -3],
+ ["2019-03-29", "2019-01-30", 1, 29],
+ ["2019-01-30", "2019-03-29", -1, -29],
+ ["2019-03-30", "2019-01-31", 1, 30],
+ ["2019-01-31", "2019-03-30", -1, -28],
+ ["2019-03-31", "2019-01-31", 2, 0],
+ ["2019-01-31", "2019-03-31", -2, 0]
+];
+for (const [end, start, months, days] of cases) {
+ const result = Temporal.PlainDate.from(start).until(end, { largestUnit: "months" });
+ TemporalHelpers.assertDuration(result, 0, months, 0, days, 0, 0, 0, 0, 0, 0, `${end} - ${start}`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/rounding-zero-year-month-week-length.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/rounding-zero-year-month-week-length.js
new file mode 100644
index 0000000000..1d56c472c9
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/rounding-zero-year-month-week-length.js
@@ -0,0 +1,34 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: >
+ A malicious calendar resulting in a year, month, or week length of zero is
+ handled correctly
+info: |
+ RoundDuration
+ 10.z. If _oneYearDays_ = 0, throw a *RangeError* exception.
+ ...
+ 11.z. If _oneMonthDays_ = 0, throw a *RangeError* exception.
+ ...
+ 12.s. If _oneWeekDays_ = 0, throw a *RangeError* exception.
+features: [Temporal]
+---*/
+
+const cal = new class extends Temporal.Calendar {
+ dateAdd(date, duration, options) {
+ // Called several times, last call sets oneYear/Month/WeekDays to 0
+ return new Temporal.PlainDate(1970, 1, 1);
+ }
+}("iso8601");
+
+const d1 = new Temporal.PlainDate(1970, 1, 1, cal);
+const d2 = new Temporal.PlainDate(1971, 1, 1, cal);
+
+assert.throws(RangeError, () => d1.until(d2, { smallestUnit: "years" }), "zero year length handled correctly");
+assert.throws(RangeError, () => d1.until(d2, { smallestUnit: "months" }), "zero month length handled correctly");
+assert.throws(RangeError, () => d1.until(d2, { smallestUnit: "weeks" }), "zero week length handled correctly");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingincrement-nan.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingincrement-nan.js
new file mode 100644
index 0000000000..7e68bdd813
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingincrement-nan.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: RangeError thrown when roundingIncrement option is NaN
+info: |
+ sec-getoption step 8.b:
+ b. If _value_ is *NaN*, throw a *RangeError* exception.
+ sec-temporal-totemporalroundingincrement step 5:
+ 5. Let _increment_ be ? GetOption(_normalizedOptions_, *"roundingIncrement"*, « Number », *undefined*, 1).
+ sec-temporal.plaindate.prototype.until step 11:
+ 11. Let _roundingIncrement_ be ? ToTemporalRoundingIncrement(_options_, *undefined*, *false*).
+features: [Temporal]
+---*/
+
+const earlier = new Temporal.PlainDate(2000, 5, 2);
+const later = new Temporal.PlainDate(2000, 5, 7);
+assert.throws(RangeError, () => earlier.until(later, { roundingIncrement: NaN }));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingincrement-non-integer.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingincrement-non-integer.js
new file mode 100644
index 0000000000..349135ad08
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingincrement-non-integer.js
@@ -0,0 +1,27 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Rounding for roundingIncrement option
+info: |
+ ToTemporalRoundingIncrement ( _normalizedOptions_ )
+
+ 1. Let _increment_ be ? GetOption(_normalizedOptions_, *"roundingIncrement"*, *"number"*, *undefined*, *1*<sub>𝔽</sub>).
+ 2. If _increment_ is not finite, throw a *RangeError* exception.
+ 3. Let _integerIncrement_ be truncate(ℝ(_increment_)).
+ 4. If _integerIncrement_ < 1 or _integerIncrement_ > 10<sup>9</sup>, throw a *RangeError* exception.
+ 5. Return _integerIncrement_.
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const earlier = new Temporal.PlainDate(2000, 5, 2);
+const later = new Temporal.PlainDate(2000, 5, 7);
+const result = earlier.until(later, { roundingIncrement: 2.5, roundingMode: "trunc" });
+TemporalHelpers.assertDuration(result, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, "roundingIncrement 2.5 truncates to 2");
+const result2 = earlier.until(later, { smallestUnit: "days", roundingIncrement: 1e9 + 0.5, roundingMode: "expand" });
+TemporalHelpers.assertDuration(result2, 0, 0, 0, 1e9, 0, 0, 0, 0, 0, 0, "roundingIncrement 1e9 + 0.5 truncates to 1e9");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingincrement-out-of-range.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingincrement-out-of-range.js
new file mode 100644
index 0000000000..26092686df
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingincrement-out-of-range.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: RangeError thrown when roundingIncrement option out of range
+info: |
+ ToTemporalRoundingIncrement ( _normalizedOptions_ )
+
+ 1. Let _increment_ be ? GetOption(_normalizedOptions_, *"roundingIncrement"*, *"number"*, *undefined*, *1*<sub>𝔽</sub>).
+ 2. If _increment_ is not finite, throw a *RangeError* exception.
+ 3. Let _integerIncrement_ be truncate(ℝ(_increment_)).
+ 4. If _integerIncrement_ < 1 or _integerIncrement_ > 10<sup>9</sup>, throw a *RangeError* exception.
+ 5. Return _integerIncrement_.
+features: [Temporal]
+---*/
+
+const earlier = new Temporal.PlainDate(2000, 5, 2);
+const later = new Temporal.PlainDate(2000, 5, 7);
+assert.throws(RangeError, () => earlier.until(later, { roundingIncrement: -Infinity }));
+assert.throws(RangeError, () => earlier.until(later, { roundingIncrement: -1 }));
+assert.throws(RangeError, () => earlier.until(later, { roundingIncrement: 0 }));
+assert.throws(RangeError, () => earlier.until(later, { roundingIncrement: 0.9 }));
+assert.throws(RangeError, () => earlier.until(later, { roundingIncrement: 1e9 + 1 }));
+assert.throws(RangeError, () => earlier.until(later, { roundingIncrement: Infinity }));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingincrement-undefined.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingincrement-undefined.js
new file mode 100644
index 0000000000..7670614029
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingincrement-undefined.js
@@ -0,0 +1,27 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Fallback value for roundingIncrement option
+info: |
+ sec-getoption step 3:
+ 3. If _value_ is *undefined*, return _fallback_.
+ sec-temporal-totemporalroundingincrement step 5:
+ 5. Let _increment_ be ? GetOption(_normalizedOptions_, *"roundingIncrement"*, « Number », *undefined*, 1).
+ sec-temporal.plaindate.prototype.until step 11:
+ 11. Let _roundingIncrement_ be ? ToTemporalRoundingIncrement(_options_, *undefined*, *false*).
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const earlier = new Temporal.PlainDate(2000, 5, 2);
+const later = new Temporal.PlainDate(2000, 5, 7);
+
+const explicit = earlier.until(later, { roundingIncrement: undefined });
+TemporalHelpers.assertDuration(explicit, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, "default roundingIncrement is 1");
+
+// See options-undefined.js for {}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingincrement-wrong-type.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingincrement-wrong-type.js
new file mode 100644
index 0000000000..c2cc289fb2
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingincrement-wrong-type.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Type conversions for roundingIncrement option
+info: |
+ sec-getoption step 8.a:
+ a. Set _value_ to ? ToNumber(value).
+ sec-temporal-totemporalroundingincrement step 5:
+ 5. Let _increment_ be ? GetOption(_normalizedOptions_, *"roundingIncrement"*, « Number », *undefined*, 1).
+ sec-temporal.plaindate.prototype.until step 11:
+ 11. Let _roundingIncrement_ be ? ToTemporalRoundingIncrement(_options_, *undefined*, *false*).
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const earlier = new Temporal.PlainDate(2000, 5, 2);
+const later = new Temporal.PlainDate(2000, 5, 7);
+
+TemporalHelpers.checkRoundingIncrementOptionWrongType(
+ (roundingIncrement) => earlier.until(later, { roundingIncrement }),
+ (result, descr) => TemporalHelpers.assertDuration(result, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, descr),
+ (result, descr) => TemporalHelpers.assertDuration(result, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, descr),
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingincrement.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingincrement.js
new file mode 100644
index 0000000000..5a86e247c8
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingincrement.js
@@ -0,0 +1,31 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Tests calculations with roundingMode "trunc".
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const earlier = Temporal.PlainDate.from("2019-01-08");
+const later = Temporal.PlainDate.from("2021-09-07");
+
+TemporalHelpers.assertDuration(
+ earlier.until(later, { smallestUnit: "years", roundingIncrement: 4, roundingMode: "halfExpand" }),
+ /* years = */ 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, "years");
+
+TemporalHelpers.assertDuration(
+ earlier.until(later, { smallestUnit: "months", roundingIncrement: 10, roundingMode: "halfExpand" }),
+ 0, /* months = */ 30, 0, 0, 0, 0, 0, 0, 0, 0, "months");
+
+TemporalHelpers.assertDuration(
+ earlier.until(later, { smallestUnit: "weeks", roundingIncrement: 12, roundingMode: "halfExpand" }),
+ 0, 0, /* weeks = */ 144, 0, 0, 0, 0, 0, 0, 0, "weeks");
+
+TemporalHelpers.assertDuration(
+ earlier.until(later, { smallestUnit: "days", roundingIncrement: 100, roundingMode: "halfExpand" }),
+ 0, 0, 0, /* days = */ 1000, 0, 0, 0, 0, 0, 0, "days");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-ceil.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-ceil.js
new file mode 100644
index 0000000000..fe40fdd5f1
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-ceil.js
@@ -0,0 +1,39 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Tests calculations with roundingMode "ceil".
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const earlier = new Temporal.PlainDate(2019, 1, 8);
+const later = new Temporal.PlainDate(2021, 9, 7);
+
+const expected = [
+ ["years", [3], [-2]],
+ ["months", [0, 32], [0, -31]],
+ ["weeks", [0, 0, 139], [0, 0, -139]],
+ ["days", [0, 0, 0, 973], [0, 0, 0, -973]],
+];
+
+const roundingMode = "ceil";
+
+expected.forEach(([smallestUnit, expectedPositive, expectedNegative]) => {
+ const [py, pm = 0, pw = 0, pd = 0, ph = 0, pmin = 0, ps = 0, pms = 0, pµs = 0, pns = 0] = expectedPositive;
+ const [ny, nm = 0, nw = 0, nd = 0, nh = 0, nmin = 0, ns = 0, nms = 0, nµs = 0, nns = 0] = expectedNegative;
+ TemporalHelpers.assertDuration(
+ earlier.until(later, { smallestUnit, roundingMode }),
+ py, pm, pw, pd, ph, pmin, ps, pms, pµs, pns,
+ `rounds to ${smallestUnit} (roundingMode = ${roundingMode}, positive case)`
+ );
+ TemporalHelpers.assertDuration(
+ later.until(earlier, { smallestUnit, roundingMode }),
+ ny, nm, nw, nd, nh, nmin, ns, nms, nµs, nns,
+ `rounds to ${smallestUnit} (rounding mode = ${roundingMode}, negative case)`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-expand.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-expand.js
new file mode 100644
index 0000000000..d3b3271b50
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-expand.js
@@ -0,0 +1,39 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Tests calculations with roundingMode "expand".
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const earlier = new Temporal.PlainDate(2019, 1, 8);
+const later = new Temporal.PlainDate(2021, 9, 7);
+
+const expected = [
+ ["years", [3], [-3]],
+ ["months", [0, 32], [0, -32]],
+ ["weeks", [0, 0, 139], [0, 0, -139]],
+ ["days", [0, 0, 0, 973], [0, 0, 0, -973]],
+];
+
+const roundingMode = "expand";
+
+expected.forEach(([smallestUnit, expectedPositive, expectedNegative]) => {
+ const [py, pm = 0, pw = 0, pd = 0, ph = 0, pmin = 0, ps = 0, pms = 0, pµs = 0, pns = 0] = expectedPositive;
+ const [ny, nm = 0, nw = 0, nd = 0, nh = 0, nmin = 0, ns = 0, nms = 0, nµs = 0, nns = 0] = expectedNegative;
+ TemporalHelpers.assertDuration(
+ earlier.until(later, { smallestUnit, roundingMode }),
+ py, pm, pw, pd, ph, pmin, ps, pms, pµs, pns,
+ `rounds to ${smallestUnit} (roundingMode = ${roundingMode}, positive case)`
+ );
+ TemporalHelpers.assertDuration(
+ later.until(earlier, { smallestUnit, roundingMode }),
+ ny, nm, nw, nd, nh, nmin, ns, nms, nµs, nns,
+ `rounds to ${smallestUnit} (rounding mode = ${roundingMode}, negative case)`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-floor.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-floor.js
new file mode 100644
index 0000000000..37c8a1c1a5
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-floor.js
@@ -0,0 +1,39 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Tests calculations with roundingMode "floor".
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const earlier = new Temporal.PlainDate(2019, 1, 8);
+const later = new Temporal.PlainDate(2021, 9, 7);
+
+const expected = [
+ ["years", [2], [-3]],
+ ["months", [0, 31], [0, -32]],
+ ["weeks", [0, 0, 139], [0, 0, -139]],
+ ["days", [0, 0, 0, 973], [0, 0, 0, -973]],
+];
+
+const roundingMode = "floor";
+
+expected.forEach(([smallestUnit, expectedPositive, expectedNegative]) => {
+ const [py, pm = 0, pw = 0, pd = 0, ph = 0, pmin = 0, ps = 0, pms = 0, pµs = 0, pns = 0] = expectedPositive;
+ const [ny, nm = 0, nw = 0, nd = 0, nh = 0, nmin = 0, ns = 0, nms = 0, nµs = 0, nns = 0] = expectedNegative;
+ TemporalHelpers.assertDuration(
+ earlier.until(later, { smallestUnit, roundingMode }),
+ py, pm, pw, pd, ph, pmin, ps, pms, pµs, pns,
+ `rounds to ${smallestUnit} (roundingMode = ${roundingMode}, positive case)`
+ );
+ TemporalHelpers.assertDuration(
+ later.until(earlier, { smallestUnit, roundingMode }),
+ ny, nm, nw, nd, nh, nmin, ns, nms, nµs, nns,
+ `rounds to ${smallestUnit} (rounding mode = ${roundingMode}, negative case)`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-halfCeil.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-halfCeil.js
new file mode 100644
index 0000000000..55118dd705
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-halfCeil.js
@@ -0,0 +1,39 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Tests calculations with roundingMode "halfCeil".
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const earlier = new Temporal.PlainDate(2019, 1, 8);
+const later = new Temporal.PlainDate(2021, 9, 7);
+
+const expected = [
+ ["years", [3], [-3]],
+ ["months", [0, 32], [0, -32]],
+ ["weeks", [0, 0, 139], [0, 0, -139]],
+ ["days", [0, 0, 0, 973], [0, 0, 0, -973]],
+];
+
+const roundingMode = "halfCeil";
+
+expected.forEach(([smallestUnit, expectedPositive, expectedNegative]) => {
+ const [py, pm = 0, pw = 0, pd = 0, ph = 0, pmin = 0, ps = 0, pms = 0, pµs = 0, pns = 0] = expectedPositive;
+ const [ny, nm = 0, nw = 0, nd = 0, nh = 0, nmin = 0, ns = 0, nms = 0, nµs = 0, nns = 0] = expectedNegative;
+ TemporalHelpers.assertDuration(
+ earlier.until(later, { smallestUnit, roundingMode }),
+ py, pm, pw, pd, ph, pmin, ps, pms, pµs, pns,
+ `rounds to ${smallestUnit} (roundingMode = ${roundingMode}, positive case)`
+ );
+ TemporalHelpers.assertDuration(
+ later.until(earlier, { smallestUnit, roundingMode }),
+ ny, nm, nw, nd, nh, nmin, ns, nms, nµs, nns,
+ `rounds to ${smallestUnit} (rounding mode = ${roundingMode}, negative case)`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-halfEven.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-halfEven.js
new file mode 100644
index 0000000000..3b42461cdb
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-halfEven.js
@@ -0,0 +1,39 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Tests calculations with roundingMode "halfEven".
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const earlier = new Temporal.PlainDate(2019, 1, 8);
+const later = new Temporal.PlainDate(2021, 9, 7);
+
+const expected = [
+ ["years", [3], [-3]],
+ ["months", [0, 32], [0, -32]],
+ ["weeks", [0, 0, 139], [0, 0, -139]],
+ ["days", [0, 0, 0, 973], [0, 0, 0, -973]],
+];
+
+const roundingMode = "halfEven";
+
+expected.forEach(([smallestUnit, expectedPositive, expectedNegative]) => {
+ const [py, pm = 0, pw = 0, pd = 0, ph = 0, pmin = 0, ps = 0, pms = 0, pµs = 0, pns = 0] = expectedPositive;
+ const [ny, nm = 0, nw = 0, nd = 0, nh = 0, nmin = 0, ns = 0, nms = 0, nµs = 0, nns = 0] = expectedNegative;
+ TemporalHelpers.assertDuration(
+ earlier.until(later, { smallestUnit, roundingMode }),
+ py, pm, pw, pd, ph, pmin, ps, pms, pµs, pns,
+ `rounds to ${smallestUnit} (roundingMode = ${roundingMode}, positive case)`
+ );
+ TemporalHelpers.assertDuration(
+ later.until(earlier, { smallestUnit, roundingMode }),
+ ny, nm, nw, nd, nh, nmin, ns, nms, nµs, nns,
+ `rounds to ${smallestUnit} (rounding mode = ${roundingMode}, negative case)`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-halfExpand.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-halfExpand.js
new file mode 100644
index 0000000000..c5fc068b82
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-halfExpand.js
@@ -0,0 +1,39 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Tests calculations with roundingMode "halfExpand".
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const earlier = new Temporal.PlainDate(2019, 1, 8);
+const later = new Temporal.PlainDate(2021, 9, 7);
+
+const expected = [
+ ["years", [3], [-3]],
+ ["months", [0, 32], [0, -32]],
+ ["weeks", [0, 0, 139], [0, 0, -139]],
+ ["days", [0, 0, 0, 973], [0, 0, 0, -973]],
+];
+
+const roundingMode = "halfExpand";
+
+expected.forEach(([smallestUnit, expectedPositive, expectedNegative]) => {
+ const [py, pm = 0, pw = 0, pd = 0, ph = 0, pmin = 0, ps = 0, pms = 0, pµs = 0, pns = 0] = expectedPositive;
+ const [ny, nm = 0, nw = 0, nd = 0, nh = 0, nmin = 0, ns = 0, nms = 0, nµs = 0, nns = 0] = expectedNegative;
+ TemporalHelpers.assertDuration(
+ earlier.until(later, { smallestUnit, roundingMode }),
+ py, pm, pw, pd, ph, pmin, ps, pms, pµs, pns,
+ `rounds to ${smallestUnit} (roundingMode = ${roundingMode}, positive case)`
+ );
+ TemporalHelpers.assertDuration(
+ later.until(earlier, { smallestUnit, roundingMode }),
+ ny, nm, nw, nd, nh, nmin, ns, nms, nµs, nns,
+ `rounds to ${smallestUnit} (rounding mode = ${roundingMode}, negative case)`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-halfFloor.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-halfFloor.js
new file mode 100644
index 0000000000..b4f0c8f767
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-halfFloor.js
@@ -0,0 +1,39 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Tests calculations with roundingMode "halfFloor".
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const earlier = new Temporal.PlainDate(2019, 1, 8);
+const later = new Temporal.PlainDate(2021, 9, 7);
+
+const expected = [
+ ["years", [3], [-3]],
+ ["months", [0, 32], [0, -32]],
+ ["weeks", [0, 0, 139], [0, 0, -139]],
+ ["days", [0, 0, 0, 973], [0, 0, 0, -973]],
+];
+
+const roundingMode = "halfFloor";
+
+expected.forEach(([smallestUnit, expectedPositive, expectedNegative]) => {
+ const [py, pm = 0, pw = 0, pd = 0, ph = 0, pmin = 0, ps = 0, pms = 0, pµs = 0, pns = 0] = expectedPositive;
+ const [ny, nm = 0, nw = 0, nd = 0, nh = 0, nmin = 0, ns = 0, nms = 0, nµs = 0, nns = 0] = expectedNegative;
+ TemporalHelpers.assertDuration(
+ earlier.until(later, { smallestUnit, roundingMode }),
+ py, pm, pw, pd, ph, pmin, ps, pms, pµs, pns,
+ `rounds to ${smallestUnit} (roundingMode = ${roundingMode}, positive case)`
+ );
+ TemporalHelpers.assertDuration(
+ later.until(earlier, { smallestUnit, roundingMode }),
+ ny, nm, nw, nd, nh, nmin, ns, nms, nµs, nns,
+ `rounds to ${smallestUnit} (rounding mode = ${roundingMode}, negative case)`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-halfTrunc.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-halfTrunc.js
new file mode 100644
index 0000000000..abd790fe34
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-halfTrunc.js
@@ -0,0 +1,39 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Tests calculations with roundingMode "halfTrunc".
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const earlier = new Temporal.PlainDate(2019, 1, 8);
+const later = new Temporal.PlainDate(2021, 9, 7);
+
+const expected = [
+ ["years", [3], [-3]],
+ ["months", [0, 32], [0, -32]],
+ ["weeks", [0, 0, 139], [0, 0, -139]],
+ ["days", [0, 0, 0, 973], [0, 0, 0, -973]],
+];
+
+const roundingMode = "halfTrunc";
+
+expected.forEach(([smallestUnit, expectedPositive, expectedNegative]) => {
+ const [py, pm = 0, pw = 0, pd = 0, ph = 0, pmin = 0, ps = 0, pms = 0, pµs = 0, pns = 0] = expectedPositive;
+ const [ny, nm = 0, nw = 0, nd = 0, nh = 0, nmin = 0, ns = 0, nms = 0, nµs = 0, nns = 0] = expectedNegative;
+ TemporalHelpers.assertDuration(
+ earlier.until(later, { smallestUnit, roundingMode }),
+ py, pm, pw, pd, ph, pmin, ps, pms, pµs, pns,
+ `rounds to ${smallestUnit} (roundingMode = ${roundingMode}, positive case)`
+ );
+ TemporalHelpers.assertDuration(
+ later.until(earlier, { smallestUnit, roundingMode }),
+ ny, nm, nw, nd, nh, nmin, ns, nms, nµs, nns,
+ `rounds to ${smallestUnit} (rounding mode = ${roundingMode}, negative case)`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-invalid-string.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-invalid-string.js
new file mode 100644
index 0000000000..7cc40d387e
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-invalid-string.js
@@ -0,0 +1,17 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: RangeError thrown when roundingMode option not one of the allowed string values
+features: [Temporal]
+---*/
+
+const earlier = new Temporal.PlainDate(2000, 5, 2);
+const later = new Temporal.PlainDate(2001, 6, 3);
+for (const roundingMode of ["other string", "cile", "CEIL", "ce\u0131l", "auto", "halfexpand", "floor\0"]) {
+ assert.throws(RangeError, () => earlier.until(later, { smallestUnit: "microsecond", roundingMode }));
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-trunc.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-trunc.js
new file mode 100644
index 0000000000..43cbe4a1f0
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-trunc.js
@@ -0,0 +1,39 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Tests calculations with roundingMode "trunc".
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const earlier = new Temporal.PlainDate(2019, 1, 8);
+const later = new Temporal.PlainDate(2021, 9, 7);
+
+const expected = [
+ ["years", [2], [-2]],
+ ["months", [0, 31], [0, -31]],
+ ["weeks", [0, 0, 139], [0, 0, -139]],
+ ["days", [0, 0, 0, 973], [0, 0, 0, -973]],
+];
+
+const roundingMode = "trunc";
+
+expected.forEach(([smallestUnit, expectedPositive, expectedNegative]) => {
+ const [py, pm = 0, pw = 0, pd = 0, ph = 0, pmin = 0, ps = 0, pms = 0, pµs = 0, pns = 0] = expectedPositive;
+ const [ny, nm = 0, nw = 0, nd = 0, nh = 0, nmin = 0, ns = 0, nms = 0, nµs = 0, nns = 0] = expectedNegative;
+ TemporalHelpers.assertDuration(
+ earlier.until(later, { smallestUnit, roundingMode }),
+ py, pm, pw, pd, ph, pmin, ps, pms, pµs, pns,
+ `rounds to ${smallestUnit} (roundingMode = ${roundingMode}, positive case)`
+ );
+ TemporalHelpers.assertDuration(
+ later.until(earlier, { smallestUnit, roundingMode }),
+ ny, nm, nw, nd, nh, nmin, ns, nms, nµs, nns,
+ `rounds to ${smallestUnit} (rounding mode = ${roundingMode}, negative case)`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-undefined.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-undefined.js
new file mode 100644
index 0000000000..0370a2e95a
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-undefined.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Fallback value for roundingMode option
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const earlier = new Temporal.PlainDate(2000, 1, 1);
+
+const later1 = new Temporal.PlainDate(2005, 2, 20);
+const explicit1 = earlier.until(later1, { smallestUnit: "year", roundingMode: undefined });
+TemporalHelpers.assertDuration(explicit1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, "default roundingMode is trunc");
+const implicit1 = earlier.until(later1, { smallestUnit: "year" });
+TemporalHelpers.assertDuration(implicit1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, "default roundingMode is trunc");
+
+const later2 = new Temporal.PlainDate(2005, 12, 15);
+const explicit2 = earlier.until(later2, { smallestUnit: "year", roundingMode: undefined });
+TemporalHelpers.assertDuration(explicit2, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, "default roundingMode is trunc");
+const implicit2 = earlier.until(later2, { smallestUnit: "year" });
+TemporalHelpers.assertDuration(implicit2, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, "default roundingMode is trunc");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-wrong-type.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-wrong-type.js
new file mode 100644
index 0000000000..0c95403f84
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/roundingmode-wrong-type.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Type conversions for roundingMode option
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const earlier = new Temporal.PlainDate(2000, 5, 2);
+const later = new Temporal.PlainDate(2001, 6, 3);
+TemporalHelpers.checkStringOptionWrongType("roundingMode", "trunc",
+ (roundingMode) => earlier.until(later, { smallestUnit: "year", roundingMode }),
+ (result, descr) => TemporalHelpers.assertDuration(result, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, descr),
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/shell.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/shell.js
new file mode 100644
index 0000000000..eda1477282
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/shell.js
@@ -0,0 +1,24 @@
+// GENERATED, DO NOT EDIT
+// file: isConstructor.js
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: |
+ Test if a given function is a constructor function.
+defines: [isConstructor]
+features: [Reflect.construct]
+---*/
+
+function isConstructor(f) {
+ if (typeof f !== "function") {
+ throw new Test262Error("isConstructor invoked with a non-function value");
+ }
+
+ try {
+ Reflect.construct(function(){}, [], f);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/smallestunit-higher-units.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/smallestunit-higher-units.js
new file mode 100644
index 0000000000..e6cd7b717a
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/smallestunit-higher-units.js
@@ -0,0 +1,27 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Tests calculations with higher smallestUnit than the default of "days"
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const earlier = Temporal.PlainDate.from("2019-01-08");
+const later = Temporal.PlainDate.from("2021-09-07");
+
+TemporalHelpers.assertDuration(
+ earlier.until(later, { smallestUnit: "years", roundingMode: "halfExpand" }),
+ /* years = */ 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, "years");
+
+TemporalHelpers.assertDuration(
+ earlier.until(later, { smallestUnit: "months", roundingMode: "halfExpand" }),
+ 0, /* months = */ 32, 0, 0, 0, 0, 0, 0, 0, 0, "months");
+
+TemporalHelpers.assertDuration(
+ earlier.until(later, { smallestUnit: "weeks", roundingMode: "halfExpand" }),
+ 0, 0, /* weeks = */ 139, 0, 0, 0, 0, 0, 0, 0, "weeks");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/smallestunit-invalid-string.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/smallestunit-invalid-string.js
new file mode 100644
index 0000000000..6979d8f13c
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/smallestunit-invalid-string.js
@@ -0,0 +1,41 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: RangeError thrown when smallestUnit option not one of the allowed string values
+features: [Temporal]
+---*/
+
+const earlier = new Temporal.PlainDate(2000, 5, 2);
+const later = new Temporal.PlainDate(2001, 6, 3);
+const badValues = [
+ "era",
+ "eraYear",
+ "hour",
+ "minute",
+ "second",
+ "millisecond",
+ "microsecond",
+ "nanosecond",
+ "month\0",
+ "YEAR",
+ "eras",
+ "eraYears",
+ "hours",
+ "minutes",
+ "seconds",
+ "milliseconds",
+ "microseconds",
+ "nanoseconds",
+ "months\0",
+ "YEARS",
+ "other string",
+];
+for (const smallestUnit of badValues) {
+ assert.throws(RangeError, () => earlier.until(later, { smallestUnit }),
+ `"${smallestUnit}" is not a valid value for smallest unit`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/smallestunit-plurals-accepted.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/smallestunit-plurals-accepted.js
new file mode 100644
index 0000000000..75f6d73609
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/smallestunit-plurals-accepted.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Plural units are accepted as well for the smallestUnit option
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const earlier = new Temporal.PlainDate(2000, 5, 2);
+const later = new Temporal.PlainDate(2001, 6, 12);
+const validUnits = [
+ "year",
+ "month",
+ "week",
+ "day",
+];
+TemporalHelpers.checkPluralUnitsAccepted((smallestUnit) => earlier.until(later, { smallestUnit }), validUnits);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/smallestunit-undefined.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/smallestunit-undefined.js
new file mode 100644
index 0000000000..359541c4f2
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/smallestunit-undefined.js
@@ -0,0 +1,20 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Fallback value for smallestUnit option
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const earlier = new Temporal.PlainDate(2000, 5, 2);
+const later = new Temporal.PlainDate(2001, 6, 3);
+
+const explicit = earlier.until(later, { smallestUnit: undefined });
+TemporalHelpers.assertDuration(explicit, 0, 0, 0, 397, 0, 0, 0, 0, 0, 0, "default smallestUnit is day");
+const implicit = earlier.until(later, {});
+TemporalHelpers.assertDuration(implicit, 0, 0, 0, 397, 0, 0, 0, 0, 0, 0, "default smallestUnit is day");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/smallestunit-wrong-type.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/smallestunit-wrong-type.js
new file mode 100644
index 0000000000..f257997db1
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/smallestunit-wrong-type.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Type conversions for smallestUnit option
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const earlier = new Temporal.PlainDate(2000, 5, 2);
+const later = new Temporal.PlainDate(2001, 6, 3);
+TemporalHelpers.checkStringOptionWrongType("smallestUnit", "year",
+ (smallestUnit) => earlier.until(later, { smallestUnit }),
+ (result, descr) => TemporalHelpers.assertDuration(result, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, descr),
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/weeks-months.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/weeks-months.js
new file mode 100644
index 0000000000..54dbd17bc1
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/weeks-months.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: until() should not return weeks and months together.
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const date = new Temporal.PlainDate(1969, 7, 24);
+const laterDate = new Temporal.PlainDate(1969, 9, 4);
+TemporalHelpers.assertDuration(date.until(laterDate, { largestUnit: "weeks" }),
+ 0, 0, /* weeks = */ 6, 0, 0, 0, 0, 0, 0, 0, "weeks");
+TemporalHelpers.assertDuration(date.until(laterDate, { largestUnit: "months" }),
+ 0, /* months = */ 1, 0, 11, 0, 0, 0, 0, 0, 0, "months");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/year-zero.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/year-zero.js
new file mode 100644
index 0000000000..f92ca0a5e4
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/year-zero.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Negative zero, as an extended year, is rejected
+features: [Temporal, arrow-function]
+---*/
+
+const invalidStrings = [
+ "-000000-10-31",
+ "-000000-10-31T00:45",
+ "-000000-10-31T00:45+01:00",
+ "-000000-10-31T00:45+00:00[UTC]",
+];
+const instance = new Temporal.PlainDate(2000, 5, 2);
+invalidStrings.forEach((arg) => {
+ assert.throws(
+ RangeError,
+ () => instance.until(arg),
+ "reject minus zero as extended year"
+ );
+});
+
+reportCompare(0, 0);