commit b44d79b5c964dac13fe90c7bbf76b9ae46f07cfa parent 12281201ca30312236dba1da1c0829b3ca894eca Author: Debadree Chatterjee <debadree333@gmail.com> Date: Fri, 14 Nov 2025 18:29:44 +0000 Bug 2000195: Part 1 - Update test262 tests. r=arai Differential Revision: https://phabricator.services.mozilla.com/D272634 Diffstat:
176 files changed, 17313 insertions(+), 85 deletions(-)
diff --git a/js/src/tests/test262/GIT-INFO b/js/src/tests/test262/GIT-INFO @@ -1,7 +1,8 @@ -commit 5c07b5224261aae30e54f69727a730956ff4ec41 +commit 2312907b9b426cb2e9ae50aff25119319ab4e2d4 Author: Philip Chimento <pchimento@igalia.com> -Date: Wed Nov 5 10:55:53 2025 -0800 +Date: Tue Nov 11 10:21:00 2025 -0800 - Temporal: Remove accidental octal literals from a test + Intl Era Monthcode: Fix up locale frontmatter for Hijri fallback - These cause the test to fail in strict mode. + We don't require an en-u-ca-islamic locale to run this test, in fact + it's the intention that it will fall back to another Hijri calendar. diff --git a/js/src/tests/test262/built-ins/Math/sumPrecise/throws-on-non-number.js b/js/src/tests/test262/built-ins/Math/sumPrecise/throws-on-non-number.js @@ -50,14 +50,14 @@ assert.throws(TypeError, function () { assert.sameValue(coercions, 0); var nextCalls = 0; -var closed = false; +var returnCalls = 0; var iterator = { next: function () { ++nextCalls; return { done: false, value: objectWithValueOf }; }, return: function () { - closed = true; + ++returnCalls; return {}; } }; @@ -72,6 +72,6 @@ assert.throws(TypeError, function () { }); assert.sameValue(coercions, 0); assert.sameValue(nextCalls, 1); -assert.sameValue(closed, true); +assert.sameValue(returnCalls, 1); reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-string-limits.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-string-limits.js @@ -25,7 +25,7 @@ const validStringsForOffsetUseIgnore = [ for (const offset of ["use", "ignore"]) { for (const arg of validStringsForOffsetUseIgnore) { - Temporal.ZonedDateTime.from(arg, { offset: "use" }); + Temporal.ZonedDateTime.from(arg, { offset }); } } diff --git a/js/src/tests/test262/built-ins/ThrowTypeError/distinct-cross-realm.js b/js/src/tests/test262/built-ins/ThrowTypeError/distinct-cross-realm.js @@ -24,6 +24,9 @@ var otherThrowTypeError = Object.getOwnPropertyDescriptor(otherArgs, "callee").g var otherThrowTypeError2 = Object.getOwnPropertyDescriptor(otherArgs, "callee").get; assert.throws(TypeError, function() { + localThrowTypeError(); +}); +assert.throws(other.TypeError, function() { otherThrowTypeError(); }); diff --git a/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-calendar-islamic-fallback.js b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-calendar-islamic-fallback.js @@ -15,7 +15,7 @@ info: | b. Set _resolvedCalendar_ to CanonicalizeUValue(*"ca"*, _fallbackCalendar_). c. If the ECMAScript implementation has a mechanism for reporting diagnostic warning messages, a warning should be issued. 10. Set _dateTimeFormat_.[[Calendar]] to _resolvedCalendar_. -locale: [en, en-u-ca-islamic] +locale: [en] features: [Intl.Era-monthcode] ---*/ diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/temporal-objects-resolved-time-zone.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/temporal-objects-resolved-time-zone.js @@ -14,7 +14,7 @@ features: [Temporal, Intl.DateTimeFormat-formatRange] // Temporal and Intl are behaving as expected? const usDayPeriodSpace = new Intl.DateTimeFormat("en-US", { timeStyle: "short" }) - .formatToParts(0) + .formatRangeToParts(0, 86400) .find((part, i, parts) => part.type === "literal" && parts[i + 1].type === "dayPeriod")?.value || ""; const usDateRangeSeparator = new Intl.DateTimeFormat("en-US", { dateStyle: "short" }) .formatRangeToParts(1 * 86400 * 1000, 366 * 86400 * 1000) diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/temporal-objects-resolved-time-zone.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/temporal-objects-resolved-time-zone.js @@ -15,7 +15,7 @@ features: [Temporal, Intl.DateTimeFormat-formatRange] // Temporal and Intl are behaving as expected? const usDayPeriodSpace = new Intl.DateTimeFormat('en-US', { timeStyle: 'short' }) - .formatToParts(0) + .formatRangeToParts(0, 86400) .find((part, i, parts) => part.type === 'literal' && parts[i + 1].type === 'dayPeriod')?.value || ''; const usDateRangeSeparator = new Intl.DateTimeFormat('en-US', { dateStyle: 'short' }) .formatRangeToParts(1 * 86400 * 1000, 366 * 86400 * 1000) diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/era.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/era.js @@ -0,0 +1,107 @@ +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-intl.datetimeformat.prototype.formattoparts +description: > + Verifies that DateTimeFormat formats era information, with distinct values for + calendars with multiple eras +locale: [en] +features: [Intl.Era-monthcode] +---*/ + +const multipleEraTests = [ + ["gregory", [-100, 2025]], + ["japanese", [-100, 1850, 1880, 1920, 1930, 1990, 2025]], + ["roc", [1900, 2025]], +]; + +for (const [calendar, isoYears] of multipleEraTests) { + const formatter = new Intl.DateTimeFormat("en", { + calendar, + era: "long", + year: "numeric", + }); + + const eras = []; + + for (const isoYear of isoYears) { + const date = new Date(isoYear, 5, 15); + const parts = formatter.formatToParts(date); + + const eraPart = parts.find(({ type }) => type === "era"); + assert.notSameValue(eraPart, undefined, `Format of ${calendar} ISO ${isoYear} should have era part`); + assert.sameValue(typeof eraPart.value, "string", `Era format of ${calendar} ISO ${isoYear} should be a string`); + assert(eraPart.value.length > 0, `Era format of ${calendar} ISO ${isoYear} should not be empty`); + eras.push(eraPart.value); + + const format = formatter.format(date); + assert(format.includes(eraPart.value), `${format} should include ${eraPart.value} era`); + + const yearPart = parts.find(({ type }) => type === "year"); + assert.notSameValue(yearPart, undefined, `Format of ${calendar} ISO ${isoYear} should have year part`); + } + + assert.sameValue(new Set(eras).size, eras.length, `${calendar} eras (${eras.join(",")}) should be unique`); +} + +// Calendars with a single era: test one year occurring before and one after the +// single era's epoch +const singleEraTests = [ + ["buddhist", [-600, 2025]], + ["ethioaa", [-5550, 2025]], + ["hebrew", [-3800, 2025]], + ["indian", [70, 2025]], + ["persian", [600, 2025]], +]; + +for (const [calendar, isoYears] of singleEraTests) { + const formatter = new Intl.DateTimeFormat("en", { + calendar, + era: "long", + year: "numeric", + }); + + const eras = []; + + for (const isoYear of isoYears) { + const date = new Date(isoYear, 5, 15); + const parts = formatter.formatToParts(date); + + const eraPart = parts.find(({ type }) => type === "era"); + assert.notSameValue(eraPart, undefined, `Format of ${calendar} ISO ${isoYear} should have era part`); + assert.sameValue(typeof eraPart.value, "string", `Era format of ${calendar} ISO ${isoYear} should be a string`); + assert(eraPart.value.length > 0, `Era format of ${calendar} ISO ${isoYear} should not be empty`); + eras.push(eraPart.value); + + const format = formatter.format(date); + assert(format.includes(eraPart.value), `${format} should include ${eraPart.value} era`); + + const yearPart = parts.find(({ type }) => type === "year"); + assert.notSameValue(yearPart, undefined, `Format of ${calendar} ISO ${isoYear} should have year part`); + } + + assert.sameValue(eras[0], eras[1], `${calendar} era does not change between negative to positive years`); +} + +const noEraTests = [ + "chinese", + "dangi", + "iso8601", +]; + +for (const calendar of noEraTests) { + const formatter = new Intl.DateTimeFormat("en", { + calendar, + era: "long", + year: "numeric", + }); + + const date = new Date(2025, 5, 15); + const parts = formatter.formatToParts(date); + + const eraPart = parts.find(({ type }) => type === "era"); + assert.sameValue(eraPart, undefined, `Format of ${calendar} should not have era part`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/lunisolar-leap-months.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/lunisolar-leap-months.js @@ -0,0 +1,45 @@ +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-intl.datetimeformat.prototype.formattoparts +description: > + Verifies that DateTimeFormat formats dates in lunisolar calendars with leap + leap months (Chinese, Dangi, Hebrew) +locale: [en-u-ca-chinese, en-u-ca-dangi, en-u-ca-hebrew] +features: [Intl.Era-monthcode] +---*/ + +const tests = [ + ["chinese", 2020, 4, 23, "relatedYear"], // May 23, 2020 is in the leap month M04L + ["chinese", 2019, 4, 15, "relatedYear"], // In regular month M04L + ["dangi", 2020, 4, 23, "relatedYear"], // As above + ["dangi", 2019, 4, 15, "relatedYear"], + ["hebrew", 2024, 2, 15, "year"], // In M05L (Adar I) of leap year 5784 + ["hebrew", 2023, 2, 15, "year"], // In M06 (Adar) of common year 5783 +]; + +for (const [calendar, isoYear, zeroMonth, day, yearPartName] of tests) { + const formatter = new Intl.DateTimeFormat(`en-u-ca-${calendar}`, { + year: "numeric", + month: "long", + day: "numeric" + }); + + const date = new Date(isoYear, zeroMonth, day); + const parts = formatter.formatToParts(date); + + const monthPart = parts.find(({ type }) => type === "month"); + assert.notSameValue(monthPart, undefined, `${calendar} calendar date should have month part`); + assert.sameValue(typeof monthPart.value, "string", `${calendar} month part value should be a string`); + + const yearPart = parts.find(({ type }) => type === yearPartName); + assert.notSameValue(yearPart, undefined, `${calendar} calendar date should have year part`); + + const formatted = formatter.format(date); + const reconstructed = parts.map((part) => part.value).join(""); + assert.sameValue(formatted, reconstructed, + `format() and formatToParts() should produce consistent results for ${calendar} calendar`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/calendar.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/calendar.js @@ -0,0 +1,47 @@ +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-intl.datetimeformat.prototype.resolvedoptions +description: Verifies that the calendar option is respected. +locale: [en] +features: [Intl.Era-monthcode] +---*/ + +const tests = [ + "buddhist", + "chinese", + "coptic", + "dangi", + "ethioaa", + "ethiopic", + "gregory", + "hebrew", + "indian", + "islamic-civil", + "islamic-tbla", + "islamic-umalqura", + "iso8601", + "japanese", + "persian", + "roc", +]; + +for (const calendar of tests) { + const formatter = new Intl.DateTimeFormat("en", { calendar }); + const options = formatter.resolvedOptions(); + assert.sameValue(options.calendar, calendar, "Resolved calendar"); +} + +const aliases = [ + ["ethiopic-amete-alem", "ethioaa"], + ["islamicc", "islamic-civil"], +]; + +for (const [alias, calendar] of aliases) { + const formatter = new Intl.DateTimeFormat("en", { calendar: alias }); + const options = formatter.resolvedOptions(); + assert.sameValue(options.calendar, calendar, "Resolved alias " + alias); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/output-object-keys.js b/js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/output-object-keys.js @@ -13,7 +13,6 @@ info: | ... 6. Perform ! CreateDataPropertyOrThrow(info, "firstDay", wi.[[FirstDay]]). 7. Perform ! CreateDataPropertyOrThrow(info, "weekend", we). - 8. Perform ! CreateDataPropertyOrThrow(info, "minimalDays", wi.[[MinimalDays]]). ... CreateDataProperty ( O, P, V ) ... @@ -28,7 +27,7 @@ function isIntegerBetweenOneAndSeven(value) { return value === 1 || value === 2 || value === 3 || value === 4 || value === 5 || value === 6 || value === 7; } -assert.compareArray(Reflect.ownKeys(result), ['firstDay', 'weekend', 'minimalDays']); +assert.compareArray(Reflect.ownKeys(result), ['firstDay', 'weekend']); verifyProperty(result, 'firstDay', { writable: true, @@ -54,14 +53,4 @@ let original = new Intl.Locale('en').getWeekInfo().weekend; let sorted = original.slice().sort(); assert.compareArray(original, sorted); -verifyProperty(result, 'minimalDays', { - writable: true, - enumerable: true, - configurable: true -}); -assert( - isIntegerBetweenOneAndSeven(new Intl.Locale('en').getWeekInfo().minimalDays), - '`minimalDays` must be an integer between one and seven (inclusive)' -); - reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/from/calendar-not-supporting-eras.js b/js/src/tests/test262/intl402/Temporal/PlainDate/from/calendar-not-supporting-eras.js @@ -6,7 +6,7 @@ esid: sec-temporal.plaindate.from description: era and eraYear are ignored (for calendars not using eras) includes: [temporalHelpers.js] -features: [Temporal] +features: [Temporal, Intl.Era-monthcode] ---*/ const result = Temporal.PlainDate.from({ diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/from/canonicalize-calendar.js b/js/src/tests/test262/intl402/Temporal/PlainDate/from/canonicalize-calendar.js @@ -5,7 +5,7 @@ /*--- esid: sec-temporal.plaindate.from description: Calendar ID is canonicalized -features: [Temporal] +features: [Temporal, Intl.Era-monthcode] ---*/ [ diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/from/canonicalize-era-codes.js b/js/src/tests/test262/intl402/Temporal/PlainDate/from/canonicalize-era-codes.js @@ -6,7 +6,7 @@ esid: sec-temporal.plaindate.from description: Calendar era code is canonicalized includes: [temporalHelpers.js] -features: [Temporal] +features: [Temporal, Intl.Era-monthcode] ---*/ const date1 = Temporal.PlainDate.from({ diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/from/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/PlainDate/from/infinity-throws-rangeerror.js @@ -6,7 +6,7 @@ description: Throws if eraYear in the property bag is Infinity or -Infinity esid: sec-temporal.plaindate.from includes: [compareArray.js, temporalHelpers.js] -features: [Temporal] +features: [Temporal, Intl.Era-monthcode] ---*/ const base = { era: "ad", month: 5, day: 2, calendar: "gregory" }; diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/from/one-of-era-erayear-undefined.js b/js/src/tests/test262/intl402/Temporal/PlainDate/from/one-of-era-erayear-undefined.js @@ -7,7 +7,7 @@ esid: sec-temporal.plaindate.from description: > Throw a TypeError if only one of era/eraYear fields is present (for calendar using eras) -features: [Temporal] +features: [Temporal, Intl.Era-monthcode] ---*/ const base = { year: 2000, month: 5, day: 2, era: "ce", calendar: "gregory" }; diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/from/remapping-era.js b/js/src/tests/test262/intl402/Temporal/PlainDate/from/remapping-era.js @@ -19,7 +19,7 @@ info: | calendars with regnal eras. includes: [temporalHelpers.js] -features: [Temporal] +features: [Temporal, Intl.Era-monthcode] ---*/ // Based on a test originally by André Bargull <andre.bargull@gmail.com> diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/basic-chinese.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/basic-chinese.js @@ -0,0 +1,64 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.add +description: Basic addition and subtraction in the chinese calendar +features: [Temporal, Intl.Era-monthcode] +includes: [temporalHelpers.js] +---*/ + +const calendar = "chinese"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const duration1 = new Temporal.Duration(0, 1); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2019, monthCode: "M11", day: 1, calendar }, options).add(duration1), + 2019, 12, "M12", 1, "add 1 month, with result in same year" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2019, monthCode: "M12", day: 1, calendar }, options).add(duration1), + 2020, 1, "M01", 1, "add 1 month, with result in next year" +); + +// Weeks + +const months2weeks3 = new Temporal.Duration(0, /* months = */ 2, /* weeks = */ 3); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2021, monthCode: "M01", day: 1, calendar }, options).add(months2weeks3), + 2021, 3, "M03", 22, "add 2 months 3 weeks from non-leap day/month, ending in same year" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2021, monthCode: "M12", day: 29, calendar }, options).add(months2weeks3), + 2022, 3, "M03", 21, "add 2 months 3 weeks from end of year to next year" +); + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ 10); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2021, monthCode: "M01", day: 1, calendar }, options).add(days10), + 2021, 1, "M01", 11, "add 10 days, ending in same month" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2021, monthCode: "M01", day: 29, calendar }, options).add(days10), + 2021, 2, "M02", 10, "add 10 days, ending in following month" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2021, monthCode: "M12", day: 29, calendar }, options).add(days10), + 2022, 1, "M01", 10, "add 10 days, ending in following year" +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/basic-dangi.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/basic-dangi.js @@ -0,0 +1,64 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.add +description: Basic addition and subtraction in the dangi calendar +features: [Temporal, Intl.Era-monthcode] +includes: [temporalHelpers.js] +---*/ + +const calendar = "dangi"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const duration1 = new Temporal.Duration(0, 1); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2019, monthCode: "M11", day: 1, calendar }, options).add(duration1), + 2019, 12, "M12", 1, "add 1 month, with result in same year" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2019, monthCode: "M12", day: 1, calendar }, options).add(duration1), + 2020, 1, "M01", 1, "add 1 month, with result in next year" +); + +// Weeks + +const months2weeks3 = new Temporal.Duration(0, /* months = */ 2, /* weeks = */ 3); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2021, monthCode: "M01", day: 1, calendar }, options).add(months2weeks3), + 2021, 3, "M03", 22, "add 2 months 3 weeks from non-leap day/month, ending in same year" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2021, monthCode: "M12", day: 29, calendar }, options).add(months2weeks3), + 2022, 3, "M03", 21, "add 2 months 3 weeks from end of year to next year" +); + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ 10); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2021, monthCode: "M01", day: 1, calendar }, options).add(days10), + 2021, 1, "M01", 11, "add 10 days, ending in same month" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2021, monthCode: "M01", day: 29, calendar }, options).add(days10), + 2021, 2, "M02", 10, "add 10 days, ending in following month" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2021, monthCode: "M12", day: 29, calendar }, options).add(days10), + 2022, 1, "M01", 10, "add 10 days, ending in following year" +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/basic-hebrew.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/basic-hebrew.js @@ -0,0 +1,30 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.add +description: Basic addition and subtraction in the hebrew calendar +features: [Temporal] +includes: [temporalHelpers.js] +---*/ + +const calendar = "hebrew"; +const options = { overflow: "reject" }; + +// Years + +// Months + +// Weeks + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ 10); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 5785, monthCode: "M01", day: 1, calendar }, options).add(days10), + 5785, 1, "M01", 11, "adding 10 days", "am", 5785 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/basic-islamic-civil.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/basic-islamic-civil.js @@ -0,0 +1,84 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.add +description: Basic addition and subtraction in the islamic-civil calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-civil"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const date1 = Temporal.PlainDate.from({ year: 1445, monthCode: "M01", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date1.add(new Temporal.Duration(0, 8)), + 1445, 9, "M09", 1, "Adding 8 months to Muharram 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + date1.add(new Temporal.Duration(0, 11)), + 1445, 12, "M12", 1, "Adding 11 months to Muharram 1445 lands in Dhu al-Hijjah", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + date1.add(new Temporal.Duration(0, 12)), + 1446, 1, "M01", 1, "Adding 12 months to Muharram 1445 lands in Muharram 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M06", day: 15, calendar }).add(new Temporal.Duration(0, 13)), + 1446, 7, "M07", 15, "Adding 13 months to Jumada II 1445 lands in Rajab 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M03", day: 15, calendar }, options).add(new Temporal.Duration(0, 6)), + 1445, 9, "M09", 15, "Adding 6 months to Rabi I 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1444, monthCode: "M10", day: 1, calendar }).add(new Temporal.Duration(0, 5)), + 1445, 3, "M03", 1, "Adding 5 months to Shawwal 1444 crosses to 1445", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1400, monthCode: "M01", day: 1, calendar }).add(new Temporal.Duration(0, 100)), + 1408, 5, "M05", 1, "Adding a large number of months", + "ah", 1408 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M09", day: 1, calendar }, options).add(new Temporal.Duration(0, -8)), + 1445, 1, "M01", 1, "Subtracting 8 months from Ramadan 1445 lands in Muharram", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M06", day: 1, calendar }, options).add(new Temporal.Duration(0, -12)), + 1444, 6, "M06", 1, "Subtracting 12 months from Jumada II 1445 lands in Jumada II 1444", + "ah", 1444 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M02", day: 15, calendar }, options).add(new Temporal.Duration(0, -5)), + 1444, 9, "M09", 15, "Subtracting 5 months from Safar 1445 crosses to Ramadan 1444", + "ah", 1444 +); + +// Weeks + +// Days + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/basic-islamic-tbla.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/basic-islamic-tbla.js @@ -0,0 +1,84 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.add +description: Basic addition and subtraction in the islamic-tbla calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-tbla"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const date1 = Temporal.PlainDate.from({ year: 1445, monthCode: "M01", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date1.add(new Temporal.Duration(0, 8)), + 1445, 9, "M09", 1, "Adding 8 months to Muharram 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + date1.add(new Temporal.Duration(0, 11)), + 1445, 12, "M12", 1, "Adding 11 months to Muharram 1445 lands in Dhu al-Hijjah", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + date1.add(new Temporal.Duration(0, 12)), + 1446, 1, "M01", 1, "Adding 12 months to Muharram 1445 lands in Muharram 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M06", day: 15, calendar }).add(new Temporal.Duration(0, 13)), + 1446, 7, "M07", 15, "Adding 13 months to Jumada II 1445 lands in Rajab 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M03", day: 15, calendar }, options).add(new Temporal.Duration(0, 6)), + 1445, 9, "M09", 15, "Adding 6 months to Rabi I 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1444, monthCode: "M10", day: 1, calendar }).add(new Temporal.Duration(0, 5)), + 1445, 3, "M03", 1, "Adding 5 months to Shawwal 1444 crosses to 1445", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1400, monthCode: "M01", day: 1, calendar }).add(new Temporal.Duration(0, 100)), + 1408, 5, "M05", 1, "Adding a large number of months", + "ah", 1408 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M09", day: 1, calendar }, options).add(new Temporal.Duration(0, -8)), + 1445, 1, "M01", 1, "Subtracting 8 months from Ramadan 1445 lands in Muharram", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M06", day: 1, calendar }, options).add(new Temporal.Duration(0, -12)), + 1444, 6, "M06", 1, "Subtracting 12 months from Jumada II 1445 lands in Jumada II 1444", + "ah", 1444 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M02", day: 15, calendar }, options).add(new Temporal.Duration(0, -5)), + 1444, 9, "M09", 15, "Subtracting 5 months from Safar 1445 crosses to Ramadan 1444", + "ah", 1444 +); + +// Weeks + +// Days + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/basic-islamic-umalqura.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/basic-islamic-umalqura.js @@ -0,0 +1,84 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.add +description: Basic addition and subtraction in the islamic-umalqura calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-umalqura"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const date1 = Temporal.PlainDate.from({ year: 1445, monthCode: "M01", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date1.add(new Temporal.Duration(0, 8)), + 1445, 9, "M09", 1, "Adding 8 months to Muharram 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + date1.add(new Temporal.Duration(0, 11)), + 1445, 12, "M12", 1, "Adding 11 months to Muharram 1445 lands in Dhu al-Hijjah", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + date1.add(new Temporal.Duration(0, 12)), + 1446, 1, "M01", 1, "Adding 12 months to Muharram 1445 lands in Muharram 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M06", day: 15, calendar }).add(new Temporal.Duration(0, 13)), + 1446, 7, "M07", 15, "Adding 13 months to Jumada II 1445 lands in Rajab 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M03", day: 15, calendar }, options).add(new Temporal.Duration(0, 6)), + 1445, 9, "M09", 15, "Adding 6 months to Rabi I 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1444, monthCode: "M10", day: 1, calendar }).add(new Temporal.Duration(0, 5)), + 1445, 3, "M03", 1, "Adding 5 months to Shawwal 1444 crosses to 1445", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1400, monthCode: "M01", day: 1, calendar }).add(new Temporal.Duration(0, 100)), + 1408, 5, "M05", 1, "Adding a large number of months", + "ah", 1408 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M09", day: 1, calendar }, options).add(new Temporal.Duration(0, -8)), + 1445, 1, "M01", 1, "Subtracting 8 months from Ramadan 1445 lands in Muharram", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M06", day: 1, calendar }, options).add(new Temporal.Duration(0, -12)), + 1444, 6, "M06", 1, "Subtracting 12 months from Jumada II 1445 lands in Jumada II 1444", + "ah", 1444 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M02", day: 15, calendar }, options).add(new Temporal.Duration(0, -5)), + 1444, 9, "M09", 15, "Subtracting 5 months from Safar 1445 crosses to Ramadan 1444", + "ah", 1444 +); + +// Weeks + +// Days + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/browser.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/browser.js diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/constrain-day-islamic-civil.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/constrain-day-islamic-civil.js @@ -0,0 +1,43 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.add +description: Constraining the day for 29/30-day months in islamic-civil calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-civil"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const months1 = new Temporal.Duration(0, 1); +const months1n = new Temporal.Duration(0, -1); + +const date1 = Temporal.PlainDate.from({ year: 1445, monthCode: "M01", day: 30, calendar }, options); +TemporalHelpers.assertPlainDate( + date1.add(months1), + 1445, 2, "M02", 29, "Day is constrained when adding months to a 30-day month and landing in a 29-day month", + "ah", 1445 +); + +assert.throws(RangeError, function () { + date1.add(months1, options); +}, "Adding months to a 30-day month and landing in a 29-day month rejects"); + +TemporalHelpers.assertPlainDate( + date1.add(months1n), + 1444, 12, "M12", 29, "Day is constrained when subtracting months from a 30-day month and landing in a 29-day month", + "ah", 1444 +); + +assert.throws(RangeError, function () { + date1.add(months1n, options); +}, "Subtracting months from a 30-day month and landing in a 29-day month rejects"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/constrain-day-islamic-tbla.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/constrain-day-islamic-tbla.js @@ -0,0 +1,43 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.add +description: Constraining the day for 29/30-day months in islamic-tbla calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-tbla"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const months1 = new Temporal.Duration(0, 1); +const months1n = new Temporal.Duration(0, -1); + +const date1 = Temporal.PlainDate.from({ year: 1445, monthCode: "M01", day: 30, calendar }, options); +TemporalHelpers.assertPlainDate( + date1.add(months1), + 1445, 2, "M02", 29, "Day is constrained when adding months to a 30-day month and landing in a 29-day month", + "ah", 1445 +); + +assert.throws(RangeError, function () { + date1.add(months1, options); +}, "Adding months to a 30-day month and landing in a 29-day month rejects"); + +TemporalHelpers.assertPlainDate( + date1.add(months1n), + 1444, 12, "M12", 29, "Day is constrained when subtracting months from a 30-day month and landing in a 29-day month", + "ah", 1444 +); + +assert.throws(RangeError, function () { + date1.add(months1n, options); +}, "Subtracting months from a 30-day month and landing in a 29-day month rejects"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/constrain-day-islamic-umalqura.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/constrain-day-islamic-umalqura.js @@ -0,0 +1,43 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.add +description: Constraining the day for 29/30-day months in islamic-umalqura calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-umalqura"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const months1 = new Temporal.Duration(0, /* months = */ 1); +const months1n = new Temporal.Duration(0, -1); + +const date1 = Temporal.PlainDate.from({ year: 1447, monthCode: "M01", day: 30, calendar }, options); +TemporalHelpers.assertPlainDate( + date1.add(months1), + 1447, 2, "M02", 29, "Day is constrained when adding months to a 30-day month and landing in a 29-day month", + "ah", 1447 +); + +assert.throws(RangeError, function () { + date1.add(months1, options); +}, "Adding months to a 30-day month and landing in a 29-day month rejects"); + +TemporalHelpers.assertPlainDate( + date1.add(months1n), + 1446, 12, "M12", 29, "Day is constrained when subtracting months from a 30-day month and landing in a 29-day month", + "ah", 1446 +); + +assert.throws(RangeError, function () { + date1.add(months1n, options); +}, "Subtracting months from a 30-day month and landing in a 29-day month rejects"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/era-boundary-ethiopic.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/era-boundary-ethiopic.js @@ -0,0 +1,51 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.add +description: Adding years works correctly across era boundaries in ethiopic calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const duration5 = new Temporal.Duration(5); +const duration5n = new Temporal.Duration(-5); +const calendar = "ethiopic"; +const options = { overflow: "reject" }; + +const date1 = Temporal.PlainDate.from({ era: "aa", eraYear: 5500, monthCode: "M01", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date1.add(new Temporal.Duration(1)), + 1, 1, "M01", 1, "Adding 1 year to last year of Amete Alem era lands in year 1 of incarnation era", + "am", 1 +); + +const date2 = Temporal.PlainDate.from({ era: "am", eraYear: 2000, monthCode: "M06", day: 15, calendar }, options); +TemporalHelpers.assertPlainDate( + date2.add(duration5), + 2005, 6, "M06", 15, "Adding 5 years within incarnation era", + "am", 2005 +); + +const date3 = Temporal.PlainDate.from({ era: "aa", eraYear: 5450, monthCode: "M07", day: 12, calendar }, options); +TemporalHelpers.assertPlainDate( + date3.add(duration5), + -45, 7, "M07", 12, "Adding 5 years within Amete Alem era", + "aa", 5455 +); + +TemporalHelpers.assertPlainDate( + date2.add(duration5n), + 1995, 6, "M06", 15, "Subtracting 5 years within incarnation era", + "am", 1995 +); + +const date4 = Temporal.PlainDate.from({ era: "am", eraYear: 5, monthCode: "M01", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date4.add(duration5n), + 0, 1, "M01", 1, "Subtracting 5 years from year 5 lands in last year of Amete Alem era, arithmetic year 0", + "aa", 5500 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/era-boundary-gregory.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/era-boundary-gregory.js @@ -0,0 +1,71 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.add +description: Adding years works correctly across era boundaries in gregory calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const duration1 = new Temporal.Duration(1); +const duration1n = new Temporal.Duration(-1); +const calendar = "gregory"; +const options = { overflow: "reject" }; + +const date1 = Temporal.PlainDate.from({ era: "bce", eraYear: 2, monthCode: "M06", day: 15, calendar }, options); +TemporalHelpers.assertPlainDate( + date1.add(duration1), + 0, 6, "M06", 15, "Adding 1 year to 2 BCE lands in 1 BCE (counts backwards)", + "bce", 1 +); + +const date2 = Temporal.PlainDate.from({ era: "bce", eraYear: 1, monthCode: "M06", day: 15, calendar }, options); +TemporalHelpers.assertPlainDate( + date2.add(duration1), + 1, 6, "M06", 15, "Adding 1 year to 1 BCE lands in 1 CE (no year zero)", + "ce", 1 +); + +const date3 = Temporal.PlainDate.from({ era: "ce", eraYear: 1, monthCode: "M06", day: 15, calendar }, options); +TemporalHelpers.assertPlainDate( + date3.add(duration1), + 2, 6, "M06", 15, "Adding 1 year to 1 CE lands in 2 CE", + "ce", 2 +); + +const date4 = Temporal.PlainDate.from({ era: "bce", eraYear: 5, monthCode: "M03", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date4.add(new Temporal.Duration(10)), + 6, 3, "M03", 1, "Adding 10 years to 5 BCE lands in 6 CE (no year zero)", + "ce", 6 +); + +const date5 = Temporal.PlainDate.from({ era: "ce", eraYear: 2, monthCode: "M06", day: 15, calendar }, options); +TemporalHelpers.assertPlainDate( + date5.add(duration1n), + 1, 6, "M06", 15, "Subtracting 1 year from 2 CE lands in 1 CE", + "ce", 1 +); + +TemporalHelpers.assertPlainDate( + date3.add(duration1n), + 0, 6, "M06", 15, "Subtracting 1 year from 1 CE lands in 1 BCE", + "bce", 1 +); + +TemporalHelpers.assertPlainDate( + date2.add(duration1n), + -1, 6, "M06", 15, "Subtracting 1 year from 1 BCE lands in 2 BCE", + "bce", 2 +); + +const date6 = Temporal.PlainDate.from({ era: "ce", eraYear: 5, monthCode: "M03", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date6.add(new Temporal.Duration(-10)), + -5, 3, "M03", 1, "Subtracting 10 years from 5 CE lands in 6 BCE", + "bce", 6 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/era-boundary-japanese.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/era-boundary-japanese.js @@ -0,0 +1,84 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.add +description: > + Adding years works correctly across era boundaries in calendars with eras +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +// Reiwa era started on May 1, 2019 (Reiwa 1 = 2019) +// Heisei era: 1989-2019 (Heisei 31 ended April 30, 2019) + +const duration1 = new Temporal.Duration(1); +const duration1n = new Temporal.Duration(-1); +const calendar = "japanese"; +const options = { overflow: "reject" }; + +const date1 = Temporal.PlainDate.from({ era: "heisei", eraYear: 30, monthCode: "M03", day: 15, calendar }, options); +TemporalHelpers.assertPlainDate( + date1.add(duration1), + 2019, 3, "M03", 15, "Adding 1 year to Heisei 30 March (before May 1) lands in Heisei 31 March", + "heisei", 31 +); + +const date2 = Temporal.PlainDate.from({ era: "heisei", eraYear: 31, monthCode: "M04", day: 15, calendar }, options); +TemporalHelpers.assertPlainDate( + date2.add(duration1), + 2020, 4, "M04", 15, "Adding 1 year to Heisei 31 April (before May 1) lands in Reiwa 2 April", + "reiwa", 2 +); + +const date3 = Temporal.PlainDate.from({ era: "heisei", eraYear: 30, monthCode: "M06", day: 12, calendar }, options); +TemporalHelpers.assertPlainDate( + date3.add(duration1), + 2019, 6, "M06", 12, "Adding 1 year to Heisei 30 June (after May 1) lands in Reiwa 1 June", + "reiwa", 1 +); + +const date4 = Temporal.PlainDate.from({ era: "reiwa", eraYear: 1, monthCode: "M06", day: 10, calendar }, options); +TemporalHelpers.assertPlainDate( + date4.add(duration1), + 2020, 6, "M06", 10, "Adding 1 year to Reiwa 1 June lands in Reiwa 2 June", + "reiwa", 2 +); + +const date5 = Temporal.PlainDate.from({ era: "heisei", eraYear: 28, monthCode: "M07", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date5.add(new Temporal.Duration(3)), + 2019, 7, "M07", 1, "Multiple years across era boundary: Adding 3 years to Heisei 28 July lands in Reiwa 1 July", + "reiwa", 1 +); + +const date6 = Temporal.PlainDate.from({ era: "reiwa", eraYear: 2, monthCode: "M06", day: 15, calendar }, options); +TemporalHelpers.assertPlainDate( + date6.add(duration1n), + 2019, 6, "M06", 15, "Subtracting 1 year from Reiwa 2 June lands in Reiwa 1 June", + "reiwa", 1 +); + +const date7 = Temporal.PlainDate.from({ era: "reiwa", eraYear: 2, monthCode: "M03", day: 15, calendar }, options); +TemporalHelpers.assertPlainDate( + date7.add(duration1n), + 2019, 3, "M03", 15, "Subtracting 1 year from Reiwa 2 March lands in Heisei 31 March", + "heisei", 31 +); + +const date8 = Temporal.PlainDate.from({ era: "reiwa", eraYear: 1, monthCode: "M07", day: 10, calendar }, options); +TemporalHelpers.assertPlainDate( + date8.add(duration1n), + 2018, 7, "M07", 10, "Subtracting 1 year from Reiwa 1 July lands in Heisei 30 July", + "heisei", 30 +); + +const date9 = Temporal.PlainDate.from({ era: "reiwa", eraYear: 4, monthCode: "M02", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date9.add(new Temporal.Duration(-5)), + 2017, 2, "M02", 1, "Subtracting 5 years from Reiwa 4 February lands in Heisei 29 February", + "heisei", 29 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/era-boundary-roc.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/era-boundary-roc.js @@ -0,0 +1,66 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.add +description: > + Adding years works correctly across era boundaries in calendars with eras +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const duration1 = new Temporal.Duration(1); +const duration1n = new Temporal.Duration(-1); +const calendar = "roc"; +const options = { overflow: "reject" }; + +const date1 = Temporal.PlainDate.from({ era: "broc", eraYear: 2, monthCode: "M06", day: 15, calendar }, options); +TemporalHelpers.assertPlainDate( + date1.add(duration1), + 0, 6, "M06", 15, "Adding 1 year to 2 BROC lands in 1 BROC (counts backwards)", + "broc", 1 +); + +const date2 = Temporal.PlainDate.from({ era: "broc", eraYear: 1, monthCode: "M06", day: 15, calendar }, options); +TemporalHelpers.assertPlainDate( + date2.add(duration1), + 1, 6, "M06", 15, "Adding 1 year to 1 BROC lands in 1 ROC (no year zero)", + "roc", 1 +); + +const date3 = Temporal.PlainDate.from({ era: "roc", eraYear: 1, monthCode: "M06", day: 15, calendar }, options); +TemporalHelpers.assertPlainDate( + date3.add(duration1), + 2, 6, "M06", 15, "Adding 1 year to 1 ROC lands in 2 ROC", + "roc", 2 +); + +const date4 = Temporal.PlainDate.from({ era: "broc", eraYear: 5, monthCode: "M03", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date4.add(new Temporal.Duration(10)), + 6, 3, "M03", 1, "Adding 10 years to 5 BROC lands in 6 ROC (no year zero)", + "roc", 6 +); + +const date5 = Temporal.PlainDate.from({ era: "roc", eraYear: 5, monthCode: "M06", day: 15, calendar }, options); +TemporalHelpers.assertPlainDate( + date5.add(duration1n), + 4, 6, "M06", 15, "Subtracting 1 year from ROC 5 lands in ROC 4", + "roc", 4 +); + +TemporalHelpers.assertPlainDate( + date3.add(duration1n), + 0, 6, "M06", 15, "Subtracting 1 year from ROC 1 lands in BROC 1", + "broc", 1 +); + +const date6 = Temporal.PlainDate.from({ era: "roc", eraYear: 10, monthCode: "M03", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date6.add(new Temporal.Duration(-15)), + -5, 3, "M03", 1, "Subtracting 15 years from ROC 10 lands in BROC 6", + "broc", 6 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/leap-months-chinese.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/leap-months-chinese.js @@ -0,0 +1,129 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.add +description: Arithmetic around leap months in the chinese calendar +features: [Temporal, Intl.Era-monthcode] +includes: [temporalHelpers.js] +---*/ + +const calendar = "chinese"; +const options = { overflow: "reject" }; + +// Years + +const years1 = new Temporal.Duration(1); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2019, monthCode: "M01", day: 1, calendar }, options).add(years1), + 2020, 1, "M01", 1, "add 1 year from non-leap day" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1966, monthCode: "M03L", day: 1, calendar }, options).add(years1), + 1967, 3, "M03", 1, "add 1 year from leap month" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1938, monthCode: "M07L", day: 30, calendar }, options).add(years1), + 1939, 7, "M07", 29, "add 1 year from leap day in leap month" +); + +// Months + +const months1 = new Temporal.Duration(0, 1); +const months1n = new Temporal.Duration(0, -1); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1947, monthCode: "M02L", day: 1, calendar }, options).add(months1), + 1947, 4, "M03", 1, "add 1 month, starting at start of leap month" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1955, monthCode: "M03L", day: 1, calendar }, options).add(months1), + 1955, 5, "M04", 1, "add 1 month, starting at start of leap month with 30 days" +); + +const date1 = Temporal.PlainDate.from({ year: 2020, monthCode: "M03", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date1.add(months1), + 2020, 4, "M04", 1, "adding 1 month to M03 in leap year lands in M04 (not M04L)" +); + +TemporalHelpers.assertPlainDate( + date1.add(new Temporal.Duration(0, 2)), + 2020, 5, "M04L", 1, "adding 2 months to M03 in leap year lands in M04L (leap month)" +); + +TemporalHelpers.assertPlainDate( + date1.add(new Temporal.Duration(0, 3)), + 2020, 6, "M05", 1, "adding 3 months to M03 in leap year lands in M05 (not M06)" +); + +const date2 = Temporal.PlainDate.from({ year: 2020, monthCode: "M06", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date2.add(months1n), + 2020, 6, "M05", 1, "Subtracting 1 month from M06 in leap year lands in M05" +); + +TemporalHelpers.assertPlainDate( + date2.add(new Temporal.Duration(0, -2)), + 2020, 5, "M04L", 1, "Subtracting 2 months from M06 in leap year lands in M04L (leap month)" +); + +TemporalHelpers.assertPlainDate( + date2.add(new Temporal.Duration(0, -3)), + 2020, 4, "M04", 1, "Subtracting 3 months from M06 in leap year lands in M04 (not M03)" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2020, monthCode: "M05", day: 1, calendar }, options).add(months1n), + 2020, 5, "M04L", 1, "Subtracting 1 month from M05 in leap year lands in M04L" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2020, monthCode: "M04L", day: 1, calendar }, options).add(months1n), + 2020, 4, "M04", 1, "Subtracting 1 month from M04L in calendar lands in M04" +); + +// Weeks + +const months2weeks3 = new Temporal.Duration(0, /* months = */ 2, /* weeks = */ 3); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1947, monthCode: "M02L", day: 29, calendar }, options).add(months2weeks3), + 1947, 6, "M05", 20, "add 2 months 3 weeks from last day leap month without leap day" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1955, monthCode: "M03L", day: 30, calendar }, options).add(months2weeks3), + 1955, 7, "M06", 21, "add 2 months 3 weeks from leap day in leap month" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1947, monthCode: "M01", day: 29, calendar }, options).add(months2weeks3), + 1947, 4, "M03", 21, "add 2 months 3 weeks from immediately before a leap month" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1955, monthCode: "M06", day: 29, calendar }, options).add(months2weeks3), + 1955, 10, "M09", 20, "add 2 months 3 weeks from immediately before a leap month" +); + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ 10); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1955, monthCode: "M03L", day: 30, calendar }, options).add(days10), + 1955, 5, "M04", 10, "add 10 days from leap day in leap month" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1947, monthCode: "M02L", day: 29, calendar }, options).add(days10), + 1947, 4, "M03", 10, "add 10 days from last day of leap month" +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/leap-months-dangi.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/leap-months-dangi.js @@ -0,0 +1,129 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.add +description: Arithmetic around leap months in the dangi calendar +features: [Temporal, Intl.Era-monthcode] +includes: [temporalHelpers.js] +---*/ + +const calendar = "dangi"; +const options = { overflow: "reject" }; + +// Years + +const years1 = new Temporal.Duration(1); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2019, monthCode: "M01", day: 1, calendar }, options).add(years1), + 2020, 1, "M01", 1, "add 1 year from non-leap day" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1966, monthCode: "M03L", day: 1, calendar }, options).add(years1), + 1967, 3, "M03", 1, "add 1 year from leap month" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1938, monthCode: "M07L", day: 30, calendar }, options).add(years1), + 1939, 7, "M07", 29, "add 1 year from leap day in leap month" +); + +// Months + +const months1 = new Temporal.Duration(0, 1); +const months1n = new Temporal.Duration(0, -1); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1947, monthCode: "M02L", day: 1, calendar }, options).add(months1), + 1947, 4, "M03", 1, "add 1 month, starting at start of leap month" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1955, monthCode: "M03L", day: 1, calendar }, options).add(months1), + 1955, 5, "M04", 1, "add 1 month, starting at start of leap month with 30 days" +); + +const date1 = Temporal.PlainDate.from({ year: 2020, monthCode: "M03", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date1.add(months1), + 2020, 4, "M04", 1, "adding 1 month to M03 in leap year lands in M04 (not M04L)" +); + +TemporalHelpers.assertPlainDate( + date1.add(new Temporal.Duration(0, 2)), + 2020, 5, "M04L", 1, "adding 2 months to M03 in leap year lands in M04L (leap month)" +); + +TemporalHelpers.assertPlainDate( + date1.add(new Temporal.Duration(0, 3)), + 2020, 6, "M05", 1, "adding 3 months to M03 in leap year lands in M05 (not M06)" +); + +const date2 = Temporal.PlainDate.from({ year: 2020, monthCode: "M06", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date2.add(months1n), + 2020, 6, "M05", 1, "Subtracting 1 month from M06 in leap year lands in M05" +); + +TemporalHelpers.assertPlainDate( + date2.add(new Temporal.Duration(0, -2)), + 2020, 5, "M04L", 1, "Subtracting 2 months from M06 in leap year lands in M04L (leap month)" +); + +TemporalHelpers.assertPlainDate( + date2.add(new Temporal.Duration(0, -3)), + 2020, 4, "M04", 1, "Subtracting 3 months from M06 in leap year lands in M04 (not M03)" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2020, monthCode: "M05", day: 1, calendar }, options).add(months1n), + 2020, 5, "M04L", 1, "Subtracting 1 month from M05 in leap year lands in M04L" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2020, monthCode: "M04L", day: 1, calendar }, options).add(months1n), + 2020, 4, "M04", 1, "Subtracting 1 month from M04L in calendar lands in M04" +); + +// Weeks + +const months2weeks3 = new Temporal.Duration(0, /* months = */ 2, /* weeks = */ 3); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1947, monthCode: "M02L", day: 29, calendar }, options).add(months2weeks3), + 1947, 6, "M05", 20, "add 2 months 3 weeks from last day leap month without leap day" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1955, monthCode: "M03L", day: 30, calendar }, options).add(months2weeks3), + 1955, 7, "M06", 21, "add 2 months 3 weeks from leap day in leap month" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1947, monthCode: "M01", day: 29, calendar }, options).add(months2weeks3), + 1947, 4, "M03", 21, "add 2 months 3 weeks from immediately before a leap month" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1955, monthCode: "M06", day: 29, calendar }, options).add(months2weeks3), + 1955, 10, "M09", 20, "add 2 months 3 weeks from immediately before a leap month" +); + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ 10); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1955, monthCode: "M03L", day: 30, calendar }, options).add(days10), + 1955, 5, "M04", 10, "add 10 days from leap day in leap month" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1947, monthCode: "M02L", day: 29, calendar }, options).add(days10), + 1947, 4, "M03", 10, "add 10 days from last day of leap month" +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/leap-months-hebrew.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/leap-months-hebrew.js @@ -0,0 +1,104 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.add +description: Arithmetic around leap months in the hebrew calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "hebrew"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const months1 = new Temporal.Duration(0, 1); +const months1n = new Temporal.Duration(0, -1); +const months2 = new Temporal.Duration(0, 2); +const months2n = new Temporal.Duration(0, -2); + +const date1 = Temporal.PlainDate.from({ year: 5784, monthCode: "M04", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date1.add(months1), + 5784, 5, "M05", 1, "Adding 1 month to M04 in leap year lands in M05 (Shevat)", + "am", 5784 +); + +TemporalHelpers.assertPlainDate( + date1.add(months2), + 5784, 6, "M05L", 1, "Adding 2 months to M04 in leap year lands in M05L (Adar I)", + "am", 5784 +); + +TemporalHelpers.assertPlainDate( + date1.add(new Temporal.Duration(0, 3)), + 5784, 7, "M06", 1, "Adding 3 months to M04 in leap year lands in M06 (Adar II)", + "am", 5784 +); + +const date2 = Temporal.PlainDate.from({ year: 5784, monthCode: "M05L", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date2.add(months1), + 5784, 7, "M06", 1, "Adding 1 month to M05L (Adar I) lands in M06 (Adar II)", + "am", 5784 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 5783, monthCode: "M04", day: 1, calendar }, options).add(months2), + 5783, 6, "M06", 1, "Adding 2 months to M04 in non-leap year lands in M06 (no M05L)", + "am", 5783 +); + +const date3 = Temporal.PlainDate.from({ year: 5784, monthCode: "M07", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date3.add(months1n), + 5784, 7, "M06", 1, "Subtracting 1 month from M07 in leap year lands in M06 (Adar II)", + "am", 5784 +); + +TemporalHelpers.assertPlainDate( + date3.add(months2n), + 5784, 6, "M05L", 1, "Subtracting 2 months from M07 in leap year lands in M05L (Adar I)", + "am", 5784 +); + +TemporalHelpers.assertPlainDate( + date3.add(new Temporal.Duration(0, -3)), + 5784, 5, "M05", 1, "Subtracting 3 months from M07 in leap year lands in M05 (Shevat)", + "am", 5784 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 5784, monthCode: "M06", day: 1, calendar }).add(months1n), + 5784, 6, "M05L", 1, "Subtracting 1 month from M06 (Adar II) in leap year lands in M05L (Adar I)", + "am", 5784 +); + +TemporalHelpers.assertPlainDate( + date2.add(months1n), + 5784, 5, "M05", 1, "Subtracting 1 month from M05L (Adar I) lands in M05 (Shevat)", + "am", 5784 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 5783, monthCode: "M07", day: 1, calendar }).add(months2n), + 5783, 5, "M05", 1, "Subtracting 2 months from M07 in non-leap year lands in M05 (no M05L)", + "am", 5783 +); + +// Weeks + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ 10); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 5784, monthCode: "M05L", day: 30, calendar }, options).add(days10), + 5784, 7, "M06", 10, "add 10 days to leap day", "am", 5784 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/shell.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/add/shell.js @@ -0,0 +1,1252 @@ +// GENERATED, DO NOT EDIT +// file: temporalHelpers.js +// Copyright (C) 2021 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +description: | + This defines helper objects and functions for testing Temporal. +defines: [TemporalHelpers] +features: [Symbol.species, Symbol.iterator, Temporal] +---*/ + +const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u; + +function formatPropertyName(propertyKey, objectName = "") { + switch (typeof propertyKey) { + case "symbol": + if (Symbol.keyFor(propertyKey) !== undefined) { + return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`; + } else if (propertyKey.description.startsWith('Symbol.')) { + return `${objectName}[${propertyKey.description}]`; + } else { + return `${objectName}[Symbol('${propertyKey.description}')]` + } + case "string": + if (propertyKey !== String(Number(propertyKey))) { + if (ASCII_IDENTIFIER.test(propertyKey)) { + return objectName ? `${objectName}.${propertyKey}` : propertyKey; + } + return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']` + } + // fall through + default: + // integer or string integer-index + return `${objectName}[${propertyKey}]`; + } +} + +const SKIP_SYMBOL = Symbol("Skip"); + +var TemporalHelpers = { + /* + * Codes and maximum lengths of months in the ISO 8601 calendar. + */ + ISOMonths: [ + { month: 1, monthCode: "M01", daysInMonth: 31 }, + { month: 2, monthCode: "M02", daysInMonth: 29 }, + { month: 3, monthCode: "M03", daysInMonth: 31 }, + { month: 4, monthCode: "M04", daysInMonth: 30 }, + { month: 5, monthCode: "M05", daysInMonth: 31 }, + { month: 6, monthCode: "M06", daysInMonth: 30 }, + { month: 7, monthCode: "M07", daysInMonth: 31 }, + { month: 8, monthCode: "M08", daysInMonth: 31 }, + { month: 9, monthCode: "M09", daysInMonth: 30 }, + { month: 10, monthCode: "M10", daysInMonth: 31 }, + { month: 11, monthCode: "M11", daysInMonth: 30 }, + { month: 12, monthCode: "M12", daysInMonth: 31 } + ], + + /* + * List of known calendar eras and their possible aliases. + * + * https://tc39.es/proposal-intl-era-monthcode/#table-eras + */ + CalendarEras: { + buddhist: [ + { era: "be" }, + ], + coptic: [ + { era: "am" }, + ], + ethiopic: [ + { era: "aa", aliases: ["mundi"] }, + { era: "am", aliases: ["incar"] }, + ], + ethioaa: [ + { era: "aa", aliases: ["mundi"] }, + ], + gregory: [ + { era: "bce", aliases: ["bc"] }, + { era: "ce", aliases: ["ad"] }, + ], + hebrew: [ + { era: "am" }, + ], + indian: [ + { era: "shaka" }, + ], + islamic: [ + { era: "ah" }, + { era: "bh" }, + ], + "islamic-civil": [ + { era: "bh" }, + { era: "ah" }, + ], + "islamic-rgsa": [ + { era: "bh" }, + { era: "ah" }, + ], + "islamic-tbla": [ + { era: "bh" }, + { era: "ah" }, + ], + "islamic-umalqura": [ + { era: "bh" }, + { era: "ah" }, + ], + japanese: [ + { era: "bce", aliases: ["bc"] }, + { era: "ce", aliases: ["ad"] }, + { era: "heisei" }, + { era: "meiji" }, + { era: "reiwa" }, + { era: "showa" }, + { era: "taisho" }, + ], + persian: [ + { era: "ap" }, + ], + roc: [ + { era: "roc", aliases: ["minguo"] }, + { era: "broc", aliases: ["before-roc", "minguo-qian"] }, + ], + }, + + /* + * Return the canonical era code. + */ + canonicalizeCalendarEra(calendarId, eraName) { + assert.sameValue(typeof calendarId, "string", "calendar must be string in canonicalizeCalendarEra"); + + if (!Object.prototype.hasOwnProperty.call(TemporalHelpers.CalendarEras, calendarId)) { + assert.sameValue(eraName, undefined); + return undefined; + } + + assert.sameValue(typeof eraName, "string", "eraName must be string or undefined in canonicalizeCalendarEra"); + + for (let {era, aliases = []} of TemporalHelpers.CalendarEras[calendarId]) { + if (era === eraName || aliases.includes(eraName)) { + return era; + } + } + throw new Test262Error(`Unsupported era name: ${eraName}`); + }, + + /* + * assertDuration(duration, years, ..., nanoseconds[, description]): + * + * Shorthand for asserting that each field of a Temporal.Duration is equal to + * an expected value. + */ + assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(duration instanceof Temporal.Duration, `${prefix}instanceof`); + assert.sameValue(duration.years, years, `${prefix}years result:`); + assert.sameValue(duration.months, months, `${prefix}months result:`); + assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`); + assert.sameValue(duration.days, days, `${prefix}days result:`); + assert.sameValue(duration.hours, hours, `${prefix}hours result:`); + assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`); + assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`); + assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`); + assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`); + assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`); + }, + + /* + * assertDateDuration(duration, years, months, weeks, days, [, description]): + * + * Shorthand for asserting that each date field of a Temporal.Duration is + * equal to an expected value. + */ + assertDateDuration(duration, years, months, weeks, days, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(duration instanceof Temporal.Duration, `${prefix}instanceof`); + assert.sameValue(duration.years, years, `${prefix}years result:`); + assert.sameValue(duration.months, months, `${prefix}months result:`); + assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`); + assert.sameValue(duration.days, days, `${prefix}days result:`); + assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`); + assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`); + assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`); + assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`); + assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`); + assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`); + }, + + /* + * assertDurationsEqual(actual, expected[, description]): + * + * Shorthand for asserting that each field of a Temporal.Duration is equal to + * the corresponding field in another Temporal.Duration. + */ + assertDurationsEqual(actual, expected, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`); + TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description); + }, + + /* + * assertInstantsEqual(actual, expected[, description]): + * + * Shorthand for asserting that two Temporal.Instants are of the correct type + * and equal according to their equals() methods. + */ + assertInstantsEqual(actual, expected, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`); + assert(actual instanceof Temporal.Instant, `${prefix}instanceof`); + assert(actual.equals(expected), `${prefix}equals method`); + }, + + /* + * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]): + * + * Shorthand for asserting that each field of a Temporal.PlainDate is equal to + * an expected value. (Except the `calendar` property, since callers may want + * to assert either object equality with an object they put in there, or the + * value of date.calendarId.) + */ + assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) { + const prefix = description ? `${description}: ` : ""; + assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`); + assert.sameValue( + TemporalHelpers.canonicalizeCalendarEra(date.calendarId, date.era), + TemporalHelpers.canonicalizeCalendarEra(date.calendarId, era), + `${prefix}era result:` + ); + assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`); + assert.sameValue(date.year, year, `${prefix}year result:`); + assert.sameValue(date.month, month, `${prefix}month result:`); + assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`); + assert.sameValue(date.day, day, `${prefix}day result:`); + }, + + /* + * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]): + * + * Shorthand for asserting that each field of a Temporal.PlainDateTime is + * equal to an expected value. (Except the `calendar` property, since callers + * may want to assert either object equality with an object they put in there, + * or the value of datetime.calendarId.) + */ + assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) { + const prefix = description ? `${description}: ` : ""; + assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`); + assert.sameValue( + TemporalHelpers.canonicalizeCalendarEra(datetime.calendarId, datetime.era), + TemporalHelpers.canonicalizeCalendarEra(datetime.calendarId, era), + `${prefix}era result:` + ); + assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`); + assert.sameValue(datetime.year, year, `${prefix}year result:`); + assert.sameValue(datetime.month, month, `${prefix}month result:`); + assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`); + assert.sameValue(datetime.day, day, `${prefix}day result:`); + assert.sameValue(datetime.hour, hour, `${prefix}hour result:`); + assert.sameValue(datetime.minute, minute, `${prefix}minute result:`); + assert.sameValue(datetime.second, second, `${prefix}second result:`); + assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`); + assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`); + assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`); + }, + + /* + * assertPlainDateTimesEqual(actual, expected[, description]): + * + * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct + * type, equal according to their equals() methods, and additionally that + * their calendar internal slots are the same value. + */ + assertPlainDateTimesEqual(actual, expected, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`); + assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`); + assert(actual.equals(expected), `${prefix}equals method`); + assert.sameValue( + actual.calendarId, + expected.calendarId, + `${prefix}calendar same value:` + ); + }, + + /* + * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]): + * + * Shorthand for asserting that each field of a Temporal.PlainMonthDay is + * equal to an expected value. (Except the `calendar` property, since callers + * may want to assert either object equality with an object they put in there, + * or the value of monthDay.calendarId().) + */ + assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) { + const prefix = description ? `${description}: ` : ""; + assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`); + assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`); + assert.sameValue(monthDay.day, day, `${prefix}day result:`); + const isoYear = Number(monthDay.toString({ calendarName: "always" }).split("-")[0]); + assert.sameValue(isoYear, referenceISOYear, `${prefix}referenceISOYear result:`); + }, + + /* + * assertPlainTime(time, hour, ..., nanosecond[, description]): + * + * Shorthand for asserting that each field of a Temporal.PlainTime is equal to + * an expected value. + */ + assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`); + assert.sameValue(time.hour, hour, `${prefix}hour result:`); + assert.sameValue(time.minute, minute, `${prefix}minute result:`); + assert.sameValue(time.second, second, `${prefix}second result:`); + assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`); + assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`); + assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`); + }, + + /* + * assertPlainTimesEqual(actual, expected[, description]): + * + * Shorthand for asserting that two Temporal.PlainTimes are of the correct + * type and equal according to their equals() methods. + */ + assertPlainTimesEqual(actual, expected, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`); + assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`); + assert(actual.equals(expected), `${prefix}equals method`); + }, + + /* + * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]): + * + * Shorthand for asserting that each field of a Temporal.PlainYearMonth is + * equal to an expected value. (Except the `calendar` property, since callers + * may want to assert either object equality with an object they put in there, + * or the value of yearMonth.calendarId.) + */ + assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) { + const prefix = description ? `${description}: ` : ""; + assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`); + assert.sameValue( + TemporalHelpers.canonicalizeCalendarEra(yearMonth.calendarId, yearMonth.era), + TemporalHelpers.canonicalizeCalendarEra(yearMonth.calendarId, era), + `${prefix}era result:` + ); + assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`); + assert.sameValue(yearMonth.year, year, `${prefix}year result:`); + assert.sameValue(yearMonth.month, month, `${prefix}month result:`); + assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`); + const isoDay = Number(yearMonth.toString({ calendarName: "always" }).slice(1).split('-')[2].slice(0, 2)); + assert.sameValue(isoDay, referenceISODay, `${prefix}referenceISODay result:`); + }, + + /* + * assertZonedDateTimesEqual(actual, expected[, description]): + * + * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct + * type, equal according to their equals() methods, and additionally that + * their time zones and calendar internal slots are the same value. + */ + assertZonedDateTimesEqual(actual, expected, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`); + assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`); + assert(actual.equals(expected), `${prefix}equals method`); + assert.sameValue(actual.timeZoneId, expected.timeZoneId, `${prefix}time zone same value:`); + assert.sameValue( + actual.calendarId, + expected.calendarId, + `${prefix}calendar same value:` + ); + }, + + /* + * assertUnreachable(description): + * + * Helper for asserting that code is not executed. + */ + assertUnreachable(description) { + let message = "This code should not be executed"; + if (description) { + message = `${message}: ${description}`; + } + throw new Test262Error(message); + }, + + /* + * checkPlainDateTimeConversionFastPath(func): + * + * ToTemporalDate and ToTemporalTime should both, if given a + * Temporal.PlainDateTime instance, convert to the desired type by reading the + * PlainDateTime's internal slots, rather than calling any getters. + * + * func(datetime) is the actual operation to test, that must + * internally call the abstract operation ToTemporalDate or ToTemporalTime. + * It is passed a Temporal.PlainDateTime instance. + */ + checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") { + const actual = []; + const expected = []; + + const calendar = "iso8601"; + const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar); + const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype); + ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => { + Object.defineProperty(datetime, property, { + get() { + actual.push(`get ${formatPropertyName(property)}`); + const value = prototypeDescrs[property].get.call(this); + return { + toString() { + actual.push(`toString ${formatPropertyName(property)}`); + return value.toString(); + }, + valueOf() { + actual.push(`valueOf ${formatPropertyName(property)}`); + return value; + }, + }; + }, + }); + }); + Object.defineProperty(datetime, "calendar", { + get() { + actual.push("get calendar"); + return calendar; + }, + }); + + func(datetime); + assert.compareArray(actual, expected, `${message}: property getters not called`); + }, + + /* + * Check that an options bag that accepts units written in the singular form, + * also accepts the same units written in the plural form. + * func(unit) should call the method with the appropriate options bag + * containing unit as a value. This will be called twice for each element of + * validSingularUnits, once with singular and once with plural, and the + * results of each pair should be the same (whether a Temporal object or a + * primitive value.) + */ + checkPluralUnitsAccepted(func, validSingularUnits) { + const plurals = { + year: 'years', + month: 'months', + week: 'weeks', + day: 'days', + hour: 'hours', + minute: 'minutes', + second: 'seconds', + millisecond: 'milliseconds', + microsecond: 'microseconds', + nanosecond: 'nanoseconds', + }; + + validSingularUnits.forEach((unit) => { + const singularValue = func(unit); + const pluralValue = func(plurals[unit]); + const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`; + if (singularValue instanceof Temporal.Duration) { + TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc); + } else if (singularValue instanceof Temporal.Instant) { + TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc); + } else if (singularValue instanceof Temporal.PlainDateTime) { + TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc); + } else if (singularValue instanceof Temporal.PlainTime) { + TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc); + } else if (singularValue instanceof Temporal.ZonedDateTime) { + TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc); + } else { + assert.sameValue(pluralValue, singularValue); + } + }); + }, + + /* + * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc): + * + * Checks the type handling of the roundingIncrement option. + * checkFunc(roundingIncrement) is a function which takes the value of + * roundingIncrement to test, and calls the method under test with it, + * returning the result. assertTrueResultFunc(result, description) should + * assert that result is the expected result with roundingIncrement: true, and + * assertObjectResultFunc(result, description) should assert that result is + * the expected result with roundingIncrement being an object with a valueOf() + * method. + */ + checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) { + // null converts to 0, which is out of range + assert.throws(RangeError, () => checkFunc(null), "null"); + // Booleans convert to either 0 or 1, and 1 is allowed + const trueResult = checkFunc(true); + assertTrueResultFunc(trueResult, "true"); + assert.throws(RangeError, () => checkFunc(false), "false"); + // Symbols and BigInts cannot convert to numbers + assert.throws(TypeError, () => checkFunc(Symbol()), "symbol"); + assert.throws(TypeError, () => checkFunc(2n), "bigint"); + + // Objects prefer their valueOf() methods when converting to a number + assert.throws(RangeError, () => checkFunc({}), "plain object"); + + const expected = [ + "get roundingIncrement.valueOf", + "call roundingIncrement.valueOf", + ]; + const actual = []; + const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement"); + const objectResult = checkFunc(observer); + assertObjectResultFunc(objectResult, "object with valueOf"); + assert.compareArray(actual, expected, "order of operations"); + }, + + /* + * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc): + * + * Checks the type handling of a string option, of which there are several in + * Temporal. + * propertyName is the name of the option, and value is the value that + * assertFunc should expect it to have. + * checkFunc(value) is a function which takes the value of the option to test, + * and calls the method under test with it, returning the result. + * assertFunc(result, description) should assert that result is the expected + * result with the option value being an object with a toString() method + * which returns the given value. + */ + checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) { + // null converts to the string "null", which is an invalid string value + assert.throws(RangeError, () => checkFunc(null), "null"); + // Booleans convert to the strings "true" or "false", which are invalid + assert.throws(RangeError, () => checkFunc(true), "true"); + assert.throws(RangeError, () => checkFunc(false), "false"); + // Symbols cannot convert to strings + assert.throws(TypeError, () => checkFunc(Symbol()), "symbol"); + // Numbers convert to strings which are invalid + assert.throws(RangeError, () => checkFunc(2), "number"); + // BigInts convert to strings which are invalid + assert.throws(RangeError, () => checkFunc(2n), "bigint"); + + // Objects prefer their toString() methods when converting to a string + assert.throws(RangeError, () => checkFunc({}), "plain object"); + + const expected = [ + `get ${propertyName}.toString`, + `call ${propertyName}.toString`, + ]; + const actual = []; + const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName); + const result = checkFunc(observer); + assertFunc(result, "object with toString"); + assert.compareArray(actual, expected, "order of operations"); + }, + + /* + * checkSubclassingIgnored(construct, constructArgs, method, methodArgs, + * resultAssertions): + * + * Methods of Temporal classes that return a new instance of the same class, + * must not take the constructor of a subclass into account, nor the @@species + * property. This helper runs tests to ensure this. + * + * construct(...constructArgs) must yield a valid instance of the Temporal + * class. instance[method](...methodArgs) is the method call under test, which + * must also yield a valid instance of the same Temporal class, not a + * subclass. See below for the individual tests that this runs. + * resultAssertions() is a function that performs additional assertions on the + * instance returned by the method under test. + */ + checkSubclassingIgnored(...args) { + this.checkSubclassConstructorNotObject(...args); + this.checkSubclassConstructorUndefined(...args); + this.checkSubclassConstructorThrows(...args); + this.checkSubclassConstructorNotCalled(...args); + this.checkSubclassSpeciesInvalidResult(...args); + this.checkSubclassSpeciesNotAConstructor(...args); + this.checkSubclassSpeciesNull(...args); + this.checkSubclassSpeciesUndefined(...args); + this.checkSubclassSpeciesThrows(...args); + }, + + /* + * Checks that replacing the 'constructor' property of the instance with + * various primitive values does not affect the returned new instance. + */ + checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) { + function check(value, description) { + const instance = new construct(...constructArgs); + instance.constructor = value; + const result = instance[method](...methodArgs); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description); + resultAssertions(result); + } + + check(null, "null"); + check(true, "true"); + check("test", "string"); + check(Symbol(), "Symbol"); + check(7, "number"); + check(7n, "bigint"); + }, + + /* + * Checks that replacing the 'constructor' property of the subclass with + * undefined does not affect the returned new instance. + */ + checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) { + let called = 0; + + class MySubclass extends construct { + constructor() { + ++called; + super(...constructArgs); + } + } + + const instance = new MySubclass(); + assert.sameValue(called, 1); + + MySubclass.prototype.constructor = undefined; + + const result = instance[method](...methodArgs); + assert.sameValue(called, 1); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + }, + + /* + * Checks that making the 'constructor' property of the instance throw when + * called does not affect the returned new instance. + */ + checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) { + function CustomError() {} + const instance = new construct(...constructArgs); + Object.defineProperty(instance, "constructor", { + get() { + throw new CustomError(); + } + }); + const result = instance[method](...methodArgs); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + }, + + /* + * Checks that when subclassing, the subclass constructor is not called by + * the method under test. + */ + checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) { + let called = 0; + + class MySubclass extends construct { + constructor() { + ++called; + super(...constructArgs); + } + } + + const instance = new MySubclass(); + assert.sameValue(called, 1); + + const result = instance[method](...methodArgs); + assert.sameValue(called, 1); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + }, + + /* + * Check that the constructor's @@species property is ignored when it's a + * constructor that returns a non-object value. + */ + checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) { + function check(value, description) { + const instance = new construct(...constructArgs); + instance.constructor = { + [Symbol.species]: function() { + return value; + }, + }; + const result = instance[method](...methodArgs); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description); + resultAssertions(result); + } + + check(undefined, "undefined"); + check(null, "null"); + check(true, "true"); + check("test", "string"); + check(Symbol(), "Symbol"); + check(7, "number"); + check(7n, "bigint"); + check({}, "plain object"); + }, + + /* + * Check that the constructor's @@species property is ignored when it's not a + * constructor. + */ + checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) { + function check(value, description) { + const instance = new construct(...constructArgs); + instance.constructor = { + [Symbol.species]: value, + }; + const result = instance[method](...methodArgs); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description); + resultAssertions(result); + } + + check(true, "true"); + check("test", "string"); + check(Symbol(), "Symbol"); + check(7, "number"); + check(7n, "bigint"); + check({}, "plain object"); + }, + + /* + * Check that the constructor's @@species property is ignored when it's null. + */ + checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) { + let called = 0; + + class MySubclass extends construct { + constructor() { + ++called; + super(...constructArgs); + } + } + + const instance = new MySubclass(); + assert.sameValue(called, 1); + + MySubclass.prototype.constructor = { + [Symbol.species]: null, + }; + + const result = instance[method](...methodArgs); + assert.sameValue(called, 1); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + }, + + /* + * Check that the constructor's @@species property is ignored when it's + * undefined. + */ + checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) { + let called = 0; + + class MySubclass extends construct { + constructor() { + ++called; + super(...constructArgs); + } + } + + const instance = new MySubclass(); + assert.sameValue(called, 1); + + MySubclass.prototype.constructor = { + [Symbol.species]: undefined, + }; + + const result = instance[method](...methodArgs); + assert.sameValue(called, 1); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + }, + + /* + * Check that the constructor's @@species property is ignored when it throws, + * i.e. it is not called at all. + */ + checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) { + function CustomError() {} + + const instance = new construct(...constructArgs); + instance.constructor = { + get [Symbol.species]() { + throw new CustomError(); + }, + }; + + const result = instance[method](...methodArgs); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + }, + + /* + * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions): + * + * Static methods of Temporal classes that return a new instance of the class, + * must not use the this-value as a constructor. This helper runs tests to + * ensure this. + * + * construct[method](...methodArgs) is the static method call under test, and + * must yield a valid instance of the Temporal class, not a subclass. See + * below for the individual tests that this runs. + * resultAssertions() is a function that performs additional assertions on the + * instance returned by the method under test. + */ + checkSubclassingIgnoredStatic(...args) { + this.checkStaticInvalidReceiver(...args); + this.checkStaticReceiverNotCalled(...args); + this.checkThisValueNotCalled(...args); + }, + + /* + * Check that calling the static method with a receiver that's not callable, + * still calls the intrinsic constructor. + */ + checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) { + function check(value, description) { + const result = construct[method].apply(value, methodArgs); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + } + + check(undefined, "undefined"); + check(null, "null"); + check(true, "true"); + check("test", "string"); + check(Symbol(), "symbol"); + check(7, "number"); + check(7n, "bigint"); + check({}, "Non-callable object"); + }, + + /* + * Check that calling the static method with a receiver that returns a value + * that's not callable, still calls the intrinsic constructor. + */ + checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) { + function check(value, description) { + const receiver = function () { + return value; + }; + const result = construct[method].apply(receiver, methodArgs); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + } + + check(undefined, "undefined"); + check(null, "null"); + check(true, "true"); + check("test", "string"); + check(Symbol(), "symbol"); + check(7, "number"); + check(7n, "bigint"); + check({}, "Non-callable object"); + }, + + /* + * Check that the receiver isn't called. + */ + checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) { + let called = false; + + class MySubclass extends construct { + constructor(...args) { + called = true; + super(...args); + } + } + + const result = MySubclass[method](...methodArgs); + assert.sameValue(called, false); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + }, + + /* + * Check that any calendar-carrying Temporal object has its [[Calendar]] + * internal slot read by ToTemporalCalendar, and does not fetch the calendar + * by calling getters. + */ + checkToTemporalCalendarFastPath(func) { + const plainDate = new Temporal.PlainDate(2000, 5, 2, "iso8601"); + const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, "iso8601"); + const plainMonthDay = new Temporal.PlainMonthDay(5, 2, "iso8601"); + const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, "iso8601"); + const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", "iso8601"); + + [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => { + const actual = []; + const expected = []; + + Object.defineProperty(temporalObject, "calendar", { + get() { + actual.push("get calendar"); + return calendar; + }, + }); + + func(temporalObject); + assert.compareArray(actual, expected, "calendar getter not called"); + }); + }, + + checkToTemporalInstantFastPath(func) { + const actual = []; + const expected = []; + + const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC"); + Object.defineProperty(datetime, 'toString', { + get() { + actual.push("get toString"); + return function (options) { + actual.push("call toString"); + return Temporal.ZonedDateTime.prototype.toString.call(this, options); + }; + }, + }); + + func(datetime); + assert.compareArray(actual, expected, "toString not called"); + }, + + checkToTemporalPlainDateTimeFastPath(func) { + const actual = []; + const expected = []; + + const date = new Temporal.PlainDate(2000, 5, 2, "iso8601"); + const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype); + ["year", "month", "monthCode", "day"].forEach((property) => { + Object.defineProperty(date, property, { + get() { + actual.push(`get ${formatPropertyName(property)}`); + const value = prototypeDescrs[property].get.call(this); + return TemporalHelpers.toPrimitiveObserver(actual, value, property); + }, + }); + }); + ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => { + Object.defineProperty(date, property, { + get() { + actual.push(`get ${formatPropertyName(property)}`); + return undefined; + }, + }); + }); + Object.defineProperty(date, "calendar", { + get() { + actual.push("get calendar"); + return "iso8601"; + }, + }); + + func(date); + assert.compareArray(actual, expected, "property getters not called"); + }, + + /* + * observeProperty(calls, object, propertyName, value): + * + * Defines an own property @object.@propertyName with value @value, that + * will log any calls to its accessors to the array @calls. + */ + observeProperty(calls, object, propertyName, value, objectName = "") { + Object.defineProperty(object, propertyName, { + get() { + calls.push(`get ${formatPropertyName(propertyName, objectName)}`); + return value; + }, + set(v) { + calls.push(`set ${formatPropertyName(propertyName, objectName)}`); + } + }); + }, + + /* + * observeMethod(calls, object, propertyName, value): + * + * Defines an own property @object.@propertyName with value @value, that + * will log any calls of @value to the array @calls. + */ + observeMethod(calls, object, propertyName, objectName = "") { + const method = object[propertyName]; + object[propertyName] = function () { + calls.push(`call ${formatPropertyName(propertyName, objectName)}`); + return method.apply(object, arguments); + }; + }, + + /* + * Used for substituteMethod to indicate default behavior instead of a + * substituted value + */ + SUBSTITUTE_SKIP: SKIP_SYMBOL, + + /* + * substituteMethod(object, propertyName, values): + * + * Defines an own property @object.@propertyName that will, for each + * subsequent call to the method previously defined as + * @object.@propertyName: + * - Call the method, if no more values remain + * - Call the method, if the value in @values for the corresponding call + * is SUBSTITUTE_SKIP + * - Otherwise, return the corresponding value in @value + */ + substituteMethod(object, propertyName, values) { + let calls = 0; + const method = object[propertyName]; + object[propertyName] = function () { + if (calls >= values.length) { + return method.apply(object, arguments); + } else if (values[calls] === SKIP_SYMBOL) { + calls++; + return method.apply(object, arguments); + } else { + return values[calls++]; + } + }; + }, + + /* + * propertyBagObserver(): + * Returns an object that behaves like the given propertyBag but tracks Get + * and Has operations on any of its properties, by appending messages to an + * array. If the value of a property in propertyBag is a primitive, the value + * of the returned object's property will additionally be a + * TemporalHelpers.toPrimitiveObserver that will track calls to its toString + * and valueOf methods in the same array. This is for the purpose of testing + * order of operations that are observable from user code. objectName is used + * in the log. + * If skipToPrimitive is given, it must be an array of property keys. Those + * properties will not have a TemporalHelpers.toPrimitiveObserver returned, + * and instead just be returned directly. + */ + propertyBagObserver(calls, propertyBag, objectName, skipToPrimitive) { + return new Proxy(propertyBag, { + ownKeys(target) { + calls.push(`ownKeys ${objectName}`); + return Reflect.ownKeys(target); + }, + getOwnPropertyDescriptor(target, key) { + calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`); + return Reflect.getOwnPropertyDescriptor(target, key); + }, + get(target, key, receiver) { + calls.push(`get ${formatPropertyName(key, objectName)}`); + const result = Reflect.get(target, key, receiver); + if (result === undefined) { + return undefined; + } + if ((result !== null && typeof result === "object") || typeof result === "function") { + return result; + } + if (skipToPrimitive && skipToPrimitive.indexOf(key) >= 0) { + return result; + } + return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`); + }, + has(target, key) { + calls.push(`has ${formatPropertyName(key, objectName)}`); + return Reflect.has(target, key); + }, + }); + }, + + /* + * Returns an object that will append logs of any Gets or Calls of its valueOf + * or toString properties to the array calls. Both valueOf and toString will + * return the actual primitiveValue. propertyName is used in the log. + */ + toPrimitiveObserver(calls, primitiveValue, propertyName) { + return { + get valueOf() { + calls.push(`get ${propertyName}.valueOf`); + return function () { + calls.push(`call ${propertyName}.valueOf`); + return primitiveValue; + }; + }, + get toString() { + calls.push(`get ${propertyName}.toString`); + return function () { + calls.push(`call ${propertyName}.toString`); + if (primitiveValue === undefined) return undefined; + return primitiveValue.toString(); + }; + }, + }; + }, + + /* + * An object containing further methods that return arrays of ISO strings, for + * testing parsers. + */ + ISO: { + /* + * PlainMonthDay strings that are not valid. + */ + plainMonthDayStringsInvalid() { + return [ + "11-18junk", + "11-18[u-ca=gregory]", + "11-18[u-ca=hebrew]", + "11-18[U-CA=iso8601]", + "11-18[u-CA=iso8601]", + "11-18[FOO=bar]", + "-999999-01-01[u-ca=gregory]", + "-999999-01-01[u-ca=chinese]", + "+999999-01-01[u-ca=gregory]", + "+999999-01-01[u-ca=chinese]", + ]; + }, + + /* + * PlainMonthDay strings that are valid and that should produce October 1st. + */ + plainMonthDayStringsValid() { + return [ + "10-01", + "1001", + "1965-10-01", + "1976-10-01T152330.1+00:00", + "19761001T15:23:30.1+00:00", + "1976-10-01T15:23:30.1+0000", + "1976-10-01T152330.1+0000", + "19761001T15:23:30.1+0000", + "19761001T152330.1+00:00", + "19761001T152330.1+0000", + "+001976-10-01T152330.1+00:00", + "+0019761001T15:23:30.1+00:00", + "+001976-10-01T15:23:30.1+0000", + "+001976-10-01T152330.1+0000", + "+0019761001T15:23:30.1+0000", + "+0019761001T152330.1+00:00", + "+0019761001T152330.1+0000", + "1976-10-01T15:23:00", + "1976-10-01T15:23", + "1976-10-01T15", + "1976-10-01", + "--10-01", + "--1001", + "-999999-10-01", + "-999999-10-01[u-ca=iso8601]", + "+999999-10-01", + "+999999-10-01[u-ca=iso8601]", + ]; + }, + + /* + * PlainTime strings that may be mistaken for PlainMonthDay or + * PlainYearMonth strings, and so require a time designator. + */ + plainTimeStringsAmbiguous() { + const ambiguousStrings = [ + "2021-12", // ambiguity between YYYY-MM and HHMM-UU + "2021-12[-12:00]", // ditto, TZ does not disambiguate + "1214", // ambiguity between MMDD and HHMM + "0229", // ditto, including MMDD that doesn't occur every year + "1130", // ditto, including DD that doesn't occur in every month + "12-14", // ambiguity between MM-DD and HH-UU + "12-14[-14:00]", // ditto, TZ does not disambiguate + "202112", // ambiguity between YYYYMM and HHMMSS + "202112[UTC]", // ditto, TZ does not disambiguate + ]; + // Adding a calendar annotation to one of these strings must not cause + // disambiguation in favour of time. + const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]'); + return ambiguousStrings.concat(stringsWithCalendar); + }, + + /* + * PlainTime strings that are of similar form to PlainMonthDay and + * PlainYearMonth strings, but are not ambiguous due to components that + * aren't valid as months or days. + */ + plainTimeStringsUnambiguous() { + return [ + "2021-13", // 13 is not a month + "202113", // ditto + "2021-13[-13:00]", // ditto + "202113[-13:00]", // ditto + "0000-00", // 0 is not a month + "000000", // ditto + "0000-00[UTC]", // ditto + "000000[UTC]", // ditto + "1314", // 13 is not a month + "13-14", // ditto + "1232", // 32 is not a day + "0230", // 30 is not a day in February + "0631", // 31 is not a day in June + "0000", // 0 is neither a month nor a day + "00-00", // ditto + ]; + }, + + /* + * PlainYearMonth-like strings that are not valid. + */ + plainYearMonthStringsInvalid() { + return [ + "2020-13", + "1976-11[u-ca=gregory]", + "1976-11[u-ca=hebrew]", + "1976-11[U-CA=iso8601]", + "1976-11[u-CA=iso8601]", + "1976-11[FOO=bar]", + "+999999-01", + "-999999-01", + ]; + }, + + /* + * PlainYearMonth-like strings that are valid and should produce November + * 1976 in the ISO 8601 calendar. + */ + plainYearMonthStringsValid() { + return [ + "1976-11", + "1976-11-10", + "1976-11-01T09:00:00+00:00", + "1976-11-01T00:00:00+05:00", + "197611", + "+00197611", + "1976-11-18T15:23:30.1-02:00", + "1976-11-18T152330.1+00:00", + "19761118T15:23:30.1+00:00", + "1976-11-18T15:23:30.1+0000", + "1976-11-18T152330.1+0000", + "19761118T15:23:30.1+0000", + "19761118T152330.1+00:00", + "19761118T152330.1+0000", + "+001976-11-18T152330.1+00:00", + "+0019761118T15:23:30.1+00:00", + "+001976-11-18T15:23:30.1+0000", + "+001976-11-18T152330.1+0000", + "+0019761118T15:23:30.1+0000", + "+0019761118T152330.1+00:00", + "+0019761118T152330.1+0000", + "1976-11-18T15:23", + "1976-11-18T15", + "1976-11-18", + ]; + }, + + /* + * PlainYearMonth-like strings that are valid and should produce November of + * the ISO year -9999. + */ + plainYearMonthStringsValidNegativeYear() { + return [ + "-009999-11", + ]; + }, + } +}; diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/since/canonicalize-calendar.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/since/canonicalize-calendar.js @@ -5,7 +5,7 @@ /*--- esid: sec-temporal.plaindate.prototype.since description: Calendar ID is canonicalized -features: [Temporal] +features: [Temporal, Intl.Era-monthcode] ---*/ const instance = new Temporal.PlainDate(2024, 7, 2, "islamic-civil"); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/since/era-boundary-gregory.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/since/era-boundary-gregory.js @@ -0,0 +1,80 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.since +description: Date difference works correctly across era boundaries +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "gregory"; +const options = { overflow: "reject" }; + +const bce5 = Temporal.PlainDate.from({ era: "bce", eraYear: 5, monthCode: "M06", day: 15, calendar }, options); +const bce2 = Temporal.PlainDate.from({ era: "bce", eraYear: 2, monthCode: "M12", day: 1, calendar }, options); +const bce1 = Temporal.PlainDate.from({ era: "bce", eraYear: 1, monthCode: "M06", day: 15, calendar }, options); +const ce1 = Temporal.PlainDate.from({ era: "ce", eraYear: 1, monthCode: "M06", day: 15, calendar }, options); +const ce2 = Temporal.PlainDate.from({ era: "ce", eraYear: 2, monthCode: "M01", day: 1, calendar }, options); +const ce5 = Temporal.PlainDate.from({ era: "ce", eraYear: 5, monthCode: "M06", day: 15, calendar }, options); + +const tests = [ + // From 5 BCE to 5 CE + [ + bce5, ce5, + [-9, 0, 0, 0, "-9y backwards from 5 BCE to 5 CE (no year 0)"], + [0, -108, 0, 0, 0, "-108mo backwards from 5 BCE to 5 CE (no year 0)"], + ], + [ + ce5, bce5, + [9, 0, 0, 0, "9y from 5 BCE to 5 CE (no year 0)"], + [0, 108, 0, 0, 0, "108mo from 5 BCE to 5 CE (no year 0)"], + ], + // CE-BCE boundary + [ + bce1, ce1, + [-1, 0, 0, 0, "-1y backwards from 1 BCE to 1 CE"], + [0, -12, 0, 0, "-12mo backwards from 1 BCE to 1 CE"], + ], + [ + ce1, bce1, + [1, 0, 0, 0, "1y from 1 BCE to 1 CE"], + [0, 12, 0, 0, "12mo from 1 BCE to 1 CE"], + ], + [ + bce2, ce2, + [-2, -1, 0, 0, "-2y -1mo backwards from 2 BCE Dec to 2 CE Jan"], + [0, -25, 0, 0, "-25mo backwards from 2 BCE Dec to 2 CE Jan"], + ], + [ + ce2, bce2, + [2, 1, 0, 0, "2y 1mo from 2 BCE Dec to 2 CE Jan"], + [0, 25, 0, 0, "25mo from 2 BCE Dec to 2 CE Jan"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.since(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.since(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.since(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.since(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.since(two); + const resultDaysISO = oneISO.since(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/since/era-boundary-japanese.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/since/era-boundary-japanese.js @@ -0,0 +1,172 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.since +description: Date difference works correctly across era boundaries +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "japanese"; +const options = { overflow: "reject" }; + +const bce1 = Temporal.PlainDate.from({ era: "bce", eraYear: 1, monthCode: "M06", day: 1, calendar }, options); +const ce1 = Temporal.PlainDate.from({ era: "ce", eraYear: 1, monthCode: "M06", day: 1, calendar }, options); +const ce1868 = Temporal.PlainDate.from({ era: "ce", eraYear: 1868, monthCode: "M10", day: 22, calendar }, options); +const meiji1 = Temporal.PlainDate.from({ era: "meiji", eraYear: 1, monthCode: "M10", day: 23, calendar}, options); +const meiji5 = Temporal.PlainDate.from({ era: "meiji", eraYear: 5, monthCode: "M01", day: 15, calendar }, options); +const meiji45 = Temporal.PlainDate.from({ era: "meiji", eraYear: 45, monthCode: "M05", day: 19, calendar }, options); +const taisho1 = Temporal.PlainDate.from({ era: "taisho", eraYear: 1, monthCode: "M08", day: 9, calendar }, options); +const taisho6 = Temporal.PlainDate.from({ era: "taisho", eraYear: 6, monthCode: "M03", day: 15, calendar }, options); +const taisho15 = Temporal.PlainDate.from({ era: "taisho", eraYear: 15, monthCode: "M05", day: 20, calendar }, options); +const showa1 = Temporal.PlainDate.from({ era: "showa", eraYear: 1, monthCode: "M12", day: 30, calendar }, options); +const showa55 = Temporal.PlainDate.from({ era: "showa", eraYear: 55, monthCode: "M03", day: 15, calendar }, options); +const showa64 = Temporal.PlainDate.from({ era: "showa", eraYear: 64, monthCode: "M01", day: 3, calendar }, options); +const heisei1 = Temporal.PlainDate.from({ era: "heisei", eraYear: 1, monthCode: "M02", day: 27, calendar }, options); +const heisei30 = Temporal.PlainDate.from({ era: "heisei", eraYear: 30, monthCode: "M03", day: 15, calendar }, options); +const heisei31 = Temporal.PlainDate.from({ era: "heisei", eraYear: 31, monthCode: "M04", day: 1, calendar }, options); +const reiwa1 = Temporal.PlainDate.from({ era: "reiwa", eraYear: 1, monthCode: "M06", day: 1, calendar }, options); +const reiwa2 = Temporal.PlainDate.from({ era: "reiwa", eraYear: 2, monthCode: "M03", day: 15, calendar }, options); + +const tests = [ + // From Heisei 30 (2018) to Reiwa 2 (2020) - crossing era boundary + [ + heisei30, reiwa2, + [-2, 0, 0, 0, "-2y backwards from Heisei 30 March to Reiwa 2 March"], + [0, -24, 0, 0, "-24mo backwards from Heisei 30 March to Reiwa 2 March"], + ], + [ + reiwa2, heisei30, + [2, 0, 0, 0, "2y from Heisei 30 March to Reiwa 2 March"], + [0, 24, 0, 0, "24mo from Heisei 30 March to Reiwa 2 March"], + + ], + // Within same year but different eras + [ + heisei31, reiwa1, + [0, -2, 0, 0, "-2mo backwards from Heisei 31 April to Reiwa 1 June"], + [0, -2, 0, 0, "-2mo backwards from Heisei 31 April to Reiwa 1 June"], + ], + [ + reiwa1, heisei31, + [0, 2, 0, 0, "2mo from Heisei 31 April to Reiwa 1 June"], + [0, 2, 0, 0, "2mo from Heisei 31 April to Reiwa 1 June"], + ], + // From Showa 55 (1980) to Heisei 30 (2018) - crossing era boundary + [ + showa55, heisei30, + [-38, 0, 0, 0, "-38y backwards from Showa 55 March to Heisei 30 March"], + [0, -456, 0, 0, "-456mo backwards from Showa 55 March to Heisei 30 March"], + ], + [ + heisei30, showa55, + [38, 0, 0, 0, "38y from Showa 55 March to Heisei 30 March"], + [0, 456, 0, 0, "456mo from Showa 55 March to Heisei 30 March"], + ], + // Within same year but different eras + [ + showa64, heisei1, + [0, -1, 0, -24, "-1mo -24d from Showa 64 January 3 to Heisei 1 February 27"], + [0, -1, 0, -24, "-1mo -24d from Showa 64 January 3 to Heisei 1 February 27"], + ], + [ + heisei1, showa64, + [0, 1, 0, 24, "1mo 24d from Showa 64 January 3 to Heisei 1 February 27"], + [0, 1, 0, 24, "1mo 24d from Showa 64 January 3 to Heisei 1 February 27"], + ], + // From Taisho 6 (1917) to Showa 55 (1980) - crossing era boundary + [ + taisho6, showa55, + [-63, 0, 0, 0, "-63y backwards from Taisho 6 March to Showa 55 March"], + [0, -756, 0, 0, "-756mo backwards from Taisho 6 March to Showa 55 March"], + ], + [ + showa55, taisho6, + [63, 0, 0, 0, "63y from Taisho 6 March to Showa 55 March"], + [0, 756, 0, 0, "756mo from Taisho 6 March to Showa 55 March"], + ], + // Within same year but different eras + [ + taisho15, showa1, + [0, -7, 0, -10, "-7mo -10d backwards from Taisho 15 July 20 to Showa 1 December 30"], + [0, -7, 0, -10, "-7mo -10d backwards from Taisho 15 July 20 to Showa 1 December 30"], + ], + [ + showa1, taisho15, + [0, 7, 0, 10, "7mo 10d from Taisho 15 July 20 to Showa 1 December 30"], + [0, 7, 0, 10, "7mo 10d from Taisho 15 July 20 to Showa 1 December 30"], + ], + // From Meiji 5 (1872) to Taisho 6 (1917) - crossing era boundary + // Note that contemporarily January 15 1872 would have been Meiji 4 in the + // pre-1873 lunisolar calendar, but the spec-mandated behaviour is proleptic + [ + meiji5, taisho6, + [-45, -2, 0, 0, "-45y -2mo backwards from Meiji 5 January to Taisho 6 March"], + [0, -542, 0, 0, "-542mo backwards from Meiji 5 January to Taisho 6 March"], + ], + [ + taisho6, meiji5, + [45, 2, 0, 0, "45y 2mo from Meiji 5 January to Taisho 6 March"], + [0, 542, 0, 0, "542mo from Meiji 5 January to Taisho 6 March"], + ], + // Within same year but different eras + [ + meiji45, taisho1, + [0, -2, 0, -21, "-2mo -21d backwards from Meiji 45 May 19 to Taisho 1 August 9"], + [0, -2, 0, -21, "-2mo -21d backwards from Meiji 45 May 19 to Taisho 1 August 9"], + ], + [ + taisho1, meiji45, + [0, 2, 0, 21, "2mo 21d from Meiji 45 May 19 to Taisho 1 August 9"], + [0, 2, 0, 21, "2mo 21d from Meiji 45 May 19 to Taisho 1 August 9"], + ], + // Last pre-Meiji day to first day of Meiji era + [ + ce1868, meiji1, + [0, 0, 0, -1, "backwards from day before Meiji era to first day"], + [0, 0, 0, -1, "backwards from day before Meiji era to first day"], + ], + [ + meiji1, ce1868, + [0, 0, 0, 1, "from day before Meiji era to first day"], + [0, 0, 0, 1, "from day before Meiji era to first day"], + ], + // CE-BCE boundary + [ + bce1, ce1, + [-1, 0, 0, 0, "-1y backwards from 1 BCE to 1 CE"], + [0, -12, 0, 0, "-12mo backwards from 1 BCE to 1 CE"], + ], + [ + ce1, bce1, + [1, 0, 0, 0, "1y from 1 BCE to 1 CE"], + [0, 12, 0, 0, "12mo from 1 BCE to 1 CE"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.since(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.since(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.since(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.since(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.since(two); + const resultDaysISO = oneISO.since(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/since/era-boundary-roc.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/since/era-boundary-roc.js @@ -0,0 +1,80 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.since +description: Date difference works correctly across era boundaries +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "roc"; +const options = { overflow: "reject" }; + +const broc5 = Temporal.PlainDate.from({ era: "broc", eraYear: 5, monthCode: "M03", day: 1, calendar }, options); +const broc3 = Temporal.PlainDate.from({ era: "broc", eraYear: 3, monthCode: "M01", day: 1, calendar }, options); +const broc1 = Temporal.PlainDate.from({ era: "broc", eraYear: 1, monthCode: "M06", day: 1, calendar }, options); +const roc1 = Temporal.PlainDate.from({ era: "roc", eraYear: 1, monthCode: "M06", day: 1, calendar }, options); +const roc5 = Temporal.PlainDate.from({ era: "roc", eraYear: 5, monthCode: "M03", day: 1, calendar }, options); +const roc10 = Temporal.PlainDate.from({ era: "roc", eraYear: 10, monthCode: "M01", day: 1, calendar }, options); + +const tests = [ + // From BROC 5 to ROC 5 + [ + broc5, roc5, + [-9, 0, 0, 0, "-9y backwards from BROC 5 to ROC 5 (no year 0)"], + [0, -108, 0, 0, 0, "-108mo backwards from BROC 5 to ROC 5 (no year 0)"], + ], + [ + roc5, broc5, + [9, 0, 0, 0, "9y from BROC 5 to ROC 5 (no year 0)"], + [0, 108, 0, 0, 0, "108mo from BROC 5 to ROC 5 (no year 0)"], + ], + // Era boundary + [ + broc1, roc1, + [-1, 0, 0, 0, "-1y backwards from BROC 1 to ROC 1"], + [0, -12, 0, 0, "-12mo backwards from BROC 1 to ROC 1"], + ], + [ + roc1, broc1, + [1, 0, 0, 0, "1y from BROC 1 to ROC 1"], + [0, 12, 0, 0, "12mo from BROC 1 to ROC 1"], + ], + [ + broc3, roc10, + [-12, 0, 0, 0, "-12y backwards from BROC 3 to ROC 10"], + [0, -144, 0, 0, "-144mo backwards from BROC 3 to ROC 10"], + ], + [ + roc10, broc3, + [12, 0, 0, 0, "12y from BROC 3 to ROC 10"], + [0, 144, 0, 0, "144mo from BROC 3 to ROC 10"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.since(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.since(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.since(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.since(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.since(two); + const resultDaysISO = oneISO.since(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/since/leap-months-chinese.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/since/leap-months-chinese.js @@ -0,0 +1,167 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Difference across leap months in chinese calendar +esid: sec-temporal.plaindate.prototype.since +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +// 2001 is a leap year with a M04L leap month. + +const calendar = "chinese"; +const options = { overflow: "reject" }; + +const common1Month4 = Temporal.PlainDate.from({ year: 2000, monthCode: "M04", day: 1, calendar }, options); +const common1Month5 = Temporal.PlainDate.from({ year: 2000, monthCode: "M05", day: 1, calendar }, options); +const common1Month6 = Temporal.PlainDate.from({ year: 2000, monthCode: "M06", day: 1, calendar }, options); +const leapMonth4 = Temporal.PlainDate.from({ year: 2001, monthCode: "M04", day: 1, calendar }, options); +const leapMonth4L = Temporal.PlainDate.from({ year: 2001, monthCode: "M04L", day: 1, calendar }, options); +const leapMonth5 = Temporal.PlainDate.from({ year: 2001, monthCode: "M05", day: 1, calendar }, options); +const common2Month4 = Temporal.PlainDate.from({ year: 2002, monthCode: "M04", day: 1, calendar }, options); +const common2Month5 = Temporal.PlainDate.from({ year: 2002, monthCode: "M05", day: 1, calendar }, options); + +// (receiver, argument, years test data, months test data) +// test data: expected years, months, weeks, days, description +// largestUnit years: make sure some cases where the answer is 12 months do not +// balance up to 1 year +// largestUnit months: similar to years, but make sure number of months in year +// is computed correctly +// For largestUnit of days and weeks, the results should be identical to what +// the ISO calendar gives for the corresponding ISO dates +const tests = [ + [ + common1Month4, leapMonth4, + [-1, 0, 0, 0, "M04-M04 common-leap backwards is -1y"], + [0, -12, 0, 0, "M04-M04 common-leap backwards is -12mo"], + ], + [ + leapMonth4, common2Month4, + [-1, 0, 0, 0, "M04-M04 leap-common backwards is -1y"], + [0, -13, 0, 0, "M04-M04 leap-common backwards is -13mo not -12mo"], + ], + [ + common1Month4, common2Month4, + [-2, 0, 0, 0, "M04-M04 common-common backwards is -2y"], + [0, -25, 0, 0, "M04-M04 common-common backwards is -25mo not -24mo"], + ], + [ + common1Month5, leapMonth5, + [-1, 0, 0, 0, "M05-M05 common-leap backwards is -1y"], + [0, -13, 0, 0, "M05-M05 common-leap backwards is -13mo not -12mo"], + ], + [ + leapMonth5, common2Month5, + [-1, 0, 0, 0, "M05-M05 leap-common backwards is -1y"], + [0, -12, 0, 0, "M05-M05 leap-common backwards is -12mo"], + ], + [ + common1Month5, common2Month5, + [-2, 0, 0, 0, "M05-M05 common-common backwards is -2y"], + [0, -25, 0, 0, "M05-M05 common-common backwards is -25mo not -24mo"], + ], + [ + common1Month4, leapMonth4L, + [-1, -1, 0, 0, "M04-M04L backwards is -1y -1mo"], + [0, -13, 0, 0, "M04-M04L backwards is -13mo"], + ], + [ + leapMonth4L, common2Month4, + [0, -12, 0, 0, "M04L-M04 backwards is -12mo not -1y"], + [0, -12, 0, 0, "M04L-M04 backwards is -12mo"], + ], + [ + common1Month5, leapMonth4L, + [0, -12, 0, 0, "M05-M04L backwards is -12mo not -1y"], + [0, -12, 0, 0, "M05-M04L backwards is -12mo"], + ], + [ + leapMonth4L, common2Month5, + [-1, -1, 0, 0, "M04L-M05 backwards is -1y -1mo (exhibits calendar-specific constraining)"], + [0, -13, 0, 0, "M04L-M05 backwards is -13mo"], + ], + [ + common1Month6, leapMonth5, + [0, -12, 0, 0, "M06-M05 common-leap backwards is -12mo not -11mo"], + [0, -12, 0, 0, "M06-M05 common-leap backwards is -12mo not -11mo"], + ], + + // Negative + [ + common2Month4, leapMonth4, + [1, 0, 0, 0, "M04-M04 common-leap is 1y"], + [0, 13, 0, 0, "M04-M04 common-leap is 13mo not 12mo"], + ], + [ + leapMonth4, common1Month4, + [1, 0, 0, 0, "M04-M04 leap-common is 1y"], + [0, 12, 0, 0, "M04-M04 leap-common is 12mo not 13mo"], + ], + [ + common2Month4, common1Month4, + [2, 0, 0, 0, "M04-M04 common-common is 2y"], + [0, 25, 0, 0, "M04-M04 common-common is 25mo not 24mo"], + ], + [ + common2Month5, leapMonth5, + [1, 0, 0, 0, "M05-M05 common-leap is 1y"], + [0, 12, 0, 0, "M05-M05 common-leap is 12mo not 13mo"], + ], + [ + leapMonth5, common1Month5, + [1, 0, 0, 0, "M05-M05 leap-common is 1y"], + [0, 13, 0, 0, "M05-M05 leap-common is 13mo not 12mo"], + ], + [ + common2Month5, common1Month5, + [2, 0, 0, 0, "M05-M05 common-common is 2y"], + [0, 25, 0, 0, "M05-M05 common-common is 25mo not 24mo"], + ], + [ + common2Month4, leapMonth4L, + [0, 12, 0, 0, "M04-M04L is 12mo not 1y"], + [0, 12, 0, 0, "M04-M04L is 12mo"], + ], + [ + leapMonth4L, common1Month4, + [1, 0, 0, 0, "M04L-M04 is 1y not 1y 1mo (exhibits calendar-specific constraining)"], + [0, 13, 0, 0, "M04L-M04 is 13mo"], + ], + [ + common2Month5, leapMonth4L, + [1, 1, 0, 0, "M05-M04L is 1y 1mo"], + [0, 13, 0, 0, "M05-M04L is 13mo"], + ], + [ + leapMonth4L, common1Month5, + [0, 12, 0, 0, "M04L-M05 is 12mo not 1y"], + [0, 12, 0, 0, "M04L-M05 is 12mo"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.since(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.since(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.since(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.since(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.since(two); + const resultDaysISO = oneISO.since(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/since/leap-months-dangi.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/since/leap-months-dangi.js @@ -0,0 +1,167 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Difference across leap months in dangi calendar +esid: sec-temporal.plaindate.prototype.since +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +// 2001 is a leap year with a M04L leap month. + +const calendar = "dangi"; +const options = { overflow: "reject" }; + +const common1Month4 = Temporal.PlainDate.from({ year: 2000, monthCode: "M04", day: 1, calendar }, options); +const common1Month5 = Temporal.PlainDate.from({ year: 2000, monthCode: "M05", day: 1, calendar }, options); +const common1Month6 = Temporal.PlainDate.from({ year: 2000, monthCode: "M06", day: 1, calendar }, options); +const leapMonth4 = Temporal.PlainDate.from({ year: 2001, monthCode: "M04", day: 1, calendar }, options); +const leapMonth4L = Temporal.PlainDate.from({ year: 2001, monthCode: "M04L", day: 1, calendar }, options); +const leapMonth5 = Temporal.PlainDate.from({ year: 2001, monthCode: "M05", day: 1, calendar }, options); +const common2Month4 = Temporal.PlainDate.from({ year: 2002, monthCode: "M04", day: 1, calendar }, options); +const common2Month5 = Temporal.PlainDate.from({ year: 2002, monthCode: "M05", day: 1, calendar }, options); + +// (receiver, argument, years test data, months test data) +// test data: expected years, months, weeks, days, description +// largestUnit years: make sure some cases where the answer is 12 months do not +// balance up to 1 year +// largestUnit months: similar to years, but make sure number of months in year +// is computed correctly +// For largestUnit of days and weeks, the results should be identical to what +// the ISO calendar gives for the corresponding ISO dates +const tests = [ + [ + common1Month4, leapMonth4, + [-1, 0, 0, 0, "M04-M04 common-leap backwards is -1y"], + [0, -12, 0, 0, "M04-M04 common-leap backwards is -12mo"], + ], + [ + leapMonth4, common2Month4, + [-1, 0, 0, 0, "M04-M04 leap-common backwards is -1y"], + [0, -13, 0, 0, "M04-M04 leap-common backwards is -13mo not -12mo"], + ], + [ + common1Month4, common2Month4, + [-2, 0, 0, 0, "M04-M04 common-common backwards is -2y"], + [0, -25, 0, 0, "M04-M04 common-common backwards is -25mo not -24mo"], + ], + [ + common1Month5, leapMonth5, + [-1, 0, 0, 0, "M05-M05 common-leap backwards is -1y"], + [0, -13, 0, 0, "M05-M05 common-leap backwards is -13mo not -12mo"], + ], + [ + leapMonth5, common2Month5, + [-1, 0, 0, 0, "M05-M05 leap-common backwards is -1y"], + [0, -12, 0, 0, "M05-M05 leap-common backwards is -12mo"], + ], + [ + common1Month5, common2Month5, + [-2, 0, 0, 0, "M05-M05 common-common backwards is -2y"], + [0, -25, 0, 0, "M05-M05 common-common backwards is -25mo not -24mo"], + ], + [ + common1Month4, leapMonth4L, + [-1, -1, 0, 0, "M04-M04L backwards is -1y -1mo"], + [0, -13, 0, 0, "M04-M04L backwards is -13mo"], + ], + [ + leapMonth4L, common2Month4, + [0, -12, 0, 0, "M04L-M04 backwards is -12mo not -1y"], + [0, -12, 0, 0, "M04L-M04 backwards is -12mo"], + ], + [ + common1Month5, leapMonth4L, + [0, -12, 0, 0, "M05-M04L backwards is -12mo not -1y"], + [0, -12, 0, 0, "M05-M04L backwards is -12mo"], + ], + [ + leapMonth4L, common2Month5, + [-1, -1, 0, 0, "M04L-M05 backwards is -1y -1mo (exhibits calendar-specific constraining)"], + [0, -13, 0, 0, "M04L-M05 backwards is -13mo"], + ], + [ + common1Month6, leapMonth5, + [0, -12, 0, 0, "M06-M05 common-leap backwards is -12mo not -11mo"], + [0, -12, 0, 0, "M06-M05 common-leap backwards is -12mo not -11mo"], + ], + + // Negative + [ + common2Month4, leapMonth4, + [1, 0, 0, 0, "M04-M04 common-leap is 1y"], + [0, 13, 0, 0, "M04-M04 common-leap is 13mo not 12mo"], + ], + [ + leapMonth4, common1Month4, + [1, 0, 0, 0, "M04-M04 leap-common is 1y"], + [0, 12, 0, 0, "M04-M04 leap-common is 12mo not 13mo"], + ], + [ + common2Month4, common1Month4, + [2, 0, 0, 0, "M04-M04 common-common is 2y"], + [0, 25, 0, 0, "M04-M04 common-common is 25mo not 24mo"], + ], + [ + common2Month5, leapMonth5, + [1, 0, 0, 0, "M05-M05 common-leap is 1y"], + [0, 12, 0, 0, "M05-M05 common-leap is 12mo not 13mo"], + ], + [ + leapMonth5, common1Month5, + [1, 0, 0, 0, "M05-M05 leap-common is 1y"], + [0, 13, 0, 0, "M05-M05 leap-common is 13mo not 12mo"], + ], + [ + common2Month5, common1Month5, + [2, 0, 0, 0, "M05-M05 common-common is 2y"], + [0, 25, 0, 0, "M05-M05 common-common is 25mo not 24mo"], + ], + [ + common2Month4, leapMonth4L, + [0, 12, 0, 0, "M04-M04L is 12mo not 1y"], + [0, 12, 0, 0, "M04-M04L is 12mo"], + ], + [ + leapMonth4L, common1Month4, + [1, 0, 0, 0, "M04L-M04 is 1y not 1y 1mo (exhibits calendar-specific constraining)"], + [0, 13, 0, 0, "M04L-M04 is 13mo"], + ], + [ + common2Month5, leapMonth4L, + [1, 1, 0, 0, "M05-M04L is 1y 1mo"], + [0, 13, 0, 0, "M05-M04L is 13mo"], + ], + [ + leapMonth4L, common1Month5, + [0, 12, 0, 0, "M04L-M05 is 12mo not 1y"], + [0, 12, 0, 0, "M04L-M05 is 12mo"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.since(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.since(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.since(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.since(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.since(two); + const resultDaysISO = oneISO.since(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/since/leap-months-hebrew.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/since/leap-months-hebrew.js @@ -0,0 +1,171 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Difference across leap months in hebrew calendar +esid: sec-temporal.plaindate.prototype.since +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +// 5784 is a leap year. +// M05 - Shevat +// M05L - Adar I +// M06 - Adar II +// M07 - Nisan + +const calendar = "hebrew"; +const options = { overflow: "reject" }; + +const common1Shevat = Temporal.PlainDate.from({ year: 5783, monthCode: "M05", day: 1, calendar }, options); +const common1Adar = Temporal.PlainDate.from({ year: 5783, monthCode: "M06", day: 1, calendar }, options); +const common1Nisan = Temporal.PlainDate.from({ year: 5783, monthCode: "M07", day: 1, calendar }, options); +const leapShevat = Temporal.PlainDate.from({ year: 5784, monthCode: "M05", day: 1, calendar }, options); +const leapAdarI = Temporal.PlainDate.from({ year: 5784, monthCode: "M05L", day: 1, calendar }, options); +const leapAdarII = Temporal.PlainDate.from({ year: 5784, monthCode: "M06", day: 1, calendar }, options); +const common2Shevat = Temporal.PlainDate.from({ year: 5785, monthCode: "M05", day: 1, calendar }, options); +const common2Adar = Temporal.PlainDate.from({ year: 5785, monthCode: "M06", day: 1, calendar }, options); + +// (receiver, argument, years test data, months test data) +// test data: expected years, months, weeks, days, description +// largestUnit years: make sure some cases where the answer is 12 months do not +// balance up to 1 year +// largestUnit months: similar to years, but make sure number of months in year +// is computed correctly +// For largestUnit of days and weeks, the results should be identical to what +// the ISO calendar gives for the corresponding ISO dates +const tests = [ + [ + common1Shevat, leapShevat, + [-1, 0, 0, 0, "M05-M05 common-leap backwards is -1y"], + [0, -12, 0, 0, "M05-M05 common-leap backwards is -12mo"], + ], + [ + leapShevat, common2Shevat, + [-1, 0, 0, 0, "M05-M05 leap-common backwards is -1y"], + [0, -13, 0, 0, "M05-M05 leap-common backwards is -13mo not -12mo"], + ], + [ + common1Shevat, common2Shevat, + [-2, 0, 0, 0, "M05-M05 common-common backwards is -2y"], + [0, -25, 0, 0, "M05-M05 common-common backwards is -25mo not -24mo"], + ], + [ + common1Adar, leapAdarII, + [-1, 0, 0, 0, "M06-M06 common-leap backwards is -1y"], + [0, -13, 0, 0, "M06-M06 common-leap backwards is -13mo not -12mo"], + ], + [ + leapAdarII, common2Adar, + [-1, 0, 0, 0, "M06-M06 leap-common backwards is -1y"], + [0, -12, 0, 0, "M06-M06 leap-common backwards is -12mo"], + ], + [ + common1Adar, common2Adar, + [-2, 0, 0, 0, "M06-M06 common-common backwards is -2y"], + [0, -25, 0, 0, "M06-M06 common-common backwards is -25mo not -24mo"], + ], + [ + common1Shevat, leapAdarI, + [-1, -1, 0, 0, "M05-M05L backwards is -1y -1mo"], + [0, -13, 0, 0, "M05-M05L backwards is -13mo"], + ], + [ + leapAdarI, common2Shevat, + [0, -12, 0, 0, "M05L-M05 backwards is -12mo not -1y"], + [0, -12, 0, 0, "M05L-M05 backwards is -12mo"], + ], + [ + common1Adar, leapAdarI, + [0, -12, 0, 0, "M06-M05L backwards is -12mo not -1y"], + [0, -12, 0, 0, "M06-M05L backwards is -12mo"], + ], + [ + leapAdarI, common2Adar, + [-1, 0, 0, 0, "M05L-M06 backwards is -1y (exhibits calendar-specific constraining)"], + [0, -13, 0, 0, "M05L-M06 backwards is -13mo"], + ], + [ + common1Nisan, leapAdarII, + [0, -12, 0, 0, "M07-M06 common-leap backwards is -12mo not -11mo"], + [0, -12, 0, 0, "M07-M06 common-leap backwards is -12mo not -11mo"], + ], + + // Negative + [ + common2Shevat, leapShevat, + [1, 0, 0, 0, "M05-M05 common-leap is 1y"], + [0, 13, 0, 0, "M05-M05 common-leap is 13mo not 12mo"], + ], + [ + leapShevat, common1Shevat, + [1, 0, 0, 0, "M05-M05 leap-common is 1y"], + [0, 12, 0, 0, "M05-M05 leap-common is 12mo not 13mo"], + ], + [ + common2Shevat, common1Shevat, + [2, 0, 0, 0, "M05-M05 common-common is 2y"], + [0, 25, 0, 0, "M05-M05 common-common is 25mo not 24mo"], + ], + [ + common2Adar, leapAdarII, + [1, 0, 0, 0, "M06-M06 common-leap is 1y"], + [0, 12, 0, 0, "M06-M06 common-leap is 12mo not 13mo"], + ], + [ + leapAdarII, common1Adar, + [1, 0, 0, 0, "M06-M06 leap-common is 1y"], + [0, 13, 0, 0, "M06-M06 leap-common is 13mo not 12mo"], + ], + [ + common2Adar, common1Adar, + [2, 0, 0, 0, "M06-M06 common-common is 2y"], + [0, 25, 0, 0, "M06-M06 common-common is 25mo not 24mo"], + ], + [ + common2Shevat, leapAdarI, + [0, 12, 0, 0, "M05-M05L is 12mo not 1y"], + [0, 12, 0, 0, "M05-M05L is 12mo"], + ], + [ + leapAdarI, common1Shevat, + [1, 1, 0, 0, "M05L-M05 is 1y 1mo (exhibits calendar-specific constraining)"], + [0, 13, 0, 0, "M05L-M05 is 13mo"], + ], + [ + common2Adar, leapAdarI, + [1, 1, 0, 0, "M06-M05L is 1y 1mo"], + [0, 13, 0, 0, "M06-M05L is 13mo"], + ], + [ + leapAdarI, common1Adar, + [0, 12, 0, 0, "M05L-M06 is 12mo not 1y"], + [0, 12, 0, 0, "M05L-M06 is 12mo"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.since(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.since(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.since(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.since(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.since(two); + const resultDaysISO = oneISO.since(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/basic-chinese.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/basic-chinese.js @@ -0,0 +1,64 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.subtract +description: Basic addition and subtraction in the chinese calendar +features: [Temporal, Intl.Era-monthcode] +includes: [temporalHelpers.js] +---*/ + +const calendar = "chinese"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const duration1 = new Temporal.Duration(0, -1); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2019, monthCode: "M11", day: 1, calendar }, options).subtract(duration1), + 2019, 12, "M12", 1, "add 1 month, with result in same year" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2019, monthCode: "M12", day: 1, calendar }, options).subtract(duration1), + 2020, 1, "M01", 1, "add 1 month, with result in next year" +); + +// Weeks + +const months2weeks3 = new Temporal.Duration(0, /* months = */ -2, /* weeks = */ -3); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2021, monthCode: "M01", day: 1, calendar }, options).subtract(months2weeks3), + 2021, 3, "M03", 22, "add 2 months 3 weeks from non-leap day/month, ending in same year" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2021, monthCode: "M12", day: 29, calendar }, options).subtract(months2weeks3), + 2022, 3, "M03", 21, "add 2 months 3 weeks from end of year to next year" +); + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ -10); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2021, monthCode: "M01", day: 1, calendar }, options).subtract(days10), + 2021, 1, "M01", 11, "add 10 days, ending in same month" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2021, monthCode: "M01", day: 29, calendar }, options).subtract(days10), + 2021, 2, "M02", 10, "add 10 days, ending in following month" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2021, monthCode: "M12", day: 29, calendar }, options).subtract(days10), + 2022, 1, "M01", 10, "add 10 days, ending in following year" +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/basic-dangi.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/basic-dangi.js @@ -0,0 +1,64 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.subtract +description: Basic addition and subtraction in the dangi calendar +features: [Temporal, Intl.Era-monthcode] +includes: [temporalHelpers.js] +---*/ + +const calendar = "dangi"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const duration1 = new Temporal.Duration(0, -1); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2019, monthCode: "M11", day: 1, calendar }, options).subtract(duration1), + 2019, 12, "M12", 1, "add 1 month, with result in same year" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2019, monthCode: "M12", day: 1, calendar }, options).subtract(duration1), + 2020, 1, "M01", 1, "add 1 month, with result in next year" +); + +// Weeks + +const months2weeks3 = new Temporal.Duration(0, /* months = */ -2, /* weeks = */ -3); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2021, monthCode: "M01", day: 1, calendar }, options).subtract(months2weeks3), + 2021, 3, "M03", 22, "add 2 months 3 weeks from non-leap day/month, ending in same year" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2021, monthCode: "M12", day: 29, calendar }, options).subtract(months2weeks3), + 2022, 3, "M03", 21, "add 2 months 3 weeks from end of year to next year" +); + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ -10); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2021, monthCode: "M01", day: 1, calendar }, options).subtract(days10), + 2021, 1, "M01", 11, "add 10 days, ending in same month" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2021, monthCode: "M01", day: 29, calendar }, options).subtract(days10), + 2021, 2, "M02", 10, "add 10 days, ending in following month" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2021, monthCode: "M12", day: 29, calendar }, options).subtract(days10), + 2022, 1, "M01", 10, "add 10 days, ending in following year" +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/basic-hebrew.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/basic-hebrew.js @@ -0,0 +1,30 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.subtract +description: Basic addition and subtraction in the hebrew calendar +features: [Temporal] +includes: [temporalHelpers.js] +---*/ + +const calendar = "hebrew"; +const options = { overflow: "reject" }; + +// Years + +// Months + +// Weeks + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ -10); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 5785, monthCode: "M01", day: 1, calendar }, options).subtract(days10), + 5785, 1, "M01", 11, "adding 10 days", "am", 5785 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/basic-islamic-civil.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/basic-islamic-civil.js @@ -0,0 +1,84 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.subtract +description: Basic addition and subtraction in the islamic-civil calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-civil"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const date1 = Temporal.PlainDate.from({ year: 1445, monthCode: "M01", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date1.subtract(new Temporal.Duration(0, -8)), + 1445, 9, "M09", 1, "Adding 8 months to Muharram 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + date1.subtract(new Temporal.Duration(0, -11)), + 1445, 12, "M12", 1, "Adding 11 months to Muharram 1445 lands in Dhu al-Hijjah", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + date1.subtract(new Temporal.Duration(0, -12)), + 1446, 1, "M01", 1, "Adding 12 months to Muharram 1445 lands in Muharram 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M06", day: 15, calendar }).subtract(new Temporal.Duration(0, -13)), + 1446, 7, "M07", 15, "Adding 13 months to Jumada II 1445 lands in Rajab 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M03", day: 15, calendar }, options).subtract(new Temporal.Duration(0, -6)), + 1445, 9, "M09", 15, "Adding 6 months to Rabi I 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1444, monthCode: "M10", day: 1, calendar }).subtract(new Temporal.Duration(0, -5)), + 1445, 3, "M03", 1, "Adding 5 months to Shawwal 1444 crosses to 1445", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1400, monthCode: "M01", day: 1, calendar }).subtract(new Temporal.Duration(0, -100)), + 1408, 5, "M05", 1, "Adding a large number of months", + "ah", 1408 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M09", day: 1, calendar }, options).subtract(new Temporal.Duration(0, 8)), + 1445, 1, "M01", 1, "Subtracting 8 months from Ramadan 1445 lands in Muharram", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M06", day: 1, calendar }, options).subtract(new Temporal.Duration(0, 12)), + 1444, 6, "M06", 1, "Subtracting 12 months from Jumada II 1445 lands in Jumada II 1444", + "ah", 1444 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M02", day: 15, calendar }, options).subtract(new Temporal.Duration(0, 5)), + 1444, 9, "M09", 15, "Subtracting 5 months from Safar 1445 crosses to Ramadan 1444", + "ah", 1444 +); + +// Weeks + +// Days + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/basic-islamic-tbla.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/basic-islamic-tbla.js @@ -0,0 +1,84 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.subtract +description: Basic addition and subtraction in the islamic-tbla calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-tbla"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const date1 = Temporal.PlainDate.from({ year: 1445, monthCode: "M01", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date1.subtract(new Temporal.Duration(0, -8)), + 1445, 9, "M09", 1, "Adding 8 months to Muharram 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + date1.subtract(new Temporal.Duration(0, -11)), + 1445, 12, "M12", 1, "Adding 11 months to Muharram 1445 lands in Dhu al-Hijjah", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + date1.subtract(new Temporal.Duration(0, -12)), + 1446, 1, "M01", 1, "Adding 12 months to Muharram 1445 lands in Muharram 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M06", day: 15, calendar }).subtract(new Temporal.Duration(0, -13)), + 1446, 7, "M07", 15, "Adding 13 months to Jumada II 1445 lands in Rajab 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M03", day: 15, calendar }, options).subtract(new Temporal.Duration(0, -6)), + 1445, 9, "M09", 15, "Adding 6 months to Rabi I 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1444, monthCode: "M10", day: 1, calendar }).subtract(new Temporal.Duration(0, -5)), + 1445, 3, "M03", 1, "Adding 5 months to Shawwal 1444 crosses to 1445", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1400, monthCode: "M01", day: 1, calendar }).subtract(new Temporal.Duration(0, -100)), + 1408, 5, "M05", 1, "Adding a large number of months", + "ah", 1408 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M09", day: 1, calendar }, options).subtract(new Temporal.Duration(0, 8)), + 1445, 1, "M01", 1, "Subtracting 8 months from Ramadan 1445 lands in Muharram", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M06", day: 1, calendar }, options).subtract(new Temporal.Duration(0, 12)), + 1444, 6, "M06", 1, "Subtracting 12 months from Jumada II 1445 lands in Jumada II 1444", + "ah", 1444 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M02", day: 15, calendar }, options).subtract(new Temporal.Duration(0, 5)), + 1444, 9, "M09", 15, "Subtracting 5 months from Safar 1445 crosses to Ramadan 1444", + "ah", 1444 +); + +// Weeks + +// Days + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/basic-islamic-umalqura.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/basic-islamic-umalqura.js @@ -0,0 +1,84 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.subtract +description: Basic addition and subtraction in the islamic-umalqura calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-umalqura"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const date1 = Temporal.PlainDate.from({ year: 1445, monthCode: "M01", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date1.subtract(new Temporal.Duration(0, -8)), + 1445, 9, "M09", 1, "Adding 8 months to Muharram 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + date1.subtract(new Temporal.Duration(0, -11)), + 1445, 12, "M12", 1, "Adding 11 months to Muharram 1445 lands in Dhu al-Hijjah", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + date1.subtract(new Temporal.Duration(0, -12)), + 1446, 1, "M01", 1, "Adding 12 months to Muharram 1445 lands in Muharram 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M06", day: 15, calendar }).subtract(new Temporal.Duration(0, -13)), + 1446, 7, "M07", 15, "Adding 13 months to Jumada II 1445 lands in Rajab 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M03", day: 15, calendar }, options).subtract(new Temporal.Duration(0, -6)), + 1445, 9, "M09", 15, "Adding 6 months to Rabi I 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1444, monthCode: "M10", day: 1, calendar }).subtract(new Temporal.Duration(0, -5)), + 1445, 3, "M03", 1, "Adding 5 months to Shawwal 1444 crosses to 1445", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1400, monthCode: "M01", day: 1, calendar }).subtract(new Temporal.Duration(0, -100)), + 1408, 5, "M05", 1, "Adding a large number of months", + "ah", 1408 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M09", day: 1, calendar }, options).subtract(new Temporal.Duration(0, 8)), + 1445, 1, "M01", 1, "Subtracting 8 months from Ramadan 1445 lands in Muharram", + "ah", 1445 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M06", day: 1, calendar }, options).subtract(new Temporal.Duration(0, 12)), + 1444, 6, "M06", 1, "Subtracting 12 months from Jumada II 1445 lands in Jumada II 1444", + "ah", 1444 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1445, monthCode: "M02", day: 15, calendar }, options).subtract(new Temporal.Duration(0, 5)), + 1444, 9, "M09", 15, "Subtracting 5 months from Safar 1445 crosses to Ramadan 1444", + "ah", 1444 +); + +// Weeks + +// Days + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/browser.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/browser.js diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/constrain-day-islamic-civil.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/constrain-day-islamic-civil.js @@ -0,0 +1,43 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.subtract +description: Constraining the day for 29/30-day months in islamic-civil calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-civil"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const months1 = new Temporal.Duration(0, -1); +const months1n = new Temporal.Duration(0, 1); + +const date1 = Temporal.PlainDate.from({ year: 1445, monthCode: "M01", day: 30, calendar }, options); +TemporalHelpers.assertPlainDate( + date1.subtract(months1), + 1445, 2, "M02", 29, "Day is constrained when adding months to a 30-day month and landing in a 29-day month", + "ah", 1445 +); + +assert.throws(RangeError, function () { + date1.subtract(months1, options); +}, "Adding months to a 30-day month and landing in a 29-day month rejects"); + +TemporalHelpers.assertPlainDate( + date1.subtract(months1n), + 1444, 12, "M12", 29, "Day is constrained when subtracting months from a 30-day month and landing in a 29-day month", + "ah", 1444 +); + +assert.throws(RangeError, function () { + date1.subtract(months1n, options); +}, "Subtracting months from a 30-day month and landing in a 29-day month rejects"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/constrain-day-islamic-tbla.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/constrain-day-islamic-tbla.js @@ -0,0 +1,43 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.subtract +description: Constraining the day for 29/30-day months in islamic-tbla calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-tbla"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const months1 = new Temporal.Duration(0, -1); +const months1n = new Temporal.Duration(0, 1); + +const date1 = Temporal.PlainDate.from({ year: 1445, monthCode: "M01", day: 30, calendar }, options); +TemporalHelpers.assertPlainDate( + date1.subtract(months1), + 1445, 2, "M02", 29, "Day is constrained when adding months to a 30-day month and landing in a 29-day month", + "ah", 1445 +); + +assert.throws(RangeError, function () { + date1.subtract(months1, options); +}, "Adding months to a 30-day month and landing in a 29-day month rejects"); + +TemporalHelpers.assertPlainDate( + date1.subtract(months1n), + 1444, 12, "M12", 29, "Day is constrained when subtracting months from a 30-day month and landing in a 29-day month", + "ah", 1444 +); + +assert.throws(RangeError, function () { + date1.subtract(months1n, options); +}, "Subtracting months from a 30-day month and landing in a 29-day month rejects"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/constrain-day-islamic-umalqura.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/constrain-day-islamic-umalqura.js @@ -0,0 +1,43 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.subtract +description: Constraining the day for 29/30-day months in islamic-umalqura calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-umalqura"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const months1 = new Temporal.Duration(0, /* months = */ -1); +const months1n = new Temporal.Duration(0, 1); + +const date1 = Temporal.PlainDate.from({ year: 1447, monthCode: "M01", day: 30, calendar }, options); +TemporalHelpers.assertPlainDate( + date1.subtract(months1), + 1447, 2, "M02", 29, "Day is constrained when adding months to a 30-day month and landing in a 29-day month", + "ah", 1447 +); + +assert.throws(RangeError, function () { + date1.subtract(months1, options); +}, "Adding months to a 30-day month and landing in a 29-day month rejects"); + +TemporalHelpers.assertPlainDate( + date1.subtract(months1n), + 1446, 12, "M12", 29, "Day is constrained when subtracting months from a 30-day month and landing in a 29-day month", + "ah", 1446 +); + +assert.throws(RangeError, function () { + date1.subtract(months1n, options); +}, "Subtracting months from a 30-day month and landing in a 29-day month rejects"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/era-boundary-ethiopic.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/era-boundary-ethiopic.js @@ -0,0 +1,51 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.subtract +description: Adding years works correctly across era boundaries in ethiopic calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const duration5 = new Temporal.Duration(-5); +const duration5n = new Temporal.Duration(5); +const calendar = "ethiopic"; +const options = { overflow: "reject" }; + +const date1 = Temporal.PlainDate.from({ era: "aa", eraYear: 5500, monthCode: "M01", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date1.subtract(new Temporal.Duration(-1)), + 1, 1, "M01", 1, "Adding 1 year to last year of Amete Alem era lands in year 1 of incarnation era", + "am", 1 +); + +const date2 = Temporal.PlainDate.from({ era: "am", eraYear: 2000, monthCode: "M06", day: 15, calendar }, options); +TemporalHelpers.assertPlainDate( + date2.subtract(duration5), + 2005, 6, "M06", 15, "Adding 5 years within incarnation era", + "am", 2005 +); + +const date3 = Temporal.PlainDate.from({ era: "aa", eraYear: 5450, monthCode: "M07", day: 12, calendar }, options); +TemporalHelpers.assertPlainDate( + date3.subtract(duration5), + -45, 7, "M07", 12, "Adding 5 years within Amete Alem era", + "aa", 5455 +); + +TemporalHelpers.assertPlainDate( + date2.subtract(duration5n), + 1995, 6, "M06", 15, "Subtracting 5 years within incarnation era", + "am", 1995 +); + +const date4 = Temporal.PlainDate.from({ era: "am", eraYear: 5, monthCode: "M01", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date4.subtract(duration5n), + 0, 1, "M01", 1, "Subtracting 5 years from year 5 lands in last year of Amete Alem era, arithmetic year 0", + "aa", 5500 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/era-boundary-gregory.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/era-boundary-gregory.js @@ -0,0 +1,71 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.subtract +description: Adding years works correctly across era boundaries in gregory calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const duration1 = new Temporal.Duration(-1); +const duration1n = new Temporal.Duration(1); +const calendar = "gregory"; +const options = { overflow: "reject" }; + +const date1 = Temporal.PlainDate.from({ era: "bce", eraYear: 2, monthCode: "M06", day: 15, calendar }, options); +TemporalHelpers.assertPlainDate( + date1.subtract(duration1), + 0, 6, "M06", 15, "Adding 1 year to 2 BCE lands in 1 BCE (counts backwards)", + "bce", 1 +); + +const date2 = Temporal.PlainDate.from({ era: "bce", eraYear: 1, monthCode: "M06", day: 15, calendar }, options); +TemporalHelpers.assertPlainDate( + date2.subtract(duration1), + 1, 6, "M06", 15, "Adding 1 year to 1 BCE lands in 1 CE (no year zero)", + "ce", 1 +); + +const date3 = Temporal.PlainDate.from({ era: "ce", eraYear: 1, monthCode: "M06", day: 15, calendar }, options); +TemporalHelpers.assertPlainDate( + date3.subtract(duration1), + 2, 6, "M06", 15, "Adding 1 year to 1 CE lands in 2 CE", + "ce", 2 +); + +const date4 = Temporal.PlainDate.from({ era: "bce", eraYear: 5, monthCode: "M03", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date4.subtract(new Temporal.Duration(-10)), + 6, 3, "M03", 1, "Adding 10 years to 5 BCE lands in 6 CE (no year zero)", + "ce", 6 +); + +const date5 = Temporal.PlainDate.from({ era: "ce", eraYear: 2, monthCode: "M06", day: 15, calendar }, options); +TemporalHelpers.assertPlainDate( + date5.subtract(duration1n), + 1, 6, "M06", 15, "Subtracting 1 year from 2 CE lands in 1 CE", + "ce", 1 +); + +TemporalHelpers.assertPlainDate( + date3.subtract(duration1n), + 0, 6, "M06", 15, "Subtracting 1 year from 1 CE lands in 1 BCE", + "bce", 1 +); + +TemporalHelpers.assertPlainDate( + date2.subtract(duration1n), + -1, 6, "M06", 15, "Subtracting 1 year from 1 BCE lands in 2 BCE", + "bce", 2 +); + +const date6 = Temporal.PlainDate.from({ era: "ce", eraYear: 5, monthCode: "M03", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date6.subtract(new Temporal.Duration(10)), + -5, 3, "M03", 1, "Subtracting 10 years from 5 CE lands in 6 BCE", + "bce", 6 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/era-boundary-japanese.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/era-boundary-japanese.js @@ -0,0 +1,84 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.subtract +description: > + Adding years works correctly across era boundaries in calendars with eras +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +// Reiwa era started on May 1, 2019 (Reiwa 1 = 2019) +// Heisei era: 1989-2019 (Heisei 31 ended April 30, 2019) + +const duration1 = new Temporal.Duration(-1); +const duration1n = new Temporal.Duration(1); +const calendar = "japanese"; +const options = { overflow: "reject" }; + +const date1 = Temporal.PlainDate.from({ era: "heisei", eraYear: 30, monthCode: "M03", day: 15, calendar }, options); +TemporalHelpers.assertPlainDate( + date1.subtract(duration1), + 2019, 3, "M03", 15, "Adding 1 year to Heisei 30 March (before May 1) lands in Heisei 31 March", + "heisei", 31 +); + +const date2 = Temporal.PlainDate.from({ era: "heisei", eraYear: 31, monthCode: "M04", day: 15, calendar }, options); +TemporalHelpers.assertPlainDate( + date2.subtract(duration1), + 2020, 4, "M04", 15, "Adding 1 year to Heisei 31 April (before May 1) lands in Reiwa 2 April", + "reiwa", 2 +); + +const date3 = Temporal.PlainDate.from({ era: "heisei", eraYear: 30, monthCode: "M06", day: 12, calendar }, options); +TemporalHelpers.assertPlainDate( + date3.subtract(duration1), + 2019, 6, "M06", 12, "Adding 1 year to Heisei 30 June (after May 1) lands in Reiwa 1 June", + "reiwa", 1 +); + +const date4 = Temporal.PlainDate.from({ era: "reiwa", eraYear: 1, monthCode: "M06", day: 10, calendar }, options); +TemporalHelpers.assertPlainDate( + date4.subtract(duration1), + 2020, 6, "M06", 10, "Adding 1 year to Reiwa 1 June lands in Reiwa 2 June", + "reiwa", 2 +); + +const date5 = Temporal.PlainDate.from({ era: "heisei", eraYear: 28, monthCode: "M07", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date5.subtract(new Temporal.Duration(-3)), + 2019, 7, "M07", 1, "Multiple years across era boundary: Adding 3 years to Heisei 28 July lands in Reiwa 1 July", + "reiwa", 1 +); + +const date6 = Temporal.PlainDate.from({ era: "reiwa", eraYear: 2, monthCode: "M06", day: 15, calendar }, options); +TemporalHelpers.assertPlainDate( + date6.subtract(duration1n), + 2019, 6, "M06", 15, "Subtracting 1 year from Reiwa 2 June lands in Reiwa 1 June", + "reiwa", 1 +); + +const date7 = Temporal.PlainDate.from({ era: "reiwa", eraYear: 2, monthCode: "M03", day: 15, calendar }, options); +TemporalHelpers.assertPlainDate( + date7.subtract(duration1n), + 2019, 3, "M03", 15, "Subtracting 1 year from Reiwa 2 March lands in Heisei 31 March", + "heisei", 31 +); + +const date8 = Temporal.PlainDate.from({ era: "reiwa", eraYear: 1, monthCode: "M07", day: 10, calendar }, options); +TemporalHelpers.assertPlainDate( + date8.subtract(duration1n), + 2018, 7, "M07", 10, "Subtracting 1 year from Reiwa 1 July lands in Heisei 30 July", + "heisei", 30 +); + +const date9 = Temporal.PlainDate.from({ era: "reiwa", eraYear: 4, monthCode: "M02", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date9.subtract(new Temporal.Duration(5)), + 2017, 2, "M02", 1, "Subtracting 5 years from Reiwa 4 February lands in Heisei 29 February", + "heisei", 29 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/era-boundary-roc.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/era-boundary-roc.js @@ -0,0 +1,66 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.subtract +description: > + Adding years works correctly across era boundaries in calendars with eras +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const duration1 = new Temporal.Duration(-1); +const duration1n = new Temporal.Duration(1); +const calendar = "roc"; +const options = { overflow: "reject" }; + +const date1 = Temporal.PlainDate.from({ era: "broc", eraYear: 2, monthCode: "M06", day: 15, calendar }, options); +TemporalHelpers.assertPlainDate( + date1.subtract(duration1), + 0, 6, "M06", 15, "Adding 1 year to 2 BROC lands in 1 BROC (counts backwards)", + "broc", 1 +); + +const date2 = Temporal.PlainDate.from({ era: "broc", eraYear: 1, monthCode: "M06", day: 15, calendar }, options); +TemporalHelpers.assertPlainDate( + date2.subtract(duration1), + 1, 6, "M06", 15, "Adding 1 year to 1 BROC lands in 1 ROC (no year zero)", + "roc", 1 +); + +const date3 = Temporal.PlainDate.from({ era: "roc", eraYear: 1, monthCode: "M06", day: 15, calendar }, options); +TemporalHelpers.assertPlainDate( + date3.subtract(duration1), + 2, 6, "M06", 15, "Adding 1 year to 1 ROC lands in 2 ROC", + "roc", 2 +); + +const date4 = Temporal.PlainDate.from({ era: "broc", eraYear: 5, monthCode: "M03", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date4.subtract(new Temporal.Duration(-10)), + 6, 3, "M03", 1, "Adding 10 years to 5 BROC lands in 6 ROC (no year zero)", + "roc", 6 +); + +const date5 = Temporal.PlainDate.from({ era: "roc", eraYear: 5, monthCode: "M06", day: 15, calendar }, options); +TemporalHelpers.assertPlainDate( + date5.subtract(duration1n), + 4, 6, "M06", 15, "Subtracting 1 year from ROC 5 lands in ROC 4", + "roc", 4 +); + +TemporalHelpers.assertPlainDate( + date3.subtract(duration1n), + 0, 6, "M06", 15, "Subtracting 1 year from ROC 1 lands in BROC 1", + "broc", 1 +); + +const date6 = Temporal.PlainDate.from({ era: "roc", eraYear: 10, monthCode: "M03", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date6.subtract(new Temporal.Duration(15)), + -5, 3, "M03", 1, "Subtracting 15 years from ROC 10 lands in BROC 6", + "broc", 6 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/leap-months-chinese.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/leap-months-chinese.js @@ -0,0 +1,129 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.subtract +description: Arithmetic around leap months in the chinese calendar +features: [Temporal, Intl.Era-monthcode] +includes: [temporalHelpers.js] +---*/ + +const calendar = "chinese"; +const options = { overflow: "reject" }; + +// Years + +const years1 = new Temporal.Duration(-1); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2019, monthCode: "M01", day: 1, calendar }, options).subtract(years1), + 2020, 1, "M01", 1, "add 1 year from non-leap day" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1966, monthCode: "M03L", day: 1, calendar }, options).subtract(years1), + 1967, 3, "M03", 1, "add 1 year from leap month" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1938, monthCode: "M07L", day: 30, calendar }, options).subtract(years1), + 1939, 7, "M07", 29, "add 1 year from leap day in leap month" +); + +// Months + +const months1 = new Temporal.Duration(0, -1); +const months1n = new Temporal.Duration(0, 1); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1947, monthCode: "M02L", day: 1, calendar }, options).subtract(months1), + 1947, 4, "M03", 1, "add 1 month, starting at start of leap month" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1955, monthCode: "M03L", day: 1, calendar }, options).subtract(months1), + 1955, 5, "M04", 1, "add 1 month, starting at start of leap month with 30 days" +); + +const date1 = Temporal.PlainDate.from({ year: 2020, monthCode: "M03", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date1.subtract(months1), + 2020, 4, "M04", 1, "adding 1 month to M03 in leap year lands in M04 (not M04L)" +); + +TemporalHelpers.assertPlainDate( + date1.subtract(new Temporal.Duration(0, -2)), + 2020, 5, "M04L", 1, "adding 2 months to M03 in leap year lands in M04L (leap month)" +); + +TemporalHelpers.assertPlainDate( + date1.subtract(new Temporal.Duration(0, -3)), + 2020, 6, "M05", 1, "adding 3 months to M03 in leap year lands in M05 (not M06)" +); + +const date2 = Temporal.PlainDate.from({ year: 2020, monthCode: "M06", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date2.subtract(months1n), + 2020, 6, "M05", 1, "Subtracting 1 month from M06 in leap year lands in M05" +); + +TemporalHelpers.assertPlainDate( + date2.subtract(new Temporal.Duration(0, 2)), + 2020, 5, "M04L", 1, "Subtracting 2 months from M06 in leap year lands in M04L (leap month)" +); + +TemporalHelpers.assertPlainDate( + date2.subtract(new Temporal.Duration(0, 3)), + 2020, 4, "M04", 1, "Subtracting 3 months from M06 in leap year lands in M04 (not M03)" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2020, monthCode: "M05", day: 1, calendar }, options).subtract(months1n), + 2020, 5, "M04L", 1, "Subtracting 1 month from M05 in leap year lands in M04L" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2020, monthCode: "M04L", day: 1, calendar }, options).subtract(months1n), + 2020, 4, "M04", 1, "Subtracting 1 month from M04L in calendar lands in M04" +); + +// Weeks + +const months2weeks3 = new Temporal.Duration(0, /* months = */ -2, /* weeks = */ -3); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1947, monthCode: "M02L", day: 29, calendar }, options).subtract(months2weeks3), + 1947, 6, "M05", 20, "add 2 months 3 weeks from last day leap month without leap day" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1955, monthCode: "M03L", day: 30, calendar }, options).subtract(months2weeks3), + 1955, 7, "M06", 21, "add 2 months 3 weeks from leap day in leap month" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1947, monthCode: "M01", day: 29, calendar }, options).subtract(months2weeks3), + 1947, 4, "M03", 21, "add 2 months 3 weeks from immediately before a leap month" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1955, monthCode: "M06", day: 29, calendar }, options).subtract(months2weeks3), + 1955, 10, "M09", 20, "add 2 months 3 weeks from immediately before a leap month" +); + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ -10); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1955, monthCode: "M03L", day: 30, calendar }, options).subtract(days10), + 1955, 5, "M04", 10, "add 10 days from leap day in leap month" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1947, monthCode: "M02L", day: 29, calendar }, options).subtract(days10), + 1947, 4, "M03", 10, "add 10 days from last day of leap month" +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/leap-months-dangi.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/leap-months-dangi.js @@ -0,0 +1,129 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.subtract +description: Arithmetic around leap months in the dangi calendar +features: [Temporal, Intl.Era-monthcode] +includes: [temporalHelpers.js] +---*/ + +const calendar = "dangi"; +const options = { overflow: "reject" }; + +// Years + +const years1 = new Temporal.Duration(-1); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2019, monthCode: "M01", day: 1, calendar }, options).subtract(years1), + 2020, 1, "M01", 1, "add 1 year from non-leap day" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1966, monthCode: "M03L", day: 1, calendar }, options).subtract(years1), + 1967, 3, "M03", 1, "add 1 year from leap month" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1938, monthCode: "M07L", day: 30, calendar }, options).subtract(years1), + 1939, 7, "M07", 29, "add 1 year from leap day in leap month" +); + +// Months + +const months1 = new Temporal.Duration(0, -1); +const months1n = new Temporal.Duration(0, 1); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1947, monthCode: "M02L", day: 1, calendar }, options).subtract(months1), + 1947, 4, "M03", 1, "add 1 month, starting at start of leap month" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1955, monthCode: "M03L", day: 1, calendar }, options).subtract(months1), + 1955, 5, "M04", 1, "add 1 month, starting at start of leap month with 30 days" +); + +const date1 = Temporal.PlainDate.from({ year: 2020, monthCode: "M03", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date1.subtract(months1), + 2020, 4, "M04", 1, "adding 1 month to M03 in leap year lands in M04 (not M04L)" +); + +TemporalHelpers.assertPlainDate( + date1.subtract(new Temporal.Duration(0, -2)), + 2020, 5, "M04L", 1, "adding 2 months to M03 in leap year lands in M04L (leap month)" +); + +TemporalHelpers.assertPlainDate( + date1.subtract(new Temporal.Duration(0, -3)), + 2020, 6, "M05", 1, "adding 3 months to M03 in leap year lands in M05 (not M06)" +); + +const date2 = Temporal.PlainDate.from({ year: 2020, monthCode: "M06", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date2.subtract(months1n), + 2020, 6, "M05", 1, "Subtracting 1 month from M06 in leap year lands in M05" +); + +TemporalHelpers.assertPlainDate( + date2.subtract(new Temporal.Duration(0, 2)), + 2020, 5, "M04L", 1, "Subtracting 2 months from M06 in leap year lands in M04L (leap month)" +); + +TemporalHelpers.assertPlainDate( + date2.subtract(new Temporal.Duration(0, 3)), + 2020, 4, "M04", 1, "Subtracting 3 months from M06 in leap year lands in M04 (not M03)" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2020, monthCode: "M05", day: 1, calendar }, options).subtract(months1n), + 2020, 5, "M04L", 1, "Subtracting 1 month from M05 in leap year lands in M04L" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 2020, monthCode: "M04L", day: 1, calendar }, options).subtract(months1n), + 2020, 4, "M04", 1, "Subtracting 1 month from M04L in calendar lands in M04" +); + +// Weeks + +const months2weeks3 = new Temporal.Duration(0, /* months = */ -2, /* weeks = */ -3); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1947, monthCode: "M02L", day: 29, calendar }, options).subtract(months2weeks3), + 1947, 6, "M05", 20, "add 2 months 3 weeks from last day leap month without leap day" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1955, monthCode: "M03L", day: 30, calendar }, options).subtract(months2weeks3), + 1955, 7, "M06", 21, "add 2 months 3 weeks from leap day in leap month" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1947, monthCode: "M01", day: 29, calendar }, options).subtract(months2weeks3), + 1947, 4, "M03", 21, "add 2 months 3 weeks from immediately before a leap month" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1955, monthCode: "M06", day: 29, calendar }, options).subtract(months2weeks3), + 1955, 10, "M09", 20, "add 2 months 3 weeks from immediately before a leap month" +); + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ -10); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1955, monthCode: "M03L", day: 30, calendar }, options).subtract(days10), + 1955, 5, "M04", 10, "add 10 days from leap day in leap month" +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 1947, monthCode: "M02L", day: 29, calendar }, options).subtract(days10), + 1947, 4, "M03", 10, "add 10 days from last day of leap month" +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/leap-months-hebrew.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/leap-months-hebrew.js @@ -0,0 +1,104 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.subtract +description: Arithmetic around leap months in the hebrew calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "hebrew"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const months1 = new Temporal.Duration(0, -1); +const months1n = new Temporal.Duration(0, 1); +const months2 = new Temporal.Duration(0, -2); +const months2n = new Temporal.Duration(0, 2); + +const date1 = Temporal.PlainDate.from({ year: 5784, monthCode: "M04", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date1.subtract(months1), + 5784, 5, "M05", 1, "Adding 1 month to M04 in leap year lands in M05 (Shevat)", + "am", 5784 +); + +TemporalHelpers.assertPlainDate( + date1.subtract(months2), + 5784, 6, "M05L", 1, "Adding 2 months to M04 in leap year lands in M05L (Adar I)", + "am", 5784 +); + +TemporalHelpers.assertPlainDate( + date1.subtract(new Temporal.Duration(0, -3)), + 5784, 7, "M06", 1, "Adding 3 months to M04 in leap year lands in M06 (Adar II)", + "am", 5784 +); + +const date2 = Temporal.PlainDate.from({ year: 5784, monthCode: "M05L", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date2.subtract(months1), + 5784, 7, "M06", 1, "Adding 1 month to M05L (Adar I) lands in M06 (Adar II)", + "am", 5784 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 5783, monthCode: "M04", day: 1, calendar }, options).subtract(months2), + 5783, 6, "M06", 1, "Adding 2 months to M04 in non-leap year lands in M06 (no M05L)", + "am", 5783 +); + +const date3 = Temporal.PlainDate.from({ year: 5784, monthCode: "M07", day: 1, calendar }, options); +TemporalHelpers.assertPlainDate( + date3.subtract(months1n), + 5784, 7, "M06", 1, "Subtracting 1 month from M07 in leap year lands in M06 (Adar II)", + "am", 5784 +); + +TemporalHelpers.assertPlainDate( + date3.subtract(months2n), + 5784, 6, "M05L", 1, "Subtracting 2 months from M07 in leap year lands in M05L (Adar I)", + "am", 5784 +); + +TemporalHelpers.assertPlainDate( + date3.subtract(new Temporal.Duration(0, 3)), + 5784, 5, "M05", 1, "Subtracting 3 months from M07 in leap year lands in M05 (Shevat)", + "am", 5784 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 5784, monthCode: "M06", day: 1, calendar }).subtract(months1n), + 5784, 6, "M05L", 1, "Subtracting 1 month from M06 (Adar II) in leap year lands in M05L (Adar I)", + "am", 5784 +); + +TemporalHelpers.assertPlainDate( + date2.subtract(months1n), + 5784, 5, "M05", 1, "Subtracting 1 month from M05L (Adar I) lands in M05 (Shevat)", + "am", 5784 +); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 5783, monthCode: "M07", day: 1, calendar }).subtract(months2n), + 5783, 5, "M05", 1, "Subtracting 2 months from M07 in non-leap year lands in M05 (no M05L)", + "am", 5783 +); + +// Weeks + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ -10); + +TemporalHelpers.assertPlainDate( + Temporal.PlainDate.from({ year: 5784, monthCode: "M05L", day: 30, calendar }, options).subtract(days10), + 5784, 7, "M06", 10, "add 10 days to leap day", "am", 5784 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/shell.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/subtract/shell.js @@ -0,0 +1,1252 @@ +// GENERATED, DO NOT EDIT +// file: temporalHelpers.js +// Copyright (C) 2021 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +description: | + This defines helper objects and functions for testing Temporal. +defines: [TemporalHelpers] +features: [Symbol.species, Symbol.iterator, Temporal] +---*/ + +const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u; + +function formatPropertyName(propertyKey, objectName = "") { + switch (typeof propertyKey) { + case "symbol": + if (Symbol.keyFor(propertyKey) !== undefined) { + return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`; + } else if (propertyKey.description.startsWith('Symbol.')) { + return `${objectName}[${propertyKey.description}]`; + } else { + return `${objectName}[Symbol('${propertyKey.description}')]` + } + case "string": + if (propertyKey !== String(Number(propertyKey))) { + if (ASCII_IDENTIFIER.test(propertyKey)) { + return objectName ? `${objectName}.${propertyKey}` : propertyKey; + } + return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']` + } + // fall through + default: + // integer or string integer-index + return `${objectName}[${propertyKey}]`; + } +} + +const SKIP_SYMBOL = Symbol("Skip"); + +var TemporalHelpers = { + /* + * Codes and maximum lengths of months in the ISO 8601 calendar. + */ + ISOMonths: [ + { month: 1, monthCode: "M01", daysInMonth: 31 }, + { month: 2, monthCode: "M02", daysInMonth: 29 }, + { month: 3, monthCode: "M03", daysInMonth: 31 }, + { month: 4, monthCode: "M04", daysInMonth: 30 }, + { month: 5, monthCode: "M05", daysInMonth: 31 }, + { month: 6, monthCode: "M06", daysInMonth: 30 }, + { month: 7, monthCode: "M07", daysInMonth: 31 }, + { month: 8, monthCode: "M08", daysInMonth: 31 }, + { month: 9, monthCode: "M09", daysInMonth: 30 }, + { month: 10, monthCode: "M10", daysInMonth: 31 }, + { month: 11, monthCode: "M11", daysInMonth: 30 }, + { month: 12, monthCode: "M12", daysInMonth: 31 } + ], + + /* + * List of known calendar eras and their possible aliases. + * + * https://tc39.es/proposal-intl-era-monthcode/#table-eras + */ + CalendarEras: { + buddhist: [ + { era: "be" }, + ], + coptic: [ + { era: "am" }, + ], + ethiopic: [ + { era: "aa", aliases: ["mundi"] }, + { era: "am", aliases: ["incar"] }, + ], + ethioaa: [ + { era: "aa", aliases: ["mundi"] }, + ], + gregory: [ + { era: "bce", aliases: ["bc"] }, + { era: "ce", aliases: ["ad"] }, + ], + hebrew: [ + { era: "am" }, + ], + indian: [ + { era: "shaka" }, + ], + islamic: [ + { era: "ah" }, + { era: "bh" }, + ], + "islamic-civil": [ + { era: "bh" }, + { era: "ah" }, + ], + "islamic-rgsa": [ + { era: "bh" }, + { era: "ah" }, + ], + "islamic-tbla": [ + { era: "bh" }, + { era: "ah" }, + ], + "islamic-umalqura": [ + { era: "bh" }, + { era: "ah" }, + ], + japanese: [ + { era: "bce", aliases: ["bc"] }, + { era: "ce", aliases: ["ad"] }, + { era: "heisei" }, + { era: "meiji" }, + { era: "reiwa" }, + { era: "showa" }, + { era: "taisho" }, + ], + persian: [ + { era: "ap" }, + ], + roc: [ + { era: "roc", aliases: ["minguo"] }, + { era: "broc", aliases: ["before-roc", "minguo-qian"] }, + ], + }, + + /* + * Return the canonical era code. + */ + canonicalizeCalendarEra(calendarId, eraName) { + assert.sameValue(typeof calendarId, "string", "calendar must be string in canonicalizeCalendarEra"); + + if (!Object.prototype.hasOwnProperty.call(TemporalHelpers.CalendarEras, calendarId)) { + assert.sameValue(eraName, undefined); + return undefined; + } + + assert.sameValue(typeof eraName, "string", "eraName must be string or undefined in canonicalizeCalendarEra"); + + for (let {era, aliases = []} of TemporalHelpers.CalendarEras[calendarId]) { + if (era === eraName || aliases.includes(eraName)) { + return era; + } + } + throw new Test262Error(`Unsupported era name: ${eraName}`); + }, + + /* + * assertDuration(duration, years, ..., nanoseconds[, description]): + * + * Shorthand for asserting that each field of a Temporal.Duration is equal to + * an expected value. + */ + assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(duration instanceof Temporal.Duration, `${prefix}instanceof`); + assert.sameValue(duration.years, years, `${prefix}years result:`); + assert.sameValue(duration.months, months, `${prefix}months result:`); + assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`); + assert.sameValue(duration.days, days, `${prefix}days result:`); + assert.sameValue(duration.hours, hours, `${prefix}hours result:`); + assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`); + assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`); + assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`); + assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`); + assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`); + }, + + /* + * assertDateDuration(duration, years, months, weeks, days, [, description]): + * + * Shorthand for asserting that each date field of a Temporal.Duration is + * equal to an expected value. + */ + assertDateDuration(duration, years, months, weeks, days, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(duration instanceof Temporal.Duration, `${prefix}instanceof`); + assert.sameValue(duration.years, years, `${prefix}years result:`); + assert.sameValue(duration.months, months, `${prefix}months result:`); + assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`); + assert.sameValue(duration.days, days, `${prefix}days result:`); + assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`); + assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`); + assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`); + assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`); + assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`); + assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`); + }, + + /* + * assertDurationsEqual(actual, expected[, description]): + * + * Shorthand for asserting that each field of a Temporal.Duration is equal to + * the corresponding field in another Temporal.Duration. + */ + assertDurationsEqual(actual, expected, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`); + TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description); + }, + + /* + * assertInstantsEqual(actual, expected[, description]): + * + * Shorthand for asserting that two Temporal.Instants are of the correct type + * and equal according to their equals() methods. + */ + assertInstantsEqual(actual, expected, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`); + assert(actual instanceof Temporal.Instant, `${prefix}instanceof`); + assert(actual.equals(expected), `${prefix}equals method`); + }, + + /* + * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]): + * + * Shorthand for asserting that each field of a Temporal.PlainDate is equal to + * an expected value. (Except the `calendar` property, since callers may want + * to assert either object equality with an object they put in there, or the + * value of date.calendarId.) + */ + assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) { + const prefix = description ? `${description}: ` : ""; + assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`); + assert.sameValue( + TemporalHelpers.canonicalizeCalendarEra(date.calendarId, date.era), + TemporalHelpers.canonicalizeCalendarEra(date.calendarId, era), + `${prefix}era result:` + ); + assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`); + assert.sameValue(date.year, year, `${prefix}year result:`); + assert.sameValue(date.month, month, `${prefix}month result:`); + assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`); + assert.sameValue(date.day, day, `${prefix}day result:`); + }, + + /* + * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]): + * + * Shorthand for asserting that each field of a Temporal.PlainDateTime is + * equal to an expected value. (Except the `calendar` property, since callers + * may want to assert either object equality with an object they put in there, + * or the value of datetime.calendarId.) + */ + assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) { + const prefix = description ? `${description}: ` : ""; + assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`); + assert.sameValue( + TemporalHelpers.canonicalizeCalendarEra(datetime.calendarId, datetime.era), + TemporalHelpers.canonicalizeCalendarEra(datetime.calendarId, era), + `${prefix}era result:` + ); + assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`); + assert.sameValue(datetime.year, year, `${prefix}year result:`); + assert.sameValue(datetime.month, month, `${prefix}month result:`); + assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`); + assert.sameValue(datetime.day, day, `${prefix}day result:`); + assert.sameValue(datetime.hour, hour, `${prefix}hour result:`); + assert.sameValue(datetime.minute, minute, `${prefix}minute result:`); + assert.sameValue(datetime.second, second, `${prefix}second result:`); + assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`); + assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`); + assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`); + }, + + /* + * assertPlainDateTimesEqual(actual, expected[, description]): + * + * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct + * type, equal according to their equals() methods, and additionally that + * their calendar internal slots are the same value. + */ + assertPlainDateTimesEqual(actual, expected, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`); + assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`); + assert(actual.equals(expected), `${prefix}equals method`); + assert.sameValue( + actual.calendarId, + expected.calendarId, + `${prefix}calendar same value:` + ); + }, + + /* + * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]): + * + * Shorthand for asserting that each field of a Temporal.PlainMonthDay is + * equal to an expected value. (Except the `calendar` property, since callers + * may want to assert either object equality with an object they put in there, + * or the value of monthDay.calendarId().) + */ + assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) { + const prefix = description ? `${description}: ` : ""; + assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`); + assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`); + assert.sameValue(monthDay.day, day, `${prefix}day result:`); + const isoYear = Number(monthDay.toString({ calendarName: "always" }).split("-")[0]); + assert.sameValue(isoYear, referenceISOYear, `${prefix}referenceISOYear result:`); + }, + + /* + * assertPlainTime(time, hour, ..., nanosecond[, description]): + * + * Shorthand for asserting that each field of a Temporal.PlainTime is equal to + * an expected value. + */ + assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`); + assert.sameValue(time.hour, hour, `${prefix}hour result:`); + assert.sameValue(time.minute, minute, `${prefix}minute result:`); + assert.sameValue(time.second, second, `${prefix}second result:`); + assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`); + assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`); + assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`); + }, + + /* + * assertPlainTimesEqual(actual, expected[, description]): + * + * Shorthand for asserting that two Temporal.PlainTimes are of the correct + * type and equal according to their equals() methods. + */ + assertPlainTimesEqual(actual, expected, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`); + assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`); + assert(actual.equals(expected), `${prefix}equals method`); + }, + + /* + * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]): + * + * Shorthand for asserting that each field of a Temporal.PlainYearMonth is + * equal to an expected value. (Except the `calendar` property, since callers + * may want to assert either object equality with an object they put in there, + * or the value of yearMonth.calendarId.) + */ + assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) { + const prefix = description ? `${description}: ` : ""; + assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`); + assert.sameValue( + TemporalHelpers.canonicalizeCalendarEra(yearMonth.calendarId, yearMonth.era), + TemporalHelpers.canonicalizeCalendarEra(yearMonth.calendarId, era), + `${prefix}era result:` + ); + assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`); + assert.sameValue(yearMonth.year, year, `${prefix}year result:`); + assert.sameValue(yearMonth.month, month, `${prefix}month result:`); + assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`); + const isoDay = Number(yearMonth.toString({ calendarName: "always" }).slice(1).split('-')[2].slice(0, 2)); + assert.sameValue(isoDay, referenceISODay, `${prefix}referenceISODay result:`); + }, + + /* + * assertZonedDateTimesEqual(actual, expected[, description]): + * + * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct + * type, equal according to their equals() methods, and additionally that + * their time zones and calendar internal slots are the same value. + */ + assertZonedDateTimesEqual(actual, expected, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`); + assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`); + assert(actual.equals(expected), `${prefix}equals method`); + assert.sameValue(actual.timeZoneId, expected.timeZoneId, `${prefix}time zone same value:`); + assert.sameValue( + actual.calendarId, + expected.calendarId, + `${prefix}calendar same value:` + ); + }, + + /* + * assertUnreachable(description): + * + * Helper for asserting that code is not executed. + */ + assertUnreachable(description) { + let message = "This code should not be executed"; + if (description) { + message = `${message}: ${description}`; + } + throw new Test262Error(message); + }, + + /* + * checkPlainDateTimeConversionFastPath(func): + * + * ToTemporalDate and ToTemporalTime should both, if given a + * Temporal.PlainDateTime instance, convert to the desired type by reading the + * PlainDateTime's internal slots, rather than calling any getters. + * + * func(datetime) is the actual operation to test, that must + * internally call the abstract operation ToTemporalDate or ToTemporalTime. + * It is passed a Temporal.PlainDateTime instance. + */ + checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") { + const actual = []; + const expected = []; + + const calendar = "iso8601"; + const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar); + const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype); + ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => { + Object.defineProperty(datetime, property, { + get() { + actual.push(`get ${formatPropertyName(property)}`); + const value = prototypeDescrs[property].get.call(this); + return { + toString() { + actual.push(`toString ${formatPropertyName(property)}`); + return value.toString(); + }, + valueOf() { + actual.push(`valueOf ${formatPropertyName(property)}`); + return value; + }, + }; + }, + }); + }); + Object.defineProperty(datetime, "calendar", { + get() { + actual.push("get calendar"); + return calendar; + }, + }); + + func(datetime); + assert.compareArray(actual, expected, `${message}: property getters not called`); + }, + + /* + * Check that an options bag that accepts units written in the singular form, + * also accepts the same units written in the plural form. + * func(unit) should call the method with the appropriate options bag + * containing unit as a value. This will be called twice for each element of + * validSingularUnits, once with singular and once with plural, and the + * results of each pair should be the same (whether a Temporal object or a + * primitive value.) + */ + checkPluralUnitsAccepted(func, validSingularUnits) { + const plurals = { + year: 'years', + month: 'months', + week: 'weeks', + day: 'days', + hour: 'hours', + minute: 'minutes', + second: 'seconds', + millisecond: 'milliseconds', + microsecond: 'microseconds', + nanosecond: 'nanoseconds', + }; + + validSingularUnits.forEach((unit) => { + const singularValue = func(unit); + const pluralValue = func(plurals[unit]); + const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`; + if (singularValue instanceof Temporal.Duration) { + TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc); + } else if (singularValue instanceof Temporal.Instant) { + TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc); + } else if (singularValue instanceof Temporal.PlainDateTime) { + TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc); + } else if (singularValue instanceof Temporal.PlainTime) { + TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc); + } else if (singularValue instanceof Temporal.ZonedDateTime) { + TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc); + } else { + assert.sameValue(pluralValue, singularValue); + } + }); + }, + + /* + * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc): + * + * Checks the type handling of the roundingIncrement option. + * checkFunc(roundingIncrement) is a function which takes the value of + * roundingIncrement to test, and calls the method under test with it, + * returning the result. assertTrueResultFunc(result, description) should + * assert that result is the expected result with roundingIncrement: true, and + * assertObjectResultFunc(result, description) should assert that result is + * the expected result with roundingIncrement being an object with a valueOf() + * method. + */ + checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) { + // null converts to 0, which is out of range + assert.throws(RangeError, () => checkFunc(null), "null"); + // Booleans convert to either 0 or 1, and 1 is allowed + const trueResult = checkFunc(true); + assertTrueResultFunc(trueResult, "true"); + assert.throws(RangeError, () => checkFunc(false), "false"); + // Symbols and BigInts cannot convert to numbers + assert.throws(TypeError, () => checkFunc(Symbol()), "symbol"); + assert.throws(TypeError, () => checkFunc(2n), "bigint"); + + // Objects prefer their valueOf() methods when converting to a number + assert.throws(RangeError, () => checkFunc({}), "plain object"); + + const expected = [ + "get roundingIncrement.valueOf", + "call roundingIncrement.valueOf", + ]; + const actual = []; + const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement"); + const objectResult = checkFunc(observer); + assertObjectResultFunc(objectResult, "object with valueOf"); + assert.compareArray(actual, expected, "order of operations"); + }, + + /* + * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc): + * + * Checks the type handling of a string option, of which there are several in + * Temporal. + * propertyName is the name of the option, and value is the value that + * assertFunc should expect it to have. + * checkFunc(value) is a function which takes the value of the option to test, + * and calls the method under test with it, returning the result. + * assertFunc(result, description) should assert that result is the expected + * result with the option value being an object with a toString() method + * which returns the given value. + */ + checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) { + // null converts to the string "null", which is an invalid string value + assert.throws(RangeError, () => checkFunc(null), "null"); + // Booleans convert to the strings "true" or "false", which are invalid + assert.throws(RangeError, () => checkFunc(true), "true"); + assert.throws(RangeError, () => checkFunc(false), "false"); + // Symbols cannot convert to strings + assert.throws(TypeError, () => checkFunc(Symbol()), "symbol"); + // Numbers convert to strings which are invalid + assert.throws(RangeError, () => checkFunc(2), "number"); + // BigInts convert to strings which are invalid + assert.throws(RangeError, () => checkFunc(2n), "bigint"); + + // Objects prefer their toString() methods when converting to a string + assert.throws(RangeError, () => checkFunc({}), "plain object"); + + const expected = [ + `get ${propertyName}.toString`, + `call ${propertyName}.toString`, + ]; + const actual = []; + const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName); + const result = checkFunc(observer); + assertFunc(result, "object with toString"); + assert.compareArray(actual, expected, "order of operations"); + }, + + /* + * checkSubclassingIgnored(construct, constructArgs, method, methodArgs, + * resultAssertions): + * + * Methods of Temporal classes that return a new instance of the same class, + * must not take the constructor of a subclass into account, nor the @@species + * property. This helper runs tests to ensure this. + * + * construct(...constructArgs) must yield a valid instance of the Temporal + * class. instance[method](...methodArgs) is the method call under test, which + * must also yield a valid instance of the same Temporal class, not a + * subclass. See below for the individual tests that this runs. + * resultAssertions() is a function that performs additional assertions on the + * instance returned by the method under test. + */ + checkSubclassingIgnored(...args) { + this.checkSubclassConstructorNotObject(...args); + this.checkSubclassConstructorUndefined(...args); + this.checkSubclassConstructorThrows(...args); + this.checkSubclassConstructorNotCalled(...args); + this.checkSubclassSpeciesInvalidResult(...args); + this.checkSubclassSpeciesNotAConstructor(...args); + this.checkSubclassSpeciesNull(...args); + this.checkSubclassSpeciesUndefined(...args); + this.checkSubclassSpeciesThrows(...args); + }, + + /* + * Checks that replacing the 'constructor' property of the instance with + * various primitive values does not affect the returned new instance. + */ + checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) { + function check(value, description) { + const instance = new construct(...constructArgs); + instance.constructor = value; + const result = instance[method](...methodArgs); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description); + resultAssertions(result); + } + + check(null, "null"); + check(true, "true"); + check("test", "string"); + check(Symbol(), "Symbol"); + check(7, "number"); + check(7n, "bigint"); + }, + + /* + * Checks that replacing the 'constructor' property of the subclass with + * undefined does not affect the returned new instance. + */ + checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) { + let called = 0; + + class MySubclass extends construct { + constructor() { + ++called; + super(...constructArgs); + } + } + + const instance = new MySubclass(); + assert.sameValue(called, 1); + + MySubclass.prototype.constructor = undefined; + + const result = instance[method](...methodArgs); + assert.sameValue(called, 1); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + }, + + /* + * Checks that making the 'constructor' property of the instance throw when + * called does not affect the returned new instance. + */ + checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) { + function CustomError() {} + const instance = new construct(...constructArgs); + Object.defineProperty(instance, "constructor", { + get() { + throw new CustomError(); + } + }); + const result = instance[method](...methodArgs); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + }, + + /* + * Checks that when subclassing, the subclass constructor is not called by + * the method under test. + */ + checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) { + let called = 0; + + class MySubclass extends construct { + constructor() { + ++called; + super(...constructArgs); + } + } + + const instance = new MySubclass(); + assert.sameValue(called, 1); + + const result = instance[method](...methodArgs); + assert.sameValue(called, 1); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + }, + + /* + * Check that the constructor's @@species property is ignored when it's a + * constructor that returns a non-object value. + */ + checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) { + function check(value, description) { + const instance = new construct(...constructArgs); + instance.constructor = { + [Symbol.species]: function() { + return value; + }, + }; + const result = instance[method](...methodArgs); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description); + resultAssertions(result); + } + + check(undefined, "undefined"); + check(null, "null"); + check(true, "true"); + check("test", "string"); + check(Symbol(), "Symbol"); + check(7, "number"); + check(7n, "bigint"); + check({}, "plain object"); + }, + + /* + * Check that the constructor's @@species property is ignored when it's not a + * constructor. + */ + checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) { + function check(value, description) { + const instance = new construct(...constructArgs); + instance.constructor = { + [Symbol.species]: value, + }; + const result = instance[method](...methodArgs); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description); + resultAssertions(result); + } + + check(true, "true"); + check("test", "string"); + check(Symbol(), "Symbol"); + check(7, "number"); + check(7n, "bigint"); + check({}, "plain object"); + }, + + /* + * Check that the constructor's @@species property is ignored when it's null. + */ + checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) { + let called = 0; + + class MySubclass extends construct { + constructor() { + ++called; + super(...constructArgs); + } + } + + const instance = new MySubclass(); + assert.sameValue(called, 1); + + MySubclass.prototype.constructor = { + [Symbol.species]: null, + }; + + const result = instance[method](...methodArgs); + assert.sameValue(called, 1); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + }, + + /* + * Check that the constructor's @@species property is ignored when it's + * undefined. + */ + checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) { + let called = 0; + + class MySubclass extends construct { + constructor() { + ++called; + super(...constructArgs); + } + } + + const instance = new MySubclass(); + assert.sameValue(called, 1); + + MySubclass.prototype.constructor = { + [Symbol.species]: undefined, + }; + + const result = instance[method](...methodArgs); + assert.sameValue(called, 1); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + }, + + /* + * Check that the constructor's @@species property is ignored when it throws, + * i.e. it is not called at all. + */ + checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) { + function CustomError() {} + + const instance = new construct(...constructArgs); + instance.constructor = { + get [Symbol.species]() { + throw new CustomError(); + }, + }; + + const result = instance[method](...methodArgs); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + }, + + /* + * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions): + * + * Static methods of Temporal classes that return a new instance of the class, + * must not use the this-value as a constructor. This helper runs tests to + * ensure this. + * + * construct[method](...methodArgs) is the static method call under test, and + * must yield a valid instance of the Temporal class, not a subclass. See + * below for the individual tests that this runs. + * resultAssertions() is a function that performs additional assertions on the + * instance returned by the method under test. + */ + checkSubclassingIgnoredStatic(...args) { + this.checkStaticInvalidReceiver(...args); + this.checkStaticReceiverNotCalled(...args); + this.checkThisValueNotCalled(...args); + }, + + /* + * Check that calling the static method with a receiver that's not callable, + * still calls the intrinsic constructor. + */ + checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) { + function check(value, description) { + const result = construct[method].apply(value, methodArgs); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + } + + check(undefined, "undefined"); + check(null, "null"); + check(true, "true"); + check("test", "string"); + check(Symbol(), "symbol"); + check(7, "number"); + check(7n, "bigint"); + check({}, "Non-callable object"); + }, + + /* + * Check that calling the static method with a receiver that returns a value + * that's not callable, still calls the intrinsic constructor. + */ + checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) { + function check(value, description) { + const receiver = function () { + return value; + }; + const result = construct[method].apply(receiver, methodArgs); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + } + + check(undefined, "undefined"); + check(null, "null"); + check(true, "true"); + check("test", "string"); + check(Symbol(), "symbol"); + check(7, "number"); + check(7n, "bigint"); + check({}, "Non-callable object"); + }, + + /* + * Check that the receiver isn't called. + */ + checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) { + let called = false; + + class MySubclass extends construct { + constructor(...args) { + called = true; + super(...args); + } + } + + const result = MySubclass[method](...methodArgs); + assert.sameValue(called, false); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + }, + + /* + * Check that any calendar-carrying Temporal object has its [[Calendar]] + * internal slot read by ToTemporalCalendar, and does not fetch the calendar + * by calling getters. + */ + checkToTemporalCalendarFastPath(func) { + const plainDate = new Temporal.PlainDate(2000, 5, 2, "iso8601"); + const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, "iso8601"); + const plainMonthDay = new Temporal.PlainMonthDay(5, 2, "iso8601"); + const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, "iso8601"); + const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", "iso8601"); + + [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => { + const actual = []; + const expected = []; + + Object.defineProperty(temporalObject, "calendar", { + get() { + actual.push("get calendar"); + return calendar; + }, + }); + + func(temporalObject); + assert.compareArray(actual, expected, "calendar getter not called"); + }); + }, + + checkToTemporalInstantFastPath(func) { + const actual = []; + const expected = []; + + const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC"); + Object.defineProperty(datetime, 'toString', { + get() { + actual.push("get toString"); + return function (options) { + actual.push("call toString"); + return Temporal.ZonedDateTime.prototype.toString.call(this, options); + }; + }, + }); + + func(datetime); + assert.compareArray(actual, expected, "toString not called"); + }, + + checkToTemporalPlainDateTimeFastPath(func) { + const actual = []; + const expected = []; + + const date = new Temporal.PlainDate(2000, 5, 2, "iso8601"); + const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype); + ["year", "month", "monthCode", "day"].forEach((property) => { + Object.defineProperty(date, property, { + get() { + actual.push(`get ${formatPropertyName(property)}`); + const value = prototypeDescrs[property].get.call(this); + return TemporalHelpers.toPrimitiveObserver(actual, value, property); + }, + }); + }); + ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => { + Object.defineProperty(date, property, { + get() { + actual.push(`get ${formatPropertyName(property)}`); + return undefined; + }, + }); + }); + Object.defineProperty(date, "calendar", { + get() { + actual.push("get calendar"); + return "iso8601"; + }, + }); + + func(date); + assert.compareArray(actual, expected, "property getters not called"); + }, + + /* + * observeProperty(calls, object, propertyName, value): + * + * Defines an own property @object.@propertyName with value @value, that + * will log any calls to its accessors to the array @calls. + */ + observeProperty(calls, object, propertyName, value, objectName = "") { + Object.defineProperty(object, propertyName, { + get() { + calls.push(`get ${formatPropertyName(propertyName, objectName)}`); + return value; + }, + set(v) { + calls.push(`set ${formatPropertyName(propertyName, objectName)}`); + } + }); + }, + + /* + * observeMethod(calls, object, propertyName, value): + * + * Defines an own property @object.@propertyName with value @value, that + * will log any calls of @value to the array @calls. + */ + observeMethod(calls, object, propertyName, objectName = "") { + const method = object[propertyName]; + object[propertyName] = function () { + calls.push(`call ${formatPropertyName(propertyName, objectName)}`); + return method.apply(object, arguments); + }; + }, + + /* + * Used for substituteMethod to indicate default behavior instead of a + * substituted value + */ + SUBSTITUTE_SKIP: SKIP_SYMBOL, + + /* + * substituteMethod(object, propertyName, values): + * + * Defines an own property @object.@propertyName that will, for each + * subsequent call to the method previously defined as + * @object.@propertyName: + * - Call the method, if no more values remain + * - Call the method, if the value in @values for the corresponding call + * is SUBSTITUTE_SKIP + * - Otherwise, return the corresponding value in @value + */ + substituteMethod(object, propertyName, values) { + let calls = 0; + const method = object[propertyName]; + object[propertyName] = function () { + if (calls >= values.length) { + return method.apply(object, arguments); + } else if (values[calls] === SKIP_SYMBOL) { + calls++; + return method.apply(object, arguments); + } else { + return values[calls++]; + } + }; + }, + + /* + * propertyBagObserver(): + * Returns an object that behaves like the given propertyBag but tracks Get + * and Has operations on any of its properties, by appending messages to an + * array. If the value of a property in propertyBag is a primitive, the value + * of the returned object's property will additionally be a + * TemporalHelpers.toPrimitiveObserver that will track calls to its toString + * and valueOf methods in the same array. This is for the purpose of testing + * order of operations that are observable from user code. objectName is used + * in the log. + * If skipToPrimitive is given, it must be an array of property keys. Those + * properties will not have a TemporalHelpers.toPrimitiveObserver returned, + * and instead just be returned directly. + */ + propertyBagObserver(calls, propertyBag, objectName, skipToPrimitive) { + return new Proxy(propertyBag, { + ownKeys(target) { + calls.push(`ownKeys ${objectName}`); + return Reflect.ownKeys(target); + }, + getOwnPropertyDescriptor(target, key) { + calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`); + return Reflect.getOwnPropertyDescriptor(target, key); + }, + get(target, key, receiver) { + calls.push(`get ${formatPropertyName(key, objectName)}`); + const result = Reflect.get(target, key, receiver); + if (result === undefined) { + return undefined; + } + if ((result !== null && typeof result === "object") || typeof result === "function") { + return result; + } + if (skipToPrimitive && skipToPrimitive.indexOf(key) >= 0) { + return result; + } + return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`); + }, + has(target, key) { + calls.push(`has ${formatPropertyName(key, objectName)}`); + return Reflect.has(target, key); + }, + }); + }, + + /* + * Returns an object that will append logs of any Gets or Calls of its valueOf + * or toString properties to the array calls. Both valueOf and toString will + * return the actual primitiveValue. propertyName is used in the log. + */ + toPrimitiveObserver(calls, primitiveValue, propertyName) { + return { + get valueOf() { + calls.push(`get ${propertyName}.valueOf`); + return function () { + calls.push(`call ${propertyName}.valueOf`); + return primitiveValue; + }; + }, + get toString() { + calls.push(`get ${propertyName}.toString`); + return function () { + calls.push(`call ${propertyName}.toString`); + if (primitiveValue === undefined) return undefined; + return primitiveValue.toString(); + }; + }, + }; + }, + + /* + * An object containing further methods that return arrays of ISO strings, for + * testing parsers. + */ + ISO: { + /* + * PlainMonthDay strings that are not valid. + */ + plainMonthDayStringsInvalid() { + return [ + "11-18junk", + "11-18[u-ca=gregory]", + "11-18[u-ca=hebrew]", + "11-18[U-CA=iso8601]", + "11-18[u-CA=iso8601]", + "11-18[FOO=bar]", + "-999999-01-01[u-ca=gregory]", + "-999999-01-01[u-ca=chinese]", + "+999999-01-01[u-ca=gregory]", + "+999999-01-01[u-ca=chinese]", + ]; + }, + + /* + * PlainMonthDay strings that are valid and that should produce October 1st. + */ + plainMonthDayStringsValid() { + return [ + "10-01", + "1001", + "1965-10-01", + "1976-10-01T152330.1+00:00", + "19761001T15:23:30.1+00:00", + "1976-10-01T15:23:30.1+0000", + "1976-10-01T152330.1+0000", + "19761001T15:23:30.1+0000", + "19761001T152330.1+00:00", + "19761001T152330.1+0000", + "+001976-10-01T152330.1+00:00", + "+0019761001T15:23:30.1+00:00", + "+001976-10-01T15:23:30.1+0000", + "+001976-10-01T152330.1+0000", + "+0019761001T15:23:30.1+0000", + "+0019761001T152330.1+00:00", + "+0019761001T152330.1+0000", + "1976-10-01T15:23:00", + "1976-10-01T15:23", + "1976-10-01T15", + "1976-10-01", + "--10-01", + "--1001", + "-999999-10-01", + "-999999-10-01[u-ca=iso8601]", + "+999999-10-01", + "+999999-10-01[u-ca=iso8601]", + ]; + }, + + /* + * PlainTime strings that may be mistaken for PlainMonthDay or + * PlainYearMonth strings, and so require a time designator. + */ + plainTimeStringsAmbiguous() { + const ambiguousStrings = [ + "2021-12", // ambiguity between YYYY-MM and HHMM-UU + "2021-12[-12:00]", // ditto, TZ does not disambiguate + "1214", // ambiguity between MMDD and HHMM + "0229", // ditto, including MMDD that doesn't occur every year + "1130", // ditto, including DD that doesn't occur in every month + "12-14", // ambiguity between MM-DD and HH-UU + "12-14[-14:00]", // ditto, TZ does not disambiguate + "202112", // ambiguity between YYYYMM and HHMMSS + "202112[UTC]", // ditto, TZ does not disambiguate + ]; + // Adding a calendar annotation to one of these strings must not cause + // disambiguation in favour of time. + const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]'); + return ambiguousStrings.concat(stringsWithCalendar); + }, + + /* + * PlainTime strings that are of similar form to PlainMonthDay and + * PlainYearMonth strings, but are not ambiguous due to components that + * aren't valid as months or days. + */ + plainTimeStringsUnambiguous() { + return [ + "2021-13", // 13 is not a month + "202113", // ditto + "2021-13[-13:00]", // ditto + "202113[-13:00]", // ditto + "0000-00", // 0 is not a month + "000000", // ditto + "0000-00[UTC]", // ditto + "000000[UTC]", // ditto + "1314", // 13 is not a month + "13-14", // ditto + "1232", // 32 is not a day + "0230", // 30 is not a day in February + "0631", // 31 is not a day in June + "0000", // 0 is neither a month nor a day + "00-00", // ditto + ]; + }, + + /* + * PlainYearMonth-like strings that are not valid. + */ + plainYearMonthStringsInvalid() { + return [ + "2020-13", + "1976-11[u-ca=gregory]", + "1976-11[u-ca=hebrew]", + "1976-11[U-CA=iso8601]", + "1976-11[u-CA=iso8601]", + "1976-11[FOO=bar]", + "+999999-01", + "-999999-01", + ]; + }, + + /* + * PlainYearMonth-like strings that are valid and should produce November + * 1976 in the ISO 8601 calendar. + */ + plainYearMonthStringsValid() { + return [ + "1976-11", + "1976-11-10", + "1976-11-01T09:00:00+00:00", + "1976-11-01T00:00:00+05:00", + "197611", + "+00197611", + "1976-11-18T15:23:30.1-02:00", + "1976-11-18T152330.1+00:00", + "19761118T15:23:30.1+00:00", + "1976-11-18T15:23:30.1+0000", + "1976-11-18T152330.1+0000", + "19761118T15:23:30.1+0000", + "19761118T152330.1+00:00", + "19761118T152330.1+0000", + "+001976-11-18T152330.1+00:00", + "+0019761118T15:23:30.1+00:00", + "+001976-11-18T15:23:30.1+0000", + "+001976-11-18T152330.1+0000", + "+0019761118T15:23:30.1+0000", + "+0019761118T152330.1+00:00", + "+0019761118T152330.1+0000", + "1976-11-18T15:23", + "1976-11-18T15", + "1976-11-18", + ]; + }, + + /* + * PlainYearMonth-like strings that are valid and should produce November of + * the ISO year -9999. + */ + plainYearMonthStringsValidNegativeYear() { + return [ + "-009999-11", + ]; + }, + } +}; diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/until/era-boundary-gregory.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/until/era-boundary-gregory.js @@ -0,0 +1,80 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 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: Date difference works correctly across era boundaries +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "gregory"; +const options = { overflow: "reject" }; + +const bce5 = Temporal.PlainDate.from({ era: "bce", eraYear: 5, monthCode: "M06", day: 15, calendar }, options); +const bce2 = Temporal.PlainDate.from({ era: "bce", eraYear: 2, monthCode: "M12", day: 1, calendar }, options); +const bce1 = Temporal.PlainDate.from({ era: "bce", eraYear: 1, monthCode: "M06", day: 15, calendar }, options); +const ce1 = Temporal.PlainDate.from({ era: "ce", eraYear: 1, monthCode: "M06", day: 15, calendar }, options); +const ce2 = Temporal.PlainDate.from({ era: "ce", eraYear: 2, monthCode: "M01", day: 1, calendar }, options); +const ce5 = Temporal.PlainDate.from({ era: "ce", eraYear: 5, monthCode: "M06", day: 15, calendar }, options); + +const tests = [ + // From 5 BCE to 5 CE + [ + bce5, ce5, + [9, 0, 0, 0, "9y from 5 BCE to 5 CE (no year 0)"], + [0, 108, 0, 0, 0, "108mo from 5 BCE to 5 CE (no year 0)"], + ], + [ + ce5, bce5, + [-9, 0, 0, 0, "-9y backwards from 5 BCE to 5 CE (no year 0)"], + [0, -108, 0, 0, 0, "-108mo backwards from 5 BCE to 5 CE (no year 0)"], + ], + // CE-BCE boundary + [ + bce1, ce1, + [1, 0, 0, 0, "1y from 1 BCE to 1 CE"], + [0, 12, 0, 0, "12mo from 1 BCE to 1 CE"], + ], + [ + ce1, bce1, + [-1, 0, 0, 0, "-1y backwards from 1 BCE to 1 CE"], + [0, -12, 0, 0, "-12mo backwards from 1 BCE to 1 CE"], + ], + [ + bce2, ce2, + [2, 1, 0, 0, "2y 1mo from 2 BCE Dec to 2 CE Jan"], + [0, 25, 0, 0, "25mo from 2 BCE Dec to 2 CE Jan"], + ], + [ + ce2, bce2, + [-2, -1, 0, 0, "-2y -1mo backwards from 2 BCE Dec to 2 CE Jan"], + [0, -25, 0, 0, "-25mo backwards from 2 BCE Dec to 2 CE Jan"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.until(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.until(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.until(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.until(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.until(two); + const resultDaysISO = oneISO.until(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/until/era-boundary-japanese.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/until/era-boundary-japanese.js @@ -0,0 +1,172 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 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: Date difference works correctly across era boundaries +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "japanese"; +const options = { overflow: "reject" }; + +const bce1 = Temporal.PlainDate.from({ era: "bce", eraYear: 1, monthCode: "M06", day: 1, calendar }, options); +const ce1 = Temporal.PlainDate.from({ era: "ce", eraYear: 1, monthCode: "M06", day: 1, calendar }, options); +const ce1868 = Temporal.PlainDate.from({ era: "ce", eraYear: 1868, monthCode: "M10", day: 22, calendar }, options); +const meiji1 = Temporal.PlainDate.from({ era: "meiji", eraYear: 1, monthCode: "M10", day: 23, calendar}, options); +const meiji5 = Temporal.PlainDate.from({ era: "meiji", eraYear: 5, monthCode: "M01", day: 15, calendar }, options); +const meiji45 = Temporal.PlainDate.from({ era: "meiji", eraYear: 45, monthCode: "M05", day: 19, calendar }, options); +const taisho1 = Temporal.PlainDate.from({ era: "taisho", eraYear: 1, monthCode: "M08", day: 9, calendar }, options); +const taisho6 = Temporal.PlainDate.from({ era: "taisho", eraYear: 6, monthCode: "M03", day: 15, calendar }, options); +const taisho15 = Temporal.PlainDate.from({ era: "taisho", eraYear: 15, monthCode: "M05", day: 20, calendar }, options); +const showa1 = Temporal.PlainDate.from({ era: "showa", eraYear: 1, monthCode: "M12", day: 30, calendar }, options); +const showa55 = Temporal.PlainDate.from({ era: "showa", eraYear: 55, monthCode: "M03", day: 15, calendar }, options); +const showa64 = Temporal.PlainDate.from({ era: "showa", eraYear: 64, monthCode: "M01", day: 3, calendar }, options); +const heisei1 = Temporal.PlainDate.from({ era: "heisei", eraYear: 1, monthCode: "M02", day: 27, calendar }, options); +const heisei30 = Temporal.PlainDate.from({ era: "heisei", eraYear: 30, monthCode: "M03", day: 15, calendar }, options); +const heisei31 = Temporal.PlainDate.from({ era: "heisei", eraYear: 31, monthCode: "M04", day: 1, calendar }, options); +const reiwa1 = Temporal.PlainDate.from({ era: "reiwa", eraYear: 1, monthCode: "M06", day: 1, calendar }, options); +const reiwa2 = Temporal.PlainDate.from({ era: "reiwa", eraYear: 2, monthCode: "M03", day: 15, calendar }, options); + +const tests = [ + // From Heisei 30 (2018) to Reiwa 2 (2020) - crossing era boundary + [ + heisei30, reiwa2, + [2, 0, 0, 0, "2y from Heisei 30 March to Reiwa 2 March"], + [0, 24, 0, 0, "24mo from Heisei 30 March to Reiwa 2 March"], + ], + [ + reiwa2, heisei30, + [-2, 0, 0, 0, "-2y backwards from Heisei 30 March to Reiwa 2 March"], + [0, -24, 0, 0, "-24mo backwards from Heisei 30 March to Reiwa 2 March"], + + ], + // Within same year but different eras + [ + heisei31, reiwa1, + [0, 2, 0, 0, "2mo from Heisei 31 April to Reiwa 1 June"], + [0, 2, 0, 0, "2mo from Heisei 31 April to Reiwa 1 June"], + ], + [ + reiwa1, heisei31, + [0, -2, 0, 0, "-2mo backwards from Heisei 31 April to Reiwa 1 June"], + [0, -2, 0, 0, "-2mo backwards from Heisei 31 April to Reiwa 1 June"], + ], + // From Showa 55 (1980) to Heisei 30 (2018) - crossing era boundary + [ + showa55, heisei30, + [38, 0, 0, 0, "38y from Showa 55 March to Heisei 30 March"], + [0, 456, 0, 0, "456mo from Showa 55 March to Heisei 30 March"], + ], + [ + heisei30, showa55, + [-38, 0, 0, 0, "-38y backwards from Showa 55 March to Heisei 30 March"], + [0, -456, 0, 0, "-456mo backwards from Showa 55 March to Heisei 30 March"], + ], + // Within same year but different eras + [ + showa64, heisei1, + [0, 1, 0, 24, "1mo 24d from Showa 64 January 3 to Heisei 1 February 27"], + [0, 1, 0, 24, "1mo 24d from Showa 64 January 3 to Heisei 1 February 27"], + ], + [ + heisei1, showa64, + [0, -1, 0, -24, "-1mo -24d from Showa 64 January 3 to Heisei 1 February 27"], + [0, -1, 0, -24, "-1mo -24d from Showa 64 January 3 to Heisei 1 February 27"], + ], + // From Taisho 6 (1917) to Showa 55 (1980) - crossing era boundary + [ + taisho6, showa55, + [63, 0, 0, 0, "63y from Taisho 6 March to Showa 55 March"], + [0, 756, 0, 0, "756mo from Taisho 6 March to Showa 55 March"], + ], + [ + showa55, taisho6, + [-63, 0, 0, 0, "-63y backwards from Taisho 6 March to Showa 55 March"], + [0, -756, 0, 0, "-756mo backwards from Taisho 6 March to Showa 55 March"], + ], + // Within same year but different eras + [ + taisho15, showa1, + [0, 7, 0, 10, "7mo 10d from Taisho 15 July 20 to Showa 1 December 30"], + [0, 7, 0, 10, "7mo 10d from Taisho 15 July 20 to Showa 1 December 30"], + ], + [ + showa1, taisho15, + [0, -7, 0, -10, "-7mo -10d backwards from Taisho 15 July 20 to Showa 1 December 30"], + [0, -7, 0, -10, "-7mo -10d backwards from Taisho 15 July 20 to Showa 1 December 30"], + ], + // From Meiji 5 (1872) to Taisho 6 (1917) - crossing era boundary + // Note that contemporarily January 15 1872 would have been Meiji 4 in the + // pre-1873 lunisolar calendar, but the spec-mandated behaviour is proleptic + [ + meiji5, taisho6, + [45, 2, 0, 0, "45y 2mo from Meiji 5 January to Taisho 6 March"], + [0, 542, 0, 0, "542mo from Meiji 5 January to Taisho 6 March"], + ], + [ + taisho6, meiji5, + [-45, -2, 0, 0, "-45y -2mo backwards from Meiji 5 January to Taisho 6 March"], + [0, -542, 0, 0, "-542mo backwards from Meiji 5 January to Taisho 6 March"], + ], + // Within same year but different eras + [ + meiji45, taisho1, + [0, 2, 0, 21, "2mo 21d from Meiji 45 May 19 to Taisho 1 August 9"], + [0, 2, 0, 21, "2mo 21d from Meiji 45 May 19 to Taisho 1 August 9"], + ], + [ + taisho1, meiji45, + [0, -2, 0, -21, "-2mo -21d backwards from Meiji 45 May 19 to Taisho 1 August 9"], + [0, -2, 0, -21, "-2mo -21d backwards from Meiji 45 May 19 to Taisho 1 August 9"], + ], + // Last pre-Meiji day to first day of Meiji era + [ + ce1868, meiji1, + [0, 0, 0, 1, "from day before Meiji era to first day"], + [0, 0, 0, 1, "from day before Meiji era to first day"], + ], + [ + meiji1, ce1868, + [0, 0, 0, -1, "backwards from day before Meiji era to first day"], + [0, 0, 0, -1, "backwards from day before Meiji era to first day"], + ], + // CE-BCE boundary + [ + bce1, ce1, + [1, 0, 0, 0, "1y from 1 BCE to 1 CE"], + [0, 12, 0, 0, "12mo from 1 BCE to 1 CE"], + ], + [ + ce1, bce1, + [-1, 0, 0, 0, "-1y backwards from 1 BCE to 1 CE"], + [0, -12, 0, 0, "-12mo backwards from 1 BCE to 1 CE"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.until(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.until(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.until(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.until(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.until(two); + const resultDaysISO = oneISO.until(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/until/era-boundary-roc.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/until/era-boundary-roc.js @@ -0,0 +1,80 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 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: Date difference works correctly across era boundaries +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "roc"; +const options = { overflow: "reject" }; + +const broc5 = Temporal.PlainDate.from({ era: "broc", eraYear: 5, monthCode: "M03", day: 1, calendar }, options); +const broc3 = Temporal.PlainDate.from({ era: "broc", eraYear: 3, monthCode: "M01", day: 1, calendar }, options); +const broc1 = Temporal.PlainDate.from({ era: "broc", eraYear: 1, monthCode: "M06", day: 1, calendar }, options); +const roc1 = Temporal.PlainDate.from({ era: "roc", eraYear: 1, monthCode: "M06", day: 1, calendar }, options); +const roc5 = Temporal.PlainDate.from({ era: "roc", eraYear: 5, monthCode: "M03", day: 1, calendar }, options); +const roc10 = Temporal.PlainDate.from({ era: "roc", eraYear: 10, monthCode: "M01", day: 1, calendar }, options); + +const tests = [ + // From BROC 5 to ROC 5 + [ + broc5, roc5, + [9, 0, 0, 0, "9y from BROC 5 to ROC 5 (no year 0)"], + [0, 108, 0, 0, 0, "108mo from BROC 5 to ROC 5 (no year 0)"], + ], + [ + roc5, broc5, + [-9, 0, 0, 0, "-9y backwards from BROC 5 to ROC 5 (no year 0)"], + [0, -108, 0, 0, 0, "-108mo backwards from BROC 5 to ROC 5 (no year 0)"], + ], + // Era boundary + [ + broc1, roc1, + [1, 0, 0, 0, "1y from BROC 1 to ROC 1"], + [0, 12, 0, 0, "12mo from BROC 1 to ROC 1"], + ], + [ + roc1, broc1, + [-1, 0, 0, 0, "-1y backwards from BROC 1 to ROC 1"], + [0, -12, 0, 0, "-12mo backwards from BROC 1 to ROC 1"], + ], + [ + broc3, roc10, + [12, 0, 0, 0, "12y from BROC 3 to ROC 10"], + [0, 144, 0, 0, "144mo from BROC 3 to ROC 10"], + ], + [ + roc10, broc3, + [-12, 0, 0, 0, "-12y backwards from BROC 3 to ROC 10"], + [0, -144, 0, 0, "-144mo backwards from BROC 3 to ROC 10"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.until(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.until(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.until(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.until(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.until(two); + const resultDaysISO = oneISO.until(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/until/leap-months-chinese.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/until/leap-months-chinese.js @@ -0,0 +1,167 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Difference across leap months in chinese calendar +esid: sec-temporal.plaindate.prototype.until +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +// 2001 is a leap year with a M04L leap month. + +const calendar = "chinese"; +const options = { overflow: "reject" }; + +const common1Month4 = Temporal.PlainDate.from({ year: 2000, monthCode: "M04", day: 1, calendar }, options); +const common1Month5 = Temporal.PlainDate.from({ year: 2000, monthCode: "M05", day: 1, calendar }, options); +const common1Month6 = Temporal.PlainDate.from({ year: 2000, monthCode: "M06", day: 1, calendar }, options); +const leapMonth4 = Temporal.PlainDate.from({ year: 2001, monthCode: "M04", day: 1, calendar }, options); +const leapMonth4L = Temporal.PlainDate.from({ year: 2001, monthCode: "M04L", day: 1, calendar }, options); +const leapMonth5 = Temporal.PlainDate.from({ year: 2001, monthCode: "M05", day: 1, calendar }, options); +const common2Month4 = Temporal.PlainDate.from({ year: 2002, monthCode: "M04", day: 1, calendar }, options); +const common2Month5 = Temporal.PlainDate.from({ year: 2002, monthCode: "M05", day: 1, calendar }, options); + +// (receiver, argument, years test data, months test data) +// test data: expected years, months, weeks, days, description +// largestUnit years: make sure some cases where the answer is 12 months do not +// balance up to 1 year +// largestUnit months: similar to years, but make sure number of months in year +// is computed correctly +// For largestUnit of days and weeks, the results should be identical to what +// the ISO calendar gives for the corresponding ISO dates +const tests = [ + [ + common1Month4, leapMonth4, + [1, 0, 0, 0, "M04-M04 common-leap is 1y"], + [0, 12, 0, 0, "M04-M04 common-leap is 12mo"], + ], + [ + leapMonth4, common2Month4, + [1, 0, 0, 0, "M04-M04 leap-common is 1y"], + [0, 13, 0, 0, "M04-M04 leap-common is 13mo not 12mo"], + ], + [ + common1Month4, common2Month4, + [2, 0, 0, 0, "M04-M04 common-common is 2y"], + [0, 25, 0, 0, "M04-M04 common-common is 25mo not 24mo"], + ], + [ + common1Month5, leapMonth5, + [1, 0, 0, 0, "M05-M05 common-leap is 1y"], + [0, 13, 0, 0, "M05-M05 common-leap is 13mo not 12mo"], + ], + [ + leapMonth5, common2Month5, + [1, 0, 0, 0, "M05-M05 leap-common is 1y"], + [0, 12, 0, 0, "M05-M05 leap-common is 12mo"], + ], + [ + common1Month5, common2Month5, + [2, 0, 0, 0, "M05-M05 common-common is 2y"], + [0, 25, 0, 0, "M05-M05 common-common is 25mo not 24mo"], + ], + [ + common1Month4, leapMonth4L, + [1, 1, 0, 0, "M04-M04L is 1y 1mo"], + [0, 13, 0, 0, "M04-M04L is 13mo"], + ], + [ + leapMonth4L, common2Month4, + [0, 12, 0, 0, "M04L-M04 is 12mo not 1y"], + [0, 12, 0, 0, "M04L-M04 is 12mo"], + ], + [ + common1Month5, leapMonth4L, + [0, 12, 0, 0, "M05-M04L is 12mo not 1y"], + [0, 12, 0, 0, "M05-M04L is 12mo"], + ], + [ + leapMonth4L, common2Month5, + [1, 1, 0, 0, "M04L-M05 is 1y 1mo (exhibits calendar-specific constraining)"], + [0, 13, 0, 0, "M04L-M05 is 13mo"], + ], + [ + common1Month6, leapMonth5, + [0, 12, 0, 0, "M06-M05 common-leap is 12mo not 11mo"], + [0, 12, 0, 0, "M06-M05 common-leap is 12mo not 11mo"], + ], + + // Negative + [ + common2Month4, leapMonth4, + [-1, 0, 0, 0, "M04-M04 common-leap backwards is -1y"], + [0, -13, 0, 0, "M04-M04 common-leap backwards is -13mo not -12mo"], + ], + [ + leapMonth4, common1Month4, + [-1, 0, 0, 0, "M04-M04 leap-common backwards is -1y"], + [0, -12, 0, 0, "M04-M04 leap-common backwards is -12mo not -13mo"], + ], + [ + common2Month4, common1Month4, + [-2, 0, 0, 0, "M04-M04 common-common backwards is -2y"], + [0, -25, 0, 0, "M04-M04 common-common backwards is -25mo not -24mo"], + ], + [ + common2Month5, leapMonth5, + [-1, 0, 0, 0, "M05-M05 common-leap backwards is -1y"], + [0, -12, 0, 0, "M05-M05 common-leap backwards is -12mo not -13mo"], + ], + [ + leapMonth5, common1Month5, + [-1, 0, 0, 0, "M05-M05 leap-common backwards is -1y"], + [0, -13, 0, 0, "M05-M05 leap-common backwards is -13mo not -12mo"], + ], + [ + common2Month5, common1Month5, + [-2, 0, 0, 0, "M05-M05 common-common backwards is -2y"], + [0, -25, 0, 0, "M05-M05 common-common backwards is -25mo not -24mo"], + ], + [ + common2Month4, leapMonth4L, + [0, -12, 0, 0, "M04-M04L backwards is -12mo not -1y"], + [0, -12, 0, 0, "M04-M04L backwards is -12mo"], + ], + [ + leapMonth4L, common1Month4, + [-1, 0, 0, 0, "M04L-M04 backwards is -1y not -1y -1mo (exhibits calendar-specific constraining)"], + [0, -13, 0, 0, "M04L-M04 backwards is -13mo"], + ], + [ + common2Month5, leapMonth4L, + [-1, -1, 0, 0, "M05-M04L backwards is -1y -1mo"], + [0, -13, 0, 0, "M05-M04L backwards is -13mo"], + ], + [ + leapMonth4L, common1Month5, + [0, -12, 0, 0, "M04L-M05 backwards is -12mo not -1y"], + [0, -12, 0, 0, "M04L-M05 backwards is -12mo"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.until(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.until(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.until(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.until(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.until(two); + const resultDaysISO = oneISO.until(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/until/leap-months-dangi.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/until/leap-months-dangi.js @@ -0,0 +1,167 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Difference across leap months in dangi calendar +esid: sec-temporal.plaindate.prototype.until +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +// 2001 is a leap year with a M04L leap month. + +const calendar = "dangi"; +const options = { overflow: "reject" }; + +const common1Month4 = Temporal.PlainDate.from({ year: 2000, monthCode: "M04", day: 1, calendar }, options); +const common1Month5 = Temporal.PlainDate.from({ year: 2000, monthCode: "M05", day: 1, calendar }, options); +const common1Month6 = Temporal.PlainDate.from({ year: 2000, monthCode: "M06", day: 1, calendar }, options); +const leapMonth4 = Temporal.PlainDate.from({ year: 2001, monthCode: "M04", day: 1, calendar }, options); +const leapMonth4L = Temporal.PlainDate.from({ year: 2001, monthCode: "M04L", day: 1, calendar }, options); +const leapMonth5 = Temporal.PlainDate.from({ year: 2001, monthCode: "M05", day: 1, calendar }, options); +const common2Month4 = Temporal.PlainDate.from({ year: 2002, monthCode: "M04", day: 1, calendar }, options); +const common2Month5 = Temporal.PlainDate.from({ year: 2002, monthCode: "M05", day: 1, calendar }, options); + +// (receiver, argument, years test data, months test data) +// test data: expected years, months, weeks, days, description +// largestUnit years: make sure some cases where the answer is 12 months do not +// balance up to 1 year +// largestUnit months: similar to years, but make sure number of months in year +// is computed correctly +// For largestUnit of days and weeks, the results should be identical to what +// the ISO calendar gives for the corresponding ISO dates +const tests = [ + [ + common1Month4, leapMonth4, + [1, 0, 0, 0, "M04-M04 common-leap is 1y"], + [0, 12, 0, 0, "M04-M04 common-leap is 12mo"], + ], + [ + leapMonth4, common2Month4, + [1, 0, 0, 0, "M04-M04 leap-common is 1y"], + [0, 13, 0, 0, "M04-M04 leap-common is 13mo not 12mo"], + ], + [ + common1Month4, common2Month4, + [2, 0, 0, 0, "M04-M04 common-common is 2y"], + [0, 25, 0, 0, "M04-M04 common-common is 25mo not 24mo"], + ], + [ + common1Month5, leapMonth5, + [1, 0, 0, 0, "M05-M05 common-leap is 1y"], + [0, 13, 0, 0, "M05-M05 common-leap is 13mo not 12mo"], + ], + [ + leapMonth5, common2Month5, + [1, 0, 0, 0, "M05-M05 leap-common is 1y"], + [0, 12, 0, 0, "M05-M05 leap-common is 12mo"], + ], + [ + common1Month5, common2Month5, + [2, 0, 0, 0, "M05-M05 common-common is 2y"], + [0, 25, 0, 0, "M05-M05 common-common is 25mo not 24mo"], + ], + [ + common1Month4, leapMonth4L, + [1, 1, 0, 0, "M04-M04L is 1y 1mo"], + [0, 13, 0, 0, "M04-M04L is 13mo"], + ], + [ + leapMonth4L, common2Month4, + [0, 12, 0, 0, "M04L-M04 is 12mo not 1y"], + [0, 12, 0, 0, "M04L-M04 is 12mo"], + ], + [ + common1Month5, leapMonth4L, + [0, 12, 0, 0, "M05-M04L is 12mo not 1y"], + [0, 12, 0, 0, "M05-M04L is 12mo"], + ], + [ + leapMonth4L, common2Month5, + [1, 1, 0, 0, "M04L-M05 is 1y 1mo (exhibits calendar-specific constraining)"], + [0, 13, 0, 0, "M04L-M05 is 13mo"], + ], + [ + common1Month6, leapMonth5, + [0, 12, 0, 0, "M06-M05 common-leap is 12mo not 11mo"], + [0, 12, 0, 0, "M06-M05 common-leap is 12mo not 11mo"], + ], + + // Negative + [ + common2Month4, leapMonth4, + [-1, 0, 0, 0, "M04-M04 common-leap backwards is -1y"], + [0, -13, 0, 0, "M04-M04 common-leap backwards is -13mo not -12mo"], + ], + [ + leapMonth4, common1Month4, + [-1, 0, 0, 0, "M04-M04 leap-common backwards is -1y"], + [0, -12, 0, 0, "M04-M04 leap-common backwards is -12mo not -13mo"], + ], + [ + common2Month4, common1Month4, + [-2, 0, 0, 0, "M04-M04 common-common backwards is -2y"], + [0, -25, 0, 0, "M04-M04 common-common backwards is -25mo not -24mo"], + ], + [ + common2Month5, leapMonth5, + [-1, 0, 0, 0, "M05-M05 common-leap backwards is -1y"], + [0, -12, 0, 0, "M05-M05 common-leap backwards is -12mo not -13mo"], + ], + [ + leapMonth5, common1Month5, + [-1, 0, 0, 0, "M05-M05 leap-common backwards is -1y"], + [0, -13, 0, 0, "M05-M05 leap-common backwards is -13mo not -12mo"], + ], + [ + common2Month5, common1Month5, + [-2, 0, 0, 0, "M05-M05 common-common backwards is -2y"], + [0, -25, 0, 0, "M05-M05 common-common backwards is -25mo not -24mo"], + ], + [ + common2Month4, leapMonth4L, + [0, -12, 0, 0, "M04-M04L backwards is -12mo not -1y"], + [0, -12, 0, 0, "M04-M04L backwards is -12mo"], + ], + [ + leapMonth4L, common1Month4, + [-1, 0, 0, 0, "M04L-M04 backwards is -1y not -1y -1mo (exhibits calendar-specific constraining)"], + [0, -13, 0, 0, "M04L-M04 backwards is -13mo"], + ], + [ + common2Month5, leapMonth4L, + [-1, -1, 0, 0, "M05-M04L backwards is -1y -1mo"], + [0, -13, 0, 0, "M05-M04L backwards is -13mo"], + ], + [ + leapMonth4L, common1Month5, + [0, -12, 0, 0, "M04L-M05 backwards is -12mo not -1y"], + [0, -12, 0, 0, "M04L-M05 backwards is -12mo"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.until(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.until(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.until(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.until(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.until(two); + const resultDaysISO = oneISO.until(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/until/leap-months-hebrew.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/until/leap-months-hebrew.js @@ -0,0 +1,171 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Difference across leap months in hebrew calendar +esid: sec-temporal.plaindate.prototype.until +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +// 5784 is a leap year. +// M05 - Shevat +// M05L - Adar I +// M06 - Adar II +// M07 - Nisan + +const calendar = "hebrew"; +const options = { overflow: "reject" }; + +const common1Shevat = Temporal.PlainDate.from({ year: 5783, monthCode: "M05", day: 1, calendar }, options); +const common1Adar = Temporal.PlainDate.from({ year: 5783, monthCode: "M06", day: 1, calendar }, options); +const common1Nisan = Temporal.PlainDate.from({ year: 5783, monthCode: "M07", day: 1, calendar }, options); +const leapShevat = Temporal.PlainDate.from({ year: 5784, monthCode: "M05", day: 1, calendar }, options); +const leapAdarI = Temporal.PlainDate.from({ year: 5784, monthCode: "M05L", day: 1, calendar }, options); +const leapAdarII = Temporal.PlainDate.from({ year: 5784, monthCode: "M06", day: 1, calendar }, options); +const common2Shevat = Temporal.PlainDate.from({ year: 5785, monthCode: "M05", day: 1, calendar }, options); +const common2Adar = Temporal.PlainDate.from({ year: 5785, monthCode: "M06", day: 1, calendar }, options); + +// (receiver, argument, years test data, months test data) +// test data: expected years, months, weeks, days, description +// largestUnit years: make sure some cases where the answer is 12 months do not +// balance up to 1 year +// largestUnit months: similar to years, but make sure number of months in year +// is computed correctly +// For largestUnit of days and weeks, the results should be identical to what +// the ISO calendar gives for the corresponding ISO dates +const tests = [ + [ + common1Shevat, leapShevat, + [1, 0, 0, 0, "M05-M05 common-leap is 1y"], + [0, 12, 0, 0, "M05-M05 common-leap is 12mo"], + ], + [ + leapShevat, common2Shevat, + [1, 0, 0, 0, "M05-M05 leap-common is 1y"], + [0, 13, 0, 0, "M05-M05 leap-common is 13mo not 12mo"], + ], + [ + common1Shevat, common2Shevat, + [2, 0, 0, 0, "M05-M05 common-common is 2y"], + [0, 25, 0, 0, "M05-M05 common-common is 25mo not 24mo"], + ], + [ + common1Adar, leapAdarII, + [1, 0, 0, 0, "M06-M06 common-leap is 1y"], + [0, 13, 0, 0, "M06-M06 common-leap is 13mo not 12mo"], + ], + [ + leapAdarII, common2Adar, + [1, 0, 0, 0, "M06-M06 leap-common is 1y"], + [0, 12, 0, 0, "M06-M06 leap-common is 12mo"], + ], + [ + common1Adar, common2Adar, + [2, 0, 0, 0, "M06-M06 common-common is 2y"], + [0, 25, 0, 0, "M06-M06 common-common is 25mo not 24mo"], + ], + [ + common1Shevat, leapAdarI, + [1, 1, 0, 0, "M05-M05L is 1y 1mo"], + [0, 13, 0, 0, "M05-M05L is 13mo"], + ], + [ + leapAdarI, common2Shevat, + [0, 12, 0, 0, "M05L-M05 is 12mo not 1y"], + [0, 12, 0, 0, "M05L-M05 is 12mo"], + ], + [ + common1Adar, leapAdarI, + [0, 12, 0, 0, "M06-M05L is 12mo not 1y"], + [0, 12, 0, 0, "M06-M05L is 12mo"], + ], + [ + leapAdarI, common2Adar, + [1, 0, 0, 0, "M05L-M06 is 1y (exhibits calendar-specific constraining)"], + [0, 13, 0, 0, "M05L-M06 is 13mo"], + ], + [ + common1Nisan, leapAdarII, + [0, 12, 0, 0, "M07-M06 common-leap is 12mo not 11mo"], + [0, 12, 0, 0, "M07-M06 common-leap is 12mo not 11mo"], + ], + + // Negative + [ + common2Shevat, leapShevat, + [-1, 0, 0, 0, "M05-M05 common-leap backwards is -1y"], + [0, -13, 0, 0, "M05-M05 common-leap backwards is -13mo not -12mo"], + ], + [ + leapShevat, common1Shevat, + [-1, 0, 0, 0, "M05-M05 leap-common backwards is -1y"], + [0, -12, 0, 0, "M05-M05 leap-common backwards is -12mo not -13mo"], + ], + [ + common2Shevat, common1Shevat, + [-2, 0, 0, 0, "M05-M05 common-common backwards is -2y"], + [0, -25, 0, 0, "M05-M05 common-common backwards is -25mo not -24mo"], + ], + [ + common2Adar, leapAdarII, + [-1, 0, 0, 0, "M06-M06 common-leap backwards is -1y"], + [0, -12, 0, 0, "M06-M06 common-leap backwards is -12mo not -13mo"], + ], + [ + leapAdarII, common1Adar, + [-1, 0, 0, 0, "M06-M06 leap-common backwards is -1y"], + [0, -13, 0, 0, "M06-M06 leap-common backwards is -13mo not -12mo"], + ], + [ + common2Adar, common1Adar, + [-2, 0, 0, 0, "M06-M06 common-common backwards is -2y"], + [0, -25, 0, 0, "M06-M06 common-common backwards is -25mo not -24mo"], + ], + [ + common2Shevat, leapAdarI, + [0, -12, 0, 0, "M05-M05L backwards is -12mo not -1y"], + [0, -12, 0, 0, "M05-M05L backwards is -12mo"], + ], + [ + leapAdarI, common1Shevat, + [-1, -1, 0, 0, "M05L-M05 backwards is -1y -1mo (exhibits calendar-specific constraining)"], + [0, -13, 0, 0, "M05L-M05 backwards is -13mo"], + ], + [ + common2Adar, leapAdarI, + [-1, -1, 0, 0, "M06-M05L backwards is -1y -1mo"], + [0, -13, 0, 0, "M06-M05L backwards is -13mo"], + ], + [ + leapAdarI, common1Adar, + [0, -12, 0, 0, "M05L-M06 backwards is -12mo not -1y"], + [0, -12, 0, 0, "M05L-M06 backwards is -12mo"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.until(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.until(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.until(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.until(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.until(two); + const resultDaysISO = oneISO.until(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/until/until-across-lunisolar-leap-months.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/until/until-across-lunisolar-leap-months.js @@ -1,33 +0,0 @@ -// |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: dateUntil works as expected after a leap month in a lunisolar calendar -esid: sec-temporal.plaindate.prototype.until -features: [Temporal] ----*/ - -// 2001 is a leap year in the Chinese calendar with a M04L leap month. -// Therefore, month: 6 is M05 in 2001 but M06 in 2000 which is not a leap year. - -const year2000 = new Temporal.PlainDate(2000, 3, 1).withCalendar("chinese").year; -const year2001 = new Temporal.PlainDate(2001, 3, 1).withCalendar("chinese").year; - -const one = Temporal.PlainDate.from({ year: year2000, month: 6, day: 1, calendar: 'chinese' }); -const two = Temporal.PlainDate.from({ year: year2001, month: 6, day: 1, calendar: 'chinese' }); - -assert.sameValue(one.inLeapYear, false, "year 2000 is not a leap year"); -assert.sameValue(one.monthCode, "M06", "sixth month in year 2000 has month code M06"); - -assert.sameValue(two.inLeapYear, true, "year 2001 is a leap year"); -assert.sameValue(two.monthCode, "M05", "sixth month in year 2001 has month code M05"); - -const expected = { years: 'P12M', months: 'P12M', weeks: 'P50W4D', days: 'P354D' }; - -Object.entries(expected).forEach(([largestUnit, expectedResult]) => { - const actualResult = one.until(two, { largestUnit }); - assert.sameValue(actualResult.toString(), expectedResult); -}); - -reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/until/wrapping-at-end-of-month-gregorian.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/until/wrapping-at-end-of-month-gregorian.js @@ -7,7 +7,7 @@ esid: sec-temporal.plaindate.prototype.until description: > Tests balancing of days to months at end of month (ISO-like non-ISO calendars) includes: [temporalHelpers.js] -features: [Temporal] +features: [Temporal, Intl.Era-monthcode] ---*/ for (const calendar of ["buddhist", "gregory", "japanese", "roc"]) { diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/weekOfYear/non-iso-week-of-year.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/weekOfYear/non-iso-week-of-year.js @@ -7,7 +7,7 @@ esid: sec-temporal.plaindate.prototype.weekofyear description: > Temporal.PlainDate.prototype.weekOfYear returns undefined for all non-ISO calendars without a well-defined week numbering system. -features: [Temporal] +features: [Temporal, Intl.Era-monthcode] ---*/ assert.sameValue( diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/with/cross-era-boundary.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/with/cross-era-boundary.js @@ -5,7 +5,7 @@ /*--- esid: sec-temporal.plaindate.prototype.with description: Behaviour when property bag forms a date out of bounds of the current era -features: [Temporal] +features: [Temporal, Intl.Era-monthcode] ---*/ // Last day of Showa era diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/with/gregorian-mutually-exclusive-fields.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/with/gregorian-mutually-exclusive-fields.js @@ -6,7 +6,7 @@ esid: sec-temporal.plaindate.prototype.with description: Calendar-specific mutually exclusive keys in mergeFields includes: [temporalHelpers.js] -features: [Temporal] +features: [Temporal, Intl.Era-monthcode] ---*/ const instance = new Temporal.PlainDate(1981, 12, 15, "gregory"); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/with/japanese-mutually-exclusive-fields.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/with/japanese-mutually-exclusive-fields.js @@ -6,7 +6,7 @@ esid: sec-temporal.plaindate.prototype.with description: Calendar-specific mutually exclusive keys in mergeFields includes: [temporalHelpers.js] -features: [Temporal] +features: [Temporal, Intl.Era-monthcode] ---*/ const lastDayOfShowa = Temporal.PlainDate.from({ era: "showa", eraYear: 64, year: 1989, month: 1, monthCode: "M01", day: 7, calendar: "japanese" }); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/with/non-iso-calendar-fields.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/with/non-iso-calendar-fields.js @@ -5,7 +5,7 @@ /*--- esid: sec-temporal.plaindate.prototype.with description: Properties passed to with() are calendar fields, not ISO date -features: [Temporal] +features: [Temporal, Intl.Era-monthcode] ---*/ const instance = new Temporal.PlainDate(2024, 8, 8, "hebrew"); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/yearOfWeek/non-iso-week-of-year.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/yearOfWeek/non-iso-week-of-year.js @@ -7,7 +7,7 @@ esid: sec-temporal.plaindate.prototype.yearofweek description: > Temporal.PlainDate.prototype.yearOfWeek returns undefined for all non-ISO calendars without a well-defined week numbering system. -features: [Temporal] +features: [Temporal, Intl.Era-monthcode] ---*/ assert.sameValue( diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/basic-chinese.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/basic-chinese.js @@ -0,0 +1,64 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.add +description: Basic addition and subtraction in the chinese calendar +features: [Temporal, Intl.Era-monthcode] +includes: [temporalHelpers.js] +---*/ + +const calendar = "chinese"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const duration1 = new Temporal.Duration(0, 1); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2019, monthCode: "M11", day: 1, hour: 12, minute: 34, calendar }, options).add(duration1), + 2019, 12, "M12", 1, 12, 34, 0, 0, 0, 0, "add 1 month, with result in same year" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2019, monthCode: "M12", day: 1, hour: 12, minute: 34, calendar }, options).add(duration1), + 2020, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "add 1 month, with result in next year" +); + +// Weeks + +const months2weeks3 = new Temporal.Duration(0, /* months = */ 2, /* weeks = */ 3); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2021, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options).add(months2weeks3), + 2021, 3, "M03", 22, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from non-leap day/month, ending in same year" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2021, monthCode: "M12", day: 29, hour: 12, minute: 34, calendar }, options).add(months2weeks3), + 2022, 3, "M03", 21, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from end of year to next year" +); + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ 10); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2021, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options).add(days10), + 2021, 1, "M01", 11, 12, 34, 0, 0, 0, 0, "add 10 days, ending in same month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2021, monthCode: "M01", day: 29, hour: 12, minute: 34, calendar }, options).add(days10), + 2021, 2, "M02", 10, 12, 34, 0, 0, 0, 0, "add 10 days, ending in following month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2021, monthCode: "M12", day: 29, hour: 12, minute: 34, calendar }, options).add(days10), + 2022, 1, "M01", 10, 12, 34, 0, 0, 0, 0, "add 10 days, ending in following year" +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/basic-dangi.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/basic-dangi.js @@ -0,0 +1,64 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.add +description: Basic addition and subtraction in the dangi calendar +features: [Temporal, Intl.Era-monthcode] +includes: [temporalHelpers.js] +---*/ + +const calendar = "dangi"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const duration1 = new Temporal.Duration(0, 1); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2019, monthCode: "M11", day: 1, hour: 12, minute: 34, calendar }, options).add(duration1), + 2019, 12, "M12", 1, 12, 34, 0, 0, 0, 0, "add 1 month, with result in same year" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2019, monthCode: "M12", day: 1, hour: 12, minute: 34, calendar }, options).add(duration1), + 2020, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "add 1 month, with result in next year" +); + +// Weeks + +const months2weeks3 = new Temporal.Duration(0, /* months = */ 2, /* weeks = */ 3); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2021, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options).add(months2weeks3), + 2021, 3, "M03", 22, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from non-leap day/month, ending in same year" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2021, monthCode: "M12", day: 29, hour: 12, minute: 34, calendar }, options).add(months2weeks3), + 2022, 3, "M03", 21, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from end of year to next year" +); + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ 10); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2021, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options).add(days10), + 2021, 1, "M01", 11, 12, 34, 0, 0, 0, 0, "add 10 days, ending in same month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2021, monthCode: "M01", day: 29, hour: 12, minute: 34, calendar }, options).add(days10), + 2021, 2, "M02", 10, 12, 34, 0, 0, 0, 0, "add 10 days, ending in following month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2021, monthCode: "M12", day: 29, hour: 12, minute: 34, calendar }, options).add(days10), + 2022, 1, "M01", 10, 12, 34, 0, 0, 0, 0, "add 10 days, ending in following year" +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/basic-hebrew.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/basic-hebrew.js @@ -0,0 +1,30 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.add +description: Basic addition and subtraction in the hebrew calendar +features: [Temporal] +includes: [temporalHelpers.js] +---*/ + +const calendar = "hebrew"; +const options = { overflow: "reject" }; + +// Years + +// Months + +// Weeks + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ 10); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 5785, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options).add(days10), + 5785, 1, "M01", 11, 12, 34, 0, 0, 0, 0, "adding 10 days", "am", 5785 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/basic-islamic-civil.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/basic-islamic-civil.js @@ -0,0 +1,84 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.add +description: Basic addition and subtraction in the islamic-civil calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-civil"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const date1 = Temporal.PlainDateTime.from({ year: 1445, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(0, 8)), + 1445, 9, "M09", 1, 12, 34, 0, 0, 0, 0, "Adding 8 months to Muharram 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(0, 11)), + 1445, 12, "M12", 1, 12, 34, 0, 0, 0, 0, "Adding 11 months to Muharram 1445 lands in Dhu al-Hijjah", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(0, 12)), + 1446, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Adding 12 months to Muharram 1445 lands in Muharram 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }).add(new Temporal.Duration(0, 13)), + 1446, 7, "M07", 15, 12, 34, 0, 0, 0, 0, "Adding 13 months to Jumada II 1445 lands in Rajab 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M03", day: 15, hour: 12, minute: 34, calendar }, options).add(new Temporal.Duration(0, 6)), + 1445, 9, "M09", 15, 12, 34, 0, 0, 0, 0, "Adding 6 months to Rabi I 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1444, monthCode: "M10", day: 1, hour: 12, minute: 34, calendar }).add(new Temporal.Duration(0, 5)), + 1445, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "Adding 5 months to Shawwal 1444 crosses to 1445", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1400, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }).add(new Temporal.Duration(0, 100)), + 1408, 5, "M05", 1, 12, 34, 0, 0, 0, 0, "Adding a large number of months", + "ah", 1408 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M09", day: 1, hour: 12, minute: 34, calendar }, options).add(new Temporal.Duration(0, -8)), + 1445, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Subtracting 8 months from Ramadan 1445 lands in Muharram", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options).add(new Temporal.Duration(0, -12)), + 1444, 6, "M06", 1, 12, 34, 0, 0, 0, 0, "Subtracting 12 months from Jumada II 1445 lands in Jumada II 1444", + "ah", 1444 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M02", day: 15, hour: 12, minute: 34, calendar }, options).add(new Temporal.Duration(0, -5)), + 1444, 9, "M09", 15, 12, 34, 0, 0, 0, 0, "Subtracting 5 months from Safar 1445 crosses to Ramadan 1444", + "ah", 1444 +); + +// Weeks + +// Days + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/basic-islamic-tbla.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/basic-islamic-tbla.js @@ -0,0 +1,84 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.add +description: Basic addition and subtraction in the islamic-tbla calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-tbla"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const date1 = Temporal.PlainDateTime.from({ year: 1445, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(0, 8)), + 1445, 9, "M09", 1, 12, 34, 0, 0, 0, 0, "Adding 8 months to Muharram 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(0, 11)), + 1445, 12, "M12", 1, 12, 34, 0, 0, 0, 0, "Adding 11 months to Muharram 1445 lands in Dhu al-Hijjah", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(0, 12)), + 1446, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Adding 12 months to Muharram 1445 lands in Muharram 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }).add(new Temporal.Duration(0, 13)), + 1446, 7, "M07", 15, 12, 34, 0, 0, 0, 0, "Adding 13 months to Jumada II 1445 lands in Rajab 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M03", day: 15, hour: 12, minute: 34, calendar }, options).add(new Temporal.Duration(0, 6)), + 1445, 9, "M09", 15, 12, 34, 0, 0, 0, 0, "Adding 6 months to Rabi I 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1444, monthCode: "M10", day: 1, hour: 12, minute: 34, calendar }).add(new Temporal.Duration(0, 5)), + 1445, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "Adding 5 months to Shawwal 1444 crosses to 1445", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1400, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }).add(new Temporal.Duration(0, 100)), + 1408, 5, "M05", 1, 12, 34, 0, 0, 0, 0, "Adding a large number of months", + "ah", 1408 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M09", day: 1, hour: 12, minute: 34, calendar }, options).add(new Temporal.Duration(0, -8)), + 1445, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Subtracting 8 months from Ramadan 1445 lands in Muharram", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options).add(new Temporal.Duration(0, -12)), + 1444, 6, "M06", 1, 12, 34, 0, 0, 0, 0, "Subtracting 12 months from Jumada II 1445 lands in Jumada II 1444", + "ah", 1444 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M02", day: 15, hour: 12, minute: 34, calendar }, options).add(new Temporal.Duration(0, -5)), + 1444, 9, "M09", 15, 12, 34, 0, 0, 0, 0, "Subtracting 5 months from Safar 1445 crosses to Ramadan 1444", + "ah", 1444 +); + +// Weeks + +// Days + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/basic-islamic-umalqura.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/basic-islamic-umalqura.js @@ -0,0 +1,84 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.add +description: Basic addition and subtraction in the islamic-umalqura calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-umalqura"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const date1 = Temporal.PlainDateTime.from({ year: 1445, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(0, 8)), + 1445, 9, "M09", 1, 12, 34, 0, 0, 0, 0, "Adding 8 months to Muharram 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(0, 11)), + 1445, 12, "M12", 1, 12, 34, 0, 0, 0, 0, "Adding 11 months to Muharram 1445 lands in Dhu al-Hijjah", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(0, 12)), + 1446, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Adding 12 months to Muharram 1445 lands in Muharram 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }).add(new Temporal.Duration(0, 13)), + 1446, 7, "M07", 15, 12, 34, 0, 0, 0, 0, "Adding 13 months to Jumada II 1445 lands in Rajab 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M03", day: 15, hour: 12, minute: 34, calendar }, options).add(new Temporal.Duration(0, 6)), + 1445, 9, "M09", 15, 12, 34, 0, 0, 0, 0, "Adding 6 months to Rabi I 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1444, monthCode: "M10", day: 1, hour: 12, minute: 34, calendar }).add(new Temporal.Duration(0, 5)), + 1445, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "Adding 5 months to Shawwal 1444 crosses to 1445", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1400, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }).add(new Temporal.Duration(0, 100)), + 1408, 5, "M05", 1, 12, 34, 0, 0, 0, 0, "Adding a large number of months", + "ah", 1408 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M09", day: 1, hour: 12, minute: 34, calendar }, options).add(new Temporal.Duration(0, -8)), + 1445, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Subtracting 8 months from Ramadan 1445 lands in Muharram", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options).add(new Temporal.Duration(0, -12)), + 1444, 6, "M06", 1, 12, 34, 0, 0, 0, 0, "Subtracting 12 months from Jumada II 1445 lands in Jumada II 1444", + "ah", 1444 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M02", day: 15, hour: 12, minute: 34, calendar }, options).add(new Temporal.Duration(0, -5)), + 1444, 9, "M09", 15, 12, 34, 0, 0, 0, 0, "Subtracting 5 months from Safar 1445 crosses to Ramadan 1444", + "ah", 1444 +); + +// Weeks + +// Days + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/browser.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/browser.js diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/constrain-day-islamic-civil.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/constrain-day-islamic-civil.js @@ -0,0 +1,43 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.add +description: Constraining the day for 29/30-day months in islamic-civil calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-civil"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const months1 = new Temporal.Duration(0, 1); +const months1n = new Temporal.Duration(0, -1); + +const date1 = Temporal.PlainDateTime.from({ year: 1445, monthCode: "M01", day: 30, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.add(months1), + 1445, 2, "M02", 29, 12, 34, 0, 0, 0, 0, "Day is constrained when adding months to a 30-day month and landing in a 29-day month", + "ah", 1445 +); + +assert.throws(RangeError, function () { + date1.add(months1, options); +}, "Adding months to a 30-day month and landing in a 29-day month rejects"); + +TemporalHelpers.assertPlainDateTime( + date1.add(months1n), + 1444, 12, "M12", 29, 12, 34, 0, 0, 0, 0, "Day is constrained when subtracting months from a 30-day month and landing in a 29-day month", + "ah", 1444 +); + +assert.throws(RangeError, function () { + date1.add(months1n, options); +}, "Subtracting months from a 30-day month and landing in a 29-day month rejects"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/constrain-day-islamic-tbla.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/constrain-day-islamic-tbla.js @@ -0,0 +1,43 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.add +description: Constraining the day for 29/30-day months in islamic-tbla calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-tbla"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const months1 = new Temporal.Duration(0, 1); +const months1n = new Temporal.Duration(0, -1); + +const date1 = Temporal.PlainDateTime.from({ year: 1445, monthCode: "M01", day: 30, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.add(months1), + 1445, 2, "M02", 29, 12, 34, 0, 0, 0, 0, "Day is constrained when adding months to a 30-day month and landing in a 29-day month", + "ah", 1445 +); + +assert.throws(RangeError, function () { + date1.add(months1, options); +}, "Adding months to a 30-day month and landing in a 29-day month rejects"); + +TemporalHelpers.assertPlainDateTime( + date1.add(months1n), + 1444, 12, "M12", 29, 12, 34, 0, 0, 0, 0, "Day is constrained when subtracting months from a 30-day month and landing in a 29-day month", + "ah", 1444 +); + +assert.throws(RangeError, function () { + date1.add(months1n, options); +}, "Subtracting months from a 30-day month and landing in a 29-day month rejects"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/constrain-day-islamic-umalqura.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/constrain-day-islamic-umalqura.js @@ -0,0 +1,43 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.add +description: Constraining the day for 29/30-day months in islamic-umalqura calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-umalqura"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const months1 = new Temporal.Duration(0, /* months = */ 1); +const months1n = new Temporal.Duration(0, -1); + +const date1 = Temporal.PlainDateTime.from({ year: 1447, monthCode: "M01", day: 30, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.add(months1), + 1447, 2, "M02", 29, 12, 34, 0, 0, 0, 0, "Day is constrained when adding months to a 30-day month and landing in a 29-day month", + "ah", 1447 +); + +assert.throws(RangeError, function () { + date1.add(months1, options); +}, "Adding months to a 30-day month and landing in a 29-day month rejects"); + +TemporalHelpers.assertPlainDateTime( + date1.add(months1n), + 1446, 12, "M12", 29, 12, 34, 0, 0, 0, 0, "Day is constrained when subtracting months from a 30-day month and landing in a 29-day month", + "ah", 1446 +); + +assert.throws(RangeError, function () { + date1.add(months1n, options); +}, "Subtracting months from a 30-day month and landing in a 29-day month rejects"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/era-boundary-ethiopic.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/era-boundary-ethiopic.js @@ -0,0 +1,51 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.add +description: Adding years works correctly across era boundaries in ethiopic calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const duration5 = new Temporal.Duration(5); +const duration5n = new Temporal.Duration(-5); +const calendar = "ethiopic"; +const options = { overflow: "reject" }; + +const date1 = Temporal.PlainDateTime.from({ era: "aa", eraYear: 5500, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(1)), + 1, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Adding 1 year to last year of Amete Alem era lands in year 1 of incarnation era", + "am", 1 +); + +const date2 = Temporal.PlainDateTime.from({ era: "am", eraYear: 2000, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date2.add(duration5), + 2005, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Adding 5 years within incarnation era", + "am", 2005 +); + +const date3 = Temporal.PlainDateTime.from({ era: "aa", eraYear: 5450, monthCode: "M07", day: 12, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date3.add(duration5), + -45, 7, "M07", 12, 12, 34, 0, 0, 0, 0, "Adding 5 years within Amete Alem era", + "aa", 5455 +); + +TemporalHelpers.assertPlainDateTime( + date2.add(duration5n), + 1995, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Subtracting 5 years within incarnation era", + "am", 1995 +); + +const date4 = Temporal.PlainDateTime.from({ era: "am", eraYear: 5, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date4.add(duration5n), + 0, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Subtracting 5 years from year 5 lands in last year of Amete Alem era, arithmetic year 0", + "aa", 5500 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/era-boundary-gregory.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/era-boundary-gregory.js @@ -0,0 +1,71 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.add +description: Adding years works correctly across era boundaries in gregory calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const duration1 = new Temporal.Duration(1); +const duration1n = new Temporal.Duration(-1); +const calendar = "gregory"; +const options = { overflow: "reject" }; + +const date1 = Temporal.PlainDateTime.from({ era: "bce", eraYear: 2, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.add(duration1), + 0, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to 2 BCE lands in 1 BCE (counts backwards)", + "bce", 1 +); + +const date2 = Temporal.PlainDateTime.from({ era: "bce", eraYear: 1, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date2.add(duration1), + 1, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to 1 BCE lands in 1 CE (no year zero)", + "ce", 1 +); + +const date3 = Temporal.PlainDateTime.from({ era: "ce", eraYear: 1, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date3.add(duration1), + 2, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to 1 CE lands in 2 CE", + "ce", 2 +); + +const date4 = Temporal.PlainDateTime.from({ era: "bce", eraYear: 5, monthCode: "M03", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date4.add(new Temporal.Duration(10)), + 6, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "Adding 10 years to 5 BCE lands in 6 CE (no year zero)", + "ce", 6 +); + +const date5 = Temporal.PlainDateTime.from({ era: "ce", eraYear: 2, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date5.add(duration1n), + 1, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from 2 CE lands in 1 CE", + "ce", 1 +); + +TemporalHelpers.assertPlainDateTime( + date3.add(duration1n), + 0, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from 1 CE lands in 1 BCE", + "bce", 1 +); + +TemporalHelpers.assertPlainDateTime( + date2.add(duration1n), + -1, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from 1 BCE lands in 2 BCE", + "bce", 2 +); + +const date6 = Temporal.PlainDateTime.from({ era: "ce", eraYear: 5, monthCode: "M03", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date6.add(new Temporal.Duration(-10)), + -5, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "Subtracting 10 years from 5 CE lands in 6 BCE", + "bce", 6 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/era-boundary-japanese.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/era-boundary-japanese.js @@ -0,0 +1,84 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.add +description: > + Adding years works correctly across era boundaries in calendars with eras +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +// Reiwa era started on May 1, 2019 (Reiwa 1 = 2019) +// Heisei era: 1989-2019 (Heisei 31 ended April 30, 2019) + +const duration1 = new Temporal.Duration(1); +const duration1n = new Temporal.Duration(-1); +const calendar = "japanese"; +const options = { overflow: "reject" }; + +const date1 = Temporal.PlainDateTime.from({ era: "heisei", eraYear: 30, monthCode: "M03", day: 15, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.add(duration1), + 2019, 3, "M03", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to Heisei 30 March (before May 1) lands in Heisei 31 March", + "heisei", 31 +); + +const date2 = Temporal.PlainDateTime.from({ era: "heisei", eraYear: 31, monthCode: "M04", day: 15, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date2.add(duration1), + 2020, 4, "M04", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to Heisei 31 April (before May 1) lands in Reiwa 2 April", + "reiwa", 2 +); + +const date3 = Temporal.PlainDateTime.from({ era: "heisei", eraYear: 30, monthCode: "M06", day: 12, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date3.add(duration1), + 2019, 6, "M06", 12, 12, 34, 0, 0, 0, 0, "Adding 1 year to Heisei 30 June (after May 1) lands in Reiwa 1 June", + "reiwa", 1 +); + +const date4 = Temporal.PlainDateTime.from({ era: "reiwa", eraYear: 1, monthCode: "M06", day: 10, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date4.add(duration1), + 2020, 6, "M06", 10, 12, 34, 0, 0, 0, 0, "Adding 1 year to Reiwa 1 June lands in Reiwa 2 June", + "reiwa", 2 +); + +const date5 = Temporal.PlainDateTime.from({ era: "heisei", eraYear: 28, monthCode: "M07", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date5.add(new Temporal.Duration(3)), + 2019, 7, "M07", 1, 12, 34, 0, 0, 0, 0, "Multiple years across era boundary: Adding 3 years to Heisei 28 July lands in Reiwa 1 July", + "reiwa", 1 +); + +const date6 = Temporal.PlainDateTime.from({ era: "reiwa", eraYear: 2, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date6.add(duration1n), + 2019, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from Reiwa 2 June lands in Reiwa 1 June", + "reiwa", 1 +); + +const date7 = Temporal.PlainDateTime.from({ era: "reiwa", eraYear: 2, monthCode: "M03", day: 15, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date7.add(duration1n), + 2019, 3, "M03", 15, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from Reiwa 2 March lands in Heisei 31 March", + "heisei", 31 +); + +const date8 = Temporal.PlainDateTime.from({ era: "reiwa", eraYear: 1, monthCode: "M07", day: 10, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date8.add(duration1n), + 2018, 7, "M07", 10, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from Reiwa 1 July lands in Heisei 30 July", + "heisei", 30 +); + +const date9 = Temporal.PlainDateTime.from({ era: "reiwa", eraYear: 4, monthCode: "M02", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date9.add(new Temporal.Duration(-5)), + 2017, 2, "M02", 1, 12, 34, 0, 0, 0, 0, "Subtracting 5 years from Reiwa 4 February lands in Heisei 29 February", + "heisei", 29 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/era-boundary-roc.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/era-boundary-roc.js @@ -0,0 +1,66 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.add +description: > + Adding years works correctly across era boundaries in calendars with eras +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const duration1 = new Temporal.Duration(1); +const duration1n = new Temporal.Duration(-1); +const calendar = "roc"; +const options = { overflow: "reject" }; + +const date1 = Temporal.PlainDateTime.from({ era: "broc", eraYear: 2, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.add(duration1), + 0, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to 2 BROC lands in 1 BROC (counts backwards)", + "broc", 1 +); + +const date2 = Temporal.PlainDateTime.from({ era: "broc", eraYear: 1, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date2.add(duration1), + 1, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to 1 BROC lands in 1 ROC (no year zero)", + "roc", 1 +); + +const date3 = Temporal.PlainDateTime.from({ era: "roc", eraYear: 1, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date3.add(duration1), + 2, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to 1 ROC lands in 2 ROC", + "roc", 2 +); + +const date4 = Temporal.PlainDateTime.from({ era: "broc", eraYear: 5, monthCode: "M03", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date4.add(new Temporal.Duration(10)), + 6, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "Adding 10 years to 5 BROC lands in 6 ROC (no year zero)", + "roc", 6 +); + +const date5 = Temporal.PlainDateTime.from({ era: "roc", eraYear: 5, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date5.add(duration1n), + 4, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from ROC 5 lands in ROC 4", + "roc", 4 +); + +TemporalHelpers.assertPlainDateTime( + date3.add(duration1n), + 0, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from ROC 1 lands in BROC 1", + "broc", 1 +); + +const date6 = Temporal.PlainDateTime.from({ era: "roc", eraYear: 10, monthCode: "M03", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date6.add(new Temporal.Duration(-15)), + -5, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "Subtracting 15 years from ROC 10 lands in BROC 6", + "broc", 6 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/leap-months-chinese.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/leap-months-chinese.js @@ -0,0 +1,129 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.add +description: Arithmetic around leap months in the chinese calendar +features: [Temporal, Intl.Era-monthcode] +includes: [temporalHelpers.js] +---*/ + +const calendar = "chinese"; +const options = { overflow: "reject" }; + +// Years + +const years1 = new Temporal.Duration(1); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2019, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options).add(years1), + 2020, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "add 1 year from non-leap day" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1966, monthCode: "M03L", day: 1, hour: 12, minute: 34, calendar }, options).add(years1), + 1967, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "add 1 year from leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1938, monthCode: "M07L", day: 30, hour: 12, minute: 34, calendar }, options).add(years1), + 1939, 7, "M07", 29, 12, 34, 0, 0, 0, 0, "add 1 year from leap day in leap month" +); + +// Months + +const months1 = new Temporal.Duration(0, 1); +const months1n = new Temporal.Duration(0, -1); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1947, monthCode: "M02L", day: 1, hour: 12, minute: 34, calendar }, options).add(months1), + 1947, 4, "M03", 1, 12, 34, 0, 0, 0, 0, "add 1 month, starting at start of leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1955, monthCode: "M03L", day: 1, hour: 12, minute: 34, calendar }, options).add(months1), + 1955, 5, "M04", 1, 12, 34, 0, 0, 0, 0, "add 1 month, starting at start of leap month with 30 days" +); + +const date1 = Temporal.PlainDateTime.from({ year: 2020, monthCode: "M03", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.add(months1), + 2020, 4, "M04", 1, 12, 34, 0, 0, 0, 0, "adding 1 month to M03 in leap year lands in M04 (not M04L)" +); + +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(0, 2)), + 2020, 5, "M04L", 1, 12, 34, 0, 0, 0, 0, "adding 2 months to M03 in leap year lands in M04L (leap month)" +); + +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(0, 3)), + 2020, 6, "M05", 1, 12, 34, 0, 0, 0, 0, "adding 3 months to M03 in leap year lands in M05 (not M06)" +); + +const date2 = Temporal.PlainDateTime.from({ year: 2020, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date2.add(months1n), + 2020, 6, "M05", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M06 in leap year lands in M05" +); + +TemporalHelpers.assertPlainDateTime( + date2.add(new Temporal.Duration(0, -2)), + 2020, 5, "M04L", 1, 12, 34, 0, 0, 0, 0, "Subtracting 2 months from M06 in leap year lands in M04L (leap month)" +); + +TemporalHelpers.assertPlainDateTime( + date2.add(new Temporal.Duration(0, -3)), + 2020, 4, "M04", 1, 12, 34, 0, 0, 0, 0, "Subtracting 3 months from M06 in leap year lands in M04 (not M03)" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2020, monthCode: "M05", day: 1, hour: 12, minute: 34, calendar }, options).add(months1n), + 2020, 5, "M04L", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M05 in leap year lands in M04L" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2020, monthCode: "M04L", day: 1, hour: 12, minute: 34, calendar }, options).add(months1n), + 2020, 4, "M04", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M04L in calendar lands in M04" +); + +// Weeks + +const months2weeks3 = new Temporal.Duration(0, /* months = */ 2, /* weeks = */ 3); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1947, monthCode: "M02L", day: 29, hour: 12, minute: 34, calendar }, options).add(months2weeks3), + 1947, 6, "M05", 20, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from last day leap month without leap day" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1955, monthCode: "M03L", day: 30, hour: 12, minute: 34, calendar }, options).add(months2weeks3), + 1955, 7, "M06", 21, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from leap day in leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1947, monthCode: "M01", day: 29, hour: 12, minute: 34, calendar }, options).add(months2weeks3), + 1947, 4, "M03", 21, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from immediately before a leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1955, monthCode: "M06", day: 29, hour: 12, minute: 34, calendar }, options).add(months2weeks3), + 1955, 10, "M09", 20, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from immediately before a leap month" +); + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ 10); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1955, monthCode: "M03L", day: 30, hour: 12, minute: 34, calendar }, options).add(days10), + 1955, 5, "M04", 10, 12, 34, 0, 0, 0, 0, "add 10 days from leap day in leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1947, monthCode: "M02L", day: 29, hour: 12, minute: 34, calendar }, options).add(days10), + 1947, 4, "M03", 10, 12, 34, 0, 0, 0, 0, "add 10 days from last day of leap month" +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/leap-months-dangi.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/leap-months-dangi.js @@ -0,0 +1,129 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.add +description: Arithmetic around leap months in the dangi calendar +features: [Temporal, Intl.Era-monthcode] +includes: [temporalHelpers.js] +---*/ + +const calendar = "dangi"; +const options = { overflow: "reject" }; + +// Years + +const years1 = new Temporal.Duration(1); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2019, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options).add(years1), + 2020, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "add 1 year from non-leap day" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1966, monthCode: "M03L", day: 1, hour: 12, minute: 34, calendar }, options).add(years1), + 1967, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "add 1 year from leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1938, monthCode: "M07L", day: 30, hour: 12, minute: 34, calendar }, options).add(years1), + 1939, 7, "M07", 29, 12, 34, 0, 0, 0, 0, "add 1 year from leap day in leap month" +); + +// Months + +const months1 = new Temporal.Duration(0, 1); +const months1n = new Temporal.Duration(0, -1); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1947, monthCode: "M02L", day: 1, hour: 12, minute: 34, calendar }, options).add(months1), + 1947, 4, "M03", 1, 12, 34, 0, 0, 0, 0, "add 1 month, starting at start of leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1955, monthCode: "M03L", day: 1, hour: 12, minute: 34, calendar }, options).add(months1), + 1955, 5, "M04", 1, 12, 34, 0, 0, 0, 0, "add 1 month, starting at start of leap month with 30 days" +); + +const date1 = Temporal.PlainDateTime.from({ year: 2020, monthCode: "M03", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.add(months1), + 2020, 4, "M04", 1, 12, 34, 0, 0, 0, 0, "adding 1 month to M03 in leap year lands in M04 (not M04L)" +); + +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(0, 2)), + 2020, 5, "M04L", 1, 12, 34, 0, 0, 0, 0, "adding 2 months to M03 in leap year lands in M04L (leap month)" +); + +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(0, 3)), + 2020, 6, "M05", 1, 12, 34, 0, 0, 0, 0, "adding 3 months to M03 in leap year lands in M05 (not M06)" +); + +const date2 = Temporal.PlainDateTime.from({ year: 2020, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date2.add(months1n), + 2020, 6, "M05", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M06 in leap year lands in M05" +); + +TemporalHelpers.assertPlainDateTime( + date2.add(new Temporal.Duration(0, -2)), + 2020, 5, "M04L", 1, 12, 34, 0, 0, 0, 0, "Subtracting 2 months from M06 in leap year lands in M04L (leap month)" +); + +TemporalHelpers.assertPlainDateTime( + date2.add(new Temporal.Duration(0, -3)), + 2020, 4, "M04", 1, 12, 34, 0, 0, 0, 0, "Subtracting 3 months from M06 in leap year lands in M04 (not M03)" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2020, monthCode: "M05", day: 1, hour: 12, minute: 34, calendar }, options).add(months1n), + 2020, 5, "M04L", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M05 in leap year lands in M04L" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2020, monthCode: "M04L", day: 1, hour: 12, minute: 34, calendar }, options).add(months1n), + 2020, 4, "M04", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M04L in calendar lands in M04" +); + +// Weeks + +const months2weeks3 = new Temporal.Duration(0, /* months = */ 2, /* weeks = */ 3); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1947, monthCode: "M02L", day: 29, hour: 12, minute: 34, calendar }, options).add(months2weeks3), + 1947, 6, "M05", 20, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from last day leap month without leap day" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1955, monthCode: "M03L", day: 30, hour: 12, minute: 34, calendar }, options).add(months2weeks3), + 1955, 7, "M06", 21, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from leap day in leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1947, monthCode: "M01", day: 29, hour: 12, minute: 34, calendar }, options).add(months2weeks3), + 1947, 4, "M03", 21, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from immediately before a leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1955, monthCode: "M06", day: 29, hour: 12, minute: 34, calendar }, options).add(months2weeks3), + 1955, 10, "M09", 20, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from immediately before a leap month" +); + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ 10); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1955, monthCode: "M03L", day: 30, hour: 12, minute: 34, calendar }, options).add(days10), + 1955, 5, "M04", 10, 12, 34, 0, 0, 0, 0, "add 10 days from leap day in leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1947, monthCode: "M02L", day: 29, hour: 12, minute: 34, calendar }, options).add(days10), + 1947, 4, "M03", 10, 12, 34, 0, 0, 0, 0, "add 10 days from last day of leap month" +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/leap-months-hebrew.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/leap-months-hebrew.js @@ -0,0 +1,104 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.add +description: Arithmetic around leap months in the hebrew calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "hebrew"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const months1 = new Temporal.Duration(0, 1); +const months1n = new Temporal.Duration(0, -1); +const months2 = new Temporal.Duration(0, 2); +const months2n = new Temporal.Duration(0, -2); + +const date1 = Temporal.PlainDateTime.from({ year: 5784, monthCode: "M04", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.add(months1), + 5784, 5, "M05", 1, 12, 34, 0, 0, 0, 0, "Adding 1 month to M04 in leap year lands in M05 (Shevat)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + date1.add(months2), + 5784, 6, "M05L", 1, 12, 34, 0, 0, 0, 0, "Adding 2 months to M04 in leap year lands in M05L (Adar I)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(0, 3)), + 5784, 7, "M06", 1, 12, 34, 0, 0, 0, 0, "Adding 3 months to M04 in leap year lands in M06 (Adar II)", + "am", 5784 +); + +const date2 = Temporal.PlainDateTime.from({ year: 5784, monthCode: "M05L", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date2.add(months1), + 5784, 7, "M06", 1, 12, 34, 0, 0, 0, 0, "Adding 1 month to M05L (Adar I) lands in M06 (Adar II)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 5783, monthCode: "M04", day: 1, hour: 12, minute: 34, calendar }, options).add(months2), + 5783, 6, "M06", 1, 12, 34, 0, 0, 0, 0, "Adding 2 months to M04 in non-leap year lands in M06 (no M05L)", + "am", 5783 +); + +const date3 = Temporal.PlainDateTime.from({ year: 5784, monthCode: "M07", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date3.add(months1n), + 5784, 7, "M06", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M07 in leap year lands in M06 (Adar II)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + date3.add(months2n), + 5784, 6, "M05L", 1, 12, 34, 0, 0, 0, 0, "Subtracting 2 months from M07 in leap year lands in M05L (Adar I)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + date3.add(new Temporal.Duration(0, -3)), + 5784, 5, "M05", 1, 12, 34, 0, 0, 0, 0, "Subtracting 3 months from M07 in leap year lands in M05 (Shevat)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 5784, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }).add(months1n), + 5784, 6, "M05L", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M06 (Adar II) in leap year lands in M05L (Adar I)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + date2.add(months1n), + 5784, 5, "M05", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M05L (Adar I) lands in M05 (Shevat)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 5783, monthCode: "M07", day: 1, hour: 12, minute: 34, calendar }).add(months2n), + 5783, 5, "M05", 1, 12, 34, 0, 0, 0, 0, "Subtracting 2 months from M07 in non-leap year lands in M05 (no M05L)", + "am", 5783 +); + +// Weeks + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ 10); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 5784, monthCode: "M05L", day: 30, hour: 12, minute: 34, calendar }, options).add(days10), + 5784, 7, "M06", 10, 12, 34, 0, 0, 0, 0, "add 10 days to leap day", "am", 5784 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/shell.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/add/shell.js @@ -0,0 +1,1252 @@ +// GENERATED, DO NOT EDIT +// file: temporalHelpers.js +// Copyright (C) 2021 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +description: | + This defines helper objects and functions for testing Temporal. +defines: [TemporalHelpers] +features: [Symbol.species, Symbol.iterator, Temporal] +---*/ + +const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u; + +function formatPropertyName(propertyKey, objectName = "") { + switch (typeof propertyKey) { + case "symbol": + if (Symbol.keyFor(propertyKey) !== undefined) { + return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`; + } else if (propertyKey.description.startsWith('Symbol.')) { + return `${objectName}[${propertyKey.description}]`; + } else { + return `${objectName}[Symbol('${propertyKey.description}')]` + } + case "string": + if (propertyKey !== String(Number(propertyKey))) { + if (ASCII_IDENTIFIER.test(propertyKey)) { + return objectName ? `${objectName}.${propertyKey}` : propertyKey; + } + return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']` + } + // fall through + default: + // integer or string integer-index + return `${objectName}[${propertyKey}]`; + } +} + +const SKIP_SYMBOL = Symbol("Skip"); + +var TemporalHelpers = { + /* + * Codes and maximum lengths of months in the ISO 8601 calendar. + */ + ISOMonths: [ + { month: 1, monthCode: "M01", daysInMonth: 31 }, + { month: 2, monthCode: "M02", daysInMonth: 29 }, + { month: 3, monthCode: "M03", daysInMonth: 31 }, + { month: 4, monthCode: "M04", daysInMonth: 30 }, + { month: 5, monthCode: "M05", daysInMonth: 31 }, + { month: 6, monthCode: "M06", daysInMonth: 30 }, + { month: 7, monthCode: "M07", daysInMonth: 31 }, + { month: 8, monthCode: "M08", daysInMonth: 31 }, + { month: 9, monthCode: "M09", daysInMonth: 30 }, + { month: 10, monthCode: "M10", daysInMonth: 31 }, + { month: 11, monthCode: "M11", daysInMonth: 30 }, + { month: 12, monthCode: "M12", daysInMonth: 31 } + ], + + /* + * List of known calendar eras and their possible aliases. + * + * https://tc39.es/proposal-intl-era-monthcode/#table-eras + */ + CalendarEras: { + buddhist: [ + { era: "be" }, + ], + coptic: [ + { era: "am" }, + ], + ethiopic: [ + { era: "aa", aliases: ["mundi"] }, + { era: "am", aliases: ["incar"] }, + ], + ethioaa: [ + { era: "aa", aliases: ["mundi"] }, + ], + gregory: [ + { era: "bce", aliases: ["bc"] }, + { era: "ce", aliases: ["ad"] }, + ], + hebrew: [ + { era: "am" }, + ], + indian: [ + { era: "shaka" }, + ], + islamic: [ + { era: "ah" }, + { era: "bh" }, + ], + "islamic-civil": [ + { era: "bh" }, + { era: "ah" }, + ], + "islamic-rgsa": [ + { era: "bh" }, + { era: "ah" }, + ], + "islamic-tbla": [ + { era: "bh" }, + { era: "ah" }, + ], + "islamic-umalqura": [ + { era: "bh" }, + { era: "ah" }, + ], + japanese: [ + { era: "bce", aliases: ["bc"] }, + { era: "ce", aliases: ["ad"] }, + { era: "heisei" }, + { era: "meiji" }, + { era: "reiwa" }, + { era: "showa" }, + { era: "taisho" }, + ], + persian: [ + { era: "ap" }, + ], + roc: [ + { era: "roc", aliases: ["minguo"] }, + { era: "broc", aliases: ["before-roc", "minguo-qian"] }, + ], + }, + + /* + * Return the canonical era code. + */ + canonicalizeCalendarEra(calendarId, eraName) { + assert.sameValue(typeof calendarId, "string", "calendar must be string in canonicalizeCalendarEra"); + + if (!Object.prototype.hasOwnProperty.call(TemporalHelpers.CalendarEras, calendarId)) { + assert.sameValue(eraName, undefined); + return undefined; + } + + assert.sameValue(typeof eraName, "string", "eraName must be string or undefined in canonicalizeCalendarEra"); + + for (let {era, aliases = []} of TemporalHelpers.CalendarEras[calendarId]) { + if (era === eraName || aliases.includes(eraName)) { + return era; + } + } + throw new Test262Error(`Unsupported era name: ${eraName}`); + }, + + /* + * assertDuration(duration, years, ..., nanoseconds[, description]): + * + * Shorthand for asserting that each field of a Temporal.Duration is equal to + * an expected value. + */ + assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(duration instanceof Temporal.Duration, `${prefix}instanceof`); + assert.sameValue(duration.years, years, `${prefix}years result:`); + assert.sameValue(duration.months, months, `${prefix}months result:`); + assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`); + assert.sameValue(duration.days, days, `${prefix}days result:`); + assert.sameValue(duration.hours, hours, `${prefix}hours result:`); + assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`); + assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`); + assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`); + assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`); + assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`); + }, + + /* + * assertDateDuration(duration, years, months, weeks, days, [, description]): + * + * Shorthand for asserting that each date field of a Temporal.Duration is + * equal to an expected value. + */ + assertDateDuration(duration, years, months, weeks, days, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(duration instanceof Temporal.Duration, `${prefix}instanceof`); + assert.sameValue(duration.years, years, `${prefix}years result:`); + assert.sameValue(duration.months, months, `${prefix}months result:`); + assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`); + assert.sameValue(duration.days, days, `${prefix}days result:`); + assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`); + assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`); + assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`); + assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`); + assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`); + assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`); + }, + + /* + * assertDurationsEqual(actual, expected[, description]): + * + * Shorthand for asserting that each field of a Temporal.Duration is equal to + * the corresponding field in another Temporal.Duration. + */ + assertDurationsEqual(actual, expected, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`); + TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description); + }, + + /* + * assertInstantsEqual(actual, expected[, description]): + * + * Shorthand for asserting that two Temporal.Instants are of the correct type + * and equal according to their equals() methods. + */ + assertInstantsEqual(actual, expected, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`); + assert(actual instanceof Temporal.Instant, `${prefix}instanceof`); + assert(actual.equals(expected), `${prefix}equals method`); + }, + + /* + * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]): + * + * Shorthand for asserting that each field of a Temporal.PlainDate is equal to + * an expected value. (Except the `calendar` property, since callers may want + * to assert either object equality with an object they put in there, or the + * value of date.calendarId.) + */ + assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) { + const prefix = description ? `${description}: ` : ""; + assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`); + assert.sameValue( + TemporalHelpers.canonicalizeCalendarEra(date.calendarId, date.era), + TemporalHelpers.canonicalizeCalendarEra(date.calendarId, era), + `${prefix}era result:` + ); + assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`); + assert.sameValue(date.year, year, `${prefix}year result:`); + assert.sameValue(date.month, month, `${prefix}month result:`); + assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`); + assert.sameValue(date.day, day, `${prefix}day result:`); + }, + + /* + * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]): + * + * Shorthand for asserting that each field of a Temporal.PlainDateTime is + * equal to an expected value. (Except the `calendar` property, since callers + * may want to assert either object equality with an object they put in there, + * or the value of datetime.calendarId.) + */ + assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) { + const prefix = description ? `${description}: ` : ""; + assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`); + assert.sameValue( + TemporalHelpers.canonicalizeCalendarEra(datetime.calendarId, datetime.era), + TemporalHelpers.canonicalizeCalendarEra(datetime.calendarId, era), + `${prefix}era result:` + ); + assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`); + assert.sameValue(datetime.year, year, `${prefix}year result:`); + assert.sameValue(datetime.month, month, `${prefix}month result:`); + assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`); + assert.sameValue(datetime.day, day, `${prefix}day result:`); + assert.sameValue(datetime.hour, hour, `${prefix}hour result:`); + assert.sameValue(datetime.minute, minute, `${prefix}minute result:`); + assert.sameValue(datetime.second, second, `${prefix}second result:`); + assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`); + assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`); + assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`); + }, + + /* + * assertPlainDateTimesEqual(actual, expected[, description]): + * + * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct + * type, equal according to their equals() methods, and additionally that + * their calendar internal slots are the same value. + */ + assertPlainDateTimesEqual(actual, expected, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`); + assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`); + assert(actual.equals(expected), `${prefix}equals method`); + assert.sameValue( + actual.calendarId, + expected.calendarId, + `${prefix}calendar same value:` + ); + }, + + /* + * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]): + * + * Shorthand for asserting that each field of a Temporal.PlainMonthDay is + * equal to an expected value. (Except the `calendar` property, since callers + * may want to assert either object equality with an object they put in there, + * or the value of monthDay.calendarId().) + */ + assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) { + const prefix = description ? `${description}: ` : ""; + assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`); + assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`); + assert.sameValue(monthDay.day, day, `${prefix}day result:`); + const isoYear = Number(monthDay.toString({ calendarName: "always" }).split("-")[0]); + assert.sameValue(isoYear, referenceISOYear, `${prefix}referenceISOYear result:`); + }, + + /* + * assertPlainTime(time, hour, ..., nanosecond[, description]): + * + * Shorthand for asserting that each field of a Temporal.PlainTime is equal to + * an expected value. + */ + assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`); + assert.sameValue(time.hour, hour, `${prefix}hour result:`); + assert.sameValue(time.minute, minute, `${prefix}minute result:`); + assert.sameValue(time.second, second, `${prefix}second result:`); + assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`); + assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`); + assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`); + }, + + /* + * assertPlainTimesEqual(actual, expected[, description]): + * + * Shorthand for asserting that two Temporal.PlainTimes are of the correct + * type and equal according to their equals() methods. + */ + assertPlainTimesEqual(actual, expected, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`); + assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`); + assert(actual.equals(expected), `${prefix}equals method`); + }, + + /* + * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]): + * + * Shorthand for asserting that each field of a Temporal.PlainYearMonth is + * equal to an expected value. (Except the `calendar` property, since callers + * may want to assert either object equality with an object they put in there, + * or the value of yearMonth.calendarId.) + */ + assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) { + const prefix = description ? `${description}: ` : ""; + assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`); + assert.sameValue( + TemporalHelpers.canonicalizeCalendarEra(yearMonth.calendarId, yearMonth.era), + TemporalHelpers.canonicalizeCalendarEra(yearMonth.calendarId, era), + `${prefix}era result:` + ); + assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`); + assert.sameValue(yearMonth.year, year, `${prefix}year result:`); + assert.sameValue(yearMonth.month, month, `${prefix}month result:`); + assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`); + const isoDay = Number(yearMonth.toString({ calendarName: "always" }).slice(1).split('-')[2].slice(0, 2)); + assert.sameValue(isoDay, referenceISODay, `${prefix}referenceISODay result:`); + }, + + /* + * assertZonedDateTimesEqual(actual, expected[, description]): + * + * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct + * type, equal according to their equals() methods, and additionally that + * their time zones and calendar internal slots are the same value. + */ + assertZonedDateTimesEqual(actual, expected, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`); + assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`); + assert(actual.equals(expected), `${prefix}equals method`); + assert.sameValue(actual.timeZoneId, expected.timeZoneId, `${prefix}time zone same value:`); + assert.sameValue( + actual.calendarId, + expected.calendarId, + `${prefix}calendar same value:` + ); + }, + + /* + * assertUnreachable(description): + * + * Helper for asserting that code is not executed. + */ + assertUnreachable(description) { + let message = "This code should not be executed"; + if (description) { + message = `${message}: ${description}`; + } + throw new Test262Error(message); + }, + + /* + * checkPlainDateTimeConversionFastPath(func): + * + * ToTemporalDate and ToTemporalTime should both, if given a + * Temporal.PlainDateTime instance, convert to the desired type by reading the + * PlainDateTime's internal slots, rather than calling any getters. + * + * func(datetime) is the actual operation to test, that must + * internally call the abstract operation ToTemporalDate or ToTemporalTime. + * It is passed a Temporal.PlainDateTime instance. + */ + checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") { + const actual = []; + const expected = []; + + const calendar = "iso8601"; + const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar); + const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype); + ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => { + Object.defineProperty(datetime, property, { + get() { + actual.push(`get ${formatPropertyName(property)}`); + const value = prototypeDescrs[property].get.call(this); + return { + toString() { + actual.push(`toString ${formatPropertyName(property)}`); + return value.toString(); + }, + valueOf() { + actual.push(`valueOf ${formatPropertyName(property)}`); + return value; + }, + }; + }, + }); + }); + Object.defineProperty(datetime, "calendar", { + get() { + actual.push("get calendar"); + return calendar; + }, + }); + + func(datetime); + assert.compareArray(actual, expected, `${message}: property getters not called`); + }, + + /* + * Check that an options bag that accepts units written in the singular form, + * also accepts the same units written in the plural form. + * func(unit) should call the method with the appropriate options bag + * containing unit as a value. This will be called twice for each element of + * validSingularUnits, once with singular and once with plural, and the + * results of each pair should be the same (whether a Temporal object or a + * primitive value.) + */ + checkPluralUnitsAccepted(func, validSingularUnits) { + const plurals = { + year: 'years', + month: 'months', + week: 'weeks', + day: 'days', + hour: 'hours', + minute: 'minutes', + second: 'seconds', + millisecond: 'milliseconds', + microsecond: 'microseconds', + nanosecond: 'nanoseconds', + }; + + validSingularUnits.forEach((unit) => { + const singularValue = func(unit); + const pluralValue = func(plurals[unit]); + const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`; + if (singularValue instanceof Temporal.Duration) { + TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc); + } else if (singularValue instanceof Temporal.Instant) { + TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc); + } else if (singularValue instanceof Temporal.PlainDateTime) { + TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc); + } else if (singularValue instanceof Temporal.PlainTime) { + TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc); + } else if (singularValue instanceof Temporal.ZonedDateTime) { + TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc); + } else { + assert.sameValue(pluralValue, singularValue); + } + }); + }, + + /* + * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc): + * + * Checks the type handling of the roundingIncrement option. + * checkFunc(roundingIncrement) is a function which takes the value of + * roundingIncrement to test, and calls the method under test with it, + * returning the result. assertTrueResultFunc(result, description) should + * assert that result is the expected result with roundingIncrement: true, and + * assertObjectResultFunc(result, description) should assert that result is + * the expected result with roundingIncrement being an object with a valueOf() + * method. + */ + checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) { + // null converts to 0, which is out of range + assert.throws(RangeError, () => checkFunc(null), "null"); + // Booleans convert to either 0 or 1, and 1 is allowed + const trueResult = checkFunc(true); + assertTrueResultFunc(trueResult, "true"); + assert.throws(RangeError, () => checkFunc(false), "false"); + // Symbols and BigInts cannot convert to numbers + assert.throws(TypeError, () => checkFunc(Symbol()), "symbol"); + assert.throws(TypeError, () => checkFunc(2n), "bigint"); + + // Objects prefer their valueOf() methods when converting to a number + assert.throws(RangeError, () => checkFunc({}), "plain object"); + + const expected = [ + "get roundingIncrement.valueOf", + "call roundingIncrement.valueOf", + ]; + const actual = []; + const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement"); + const objectResult = checkFunc(observer); + assertObjectResultFunc(objectResult, "object with valueOf"); + assert.compareArray(actual, expected, "order of operations"); + }, + + /* + * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc): + * + * Checks the type handling of a string option, of which there are several in + * Temporal. + * propertyName is the name of the option, and value is the value that + * assertFunc should expect it to have. + * checkFunc(value) is a function which takes the value of the option to test, + * and calls the method under test with it, returning the result. + * assertFunc(result, description) should assert that result is the expected + * result with the option value being an object with a toString() method + * which returns the given value. + */ + checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) { + // null converts to the string "null", which is an invalid string value + assert.throws(RangeError, () => checkFunc(null), "null"); + // Booleans convert to the strings "true" or "false", which are invalid + assert.throws(RangeError, () => checkFunc(true), "true"); + assert.throws(RangeError, () => checkFunc(false), "false"); + // Symbols cannot convert to strings + assert.throws(TypeError, () => checkFunc(Symbol()), "symbol"); + // Numbers convert to strings which are invalid + assert.throws(RangeError, () => checkFunc(2), "number"); + // BigInts convert to strings which are invalid + assert.throws(RangeError, () => checkFunc(2n), "bigint"); + + // Objects prefer their toString() methods when converting to a string + assert.throws(RangeError, () => checkFunc({}), "plain object"); + + const expected = [ + `get ${propertyName}.toString`, + `call ${propertyName}.toString`, + ]; + const actual = []; + const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName); + const result = checkFunc(observer); + assertFunc(result, "object with toString"); + assert.compareArray(actual, expected, "order of operations"); + }, + + /* + * checkSubclassingIgnored(construct, constructArgs, method, methodArgs, + * resultAssertions): + * + * Methods of Temporal classes that return a new instance of the same class, + * must not take the constructor of a subclass into account, nor the @@species + * property. This helper runs tests to ensure this. + * + * construct(...constructArgs) must yield a valid instance of the Temporal + * class. instance[method](...methodArgs) is the method call under test, which + * must also yield a valid instance of the same Temporal class, not a + * subclass. See below for the individual tests that this runs. + * resultAssertions() is a function that performs additional assertions on the + * instance returned by the method under test. + */ + checkSubclassingIgnored(...args) { + this.checkSubclassConstructorNotObject(...args); + this.checkSubclassConstructorUndefined(...args); + this.checkSubclassConstructorThrows(...args); + this.checkSubclassConstructorNotCalled(...args); + this.checkSubclassSpeciesInvalidResult(...args); + this.checkSubclassSpeciesNotAConstructor(...args); + this.checkSubclassSpeciesNull(...args); + this.checkSubclassSpeciesUndefined(...args); + this.checkSubclassSpeciesThrows(...args); + }, + + /* + * Checks that replacing the 'constructor' property of the instance with + * various primitive values does not affect the returned new instance. + */ + checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) { + function check(value, description) { + const instance = new construct(...constructArgs); + instance.constructor = value; + const result = instance[method](...methodArgs); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description); + resultAssertions(result); + } + + check(null, "null"); + check(true, "true"); + check("test", "string"); + check(Symbol(), "Symbol"); + check(7, "number"); + check(7n, "bigint"); + }, + + /* + * Checks that replacing the 'constructor' property of the subclass with + * undefined does not affect the returned new instance. + */ + checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) { + let called = 0; + + class MySubclass extends construct { + constructor() { + ++called; + super(...constructArgs); + } + } + + const instance = new MySubclass(); + assert.sameValue(called, 1); + + MySubclass.prototype.constructor = undefined; + + const result = instance[method](...methodArgs); + assert.sameValue(called, 1); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + }, + + /* + * Checks that making the 'constructor' property of the instance throw when + * called does not affect the returned new instance. + */ + checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) { + function CustomError() {} + const instance = new construct(...constructArgs); + Object.defineProperty(instance, "constructor", { + get() { + throw new CustomError(); + } + }); + const result = instance[method](...methodArgs); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + }, + + /* + * Checks that when subclassing, the subclass constructor is not called by + * the method under test. + */ + checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) { + let called = 0; + + class MySubclass extends construct { + constructor() { + ++called; + super(...constructArgs); + } + } + + const instance = new MySubclass(); + assert.sameValue(called, 1); + + const result = instance[method](...methodArgs); + assert.sameValue(called, 1); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + }, + + /* + * Check that the constructor's @@species property is ignored when it's a + * constructor that returns a non-object value. + */ + checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) { + function check(value, description) { + const instance = new construct(...constructArgs); + instance.constructor = { + [Symbol.species]: function() { + return value; + }, + }; + const result = instance[method](...methodArgs); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description); + resultAssertions(result); + } + + check(undefined, "undefined"); + check(null, "null"); + check(true, "true"); + check("test", "string"); + check(Symbol(), "Symbol"); + check(7, "number"); + check(7n, "bigint"); + check({}, "plain object"); + }, + + /* + * Check that the constructor's @@species property is ignored when it's not a + * constructor. + */ + checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) { + function check(value, description) { + const instance = new construct(...constructArgs); + instance.constructor = { + [Symbol.species]: value, + }; + const result = instance[method](...methodArgs); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description); + resultAssertions(result); + } + + check(true, "true"); + check("test", "string"); + check(Symbol(), "Symbol"); + check(7, "number"); + check(7n, "bigint"); + check({}, "plain object"); + }, + + /* + * Check that the constructor's @@species property is ignored when it's null. + */ + checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) { + let called = 0; + + class MySubclass extends construct { + constructor() { + ++called; + super(...constructArgs); + } + } + + const instance = new MySubclass(); + assert.sameValue(called, 1); + + MySubclass.prototype.constructor = { + [Symbol.species]: null, + }; + + const result = instance[method](...methodArgs); + assert.sameValue(called, 1); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + }, + + /* + * Check that the constructor's @@species property is ignored when it's + * undefined. + */ + checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) { + let called = 0; + + class MySubclass extends construct { + constructor() { + ++called; + super(...constructArgs); + } + } + + const instance = new MySubclass(); + assert.sameValue(called, 1); + + MySubclass.prototype.constructor = { + [Symbol.species]: undefined, + }; + + const result = instance[method](...methodArgs); + assert.sameValue(called, 1); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + }, + + /* + * Check that the constructor's @@species property is ignored when it throws, + * i.e. it is not called at all. + */ + checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) { + function CustomError() {} + + const instance = new construct(...constructArgs); + instance.constructor = { + get [Symbol.species]() { + throw new CustomError(); + }, + }; + + const result = instance[method](...methodArgs); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + }, + + /* + * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions): + * + * Static methods of Temporal classes that return a new instance of the class, + * must not use the this-value as a constructor. This helper runs tests to + * ensure this. + * + * construct[method](...methodArgs) is the static method call under test, and + * must yield a valid instance of the Temporal class, not a subclass. See + * below for the individual tests that this runs. + * resultAssertions() is a function that performs additional assertions on the + * instance returned by the method under test. + */ + checkSubclassingIgnoredStatic(...args) { + this.checkStaticInvalidReceiver(...args); + this.checkStaticReceiverNotCalled(...args); + this.checkThisValueNotCalled(...args); + }, + + /* + * Check that calling the static method with a receiver that's not callable, + * still calls the intrinsic constructor. + */ + checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) { + function check(value, description) { + const result = construct[method].apply(value, methodArgs); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + } + + check(undefined, "undefined"); + check(null, "null"); + check(true, "true"); + check("test", "string"); + check(Symbol(), "symbol"); + check(7, "number"); + check(7n, "bigint"); + check({}, "Non-callable object"); + }, + + /* + * Check that calling the static method with a receiver that returns a value + * that's not callable, still calls the intrinsic constructor. + */ + checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) { + function check(value, description) { + const receiver = function () { + return value; + }; + const result = construct[method].apply(receiver, methodArgs); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + } + + check(undefined, "undefined"); + check(null, "null"); + check(true, "true"); + check("test", "string"); + check(Symbol(), "symbol"); + check(7, "number"); + check(7n, "bigint"); + check({}, "Non-callable object"); + }, + + /* + * Check that the receiver isn't called. + */ + checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) { + let called = false; + + class MySubclass extends construct { + constructor(...args) { + called = true; + super(...args); + } + } + + const result = MySubclass[method](...methodArgs); + assert.sameValue(called, false); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + }, + + /* + * Check that any calendar-carrying Temporal object has its [[Calendar]] + * internal slot read by ToTemporalCalendar, and does not fetch the calendar + * by calling getters. + */ + checkToTemporalCalendarFastPath(func) { + const plainDate = new Temporal.PlainDate(2000, 5, 2, "iso8601"); + const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, "iso8601"); + const plainMonthDay = new Temporal.PlainMonthDay(5, 2, "iso8601"); + const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, "iso8601"); + const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", "iso8601"); + + [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => { + const actual = []; + const expected = []; + + Object.defineProperty(temporalObject, "calendar", { + get() { + actual.push("get calendar"); + return calendar; + }, + }); + + func(temporalObject); + assert.compareArray(actual, expected, "calendar getter not called"); + }); + }, + + checkToTemporalInstantFastPath(func) { + const actual = []; + const expected = []; + + const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC"); + Object.defineProperty(datetime, 'toString', { + get() { + actual.push("get toString"); + return function (options) { + actual.push("call toString"); + return Temporal.ZonedDateTime.prototype.toString.call(this, options); + }; + }, + }); + + func(datetime); + assert.compareArray(actual, expected, "toString not called"); + }, + + checkToTemporalPlainDateTimeFastPath(func) { + const actual = []; + const expected = []; + + const date = new Temporal.PlainDate(2000, 5, 2, "iso8601"); + const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype); + ["year", "month", "monthCode", "day"].forEach((property) => { + Object.defineProperty(date, property, { + get() { + actual.push(`get ${formatPropertyName(property)}`); + const value = prototypeDescrs[property].get.call(this); + return TemporalHelpers.toPrimitiveObserver(actual, value, property); + }, + }); + }); + ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => { + Object.defineProperty(date, property, { + get() { + actual.push(`get ${formatPropertyName(property)}`); + return undefined; + }, + }); + }); + Object.defineProperty(date, "calendar", { + get() { + actual.push("get calendar"); + return "iso8601"; + }, + }); + + func(date); + assert.compareArray(actual, expected, "property getters not called"); + }, + + /* + * observeProperty(calls, object, propertyName, value): + * + * Defines an own property @object.@propertyName with value @value, that + * will log any calls to its accessors to the array @calls. + */ + observeProperty(calls, object, propertyName, value, objectName = "") { + Object.defineProperty(object, propertyName, { + get() { + calls.push(`get ${formatPropertyName(propertyName, objectName)}`); + return value; + }, + set(v) { + calls.push(`set ${formatPropertyName(propertyName, objectName)}`); + } + }); + }, + + /* + * observeMethod(calls, object, propertyName, value): + * + * Defines an own property @object.@propertyName with value @value, that + * will log any calls of @value to the array @calls. + */ + observeMethod(calls, object, propertyName, objectName = "") { + const method = object[propertyName]; + object[propertyName] = function () { + calls.push(`call ${formatPropertyName(propertyName, objectName)}`); + return method.apply(object, arguments); + }; + }, + + /* + * Used for substituteMethod to indicate default behavior instead of a + * substituted value + */ + SUBSTITUTE_SKIP: SKIP_SYMBOL, + + /* + * substituteMethod(object, propertyName, values): + * + * Defines an own property @object.@propertyName that will, for each + * subsequent call to the method previously defined as + * @object.@propertyName: + * - Call the method, if no more values remain + * - Call the method, if the value in @values for the corresponding call + * is SUBSTITUTE_SKIP + * - Otherwise, return the corresponding value in @value + */ + substituteMethod(object, propertyName, values) { + let calls = 0; + const method = object[propertyName]; + object[propertyName] = function () { + if (calls >= values.length) { + return method.apply(object, arguments); + } else if (values[calls] === SKIP_SYMBOL) { + calls++; + return method.apply(object, arguments); + } else { + return values[calls++]; + } + }; + }, + + /* + * propertyBagObserver(): + * Returns an object that behaves like the given propertyBag but tracks Get + * and Has operations on any of its properties, by appending messages to an + * array. If the value of a property in propertyBag is a primitive, the value + * of the returned object's property will additionally be a + * TemporalHelpers.toPrimitiveObserver that will track calls to its toString + * and valueOf methods in the same array. This is for the purpose of testing + * order of operations that are observable from user code. objectName is used + * in the log. + * If skipToPrimitive is given, it must be an array of property keys. Those + * properties will not have a TemporalHelpers.toPrimitiveObserver returned, + * and instead just be returned directly. + */ + propertyBagObserver(calls, propertyBag, objectName, skipToPrimitive) { + return new Proxy(propertyBag, { + ownKeys(target) { + calls.push(`ownKeys ${objectName}`); + return Reflect.ownKeys(target); + }, + getOwnPropertyDescriptor(target, key) { + calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`); + return Reflect.getOwnPropertyDescriptor(target, key); + }, + get(target, key, receiver) { + calls.push(`get ${formatPropertyName(key, objectName)}`); + const result = Reflect.get(target, key, receiver); + if (result === undefined) { + return undefined; + } + if ((result !== null && typeof result === "object") || typeof result === "function") { + return result; + } + if (skipToPrimitive && skipToPrimitive.indexOf(key) >= 0) { + return result; + } + return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`); + }, + has(target, key) { + calls.push(`has ${formatPropertyName(key, objectName)}`); + return Reflect.has(target, key); + }, + }); + }, + + /* + * Returns an object that will append logs of any Gets or Calls of its valueOf + * or toString properties to the array calls. Both valueOf and toString will + * return the actual primitiveValue. propertyName is used in the log. + */ + toPrimitiveObserver(calls, primitiveValue, propertyName) { + return { + get valueOf() { + calls.push(`get ${propertyName}.valueOf`); + return function () { + calls.push(`call ${propertyName}.valueOf`); + return primitiveValue; + }; + }, + get toString() { + calls.push(`get ${propertyName}.toString`); + return function () { + calls.push(`call ${propertyName}.toString`); + if (primitiveValue === undefined) return undefined; + return primitiveValue.toString(); + }; + }, + }; + }, + + /* + * An object containing further methods that return arrays of ISO strings, for + * testing parsers. + */ + ISO: { + /* + * PlainMonthDay strings that are not valid. + */ + plainMonthDayStringsInvalid() { + return [ + "11-18junk", + "11-18[u-ca=gregory]", + "11-18[u-ca=hebrew]", + "11-18[U-CA=iso8601]", + "11-18[u-CA=iso8601]", + "11-18[FOO=bar]", + "-999999-01-01[u-ca=gregory]", + "-999999-01-01[u-ca=chinese]", + "+999999-01-01[u-ca=gregory]", + "+999999-01-01[u-ca=chinese]", + ]; + }, + + /* + * PlainMonthDay strings that are valid and that should produce October 1st. + */ + plainMonthDayStringsValid() { + return [ + "10-01", + "1001", + "1965-10-01", + "1976-10-01T152330.1+00:00", + "19761001T15:23:30.1+00:00", + "1976-10-01T15:23:30.1+0000", + "1976-10-01T152330.1+0000", + "19761001T15:23:30.1+0000", + "19761001T152330.1+00:00", + "19761001T152330.1+0000", + "+001976-10-01T152330.1+00:00", + "+0019761001T15:23:30.1+00:00", + "+001976-10-01T15:23:30.1+0000", + "+001976-10-01T152330.1+0000", + "+0019761001T15:23:30.1+0000", + "+0019761001T152330.1+00:00", + "+0019761001T152330.1+0000", + "1976-10-01T15:23:00", + "1976-10-01T15:23", + "1976-10-01T15", + "1976-10-01", + "--10-01", + "--1001", + "-999999-10-01", + "-999999-10-01[u-ca=iso8601]", + "+999999-10-01", + "+999999-10-01[u-ca=iso8601]", + ]; + }, + + /* + * PlainTime strings that may be mistaken for PlainMonthDay or + * PlainYearMonth strings, and so require a time designator. + */ + plainTimeStringsAmbiguous() { + const ambiguousStrings = [ + "2021-12", // ambiguity between YYYY-MM and HHMM-UU + "2021-12[-12:00]", // ditto, TZ does not disambiguate + "1214", // ambiguity between MMDD and HHMM + "0229", // ditto, including MMDD that doesn't occur every year + "1130", // ditto, including DD that doesn't occur in every month + "12-14", // ambiguity between MM-DD and HH-UU + "12-14[-14:00]", // ditto, TZ does not disambiguate + "202112", // ambiguity between YYYYMM and HHMMSS + "202112[UTC]", // ditto, TZ does not disambiguate + ]; + // Adding a calendar annotation to one of these strings must not cause + // disambiguation in favour of time. + const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]'); + return ambiguousStrings.concat(stringsWithCalendar); + }, + + /* + * PlainTime strings that are of similar form to PlainMonthDay and + * PlainYearMonth strings, but are not ambiguous due to components that + * aren't valid as months or days. + */ + plainTimeStringsUnambiguous() { + return [ + "2021-13", // 13 is not a month + "202113", // ditto + "2021-13[-13:00]", // ditto + "202113[-13:00]", // ditto + "0000-00", // 0 is not a month + "000000", // ditto + "0000-00[UTC]", // ditto + "000000[UTC]", // ditto + "1314", // 13 is not a month + "13-14", // ditto + "1232", // 32 is not a day + "0230", // 30 is not a day in February + "0631", // 31 is not a day in June + "0000", // 0 is neither a month nor a day + "00-00", // ditto + ]; + }, + + /* + * PlainYearMonth-like strings that are not valid. + */ + plainYearMonthStringsInvalid() { + return [ + "2020-13", + "1976-11[u-ca=gregory]", + "1976-11[u-ca=hebrew]", + "1976-11[U-CA=iso8601]", + "1976-11[u-CA=iso8601]", + "1976-11[FOO=bar]", + "+999999-01", + "-999999-01", + ]; + }, + + /* + * PlainYearMonth-like strings that are valid and should produce November + * 1976 in the ISO 8601 calendar. + */ + plainYearMonthStringsValid() { + return [ + "1976-11", + "1976-11-10", + "1976-11-01T09:00:00+00:00", + "1976-11-01T00:00:00+05:00", + "197611", + "+00197611", + "1976-11-18T15:23:30.1-02:00", + "1976-11-18T152330.1+00:00", + "19761118T15:23:30.1+00:00", + "1976-11-18T15:23:30.1+0000", + "1976-11-18T152330.1+0000", + "19761118T15:23:30.1+0000", + "19761118T152330.1+00:00", + "19761118T152330.1+0000", + "+001976-11-18T152330.1+00:00", + "+0019761118T15:23:30.1+00:00", + "+001976-11-18T15:23:30.1+0000", + "+001976-11-18T152330.1+0000", + "+0019761118T15:23:30.1+0000", + "+0019761118T152330.1+00:00", + "+0019761118T152330.1+0000", + "1976-11-18T15:23", + "1976-11-18T15", + "1976-11-18", + ]; + }, + + /* + * PlainYearMonth-like strings that are valid and should produce November of + * the ISO year -9999. + */ + plainYearMonthStringsValidNegativeYear() { + return [ + "-009999-11", + ]; + }, + } +}; diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/since/era-boundary-gregory.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/since/era-boundary-gregory.js @@ -0,0 +1,80 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.since +description: Date difference works correctly across era boundaries +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "gregory"; +const options = { overflow: "reject" }; + +const bce5 = Temporal.PlainDateTime.from({ era: "bce", eraYear: 5, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }, options); +const bce2 = Temporal.PlainDateTime.from({ era: "bce", eraYear: 2, monthCode: "M12", day: 1, hour: 12, minute: 34, calendar }, options); +const bce1 = Temporal.PlainDateTime.from({ era: "bce", eraYear: 1, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }, options); +const ce1 = Temporal.PlainDateTime.from({ era: "ce", eraYear: 1, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }, options); +const ce2 = Temporal.PlainDateTime.from({ era: "ce", eraYear: 2, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options); +const ce5 = Temporal.PlainDateTime.from({ era: "ce", eraYear: 5, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }, options); + +const tests = [ + // From 5 BCE to 5 CE + [ + bce5, ce5, + [-9, 0, 0, 0, "-9y backwards from 5 BCE to 5 CE (no year 0)"], + [0, -108, 0, 0, 0, "-108mo backwards from 5 BCE to 5 CE (no year 0)"], + ], + [ + ce5, bce5, + [9, 0, 0, 0, "9y from 5 BCE to 5 CE (no year 0)"], + [0, 108, 0, 0, 0, "108mo from 5 BCE to 5 CE (no year 0)"], + ], + // CE-BCE boundary + [ + bce1, ce1, + [-1, 0, 0, 0, "-1y backwards from 1 BCE to 1 CE"], + [0, -12, 0, 0, "-12mo backwards from 1 BCE to 1 CE"], + ], + [ + ce1, bce1, + [1, 0, 0, 0, "1y from 1 BCE to 1 CE"], + [0, 12, 0, 0, "12mo from 1 BCE to 1 CE"], + ], + [ + bce2, ce2, + [-2, -1, 0, 0, "-2y -1mo backwards from 2 BCE Dec to 2 CE Jan"], + [0, -25, 0, 0, "-25mo backwards from 2 BCE Dec to 2 CE Jan"], + ], + [ + ce2, bce2, + [2, 1, 0, 0, "2y 1mo from 2 BCE Dec to 2 CE Jan"], + [0, 25, 0, 0, "25mo from 2 BCE Dec to 2 CE Jan"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.since(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.since(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.since(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.since(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.since(two); + const resultDaysISO = oneISO.since(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/since/era-boundary-japanese.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/since/era-boundary-japanese.js @@ -0,0 +1,172 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.since +description: Date difference works correctly across era boundaries +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "japanese"; +const options = { overflow: "reject" }; + +const bce1 = Temporal.PlainDateTime.from({ era: "bce", eraYear: 1, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options); +const ce1 = Temporal.PlainDateTime.from({ era: "ce", eraYear: 1, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options); +const ce1868 = Temporal.PlainDateTime.from({ era: "ce", eraYear: 1868, monthCode: "M10", day: 22, hour: 12, minute: 34, calendar }, options); +const meiji1 = Temporal.PlainDateTime.from({ era: "meiji", eraYear: 1, monthCode: "M10", day: 23, hour: 12, minute: 34, calendar}, options); +const meiji5 = Temporal.PlainDateTime.from({ era: "meiji", eraYear: 5, monthCode: "M01", day: 15, hour: 12, minute: 34, calendar }, options); +const meiji45 = Temporal.PlainDateTime.from({ era: "meiji", eraYear: 45, monthCode: "M05", day: 19, hour: 12, minute: 34, calendar }, options); +const taisho1 = Temporal.PlainDateTime.from({ era: "taisho", eraYear: 1, monthCode: "M08", day: 9, hour: 12, minute: 34, calendar }, options); +const taisho6 = Temporal.PlainDateTime.from({ era: "taisho", eraYear: 6, monthCode: "M03", day: 15, hour: 12, minute: 34, calendar }, options); +const taisho15 = Temporal.PlainDateTime.from({ era: "taisho", eraYear: 15, monthCode: "M05", day: 20, hour: 12, minute: 34, calendar }, options); +const showa1 = Temporal.PlainDateTime.from({ era: "showa", eraYear: 1, monthCode: "M12", day: 30, hour: 12, minute: 34, calendar }, options); +const showa55 = Temporal.PlainDateTime.from({ era: "showa", eraYear: 55, monthCode: "M03", day: 15, hour: 12, minute: 34, calendar }, options); +const showa64 = Temporal.PlainDateTime.from({ era: "showa", eraYear: 64, monthCode: "M01", day: 3, hour: 12, minute: 34, calendar }, options); +const heisei1 = Temporal.PlainDateTime.from({ era: "heisei", eraYear: 1, monthCode: "M02", day: 27, hour: 12, minute: 34, calendar }, options); +const heisei30 = Temporal.PlainDateTime.from({ era: "heisei", eraYear: 30, monthCode: "M03", day: 15, hour: 12, minute: 34, calendar }, options); +const heisei31 = Temporal.PlainDateTime.from({ era: "heisei", eraYear: 31, monthCode: "M04", day: 1, hour: 12, minute: 34, calendar }, options); +const reiwa1 = Temporal.PlainDateTime.from({ era: "reiwa", eraYear: 1, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options); +const reiwa2 = Temporal.PlainDateTime.from({ era: "reiwa", eraYear: 2, monthCode: "M03", day: 15, hour: 12, minute: 34, calendar }, options); + +const tests = [ + // From Heisei 30 (2018) to Reiwa 2 (2020) - crossing era boundary + [ + heisei30, reiwa2, + [-2, 0, 0, 0, "-2y backwards from Heisei 30 March to Reiwa 2 March"], + [0, -24, 0, 0, "-24mo backwards from Heisei 30 March to Reiwa 2 March"], + ], + [ + reiwa2, heisei30, + [2, 0, 0, 0, "2y from Heisei 30 March to Reiwa 2 March"], + [0, 24, 0, 0, "24mo from Heisei 30 March to Reiwa 2 March"], + + ], + // Within same year but different eras + [ + heisei31, reiwa1, + [0, -2, 0, 0, "-2mo backwards from Heisei 31 April to Reiwa 1 June"], + [0, -2, 0, 0, "-2mo backwards from Heisei 31 April to Reiwa 1 June"], + ], + [ + reiwa1, heisei31, + [0, 2, 0, 0, "2mo from Heisei 31 April to Reiwa 1 June"], + [0, 2, 0, 0, "2mo from Heisei 31 April to Reiwa 1 June"], + ], + // From Showa 55 (1980) to Heisei 30 (2018) - crossing era boundary + [ + showa55, heisei30, + [-38, 0, 0, 0, "-38y backwards from Showa 55 March to Heisei 30 March"], + [0, -456, 0, 0, "-456mo backwards from Showa 55 March to Heisei 30 March"], + ], + [ + heisei30, showa55, + [38, 0, 0, 0, "38y from Showa 55 March to Heisei 30 March"], + [0, 456, 0, 0, "456mo from Showa 55 March to Heisei 30 March"], + ], + // Within same year but different eras + [ + showa64, heisei1, + [0, -1, 0, -24, "-1mo -24d from Showa 64 January 3 to Heisei 1 February 27"], + [0, -1, 0, -24, "-1mo -24d from Showa 64 January 3 to Heisei 1 February 27"], + ], + [ + heisei1, showa64, + [0, 1, 0, 24, "1mo 24d from Showa 64 January 3 to Heisei 1 February 27"], + [0, 1, 0, 24, "1mo 24d from Showa 64 January 3 to Heisei 1 February 27"], + ], + // From Taisho 6 (1917) to Showa 55 (1980) - crossing era boundary + [ + taisho6, showa55, + [-63, 0, 0, 0, "-63y backwards from Taisho 6 March to Showa 55 March"], + [0, -756, 0, 0, "-756mo backwards from Taisho 6 March to Showa 55 March"], + ], + [ + showa55, taisho6, + [63, 0, 0, 0, "63y from Taisho 6 March to Showa 55 March"], + [0, 756, 0, 0, "756mo from Taisho 6 March to Showa 55 March"], + ], + // Within same year but different eras + [ + taisho15, showa1, + [0, -7, 0, -10, "-7mo -10d backwards from Taisho 15 July 20 to Showa 1 December 30"], + [0, -7, 0, -10, "-7mo -10d backwards from Taisho 15 July 20 to Showa 1 December 30"], + ], + [ + showa1, taisho15, + [0, 7, 0, 10, "7mo 10d from Taisho 15 July 20 to Showa 1 December 30"], + [0, 7, 0, 10, "7mo 10d from Taisho 15 July 20 to Showa 1 December 30"], + ], + // From Meiji 5 (1872) to Taisho 6 (1917) - crossing era boundary + // Note that contemporarily January 15 1872 would have been Meiji 4 in the + // pre-1873 lunisolar calendar, but the spec-mandated behaviour is proleptic + [ + meiji5, taisho6, + [-45, -2, 0, 0, "-45y -2mo backwards from Meiji 5 January to Taisho 6 March"], + [0, -542, 0, 0, "-542mo backwards from Meiji 5 January to Taisho 6 March"], + ], + [ + taisho6, meiji5, + [45, 2, 0, 0, "45y 2mo from Meiji 5 January to Taisho 6 March"], + [0, 542, 0, 0, "542mo from Meiji 5 January to Taisho 6 March"], + ], + // Within same year but different eras + [ + meiji45, taisho1, + [0, -2, 0, -21, "-2mo -21d backwards from Meiji 45 May 19 to Taisho 1 August 9"], + [0, -2, 0, -21, "-2mo -21d backwards from Meiji 45 May 19 to Taisho 1 August 9"], + ], + [ + taisho1, meiji45, + [0, 2, 0, 21, "2mo 21d from Meiji 45 May 19 to Taisho 1 August 9"], + [0, 2, 0, 21, "2mo 21d from Meiji 45 May 19 to Taisho 1 August 9"], + ], + // Last pre-Meiji day to first day of Meiji era + [ + ce1868, meiji1, + [0, 0, 0, -1, "backwards from day before Meiji era to first day"], + [0, 0, 0, -1, "backwards from day before Meiji era to first day"], + ], + [ + meiji1, ce1868, + [0, 0, 0, 1, "from day before Meiji era to first day"], + [0, 0, 0, 1, "from day before Meiji era to first day"], + ], + // CE-BCE boundary + [ + bce1, ce1, + [-1, 0, 0, 0, "-1y backwards from 1 BCE to 1 CE"], + [0, -12, 0, 0, "-12mo backwards from 1 BCE to 1 CE"], + ], + [ + ce1, bce1, + [1, 0, 0, 0, "1y from 1 BCE to 1 CE"], + [0, 12, 0, 0, "12mo from 1 BCE to 1 CE"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.since(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.since(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.since(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.since(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.since(two); + const resultDaysISO = oneISO.since(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/since/era-boundary-roc.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/since/era-boundary-roc.js @@ -0,0 +1,80 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.since +description: Date difference works correctly across era boundaries +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "roc"; +const options = { overflow: "reject" }; + +const broc5 = Temporal.PlainDateTime.from({ era: "broc", eraYear: 5, monthCode: "M03", day: 1, hour: 12, minute: 34, calendar }, options); +const broc3 = Temporal.PlainDateTime.from({ era: "broc", eraYear: 3, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options); +const broc1 = Temporal.PlainDateTime.from({ era: "broc", eraYear: 1, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options); +const roc1 = Temporal.PlainDateTime.from({ era: "roc", eraYear: 1, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options); +const roc5 = Temporal.PlainDateTime.from({ era: "roc", eraYear: 5, monthCode: "M03", day: 1, hour: 12, minute: 34, calendar }, options); +const roc10 = Temporal.PlainDateTime.from({ era: "roc", eraYear: 10, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options); + +const tests = [ + // From BROC 5 to ROC 5 + [ + broc5, roc5, + [-9, 0, 0, 0, "-9y backwards from BROC 5 to ROC 5 (no year 0)"], + [0, -108, 0, 0, 0, "-108mo backwards from BROC 5 to ROC 5 (no year 0)"], + ], + [ + roc5, broc5, + [9, 0, 0, 0, "9y from BROC 5 to ROC 5 (no year 0)"], + [0, 108, 0, 0, 0, "108mo from BROC 5 to ROC 5 (no year 0)"], + ], + // Era boundary + [ + broc1, roc1, + [-1, 0, 0, 0, "-1y backwards from BROC 1 to ROC 1"], + [0, -12, 0, 0, "-12mo backwards from BROC 1 to ROC 1"], + ], + [ + roc1, broc1, + [1, 0, 0, 0, "1y from BROC 1 to ROC 1"], + [0, 12, 0, 0, "12mo from BROC 1 to ROC 1"], + ], + [ + broc3, roc10, + [-12, 0, 0, 0, "-12y backwards from BROC 3 to ROC 10"], + [0, -144, 0, 0, "-144mo backwards from BROC 3 to ROC 10"], + ], + [ + roc10, broc3, + [12, 0, 0, 0, "12y from BROC 3 to ROC 10"], + [0, 144, 0, 0, "144mo from BROC 3 to ROC 10"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.since(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.since(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.since(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.since(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.since(two); + const resultDaysISO = oneISO.since(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/since/leap-months-chinese.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/since/leap-months-chinese.js @@ -0,0 +1,167 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Difference across leap months in chinese calendar +esid: sec-temporal.plaindatetime.prototype.since +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +// 2001 is a leap year with a M04L leap month. + +const calendar = "chinese"; +const options = { overflow: "reject" }; + +const common1Month4 = Temporal.PlainDateTime.from({ year: 2000, monthCode: "M04", day: 1, hour: 12, minute: 34, calendar }, options); +const common1Month5 = Temporal.PlainDateTime.from({ year: 2000, monthCode: "M05", day: 1, hour: 12, minute: 34, calendar }, options); +const common1Month6 = Temporal.PlainDateTime.from({ year: 2000, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options); +const leapMonth4 = Temporal.PlainDateTime.from({ year: 2001, monthCode: "M04", day: 1, hour: 12, minute: 34, calendar }, options); +const leapMonth4L = Temporal.PlainDateTime.from({ year: 2001, monthCode: "M04L", day: 1, hour: 12, minute: 34, calendar }, options); +const leapMonth5 = Temporal.PlainDateTime.from({ year: 2001, monthCode: "M05", day: 1, hour: 12, minute: 34, calendar }, options); +const common2Month4 = Temporal.PlainDateTime.from({ year: 2002, monthCode: "M04", day: 1, hour: 12, minute: 34, calendar }, options); +const common2Month5 = Temporal.PlainDateTime.from({ year: 2002, monthCode: "M05", day: 1, hour: 12, minute: 34, calendar }, options); + +// (receiver, argument, years test data, months test data) +// test data: expected years, months, weeks, days, description +// largestUnit years: make sure some cases where the answer is 12 months do not +// balance up to 1 year +// largestUnit months: similar to years, but make sure number of months in year +// is computed correctly +// For largestUnit of days and weeks, the results should be identical to what +// the ISO calendar gives for the corresponding ISO dates +const tests = [ + [ + common1Month4, leapMonth4, + [-1, 0, 0, 0, "M04-M04 common-leap backwards is -1y"], + [0, -12, 0, 0, "M04-M04 common-leap backwards is -12mo"], + ], + [ + leapMonth4, common2Month4, + [-1, 0, 0, 0, "M04-M04 leap-common backwards is -1y"], + [0, -13, 0, 0, "M04-M04 leap-common backwards is -13mo not -12mo"], + ], + [ + common1Month4, common2Month4, + [-2, 0, 0, 0, "M04-M04 common-common backwards is -2y"], + [0, -25, 0, 0, "M04-M04 common-common backwards is -25mo not -24mo"], + ], + [ + common1Month5, leapMonth5, + [-1, 0, 0, 0, "M05-M05 common-leap backwards is -1y"], + [0, -13, 0, 0, "M05-M05 common-leap backwards is -13mo not -12mo"], + ], + [ + leapMonth5, common2Month5, + [-1, 0, 0, 0, "M05-M05 leap-common backwards is -1y"], + [0, -12, 0, 0, "M05-M05 leap-common backwards is -12mo"], + ], + [ + common1Month5, common2Month5, + [-2, 0, 0, 0, "M05-M05 common-common backwards is -2y"], + [0, -25, 0, 0, "M05-M05 common-common backwards is -25mo not -24mo"], + ], + [ + common1Month4, leapMonth4L, + [-1, -1, 0, 0, "M04-M04L backwards is -1y -1mo"], + [0, -13, 0, 0, "M04-M04L backwards is -13mo"], + ], + [ + leapMonth4L, common2Month4, + [0, -12, 0, 0, "M04L-M04 backwards is -12mo not -1y"], + [0, -12, 0, 0, "M04L-M04 backwards is -12mo"], + ], + [ + common1Month5, leapMonth4L, + [0, -12, 0, 0, "M05-M04L backwards is -12mo not -1y"], + [0, -12, 0, 0, "M05-M04L backwards is -12mo"], + ], + [ + leapMonth4L, common2Month5, + [-1, -1, 0, 0, "M04L-M05 backwards is -1y -1mo (exhibits calendar-specific constraining)"], + [0, -13, 0, 0, "M04L-M05 backwards is -13mo"], + ], + [ + common1Month6, leapMonth5, + [0, -12, 0, 0, "M06-M05 common-leap backwards is -12mo not -11mo"], + [0, -12, 0, 0, "M06-M05 common-leap backwards is -12mo not -11mo"], + ], + + // Negative + [ + common2Month4, leapMonth4, + [1, 0, 0, 0, "M04-M04 common-leap is 1y"], + [0, 13, 0, 0, "M04-M04 common-leap is 13mo not 12mo"], + ], + [ + leapMonth4, common1Month4, + [1, 0, 0, 0, "M04-M04 leap-common is 1y"], + [0, 12, 0, 0, "M04-M04 leap-common is 12mo not 13mo"], + ], + [ + common2Month4, common1Month4, + [2, 0, 0, 0, "M04-M04 common-common is 2y"], + [0, 25, 0, 0, "M04-M04 common-common is 25mo not 24mo"], + ], + [ + common2Month5, leapMonth5, + [1, 0, 0, 0, "M05-M05 common-leap is 1y"], + [0, 12, 0, 0, "M05-M05 common-leap is 12mo not 13mo"], + ], + [ + leapMonth5, common1Month5, + [1, 0, 0, 0, "M05-M05 leap-common is 1y"], + [0, 13, 0, 0, "M05-M05 leap-common is 13mo not 12mo"], + ], + [ + common2Month5, common1Month5, + [2, 0, 0, 0, "M05-M05 common-common is 2y"], + [0, 25, 0, 0, "M05-M05 common-common is 25mo not 24mo"], + ], + [ + common2Month4, leapMonth4L, + [0, 12, 0, 0, "M04-M04L is 12mo not 1y"], + [0, 12, 0, 0, "M04-M04L is 12mo"], + ], + [ + leapMonth4L, common1Month4, + [1, 0, 0, 0, "M04L-M04 is 1y not 1y 1mo (exhibits calendar-specific constraining)"], + [0, 13, 0, 0, "M04L-M04 is 13mo"], + ], + [ + common2Month5, leapMonth4L, + [1, 1, 0, 0, "M05-M04L is 1y 1mo"], + [0, 13, 0, 0, "M05-M04L is 13mo"], + ], + [ + leapMonth4L, common1Month5, + [0, 12, 0, 0, "M04L-M05 is 12mo not 1y"], + [0, 12, 0, 0, "M04L-M05 is 12mo"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.since(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.since(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.since(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.since(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.since(two); + const resultDaysISO = oneISO.since(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/since/leap-months-dangi.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/since/leap-months-dangi.js @@ -0,0 +1,167 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Difference across leap months in dangi calendar +esid: sec-temporal.plaindatetime.prototype.since +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +// 2001 is a leap year with a M04L leap month. + +const calendar = "dangi"; +const options = { overflow: "reject" }; + +const common1Month4 = Temporal.PlainDateTime.from({ year: 2000, monthCode: "M04", day: 1, hour: 12, minute: 34, calendar }, options); +const common1Month5 = Temporal.PlainDateTime.from({ year: 2000, monthCode: "M05", day: 1, hour: 12, minute: 34, calendar }, options); +const common1Month6 = Temporal.PlainDateTime.from({ year: 2000, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options); +const leapMonth4 = Temporal.PlainDateTime.from({ year: 2001, monthCode: "M04", day: 1, hour: 12, minute: 34, calendar }, options); +const leapMonth4L = Temporal.PlainDateTime.from({ year: 2001, monthCode: "M04L", day: 1, hour: 12, minute: 34, calendar }, options); +const leapMonth5 = Temporal.PlainDateTime.from({ year: 2001, monthCode: "M05", day: 1, hour: 12, minute: 34, calendar }, options); +const common2Month4 = Temporal.PlainDateTime.from({ year: 2002, monthCode: "M04", day: 1, hour: 12, minute: 34, calendar }, options); +const common2Month5 = Temporal.PlainDateTime.from({ year: 2002, monthCode: "M05", day: 1, hour: 12, minute: 34, calendar }, options); + +// (receiver, argument, years test data, months test data) +// test data: expected years, months, weeks, days, description +// largestUnit years: make sure some cases where the answer is 12 months do not +// balance up to 1 year +// largestUnit months: similar to years, but make sure number of months in year +// is computed correctly +// For largestUnit of days and weeks, the results should be identical to what +// the ISO calendar gives for the corresponding ISO dates +const tests = [ + [ + common1Month4, leapMonth4, + [-1, 0, 0, 0, "M04-M04 common-leap backwards is -1y"], + [0, -12, 0, 0, "M04-M04 common-leap backwards is -12mo"], + ], + [ + leapMonth4, common2Month4, + [-1, 0, 0, 0, "M04-M04 leap-common backwards is -1y"], + [0, -13, 0, 0, "M04-M04 leap-common backwards is -13mo not -12mo"], + ], + [ + common1Month4, common2Month4, + [-2, 0, 0, 0, "M04-M04 common-common backwards is -2y"], + [0, -25, 0, 0, "M04-M04 common-common backwards is -25mo not -24mo"], + ], + [ + common1Month5, leapMonth5, + [-1, 0, 0, 0, "M05-M05 common-leap backwards is -1y"], + [0, -13, 0, 0, "M05-M05 common-leap backwards is -13mo not -12mo"], + ], + [ + leapMonth5, common2Month5, + [-1, 0, 0, 0, "M05-M05 leap-common backwards is -1y"], + [0, -12, 0, 0, "M05-M05 leap-common backwards is -12mo"], + ], + [ + common1Month5, common2Month5, + [-2, 0, 0, 0, "M05-M05 common-common backwards is -2y"], + [0, -25, 0, 0, "M05-M05 common-common backwards is -25mo not -24mo"], + ], + [ + common1Month4, leapMonth4L, + [-1, -1, 0, 0, "M04-M04L backwards is -1y -1mo"], + [0, -13, 0, 0, "M04-M04L backwards is -13mo"], + ], + [ + leapMonth4L, common2Month4, + [0, -12, 0, 0, "M04L-M04 backwards is -12mo not -1y"], + [0, -12, 0, 0, "M04L-M04 backwards is -12mo"], + ], + [ + common1Month5, leapMonth4L, + [0, -12, 0, 0, "M05-M04L backwards is -12mo not -1y"], + [0, -12, 0, 0, "M05-M04L backwards is -12mo"], + ], + [ + leapMonth4L, common2Month5, + [-1, -1, 0, 0, "M04L-M05 backwards is -1y -1mo (exhibits calendar-specific constraining)"], + [0, -13, 0, 0, "M04L-M05 backwards is -13mo"], + ], + [ + common1Month6, leapMonth5, + [0, -12, 0, 0, "M06-M05 common-leap backwards is -12mo not -11mo"], + [0, -12, 0, 0, "M06-M05 common-leap backwards is -12mo not -11mo"], + ], + + // Negative + [ + common2Month4, leapMonth4, + [1, 0, 0, 0, "M04-M04 common-leap is 1y"], + [0, 13, 0, 0, "M04-M04 common-leap is 13mo not 12mo"], + ], + [ + leapMonth4, common1Month4, + [1, 0, 0, 0, "M04-M04 leap-common is 1y"], + [0, 12, 0, 0, "M04-M04 leap-common is 12mo not 13mo"], + ], + [ + common2Month4, common1Month4, + [2, 0, 0, 0, "M04-M04 common-common is 2y"], + [0, 25, 0, 0, "M04-M04 common-common is 25mo not 24mo"], + ], + [ + common2Month5, leapMonth5, + [1, 0, 0, 0, "M05-M05 common-leap is 1y"], + [0, 12, 0, 0, "M05-M05 common-leap is 12mo not 13mo"], + ], + [ + leapMonth5, common1Month5, + [1, 0, 0, 0, "M05-M05 leap-common is 1y"], + [0, 13, 0, 0, "M05-M05 leap-common is 13mo not 12mo"], + ], + [ + common2Month5, common1Month5, + [2, 0, 0, 0, "M05-M05 common-common is 2y"], + [0, 25, 0, 0, "M05-M05 common-common is 25mo not 24mo"], + ], + [ + common2Month4, leapMonth4L, + [0, 12, 0, 0, "M04-M04L is 12mo not 1y"], + [0, 12, 0, 0, "M04-M04L is 12mo"], + ], + [ + leapMonth4L, common1Month4, + [1, 0, 0, 0, "M04L-M04 is 1y not 1y 1mo (exhibits calendar-specific constraining)"], + [0, 13, 0, 0, "M04L-M04 is 13mo"], + ], + [ + common2Month5, leapMonth4L, + [1, 1, 0, 0, "M05-M04L is 1y 1mo"], + [0, 13, 0, 0, "M05-M04L is 13mo"], + ], + [ + leapMonth4L, common1Month5, + [0, 12, 0, 0, "M04L-M05 is 12mo not 1y"], + [0, 12, 0, 0, "M04L-M05 is 12mo"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.since(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.since(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.since(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.since(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.since(two); + const resultDaysISO = oneISO.since(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/since/leap-months-hebrew.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/since/leap-months-hebrew.js @@ -0,0 +1,171 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Difference across leap months in hebrew calendar +esid: sec-temporal.plaindatetime.prototype.since +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +// 5784 is a leap year. +// M05 - Shevat +// M05L - Adar I +// M06 - Adar II +// M07 - Nisan + +const calendar = "hebrew"; +const options = { overflow: "reject" }; + +const common1Shevat = Temporal.PlainDateTime.from({ year: 5783, monthCode: "M05", day: 1, hour: 12, minute: 34, calendar }, options); +const common1Adar = Temporal.PlainDateTime.from({ year: 5783, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options); +const common1Nisan = Temporal.PlainDateTime.from({ year: 5783, monthCode: "M07", day: 1, hour: 12, minute: 34, calendar }, options); +const leapShevat = Temporal.PlainDateTime.from({ year: 5784, monthCode: "M05", day: 1, hour: 12, minute: 34, calendar }, options); +const leapAdarI = Temporal.PlainDateTime.from({ year: 5784, monthCode: "M05L", day: 1, hour: 12, minute: 34, calendar }, options); +const leapAdarII = Temporal.PlainDateTime.from({ year: 5784, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options); +const common2Shevat = Temporal.PlainDateTime.from({ year: 5785, monthCode: "M05", day: 1, hour: 12, minute: 34, calendar }, options); +const common2Adar = Temporal.PlainDateTime.from({ year: 5785, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options); + +// (receiver, argument, years test data, months test data) +// test data: expected years, months, weeks, days, description +// largestUnit years: make sure some cases where the answer is 12 months do not +// balance up to 1 year +// largestUnit months: similar to years, but make sure number of months in year +// is computed correctly +// For largestUnit of days and weeks, the results should be identical to what +// the ISO calendar gives for the corresponding ISO dates +const tests = [ + [ + common1Shevat, leapShevat, + [-1, 0, 0, 0, "M05-M05 common-leap backwards is -1y"], + [0, -12, 0, 0, "M05-M05 common-leap backwards is -12mo"], + ], + [ + leapShevat, common2Shevat, + [-1, 0, 0, 0, "M05-M05 leap-common backwards is -1y"], + [0, -13, 0, 0, "M05-M05 leap-common backwards is -13mo not -12mo"], + ], + [ + common1Shevat, common2Shevat, + [-2, 0, 0, 0, "M05-M05 common-common backwards is -2y"], + [0, -25, 0, 0, "M05-M05 common-common backwards is -25mo not -24mo"], + ], + [ + common1Adar, leapAdarII, + [-1, 0, 0, 0, "M06-M06 common-leap backwards is -1y"], + [0, -13, 0, 0, "M06-M06 common-leap backwards is -13mo not -12mo"], + ], + [ + leapAdarII, common2Adar, + [-1, 0, 0, 0, "M06-M06 leap-common backwards is -1y"], + [0, -12, 0, 0, "M06-M06 leap-common backwards is -12mo"], + ], + [ + common1Adar, common2Adar, + [-2, 0, 0, 0, "M06-M06 common-common backwards is -2y"], + [0, -25, 0, 0, "M06-M06 common-common backwards is -25mo not -24mo"], + ], + [ + common1Shevat, leapAdarI, + [-1, -1, 0, 0, "M05-M05L backwards is -1y -1mo"], + [0, -13, 0, 0, "M05-M05L backwards is -13mo"], + ], + [ + leapAdarI, common2Shevat, + [0, -12, 0, 0, "M05L-M05 backwards is -12mo not -1y"], + [0, -12, 0, 0, "M05L-M05 backwards is -12mo"], + ], + [ + common1Adar, leapAdarI, + [0, -12, 0, 0, "M06-M05L backwards is -12mo not -1y"], + [0, -12, 0, 0, "M06-M05L backwards is -12mo"], + ], + [ + leapAdarI, common2Adar, + [-1, 0, 0, 0, "M05L-M06 backwards is -1y (exhibits calendar-specific constraining)"], + [0, -13, 0, 0, "M05L-M06 backwards is -13mo"], + ], + [ + common1Nisan, leapAdarII, + [0, -12, 0, 0, "M07-M06 common-leap backwards is -12mo not -11mo"], + [0, -12, 0, 0, "M07-M06 common-leap backwards is -12mo not -11mo"], + ], + + // Negative + [ + common2Shevat, leapShevat, + [1, 0, 0, 0, "M05-M05 common-leap is 1y"], + [0, 13, 0, 0, "M05-M05 common-leap is 13mo not 12mo"], + ], + [ + leapShevat, common1Shevat, + [1, 0, 0, 0, "M05-M05 leap-common is 1y"], + [0, 12, 0, 0, "M05-M05 leap-common is 12mo not 13mo"], + ], + [ + common2Shevat, common1Shevat, + [2, 0, 0, 0, "M05-M05 common-common is 2y"], + [0, 25, 0, 0, "M05-M05 common-common is 25mo not 24mo"], + ], + [ + common2Adar, leapAdarII, + [1, 0, 0, 0, "M06-M06 common-leap is 1y"], + [0, 12, 0, 0, "M06-M06 common-leap is 12mo not 13mo"], + ], + [ + leapAdarII, common1Adar, + [1, 0, 0, 0, "M06-M06 leap-common is 1y"], + [0, 13, 0, 0, "M06-M06 leap-common is 13mo not 12mo"], + ], + [ + common2Adar, common1Adar, + [2, 0, 0, 0, "M06-M06 common-common is 2y"], + [0, 25, 0, 0, "M06-M06 common-common is 25mo not 24mo"], + ], + [ + common2Shevat, leapAdarI, + [0, 12, 0, 0, "M05-M05L is 12mo not 1y"], + [0, 12, 0, 0, "M05-M05L is 12mo"], + ], + [ + leapAdarI, common1Shevat, + [1, 1, 0, 0, "M05L-M05 is 1y 1mo (exhibits calendar-specific constraining)"], + [0, 13, 0, 0, "M05L-M05 is 13mo"], + ], + [ + common2Adar, leapAdarI, + [1, 1, 0, 0, "M06-M05L is 1y 1mo"], + [0, 13, 0, 0, "M06-M05L is 13mo"], + ], + [ + leapAdarI, common1Adar, + [0, 12, 0, 0, "M05L-M06 is 12mo not 1y"], + [0, 12, 0, 0, "M05L-M06 is 12mo"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.since(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.since(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.since(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.since(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.since(two); + const resultDaysISO = oneISO.since(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/basic-chinese.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/basic-chinese.js @@ -0,0 +1,64 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.subtract +description: Basic addition and subtraction in the chinese calendar +features: [Temporal, Intl.Era-monthcode] +includes: [temporalHelpers.js] +---*/ + +const calendar = "chinese"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const duration1 = new Temporal.Duration(0, -1); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2019, monthCode: "M11", day: 1, hour: 12, minute: 34, calendar }, options).subtract(duration1), + 2019, 12, "M12", 1, 12, 34, 0, 0, 0, 0, "add 1 month, with result in same year" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2019, monthCode: "M12", day: 1, hour: 12, minute: 34, calendar }, options).subtract(duration1), + 2020, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "add 1 month, with result in next year" +); + +// Weeks + +const months2weeks3 = new Temporal.Duration(0, /* months = */ -2, /* weeks = */ -3); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2021, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options).subtract(months2weeks3), + 2021, 3, "M03", 22, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from non-leap day/month, ending in same year" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2021, monthCode: "M12", day: 29, hour: 12, minute: 34, calendar }, options).subtract(months2weeks3), + 2022, 3, "M03", 21, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from end of year to next year" +); + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ -10); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2021, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options).subtract(days10), + 2021, 1, "M01", 11, 12, 34, 0, 0, 0, 0, "add 10 days, ending in same month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2021, monthCode: "M01", day: 29, hour: 12, minute: 34, calendar }, options).subtract(days10), + 2021, 2, "M02", 10, 12, 34, 0, 0, 0, 0, "add 10 days, ending in following month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2021, monthCode: "M12", day: 29, hour: 12, minute: 34, calendar }, options).subtract(days10), + 2022, 1, "M01", 10, 12, 34, 0, 0, 0, 0, "add 10 days, ending in following year" +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/basic-dangi.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/basic-dangi.js @@ -0,0 +1,64 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.subtract +description: Basic addition and subtraction in the dangi calendar +features: [Temporal, Intl.Era-monthcode] +includes: [temporalHelpers.js] +---*/ + +const calendar = "dangi"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const duration1 = new Temporal.Duration(0, -1); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2019, monthCode: "M11", day: 1, hour: 12, minute: 34, calendar }, options).subtract(duration1), + 2019, 12, "M12", 1, 12, 34, 0, 0, 0, 0, "add 1 month, with result in same year" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2019, monthCode: "M12", day: 1, hour: 12, minute: 34, calendar }, options).subtract(duration1), + 2020, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "add 1 month, with result in next year" +); + +// Weeks + +const months2weeks3 = new Temporal.Duration(0, /* months = */ -2, /* weeks = */ -3); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2021, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options).subtract(months2weeks3), + 2021, 3, "M03", 22, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from non-leap day/month, ending in same year" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2021, monthCode: "M12", day: 29, hour: 12, minute: 34, calendar }, options).subtract(months2weeks3), + 2022, 3, "M03", 21, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from end of year to next year" +); + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ -10); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2021, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options).subtract(days10), + 2021, 1, "M01", 11, 12, 34, 0, 0, 0, 0, "add 10 days, ending in same month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2021, monthCode: "M01", day: 29, hour: 12, minute: 34, calendar }, options).subtract(days10), + 2021, 2, "M02", 10, 12, 34, 0, 0, 0, 0, "add 10 days, ending in following month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2021, monthCode: "M12", day: 29, hour: 12, minute: 34, calendar }, options).subtract(days10), + 2022, 1, "M01", 10, 12, 34, 0, 0, 0, 0, "add 10 days, ending in following year" +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/basic-hebrew.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/basic-hebrew.js @@ -0,0 +1,30 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.subtract +description: Basic addition and subtraction in the hebrew calendar +features: [Temporal] +includes: [temporalHelpers.js] +---*/ + +const calendar = "hebrew"; +const options = { overflow: "reject" }; + +// Years + +// Months + +// Weeks + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ -10); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 5785, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options).subtract(days10), + 5785, 1, "M01", 11, 12, 34, 0, 0, 0, 0, "adding 10 days", "am", 5785 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/basic-islamic-civil.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/basic-islamic-civil.js @@ -0,0 +1,84 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.subtract +description: Basic addition and subtraction in the islamic-civil calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-civil"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const date1 = Temporal.PlainDateTime.from({ year: 1445, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(0, -8)), + 1445, 9, "M09", 1, 12, 34, 0, 0, 0, 0, "Adding 8 months to Muharram 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(0, -11)), + 1445, 12, "M12", 1, 12, 34, 0, 0, 0, 0, "Adding 11 months to Muharram 1445 lands in Dhu al-Hijjah", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(0, -12)), + 1446, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Adding 12 months to Muharram 1445 lands in Muharram 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }).subtract(new Temporal.Duration(0, -13)), + 1446, 7, "M07", 15, 12, 34, 0, 0, 0, 0, "Adding 13 months to Jumada II 1445 lands in Rajab 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M03", day: 15, hour: 12, minute: 34, calendar }, options).subtract(new Temporal.Duration(0, -6)), + 1445, 9, "M09", 15, 12, 34, 0, 0, 0, 0, "Adding 6 months to Rabi I 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1444, monthCode: "M10", day: 1, hour: 12, minute: 34, calendar }).subtract(new Temporal.Duration(0, -5)), + 1445, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "Adding 5 months to Shawwal 1444 crosses to 1445", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1400, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }).subtract(new Temporal.Duration(0, -100)), + 1408, 5, "M05", 1, 12, 34, 0, 0, 0, 0, "Adding a large number of months", + "ah", 1408 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M09", day: 1, hour: 12, minute: 34, calendar }, options).subtract(new Temporal.Duration(0, 8)), + 1445, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Subtracting 8 months from Ramadan 1445 lands in Muharram", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options).subtract(new Temporal.Duration(0, 12)), + 1444, 6, "M06", 1, 12, 34, 0, 0, 0, 0, "Subtracting 12 months from Jumada II 1445 lands in Jumada II 1444", + "ah", 1444 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M02", day: 15, hour: 12, minute: 34, calendar }, options).subtract(new Temporal.Duration(0, 5)), + 1444, 9, "M09", 15, 12, 34, 0, 0, 0, 0, "Subtracting 5 months from Safar 1445 crosses to Ramadan 1444", + "ah", 1444 +); + +// Weeks + +// Days + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/basic-islamic-tbla.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/basic-islamic-tbla.js @@ -0,0 +1,84 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.subtract +description: Basic addition and subtraction in the islamic-tbla calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-tbla"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const date1 = Temporal.PlainDateTime.from({ year: 1445, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(0, -8)), + 1445, 9, "M09", 1, 12, 34, 0, 0, 0, 0, "Adding 8 months to Muharram 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(0, -11)), + 1445, 12, "M12", 1, 12, 34, 0, 0, 0, 0, "Adding 11 months to Muharram 1445 lands in Dhu al-Hijjah", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(0, -12)), + 1446, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Adding 12 months to Muharram 1445 lands in Muharram 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }).subtract(new Temporal.Duration(0, -13)), + 1446, 7, "M07", 15, 12, 34, 0, 0, 0, 0, "Adding 13 months to Jumada II 1445 lands in Rajab 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M03", day: 15, hour: 12, minute: 34, calendar }, options).subtract(new Temporal.Duration(0, -6)), + 1445, 9, "M09", 15, 12, 34, 0, 0, 0, 0, "Adding 6 months to Rabi I 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1444, monthCode: "M10", day: 1, hour: 12, minute: 34, calendar }).subtract(new Temporal.Duration(0, -5)), + 1445, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "Adding 5 months to Shawwal 1444 crosses to 1445", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1400, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }).subtract(new Temporal.Duration(0, -100)), + 1408, 5, "M05", 1, 12, 34, 0, 0, 0, 0, "Adding a large number of months", + "ah", 1408 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M09", day: 1, hour: 12, minute: 34, calendar }, options).subtract(new Temporal.Duration(0, 8)), + 1445, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Subtracting 8 months from Ramadan 1445 lands in Muharram", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options).subtract(new Temporal.Duration(0, 12)), + 1444, 6, "M06", 1, 12, 34, 0, 0, 0, 0, "Subtracting 12 months from Jumada II 1445 lands in Jumada II 1444", + "ah", 1444 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M02", day: 15, hour: 12, minute: 34, calendar }, options).subtract(new Temporal.Duration(0, 5)), + 1444, 9, "M09", 15, 12, 34, 0, 0, 0, 0, "Subtracting 5 months from Safar 1445 crosses to Ramadan 1444", + "ah", 1444 +); + +// Weeks + +// Days + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/basic-islamic-umalqura.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/basic-islamic-umalqura.js @@ -0,0 +1,84 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.subtract +description: Basic addition and subtraction in the islamic-umalqura calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-umalqura"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const date1 = Temporal.PlainDateTime.from({ year: 1445, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(0, -8)), + 1445, 9, "M09", 1, 12, 34, 0, 0, 0, 0, "Adding 8 months to Muharram 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(0, -11)), + 1445, 12, "M12", 1, 12, 34, 0, 0, 0, 0, "Adding 11 months to Muharram 1445 lands in Dhu al-Hijjah", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(0, -12)), + 1446, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Adding 12 months to Muharram 1445 lands in Muharram 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }).subtract(new Temporal.Duration(0, -13)), + 1446, 7, "M07", 15, 12, 34, 0, 0, 0, 0, "Adding 13 months to Jumada II 1445 lands in Rajab 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M03", day: 15, hour: 12, minute: 34, calendar }, options).subtract(new Temporal.Duration(0, -6)), + 1445, 9, "M09", 15, 12, 34, 0, 0, 0, 0, "Adding 6 months to Rabi I 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1444, monthCode: "M10", day: 1, hour: 12, minute: 34, calendar }).subtract(new Temporal.Duration(0, -5)), + 1445, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "Adding 5 months to Shawwal 1444 crosses to 1445", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1400, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }).subtract(new Temporal.Duration(0, -100)), + 1408, 5, "M05", 1, 12, 34, 0, 0, 0, 0, "Adding a large number of months", + "ah", 1408 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M09", day: 1, hour: 12, minute: 34, calendar }, options).subtract(new Temporal.Duration(0, 8)), + 1445, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Subtracting 8 months from Ramadan 1445 lands in Muharram", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options).subtract(new Temporal.Duration(0, 12)), + 1444, 6, "M06", 1, 12, 34, 0, 0, 0, 0, "Subtracting 12 months from Jumada II 1445 lands in Jumada II 1444", + "ah", 1444 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1445, monthCode: "M02", day: 15, hour: 12, minute: 34, calendar }, options).subtract(new Temporal.Duration(0, 5)), + 1444, 9, "M09", 15, 12, 34, 0, 0, 0, 0, "Subtracting 5 months from Safar 1445 crosses to Ramadan 1444", + "ah", 1444 +); + +// Weeks + +// Days + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/browser.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/browser.js diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/constrain-day-islamic-civil.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/constrain-day-islamic-civil.js @@ -0,0 +1,43 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.subtract +description: Constraining the day for 29/30-day months in islamic-civil calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-civil"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const months1 = new Temporal.Duration(0, -1); +const months1n = new Temporal.Duration(0, 1); + +const date1 = Temporal.PlainDateTime.from({ year: 1445, monthCode: "M01", day: 30, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.subtract(months1), + 1445, 2, "M02", 29, 12, 34, 0, 0, 0, 0, "Day is constrained when adding months to a 30-day month and landing in a 29-day month", + "ah", 1445 +); + +assert.throws(RangeError, function () { + date1.subtract(months1, options); +}, "Adding months to a 30-day month and landing in a 29-day month rejects"); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(months1n), + 1444, 12, "M12", 29, 12, 34, 0, 0, 0, 0, "Day is constrained when subtracting months from a 30-day month and landing in a 29-day month", + "ah", 1444 +); + +assert.throws(RangeError, function () { + date1.subtract(months1n, options); +}, "Subtracting months from a 30-day month and landing in a 29-day month rejects"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/constrain-day-islamic-tbla.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/constrain-day-islamic-tbla.js @@ -0,0 +1,43 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.subtract +description: Constraining the day for 29/30-day months in islamic-tbla calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-tbla"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const months1 = new Temporal.Duration(0, -1); +const months1n = new Temporal.Duration(0, 1); + +const date1 = Temporal.PlainDateTime.from({ year: 1445, monthCode: "M01", day: 30, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.subtract(months1), + 1445, 2, "M02", 29, 12, 34, 0, 0, 0, 0, "Day is constrained when adding months to a 30-day month and landing in a 29-day month", + "ah", 1445 +); + +assert.throws(RangeError, function () { + date1.subtract(months1, options); +}, "Adding months to a 30-day month and landing in a 29-day month rejects"); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(months1n), + 1444, 12, "M12", 29, 12, 34, 0, 0, 0, 0, "Day is constrained when subtracting months from a 30-day month and landing in a 29-day month", + "ah", 1444 +); + +assert.throws(RangeError, function () { + date1.subtract(months1n, options); +}, "Subtracting months from a 30-day month and landing in a 29-day month rejects"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/constrain-day-islamic-umalqura.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/constrain-day-islamic-umalqura.js @@ -0,0 +1,43 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.subtract +description: Constraining the day for 29/30-day months in islamic-umalqura calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-umalqura"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const months1 = new Temporal.Duration(0, /* months = */ -1); +const months1n = new Temporal.Duration(0, 1); + +const date1 = Temporal.PlainDateTime.from({ year: 1447, monthCode: "M01", day: 30, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.subtract(months1), + 1447, 2, "M02", 29, 12, 34, 0, 0, 0, 0, "Day is constrained when adding months to a 30-day month and landing in a 29-day month", + "ah", 1447 +); + +assert.throws(RangeError, function () { + date1.subtract(months1, options); +}, "Adding months to a 30-day month and landing in a 29-day month rejects"); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(months1n), + 1446, 12, "M12", 29, 12, 34, 0, 0, 0, 0, "Day is constrained when subtracting months from a 30-day month and landing in a 29-day month", + "ah", 1446 +); + +assert.throws(RangeError, function () { + date1.subtract(months1n, options); +}, "Subtracting months from a 30-day month and landing in a 29-day month rejects"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/era-boundary-ethiopic.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/era-boundary-ethiopic.js @@ -0,0 +1,51 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.subtract +description: Adding years works correctly across era boundaries in ethiopic calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const duration5 = new Temporal.Duration(-5); +const duration5n = new Temporal.Duration(5); +const calendar = "ethiopic"; +const options = { overflow: "reject" }; + +const date1 = Temporal.PlainDateTime.from({ era: "aa", eraYear: 5500, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(-1)), + 1, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Adding 1 year to last year of Amete Alem era lands in year 1 of incarnation era", + "am", 1 +); + +const date2 = Temporal.PlainDateTime.from({ era: "am", eraYear: 2000, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date2.subtract(duration5), + 2005, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Adding 5 years within incarnation era", + "am", 2005 +); + +const date3 = Temporal.PlainDateTime.from({ era: "aa", eraYear: 5450, monthCode: "M07", day: 12, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date3.subtract(duration5), + -45, 7, "M07", 12, 12, 34, 0, 0, 0, 0, "Adding 5 years within Amete Alem era", + "aa", 5455 +); + +TemporalHelpers.assertPlainDateTime( + date2.subtract(duration5n), + 1995, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Subtracting 5 years within incarnation era", + "am", 1995 +); + +const date4 = Temporal.PlainDateTime.from({ era: "am", eraYear: 5, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date4.subtract(duration5n), + 0, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Subtracting 5 years from year 5 lands in last year of Amete Alem era, arithmetic year 0", + "aa", 5500 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/era-boundary-gregory.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/era-boundary-gregory.js @@ -0,0 +1,71 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.subtract +description: Adding years works correctly across era boundaries in gregory calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const duration1 = new Temporal.Duration(-1); +const duration1n = new Temporal.Duration(1); +const calendar = "gregory"; +const options = { overflow: "reject" }; + +const date1 = Temporal.PlainDateTime.from({ era: "bce", eraYear: 2, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.subtract(duration1), + 0, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to 2 BCE lands in 1 BCE (counts backwards)", + "bce", 1 +); + +const date2 = Temporal.PlainDateTime.from({ era: "bce", eraYear: 1, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date2.subtract(duration1), + 1, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to 1 BCE lands in 1 CE (no year zero)", + "ce", 1 +); + +const date3 = Temporal.PlainDateTime.from({ era: "ce", eraYear: 1, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date3.subtract(duration1), + 2, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to 1 CE lands in 2 CE", + "ce", 2 +); + +const date4 = Temporal.PlainDateTime.from({ era: "bce", eraYear: 5, monthCode: "M03", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date4.subtract(new Temporal.Duration(-10)), + 6, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "Adding 10 years to 5 BCE lands in 6 CE (no year zero)", + "ce", 6 +); + +const date5 = Temporal.PlainDateTime.from({ era: "ce", eraYear: 2, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date5.subtract(duration1n), + 1, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from 2 CE lands in 1 CE", + "ce", 1 +); + +TemporalHelpers.assertPlainDateTime( + date3.subtract(duration1n), + 0, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from 1 CE lands in 1 BCE", + "bce", 1 +); + +TemporalHelpers.assertPlainDateTime( + date2.subtract(duration1n), + -1, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from 1 BCE lands in 2 BCE", + "bce", 2 +); + +const date6 = Temporal.PlainDateTime.from({ era: "ce", eraYear: 5, monthCode: "M03", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date6.subtract(new Temporal.Duration(10)), + -5, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "Subtracting 10 years from 5 CE lands in 6 BCE", + "bce", 6 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/era-boundary-japanese.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/era-boundary-japanese.js @@ -0,0 +1,84 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.subtract +description: > + Adding years works correctly across era boundaries in calendars with eras +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +// Reiwa era started on May 1, 2019 (Reiwa 1 = 2019) +// Heisei era: 1989-2019 (Heisei 31 ended April 30, 2019) + +const duration1 = new Temporal.Duration(-1); +const duration1n = new Temporal.Duration(1); +const calendar = "japanese"; +const options = { overflow: "reject" }; + +const date1 = Temporal.PlainDateTime.from({ era: "heisei", eraYear: 30, monthCode: "M03", day: 15, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.subtract(duration1), + 2019, 3, "M03", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to Heisei 30 March (before May 1) lands in Heisei 31 March", + "heisei", 31 +); + +const date2 = Temporal.PlainDateTime.from({ era: "heisei", eraYear: 31, monthCode: "M04", day: 15, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date2.subtract(duration1), + 2020, 4, "M04", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to Heisei 31 April (before May 1) lands in Reiwa 2 April", + "reiwa", 2 +); + +const date3 = Temporal.PlainDateTime.from({ era: "heisei", eraYear: 30, monthCode: "M06", day: 12, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date3.subtract(duration1), + 2019, 6, "M06", 12, 12, 34, 0, 0, 0, 0, "Adding 1 year to Heisei 30 June (after May 1) lands in Reiwa 1 June", + "reiwa", 1 +); + +const date4 = Temporal.PlainDateTime.from({ era: "reiwa", eraYear: 1, monthCode: "M06", day: 10, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date4.subtract(duration1), + 2020, 6, "M06", 10, 12, 34, 0, 0, 0, 0, "Adding 1 year to Reiwa 1 June lands in Reiwa 2 June", + "reiwa", 2 +); + +const date5 = Temporal.PlainDateTime.from({ era: "heisei", eraYear: 28, monthCode: "M07", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date5.subtract(new Temporal.Duration(-3)), + 2019, 7, "M07", 1, 12, 34, 0, 0, 0, 0, "Multiple years across era boundary: Adding 3 years to Heisei 28 July lands in Reiwa 1 July", + "reiwa", 1 +); + +const date6 = Temporal.PlainDateTime.from({ era: "reiwa", eraYear: 2, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date6.subtract(duration1n), + 2019, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from Reiwa 2 June lands in Reiwa 1 June", + "reiwa", 1 +); + +const date7 = Temporal.PlainDateTime.from({ era: "reiwa", eraYear: 2, monthCode: "M03", day: 15, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date7.subtract(duration1n), + 2019, 3, "M03", 15, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from Reiwa 2 March lands in Heisei 31 March", + "heisei", 31 +); + +const date8 = Temporal.PlainDateTime.from({ era: "reiwa", eraYear: 1, monthCode: "M07", day: 10, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date8.subtract(duration1n), + 2018, 7, "M07", 10, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from Reiwa 1 July lands in Heisei 30 July", + "heisei", 30 +); + +const date9 = Temporal.PlainDateTime.from({ era: "reiwa", eraYear: 4, monthCode: "M02", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date9.subtract(new Temporal.Duration(5)), + 2017, 2, "M02", 1, 12, 34, 0, 0, 0, 0, "Subtracting 5 years from Reiwa 4 February lands in Heisei 29 February", + "heisei", 29 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/era-boundary-roc.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/era-boundary-roc.js @@ -0,0 +1,66 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.subtract +description: > + Adding years works correctly across era boundaries in calendars with eras +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const duration1 = new Temporal.Duration(-1); +const duration1n = new Temporal.Duration(1); +const calendar = "roc"; +const options = { overflow: "reject" }; + +const date1 = Temporal.PlainDateTime.from({ era: "broc", eraYear: 2, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.subtract(duration1), + 0, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to 2 BROC lands in 1 BROC (counts backwards)", + "broc", 1 +); + +const date2 = Temporal.PlainDateTime.from({ era: "broc", eraYear: 1, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date2.subtract(duration1), + 1, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to 1 BROC lands in 1 ROC (no year zero)", + "roc", 1 +); + +const date3 = Temporal.PlainDateTime.from({ era: "roc", eraYear: 1, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date3.subtract(duration1), + 2, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to 1 ROC lands in 2 ROC", + "roc", 2 +); + +const date4 = Temporal.PlainDateTime.from({ era: "broc", eraYear: 5, monthCode: "M03", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date4.subtract(new Temporal.Duration(-10)), + 6, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "Adding 10 years to 5 BROC lands in 6 ROC (no year zero)", + "roc", 6 +); + +const date5 = Temporal.PlainDateTime.from({ era: "roc", eraYear: 5, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date5.subtract(duration1n), + 4, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from ROC 5 lands in ROC 4", + "roc", 4 +); + +TemporalHelpers.assertPlainDateTime( + date3.subtract(duration1n), + 0, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from ROC 1 lands in BROC 1", + "broc", 1 +); + +const date6 = Temporal.PlainDateTime.from({ era: "roc", eraYear: 10, monthCode: "M03", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date6.subtract(new Temporal.Duration(15)), + -5, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "Subtracting 15 years from ROC 10 lands in BROC 6", + "broc", 6 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/leap-months-chinese.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/leap-months-chinese.js @@ -0,0 +1,129 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.subtract +description: Arithmetic around leap months in the chinese calendar +features: [Temporal, Intl.Era-monthcode] +includes: [temporalHelpers.js] +---*/ + +const calendar = "chinese"; +const options = { overflow: "reject" }; + +// Years + +const years1 = new Temporal.Duration(-1); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2019, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options).subtract(years1), + 2020, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "add 1 year from non-leap day" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1966, monthCode: "M03L", day: 1, hour: 12, minute: 34, calendar }, options).subtract(years1), + 1967, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "add 1 year from leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1938, monthCode: "M07L", day: 30, hour: 12, minute: 34, calendar }, options).subtract(years1), + 1939, 7, "M07", 29, 12, 34, 0, 0, 0, 0, "add 1 year from leap day in leap month" +); + +// Months + +const months1 = new Temporal.Duration(0, -1); +const months1n = new Temporal.Duration(0, 1); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1947, monthCode: "M02L", day: 1, hour: 12, minute: 34, calendar }, options).subtract(months1), + 1947, 4, "M03", 1, 12, 34, 0, 0, 0, 0, "add 1 month, starting at start of leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1955, monthCode: "M03L", day: 1, hour: 12, minute: 34, calendar }, options).subtract(months1), + 1955, 5, "M04", 1, 12, 34, 0, 0, 0, 0, "add 1 month, starting at start of leap month with 30 days" +); + +const date1 = Temporal.PlainDateTime.from({ year: 2020, monthCode: "M03", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.subtract(months1), + 2020, 4, "M04", 1, 12, 34, 0, 0, 0, 0, "adding 1 month to M03 in leap year lands in M04 (not M04L)" +); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(0, -2)), + 2020, 5, "M04L", 1, 12, 34, 0, 0, 0, 0, "adding 2 months to M03 in leap year lands in M04L (leap month)" +); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(0, -3)), + 2020, 6, "M05", 1, 12, 34, 0, 0, 0, 0, "adding 3 months to M03 in leap year lands in M05 (not M06)" +); + +const date2 = Temporal.PlainDateTime.from({ year: 2020, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date2.subtract(months1n), + 2020, 6, "M05", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M06 in leap year lands in M05" +); + +TemporalHelpers.assertPlainDateTime( + date2.subtract(new Temporal.Duration(0, 2)), + 2020, 5, "M04L", 1, 12, 34, 0, 0, 0, 0, "Subtracting 2 months from M06 in leap year lands in M04L (leap month)" +); + +TemporalHelpers.assertPlainDateTime( + date2.subtract(new Temporal.Duration(0, 3)), + 2020, 4, "M04", 1, 12, 34, 0, 0, 0, 0, "Subtracting 3 months from M06 in leap year lands in M04 (not M03)" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2020, monthCode: "M05", day: 1, hour: 12, minute: 34, calendar }, options).subtract(months1n), + 2020, 5, "M04L", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M05 in leap year lands in M04L" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2020, monthCode: "M04L", day: 1, hour: 12, minute: 34, calendar }, options).subtract(months1n), + 2020, 4, "M04", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M04L in calendar lands in M04" +); + +// Weeks + +const months2weeks3 = new Temporal.Duration(0, /* months = */ -2, /* weeks = */ -3); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1947, monthCode: "M02L", day: 29, hour: 12, minute: 34, calendar }, options).subtract(months2weeks3), + 1947, 6, "M05", 20, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from last day leap month without leap day" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1955, monthCode: "M03L", day: 30, hour: 12, minute: 34, calendar }, options).subtract(months2weeks3), + 1955, 7, "M06", 21, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from leap day in leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1947, monthCode: "M01", day: 29, hour: 12, minute: 34, calendar }, options).subtract(months2weeks3), + 1947, 4, "M03", 21, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from immediately before a leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1955, monthCode: "M06", day: 29, hour: 12, minute: 34, calendar }, options).subtract(months2weeks3), + 1955, 10, "M09", 20, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from immediately before a leap month" +); + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ -10); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1955, monthCode: "M03L", day: 30, hour: 12, minute: 34, calendar }, options).subtract(days10), + 1955, 5, "M04", 10, 12, 34, 0, 0, 0, 0, "add 10 days from leap day in leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1947, monthCode: "M02L", day: 29, hour: 12, minute: 34, calendar }, options).subtract(days10), + 1947, 4, "M03", 10, 12, 34, 0, 0, 0, 0, "add 10 days from last day of leap month" +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/leap-months-dangi.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/leap-months-dangi.js @@ -0,0 +1,129 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.subtract +description: Arithmetic around leap months in the dangi calendar +features: [Temporal, Intl.Era-monthcode] +includes: [temporalHelpers.js] +---*/ + +const calendar = "dangi"; +const options = { overflow: "reject" }; + +// Years + +const years1 = new Temporal.Duration(-1); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2019, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options).subtract(years1), + 2020, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "add 1 year from non-leap day" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1966, monthCode: "M03L", day: 1, hour: 12, minute: 34, calendar }, options).subtract(years1), + 1967, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "add 1 year from leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1938, monthCode: "M07L", day: 30, hour: 12, minute: 34, calendar }, options).subtract(years1), + 1939, 7, "M07", 29, 12, 34, 0, 0, 0, 0, "add 1 year from leap day in leap month" +); + +// Months + +const months1 = new Temporal.Duration(0, -1); +const months1n = new Temporal.Duration(0, 1); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1947, monthCode: "M02L", day: 1, hour: 12, minute: 34, calendar }, options).subtract(months1), + 1947, 4, "M03", 1, 12, 34, 0, 0, 0, 0, "add 1 month, starting at start of leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1955, monthCode: "M03L", day: 1, hour: 12, minute: 34, calendar }, options).subtract(months1), + 1955, 5, "M04", 1, 12, 34, 0, 0, 0, 0, "add 1 month, starting at start of leap month with 30 days" +); + +const date1 = Temporal.PlainDateTime.from({ year: 2020, monthCode: "M03", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.subtract(months1), + 2020, 4, "M04", 1, 12, 34, 0, 0, 0, 0, "adding 1 month to M03 in leap year lands in M04 (not M04L)" +); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(0, -2)), + 2020, 5, "M04L", 1, 12, 34, 0, 0, 0, 0, "adding 2 months to M03 in leap year lands in M04L (leap month)" +); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(0, -3)), + 2020, 6, "M05", 1, 12, 34, 0, 0, 0, 0, "adding 3 months to M03 in leap year lands in M05 (not M06)" +); + +const date2 = Temporal.PlainDateTime.from({ year: 2020, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date2.subtract(months1n), + 2020, 6, "M05", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M06 in leap year lands in M05" +); + +TemporalHelpers.assertPlainDateTime( + date2.subtract(new Temporal.Duration(0, 2)), + 2020, 5, "M04L", 1, 12, 34, 0, 0, 0, 0, "Subtracting 2 months from M06 in leap year lands in M04L (leap month)" +); + +TemporalHelpers.assertPlainDateTime( + date2.subtract(new Temporal.Duration(0, 3)), + 2020, 4, "M04", 1, 12, 34, 0, 0, 0, 0, "Subtracting 3 months from M06 in leap year lands in M04 (not M03)" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2020, monthCode: "M05", day: 1, hour: 12, minute: 34, calendar }, options).subtract(months1n), + 2020, 5, "M04L", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M05 in leap year lands in M04L" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 2020, monthCode: "M04L", day: 1, hour: 12, minute: 34, calendar }, options).subtract(months1n), + 2020, 4, "M04", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M04L in calendar lands in M04" +); + +// Weeks + +const months2weeks3 = new Temporal.Duration(0, /* months = */ -2, /* weeks = */ -3); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1947, monthCode: "M02L", day: 29, hour: 12, minute: 34, calendar }, options).subtract(months2weeks3), + 1947, 6, "M05", 20, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from last day leap month without leap day" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1955, monthCode: "M03L", day: 30, hour: 12, minute: 34, calendar }, options).subtract(months2weeks3), + 1955, 7, "M06", 21, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from leap day in leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1947, monthCode: "M01", day: 29, hour: 12, minute: 34, calendar }, options).subtract(months2weeks3), + 1947, 4, "M03", 21, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from immediately before a leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1955, monthCode: "M06", day: 29, hour: 12, minute: 34, calendar }, options).subtract(months2weeks3), + 1955, 10, "M09", 20, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from immediately before a leap month" +); + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ -10); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1955, monthCode: "M03L", day: 30, hour: 12, minute: 34, calendar }, options).subtract(days10), + 1955, 5, "M04", 10, 12, 34, 0, 0, 0, 0, "add 10 days from leap day in leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 1947, monthCode: "M02L", day: 29, hour: 12, minute: 34, calendar }, options).subtract(days10), + 1947, 4, "M03", 10, 12, 34, 0, 0, 0, 0, "add 10 days from last day of leap month" +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/leap-months-hebrew.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/leap-months-hebrew.js @@ -0,0 +1,104 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.subtract +description: Arithmetic around leap months in the hebrew calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "hebrew"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const months1 = new Temporal.Duration(0, -1); +const months1n = new Temporal.Duration(0, 1); +const months2 = new Temporal.Duration(0, -2); +const months2n = new Temporal.Duration(0, 2); + +const date1 = Temporal.PlainDateTime.from({ year: 5784, monthCode: "M04", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.subtract(months1), + 5784, 5, "M05", 1, 12, 34, 0, 0, 0, 0, "Adding 1 month to M04 in leap year lands in M05 (Shevat)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(months2), + 5784, 6, "M05L", 1, 12, 34, 0, 0, 0, 0, "Adding 2 months to M04 in leap year lands in M05L (Adar I)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(0, -3)), + 5784, 7, "M06", 1, 12, 34, 0, 0, 0, 0, "Adding 3 months to M04 in leap year lands in M06 (Adar II)", + "am", 5784 +); + +const date2 = Temporal.PlainDateTime.from({ year: 5784, monthCode: "M05L", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date2.subtract(months1), + 5784, 7, "M06", 1, 12, 34, 0, 0, 0, 0, "Adding 1 month to M05L (Adar I) lands in M06 (Adar II)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 5783, monthCode: "M04", day: 1, hour: 12, minute: 34, calendar }, options).subtract(months2), + 5783, 6, "M06", 1, 12, 34, 0, 0, 0, 0, "Adding 2 months to M04 in non-leap year lands in M06 (no M05L)", + "am", 5783 +); + +const date3 = Temporal.PlainDateTime.from({ year: 5784, monthCode: "M07", day: 1, hour: 12, minute: 34, calendar }, options); +TemporalHelpers.assertPlainDateTime( + date3.subtract(months1n), + 5784, 7, "M06", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M07 in leap year lands in M06 (Adar II)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + date3.subtract(months2n), + 5784, 6, "M05L", 1, 12, 34, 0, 0, 0, 0, "Subtracting 2 months from M07 in leap year lands in M05L (Adar I)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + date3.subtract(new Temporal.Duration(0, 3)), + 5784, 5, "M05", 1, 12, 34, 0, 0, 0, 0, "Subtracting 3 months from M07 in leap year lands in M05 (Shevat)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 5784, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }).subtract(months1n), + 5784, 6, "M05L", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M06 (Adar II) in leap year lands in M05L (Adar I)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + date2.subtract(months1n), + 5784, 5, "M05", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M05L (Adar I) lands in M05 (Shevat)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 5783, monthCode: "M07", day: 1, hour: 12, minute: 34, calendar }).subtract(months2n), + 5783, 5, "M05", 1, 12, 34, 0, 0, 0, 0, "Subtracting 2 months from M07 in non-leap year lands in M05 (no M05L)", + "am", 5783 +); + +// Weeks + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ -10); + +TemporalHelpers.assertPlainDateTime( + Temporal.PlainDateTime.from({ year: 5784, monthCode: "M05L", day: 30, hour: 12, minute: 34, calendar }, options).subtract(days10), + 5784, 7, "M06", 10, 12, 34, 0, 0, 0, 0, "add 10 days to leap day", "am", 5784 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/shell.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/subtract/shell.js @@ -0,0 +1,1252 @@ +// GENERATED, DO NOT EDIT +// file: temporalHelpers.js +// Copyright (C) 2021 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +description: | + This defines helper objects and functions for testing Temporal. +defines: [TemporalHelpers] +features: [Symbol.species, Symbol.iterator, Temporal] +---*/ + +const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u; + +function formatPropertyName(propertyKey, objectName = "") { + switch (typeof propertyKey) { + case "symbol": + if (Symbol.keyFor(propertyKey) !== undefined) { + return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`; + } else if (propertyKey.description.startsWith('Symbol.')) { + return `${objectName}[${propertyKey.description}]`; + } else { + return `${objectName}[Symbol('${propertyKey.description}')]` + } + case "string": + if (propertyKey !== String(Number(propertyKey))) { + if (ASCII_IDENTIFIER.test(propertyKey)) { + return objectName ? `${objectName}.${propertyKey}` : propertyKey; + } + return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']` + } + // fall through + default: + // integer or string integer-index + return `${objectName}[${propertyKey}]`; + } +} + +const SKIP_SYMBOL = Symbol("Skip"); + +var TemporalHelpers = { + /* + * Codes and maximum lengths of months in the ISO 8601 calendar. + */ + ISOMonths: [ + { month: 1, monthCode: "M01", daysInMonth: 31 }, + { month: 2, monthCode: "M02", daysInMonth: 29 }, + { month: 3, monthCode: "M03", daysInMonth: 31 }, + { month: 4, monthCode: "M04", daysInMonth: 30 }, + { month: 5, monthCode: "M05", daysInMonth: 31 }, + { month: 6, monthCode: "M06", daysInMonth: 30 }, + { month: 7, monthCode: "M07", daysInMonth: 31 }, + { month: 8, monthCode: "M08", daysInMonth: 31 }, + { month: 9, monthCode: "M09", daysInMonth: 30 }, + { month: 10, monthCode: "M10", daysInMonth: 31 }, + { month: 11, monthCode: "M11", daysInMonth: 30 }, + { month: 12, monthCode: "M12", daysInMonth: 31 } + ], + + /* + * List of known calendar eras and their possible aliases. + * + * https://tc39.es/proposal-intl-era-monthcode/#table-eras + */ + CalendarEras: { + buddhist: [ + { era: "be" }, + ], + coptic: [ + { era: "am" }, + ], + ethiopic: [ + { era: "aa", aliases: ["mundi"] }, + { era: "am", aliases: ["incar"] }, + ], + ethioaa: [ + { era: "aa", aliases: ["mundi"] }, + ], + gregory: [ + { era: "bce", aliases: ["bc"] }, + { era: "ce", aliases: ["ad"] }, + ], + hebrew: [ + { era: "am" }, + ], + indian: [ + { era: "shaka" }, + ], + islamic: [ + { era: "ah" }, + { era: "bh" }, + ], + "islamic-civil": [ + { era: "bh" }, + { era: "ah" }, + ], + "islamic-rgsa": [ + { era: "bh" }, + { era: "ah" }, + ], + "islamic-tbla": [ + { era: "bh" }, + { era: "ah" }, + ], + "islamic-umalqura": [ + { era: "bh" }, + { era: "ah" }, + ], + japanese: [ + { era: "bce", aliases: ["bc"] }, + { era: "ce", aliases: ["ad"] }, + { era: "heisei" }, + { era: "meiji" }, + { era: "reiwa" }, + { era: "showa" }, + { era: "taisho" }, + ], + persian: [ + { era: "ap" }, + ], + roc: [ + { era: "roc", aliases: ["minguo"] }, + { era: "broc", aliases: ["before-roc", "minguo-qian"] }, + ], + }, + + /* + * Return the canonical era code. + */ + canonicalizeCalendarEra(calendarId, eraName) { + assert.sameValue(typeof calendarId, "string", "calendar must be string in canonicalizeCalendarEra"); + + if (!Object.prototype.hasOwnProperty.call(TemporalHelpers.CalendarEras, calendarId)) { + assert.sameValue(eraName, undefined); + return undefined; + } + + assert.sameValue(typeof eraName, "string", "eraName must be string or undefined in canonicalizeCalendarEra"); + + for (let {era, aliases = []} of TemporalHelpers.CalendarEras[calendarId]) { + if (era === eraName || aliases.includes(eraName)) { + return era; + } + } + throw new Test262Error(`Unsupported era name: ${eraName}`); + }, + + /* + * assertDuration(duration, years, ..., nanoseconds[, description]): + * + * Shorthand for asserting that each field of a Temporal.Duration is equal to + * an expected value. + */ + assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(duration instanceof Temporal.Duration, `${prefix}instanceof`); + assert.sameValue(duration.years, years, `${prefix}years result:`); + assert.sameValue(duration.months, months, `${prefix}months result:`); + assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`); + assert.sameValue(duration.days, days, `${prefix}days result:`); + assert.sameValue(duration.hours, hours, `${prefix}hours result:`); + assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`); + assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`); + assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`); + assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`); + assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`); + }, + + /* + * assertDateDuration(duration, years, months, weeks, days, [, description]): + * + * Shorthand for asserting that each date field of a Temporal.Duration is + * equal to an expected value. + */ + assertDateDuration(duration, years, months, weeks, days, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(duration instanceof Temporal.Duration, `${prefix}instanceof`); + assert.sameValue(duration.years, years, `${prefix}years result:`); + assert.sameValue(duration.months, months, `${prefix}months result:`); + assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`); + assert.sameValue(duration.days, days, `${prefix}days result:`); + assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`); + assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`); + assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`); + assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`); + assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`); + assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`); + }, + + /* + * assertDurationsEqual(actual, expected[, description]): + * + * Shorthand for asserting that each field of a Temporal.Duration is equal to + * the corresponding field in another Temporal.Duration. + */ + assertDurationsEqual(actual, expected, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`); + TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description); + }, + + /* + * assertInstantsEqual(actual, expected[, description]): + * + * Shorthand for asserting that two Temporal.Instants are of the correct type + * and equal according to their equals() methods. + */ + assertInstantsEqual(actual, expected, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`); + assert(actual instanceof Temporal.Instant, `${prefix}instanceof`); + assert(actual.equals(expected), `${prefix}equals method`); + }, + + /* + * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]): + * + * Shorthand for asserting that each field of a Temporal.PlainDate is equal to + * an expected value. (Except the `calendar` property, since callers may want + * to assert either object equality with an object they put in there, or the + * value of date.calendarId.) + */ + assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) { + const prefix = description ? `${description}: ` : ""; + assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`); + assert.sameValue( + TemporalHelpers.canonicalizeCalendarEra(date.calendarId, date.era), + TemporalHelpers.canonicalizeCalendarEra(date.calendarId, era), + `${prefix}era result:` + ); + assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`); + assert.sameValue(date.year, year, `${prefix}year result:`); + assert.sameValue(date.month, month, `${prefix}month result:`); + assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`); + assert.sameValue(date.day, day, `${prefix}day result:`); + }, + + /* + * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]): + * + * Shorthand for asserting that each field of a Temporal.PlainDateTime is + * equal to an expected value. (Except the `calendar` property, since callers + * may want to assert either object equality with an object they put in there, + * or the value of datetime.calendarId.) + */ + assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) { + const prefix = description ? `${description}: ` : ""; + assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`); + assert.sameValue( + TemporalHelpers.canonicalizeCalendarEra(datetime.calendarId, datetime.era), + TemporalHelpers.canonicalizeCalendarEra(datetime.calendarId, era), + `${prefix}era result:` + ); + assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`); + assert.sameValue(datetime.year, year, `${prefix}year result:`); + assert.sameValue(datetime.month, month, `${prefix}month result:`); + assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`); + assert.sameValue(datetime.day, day, `${prefix}day result:`); + assert.sameValue(datetime.hour, hour, `${prefix}hour result:`); + assert.sameValue(datetime.minute, minute, `${prefix}minute result:`); + assert.sameValue(datetime.second, second, `${prefix}second result:`); + assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`); + assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`); + assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`); + }, + + /* + * assertPlainDateTimesEqual(actual, expected[, description]): + * + * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct + * type, equal according to their equals() methods, and additionally that + * their calendar internal slots are the same value. + */ + assertPlainDateTimesEqual(actual, expected, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`); + assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`); + assert(actual.equals(expected), `${prefix}equals method`); + assert.sameValue( + actual.calendarId, + expected.calendarId, + `${prefix}calendar same value:` + ); + }, + + /* + * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]): + * + * Shorthand for asserting that each field of a Temporal.PlainMonthDay is + * equal to an expected value. (Except the `calendar` property, since callers + * may want to assert either object equality with an object they put in there, + * or the value of monthDay.calendarId().) + */ + assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) { + const prefix = description ? `${description}: ` : ""; + assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`); + assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`); + assert.sameValue(monthDay.day, day, `${prefix}day result:`); + const isoYear = Number(monthDay.toString({ calendarName: "always" }).split("-")[0]); + assert.sameValue(isoYear, referenceISOYear, `${prefix}referenceISOYear result:`); + }, + + /* + * assertPlainTime(time, hour, ..., nanosecond[, description]): + * + * Shorthand for asserting that each field of a Temporal.PlainTime is equal to + * an expected value. + */ + assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`); + assert.sameValue(time.hour, hour, `${prefix}hour result:`); + assert.sameValue(time.minute, minute, `${prefix}minute result:`); + assert.sameValue(time.second, second, `${prefix}second result:`); + assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`); + assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`); + assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`); + }, + + /* + * assertPlainTimesEqual(actual, expected[, description]): + * + * Shorthand for asserting that two Temporal.PlainTimes are of the correct + * type and equal according to their equals() methods. + */ + assertPlainTimesEqual(actual, expected, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`); + assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`); + assert(actual.equals(expected), `${prefix}equals method`); + }, + + /* + * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]): + * + * Shorthand for asserting that each field of a Temporal.PlainYearMonth is + * equal to an expected value. (Except the `calendar` property, since callers + * may want to assert either object equality with an object they put in there, + * or the value of yearMonth.calendarId.) + */ + assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) { + const prefix = description ? `${description}: ` : ""; + assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`); + assert.sameValue( + TemporalHelpers.canonicalizeCalendarEra(yearMonth.calendarId, yearMonth.era), + TemporalHelpers.canonicalizeCalendarEra(yearMonth.calendarId, era), + `${prefix}era result:` + ); + assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`); + assert.sameValue(yearMonth.year, year, `${prefix}year result:`); + assert.sameValue(yearMonth.month, month, `${prefix}month result:`); + assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`); + const isoDay = Number(yearMonth.toString({ calendarName: "always" }).slice(1).split('-')[2].slice(0, 2)); + assert.sameValue(isoDay, referenceISODay, `${prefix}referenceISODay result:`); + }, + + /* + * assertZonedDateTimesEqual(actual, expected[, description]): + * + * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct + * type, equal according to their equals() methods, and additionally that + * their time zones and calendar internal slots are the same value. + */ + assertZonedDateTimesEqual(actual, expected, description = "") { + const prefix = description ? `${description}: ` : ""; + assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`); + assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`); + assert(actual.equals(expected), `${prefix}equals method`); + assert.sameValue(actual.timeZoneId, expected.timeZoneId, `${prefix}time zone same value:`); + assert.sameValue( + actual.calendarId, + expected.calendarId, + `${prefix}calendar same value:` + ); + }, + + /* + * assertUnreachable(description): + * + * Helper for asserting that code is not executed. + */ + assertUnreachable(description) { + let message = "This code should not be executed"; + if (description) { + message = `${message}: ${description}`; + } + throw new Test262Error(message); + }, + + /* + * checkPlainDateTimeConversionFastPath(func): + * + * ToTemporalDate and ToTemporalTime should both, if given a + * Temporal.PlainDateTime instance, convert to the desired type by reading the + * PlainDateTime's internal slots, rather than calling any getters. + * + * func(datetime) is the actual operation to test, that must + * internally call the abstract operation ToTemporalDate or ToTemporalTime. + * It is passed a Temporal.PlainDateTime instance. + */ + checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") { + const actual = []; + const expected = []; + + const calendar = "iso8601"; + const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar); + const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype); + ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => { + Object.defineProperty(datetime, property, { + get() { + actual.push(`get ${formatPropertyName(property)}`); + const value = prototypeDescrs[property].get.call(this); + return { + toString() { + actual.push(`toString ${formatPropertyName(property)}`); + return value.toString(); + }, + valueOf() { + actual.push(`valueOf ${formatPropertyName(property)}`); + return value; + }, + }; + }, + }); + }); + Object.defineProperty(datetime, "calendar", { + get() { + actual.push("get calendar"); + return calendar; + }, + }); + + func(datetime); + assert.compareArray(actual, expected, `${message}: property getters not called`); + }, + + /* + * Check that an options bag that accepts units written in the singular form, + * also accepts the same units written in the plural form. + * func(unit) should call the method with the appropriate options bag + * containing unit as a value. This will be called twice for each element of + * validSingularUnits, once with singular and once with plural, and the + * results of each pair should be the same (whether a Temporal object or a + * primitive value.) + */ + checkPluralUnitsAccepted(func, validSingularUnits) { + const plurals = { + year: 'years', + month: 'months', + week: 'weeks', + day: 'days', + hour: 'hours', + minute: 'minutes', + second: 'seconds', + millisecond: 'milliseconds', + microsecond: 'microseconds', + nanosecond: 'nanoseconds', + }; + + validSingularUnits.forEach((unit) => { + const singularValue = func(unit); + const pluralValue = func(plurals[unit]); + const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`; + if (singularValue instanceof Temporal.Duration) { + TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc); + } else if (singularValue instanceof Temporal.Instant) { + TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc); + } else if (singularValue instanceof Temporal.PlainDateTime) { + TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc); + } else if (singularValue instanceof Temporal.PlainTime) { + TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc); + } else if (singularValue instanceof Temporal.ZonedDateTime) { + TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc); + } else { + assert.sameValue(pluralValue, singularValue); + } + }); + }, + + /* + * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc): + * + * Checks the type handling of the roundingIncrement option. + * checkFunc(roundingIncrement) is a function which takes the value of + * roundingIncrement to test, and calls the method under test with it, + * returning the result. assertTrueResultFunc(result, description) should + * assert that result is the expected result with roundingIncrement: true, and + * assertObjectResultFunc(result, description) should assert that result is + * the expected result with roundingIncrement being an object with a valueOf() + * method. + */ + checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) { + // null converts to 0, which is out of range + assert.throws(RangeError, () => checkFunc(null), "null"); + // Booleans convert to either 0 or 1, and 1 is allowed + const trueResult = checkFunc(true); + assertTrueResultFunc(trueResult, "true"); + assert.throws(RangeError, () => checkFunc(false), "false"); + // Symbols and BigInts cannot convert to numbers + assert.throws(TypeError, () => checkFunc(Symbol()), "symbol"); + assert.throws(TypeError, () => checkFunc(2n), "bigint"); + + // Objects prefer their valueOf() methods when converting to a number + assert.throws(RangeError, () => checkFunc({}), "plain object"); + + const expected = [ + "get roundingIncrement.valueOf", + "call roundingIncrement.valueOf", + ]; + const actual = []; + const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement"); + const objectResult = checkFunc(observer); + assertObjectResultFunc(objectResult, "object with valueOf"); + assert.compareArray(actual, expected, "order of operations"); + }, + + /* + * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc): + * + * Checks the type handling of a string option, of which there are several in + * Temporal. + * propertyName is the name of the option, and value is the value that + * assertFunc should expect it to have. + * checkFunc(value) is a function which takes the value of the option to test, + * and calls the method under test with it, returning the result. + * assertFunc(result, description) should assert that result is the expected + * result with the option value being an object with a toString() method + * which returns the given value. + */ + checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) { + // null converts to the string "null", which is an invalid string value + assert.throws(RangeError, () => checkFunc(null), "null"); + // Booleans convert to the strings "true" or "false", which are invalid + assert.throws(RangeError, () => checkFunc(true), "true"); + assert.throws(RangeError, () => checkFunc(false), "false"); + // Symbols cannot convert to strings + assert.throws(TypeError, () => checkFunc(Symbol()), "symbol"); + // Numbers convert to strings which are invalid + assert.throws(RangeError, () => checkFunc(2), "number"); + // BigInts convert to strings which are invalid + assert.throws(RangeError, () => checkFunc(2n), "bigint"); + + // Objects prefer their toString() methods when converting to a string + assert.throws(RangeError, () => checkFunc({}), "plain object"); + + const expected = [ + `get ${propertyName}.toString`, + `call ${propertyName}.toString`, + ]; + const actual = []; + const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName); + const result = checkFunc(observer); + assertFunc(result, "object with toString"); + assert.compareArray(actual, expected, "order of operations"); + }, + + /* + * checkSubclassingIgnored(construct, constructArgs, method, methodArgs, + * resultAssertions): + * + * Methods of Temporal classes that return a new instance of the same class, + * must not take the constructor of a subclass into account, nor the @@species + * property. This helper runs tests to ensure this. + * + * construct(...constructArgs) must yield a valid instance of the Temporal + * class. instance[method](...methodArgs) is the method call under test, which + * must also yield a valid instance of the same Temporal class, not a + * subclass. See below for the individual tests that this runs. + * resultAssertions() is a function that performs additional assertions on the + * instance returned by the method under test. + */ + checkSubclassingIgnored(...args) { + this.checkSubclassConstructorNotObject(...args); + this.checkSubclassConstructorUndefined(...args); + this.checkSubclassConstructorThrows(...args); + this.checkSubclassConstructorNotCalled(...args); + this.checkSubclassSpeciesInvalidResult(...args); + this.checkSubclassSpeciesNotAConstructor(...args); + this.checkSubclassSpeciesNull(...args); + this.checkSubclassSpeciesUndefined(...args); + this.checkSubclassSpeciesThrows(...args); + }, + + /* + * Checks that replacing the 'constructor' property of the instance with + * various primitive values does not affect the returned new instance. + */ + checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) { + function check(value, description) { + const instance = new construct(...constructArgs); + instance.constructor = value; + const result = instance[method](...methodArgs); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description); + resultAssertions(result); + } + + check(null, "null"); + check(true, "true"); + check("test", "string"); + check(Symbol(), "Symbol"); + check(7, "number"); + check(7n, "bigint"); + }, + + /* + * Checks that replacing the 'constructor' property of the subclass with + * undefined does not affect the returned new instance. + */ + checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) { + let called = 0; + + class MySubclass extends construct { + constructor() { + ++called; + super(...constructArgs); + } + } + + const instance = new MySubclass(); + assert.sameValue(called, 1); + + MySubclass.prototype.constructor = undefined; + + const result = instance[method](...methodArgs); + assert.sameValue(called, 1); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + }, + + /* + * Checks that making the 'constructor' property of the instance throw when + * called does not affect the returned new instance. + */ + checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) { + function CustomError() {} + const instance = new construct(...constructArgs); + Object.defineProperty(instance, "constructor", { + get() { + throw new CustomError(); + } + }); + const result = instance[method](...methodArgs); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + }, + + /* + * Checks that when subclassing, the subclass constructor is not called by + * the method under test. + */ + checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) { + let called = 0; + + class MySubclass extends construct { + constructor() { + ++called; + super(...constructArgs); + } + } + + const instance = new MySubclass(); + assert.sameValue(called, 1); + + const result = instance[method](...methodArgs); + assert.sameValue(called, 1); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + }, + + /* + * Check that the constructor's @@species property is ignored when it's a + * constructor that returns a non-object value. + */ + checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) { + function check(value, description) { + const instance = new construct(...constructArgs); + instance.constructor = { + [Symbol.species]: function() { + return value; + }, + }; + const result = instance[method](...methodArgs); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description); + resultAssertions(result); + } + + check(undefined, "undefined"); + check(null, "null"); + check(true, "true"); + check("test", "string"); + check(Symbol(), "Symbol"); + check(7, "number"); + check(7n, "bigint"); + check({}, "plain object"); + }, + + /* + * Check that the constructor's @@species property is ignored when it's not a + * constructor. + */ + checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) { + function check(value, description) { + const instance = new construct(...constructArgs); + instance.constructor = { + [Symbol.species]: value, + }; + const result = instance[method](...methodArgs); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description); + resultAssertions(result); + } + + check(true, "true"); + check("test", "string"); + check(Symbol(), "Symbol"); + check(7, "number"); + check(7n, "bigint"); + check({}, "plain object"); + }, + + /* + * Check that the constructor's @@species property is ignored when it's null. + */ + checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) { + let called = 0; + + class MySubclass extends construct { + constructor() { + ++called; + super(...constructArgs); + } + } + + const instance = new MySubclass(); + assert.sameValue(called, 1); + + MySubclass.prototype.constructor = { + [Symbol.species]: null, + }; + + const result = instance[method](...methodArgs); + assert.sameValue(called, 1); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + }, + + /* + * Check that the constructor's @@species property is ignored when it's + * undefined. + */ + checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) { + let called = 0; + + class MySubclass extends construct { + constructor() { + ++called; + super(...constructArgs); + } + } + + const instance = new MySubclass(); + assert.sameValue(called, 1); + + MySubclass.prototype.constructor = { + [Symbol.species]: undefined, + }; + + const result = instance[method](...methodArgs); + assert.sameValue(called, 1); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + }, + + /* + * Check that the constructor's @@species property is ignored when it throws, + * i.e. it is not called at all. + */ + checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) { + function CustomError() {} + + const instance = new construct(...constructArgs); + instance.constructor = { + get [Symbol.species]() { + throw new CustomError(); + }, + }; + + const result = instance[method](...methodArgs); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + }, + + /* + * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions): + * + * Static methods of Temporal classes that return a new instance of the class, + * must not use the this-value as a constructor. This helper runs tests to + * ensure this. + * + * construct[method](...methodArgs) is the static method call under test, and + * must yield a valid instance of the Temporal class, not a subclass. See + * below for the individual tests that this runs. + * resultAssertions() is a function that performs additional assertions on the + * instance returned by the method under test. + */ + checkSubclassingIgnoredStatic(...args) { + this.checkStaticInvalidReceiver(...args); + this.checkStaticReceiverNotCalled(...args); + this.checkThisValueNotCalled(...args); + }, + + /* + * Check that calling the static method with a receiver that's not callable, + * still calls the intrinsic constructor. + */ + checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) { + function check(value, description) { + const result = construct[method].apply(value, methodArgs); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + } + + check(undefined, "undefined"); + check(null, "null"); + check(true, "true"); + check("test", "string"); + check(Symbol(), "symbol"); + check(7, "number"); + check(7n, "bigint"); + check({}, "Non-callable object"); + }, + + /* + * Check that calling the static method with a receiver that returns a value + * that's not callable, still calls the intrinsic constructor. + */ + checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) { + function check(value, description) { + const receiver = function () { + return value; + }; + const result = construct[method].apply(receiver, methodArgs); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + } + + check(undefined, "undefined"); + check(null, "null"); + check(true, "true"); + check("test", "string"); + check(Symbol(), "symbol"); + check(7, "number"); + check(7n, "bigint"); + check({}, "Non-callable object"); + }, + + /* + * Check that the receiver isn't called. + */ + checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) { + let called = false; + + class MySubclass extends construct { + constructor(...args) { + called = true; + super(...args); + } + } + + const result = MySubclass[method](...methodArgs); + assert.sameValue(called, false); + assert.sameValue(Object.getPrototypeOf(result), construct.prototype); + resultAssertions(result); + }, + + /* + * Check that any calendar-carrying Temporal object has its [[Calendar]] + * internal slot read by ToTemporalCalendar, and does not fetch the calendar + * by calling getters. + */ + checkToTemporalCalendarFastPath(func) { + const plainDate = new Temporal.PlainDate(2000, 5, 2, "iso8601"); + const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, "iso8601"); + const plainMonthDay = new Temporal.PlainMonthDay(5, 2, "iso8601"); + const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, "iso8601"); + const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", "iso8601"); + + [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => { + const actual = []; + const expected = []; + + Object.defineProperty(temporalObject, "calendar", { + get() { + actual.push("get calendar"); + return calendar; + }, + }); + + func(temporalObject); + assert.compareArray(actual, expected, "calendar getter not called"); + }); + }, + + checkToTemporalInstantFastPath(func) { + const actual = []; + const expected = []; + + const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC"); + Object.defineProperty(datetime, 'toString', { + get() { + actual.push("get toString"); + return function (options) { + actual.push("call toString"); + return Temporal.ZonedDateTime.prototype.toString.call(this, options); + }; + }, + }); + + func(datetime); + assert.compareArray(actual, expected, "toString not called"); + }, + + checkToTemporalPlainDateTimeFastPath(func) { + const actual = []; + const expected = []; + + const date = new Temporal.PlainDate(2000, 5, 2, "iso8601"); + const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype); + ["year", "month", "monthCode", "day"].forEach((property) => { + Object.defineProperty(date, property, { + get() { + actual.push(`get ${formatPropertyName(property)}`); + const value = prototypeDescrs[property].get.call(this); + return TemporalHelpers.toPrimitiveObserver(actual, value, property); + }, + }); + }); + ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => { + Object.defineProperty(date, property, { + get() { + actual.push(`get ${formatPropertyName(property)}`); + return undefined; + }, + }); + }); + Object.defineProperty(date, "calendar", { + get() { + actual.push("get calendar"); + return "iso8601"; + }, + }); + + func(date); + assert.compareArray(actual, expected, "property getters not called"); + }, + + /* + * observeProperty(calls, object, propertyName, value): + * + * Defines an own property @object.@propertyName with value @value, that + * will log any calls to its accessors to the array @calls. + */ + observeProperty(calls, object, propertyName, value, objectName = "") { + Object.defineProperty(object, propertyName, { + get() { + calls.push(`get ${formatPropertyName(propertyName, objectName)}`); + return value; + }, + set(v) { + calls.push(`set ${formatPropertyName(propertyName, objectName)}`); + } + }); + }, + + /* + * observeMethod(calls, object, propertyName, value): + * + * Defines an own property @object.@propertyName with value @value, that + * will log any calls of @value to the array @calls. + */ + observeMethod(calls, object, propertyName, objectName = "") { + const method = object[propertyName]; + object[propertyName] = function () { + calls.push(`call ${formatPropertyName(propertyName, objectName)}`); + return method.apply(object, arguments); + }; + }, + + /* + * Used for substituteMethod to indicate default behavior instead of a + * substituted value + */ + SUBSTITUTE_SKIP: SKIP_SYMBOL, + + /* + * substituteMethod(object, propertyName, values): + * + * Defines an own property @object.@propertyName that will, for each + * subsequent call to the method previously defined as + * @object.@propertyName: + * - Call the method, if no more values remain + * - Call the method, if the value in @values for the corresponding call + * is SUBSTITUTE_SKIP + * - Otherwise, return the corresponding value in @value + */ + substituteMethod(object, propertyName, values) { + let calls = 0; + const method = object[propertyName]; + object[propertyName] = function () { + if (calls >= values.length) { + return method.apply(object, arguments); + } else if (values[calls] === SKIP_SYMBOL) { + calls++; + return method.apply(object, arguments); + } else { + return values[calls++]; + } + }; + }, + + /* + * propertyBagObserver(): + * Returns an object that behaves like the given propertyBag but tracks Get + * and Has operations on any of its properties, by appending messages to an + * array. If the value of a property in propertyBag is a primitive, the value + * of the returned object's property will additionally be a + * TemporalHelpers.toPrimitiveObserver that will track calls to its toString + * and valueOf methods in the same array. This is for the purpose of testing + * order of operations that are observable from user code. objectName is used + * in the log. + * If skipToPrimitive is given, it must be an array of property keys. Those + * properties will not have a TemporalHelpers.toPrimitiveObserver returned, + * and instead just be returned directly. + */ + propertyBagObserver(calls, propertyBag, objectName, skipToPrimitive) { + return new Proxy(propertyBag, { + ownKeys(target) { + calls.push(`ownKeys ${objectName}`); + return Reflect.ownKeys(target); + }, + getOwnPropertyDescriptor(target, key) { + calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`); + return Reflect.getOwnPropertyDescriptor(target, key); + }, + get(target, key, receiver) { + calls.push(`get ${formatPropertyName(key, objectName)}`); + const result = Reflect.get(target, key, receiver); + if (result === undefined) { + return undefined; + } + if ((result !== null && typeof result === "object") || typeof result === "function") { + return result; + } + if (skipToPrimitive && skipToPrimitive.indexOf(key) >= 0) { + return result; + } + return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`); + }, + has(target, key) { + calls.push(`has ${formatPropertyName(key, objectName)}`); + return Reflect.has(target, key); + }, + }); + }, + + /* + * Returns an object that will append logs of any Gets or Calls of its valueOf + * or toString properties to the array calls. Both valueOf and toString will + * return the actual primitiveValue. propertyName is used in the log. + */ + toPrimitiveObserver(calls, primitiveValue, propertyName) { + return { + get valueOf() { + calls.push(`get ${propertyName}.valueOf`); + return function () { + calls.push(`call ${propertyName}.valueOf`); + return primitiveValue; + }; + }, + get toString() { + calls.push(`get ${propertyName}.toString`); + return function () { + calls.push(`call ${propertyName}.toString`); + if (primitiveValue === undefined) return undefined; + return primitiveValue.toString(); + }; + }, + }; + }, + + /* + * An object containing further methods that return arrays of ISO strings, for + * testing parsers. + */ + ISO: { + /* + * PlainMonthDay strings that are not valid. + */ + plainMonthDayStringsInvalid() { + return [ + "11-18junk", + "11-18[u-ca=gregory]", + "11-18[u-ca=hebrew]", + "11-18[U-CA=iso8601]", + "11-18[u-CA=iso8601]", + "11-18[FOO=bar]", + "-999999-01-01[u-ca=gregory]", + "-999999-01-01[u-ca=chinese]", + "+999999-01-01[u-ca=gregory]", + "+999999-01-01[u-ca=chinese]", + ]; + }, + + /* + * PlainMonthDay strings that are valid and that should produce October 1st. + */ + plainMonthDayStringsValid() { + return [ + "10-01", + "1001", + "1965-10-01", + "1976-10-01T152330.1+00:00", + "19761001T15:23:30.1+00:00", + "1976-10-01T15:23:30.1+0000", + "1976-10-01T152330.1+0000", + "19761001T15:23:30.1+0000", + "19761001T152330.1+00:00", + "19761001T152330.1+0000", + "+001976-10-01T152330.1+00:00", + "+0019761001T15:23:30.1+00:00", + "+001976-10-01T15:23:30.1+0000", + "+001976-10-01T152330.1+0000", + "+0019761001T15:23:30.1+0000", + "+0019761001T152330.1+00:00", + "+0019761001T152330.1+0000", + "1976-10-01T15:23:00", + "1976-10-01T15:23", + "1976-10-01T15", + "1976-10-01", + "--10-01", + "--1001", + "-999999-10-01", + "-999999-10-01[u-ca=iso8601]", + "+999999-10-01", + "+999999-10-01[u-ca=iso8601]", + ]; + }, + + /* + * PlainTime strings that may be mistaken for PlainMonthDay or + * PlainYearMonth strings, and so require a time designator. + */ + plainTimeStringsAmbiguous() { + const ambiguousStrings = [ + "2021-12", // ambiguity between YYYY-MM and HHMM-UU + "2021-12[-12:00]", // ditto, TZ does not disambiguate + "1214", // ambiguity between MMDD and HHMM + "0229", // ditto, including MMDD that doesn't occur every year + "1130", // ditto, including DD that doesn't occur in every month + "12-14", // ambiguity between MM-DD and HH-UU + "12-14[-14:00]", // ditto, TZ does not disambiguate + "202112", // ambiguity between YYYYMM and HHMMSS + "202112[UTC]", // ditto, TZ does not disambiguate + ]; + // Adding a calendar annotation to one of these strings must not cause + // disambiguation in favour of time. + const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]'); + return ambiguousStrings.concat(stringsWithCalendar); + }, + + /* + * PlainTime strings that are of similar form to PlainMonthDay and + * PlainYearMonth strings, but are not ambiguous due to components that + * aren't valid as months or days. + */ + plainTimeStringsUnambiguous() { + return [ + "2021-13", // 13 is not a month + "202113", // ditto + "2021-13[-13:00]", // ditto + "202113[-13:00]", // ditto + "0000-00", // 0 is not a month + "000000", // ditto + "0000-00[UTC]", // ditto + "000000[UTC]", // ditto + "1314", // 13 is not a month + "13-14", // ditto + "1232", // 32 is not a day + "0230", // 30 is not a day in February + "0631", // 31 is not a day in June + "0000", // 0 is neither a month nor a day + "00-00", // ditto + ]; + }, + + /* + * PlainYearMonth-like strings that are not valid. + */ + plainYearMonthStringsInvalid() { + return [ + "2020-13", + "1976-11[u-ca=gregory]", + "1976-11[u-ca=hebrew]", + "1976-11[U-CA=iso8601]", + "1976-11[u-CA=iso8601]", + "1976-11[FOO=bar]", + "+999999-01", + "-999999-01", + ]; + }, + + /* + * PlainYearMonth-like strings that are valid and should produce November + * 1976 in the ISO 8601 calendar. + */ + plainYearMonthStringsValid() { + return [ + "1976-11", + "1976-11-10", + "1976-11-01T09:00:00+00:00", + "1976-11-01T00:00:00+05:00", + "197611", + "+00197611", + "1976-11-18T15:23:30.1-02:00", + "1976-11-18T152330.1+00:00", + "19761118T15:23:30.1+00:00", + "1976-11-18T15:23:30.1+0000", + "1976-11-18T152330.1+0000", + "19761118T15:23:30.1+0000", + "19761118T152330.1+00:00", + "19761118T152330.1+0000", + "+001976-11-18T152330.1+00:00", + "+0019761118T15:23:30.1+00:00", + "+001976-11-18T15:23:30.1+0000", + "+001976-11-18T152330.1+0000", + "+0019761118T15:23:30.1+0000", + "+0019761118T152330.1+00:00", + "+0019761118T152330.1+0000", + "1976-11-18T15:23", + "1976-11-18T15", + "1976-11-18", + ]; + }, + + /* + * PlainYearMonth-like strings that are valid and should produce November of + * the ISO year -9999. + */ + plainYearMonthStringsValidNegativeYear() { + return [ + "-009999-11", + ]; + }, + } +}; diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/until/era-boundary-gregory.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/until/era-boundary-gregory.js @@ -0,0 +1,80 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.until +description: Date difference works correctly across era boundaries +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "gregory"; +const options = { overflow: "reject" }; + +const bce5 = Temporal.PlainDateTime.from({ era: "bce", eraYear: 5, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }, options); +const bce2 = Temporal.PlainDateTime.from({ era: "bce", eraYear: 2, monthCode: "M12", day: 1, hour: 12, minute: 34, calendar }, options); +const bce1 = Temporal.PlainDateTime.from({ era: "bce", eraYear: 1, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }, options); +const ce1 = Temporal.PlainDateTime.from({ era: "ce", eraYear: 1, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }, options); +const ce2 = Temporal.PlainDateTime.from({ era: "ce", eraYear: 2, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options); +const ce5 = Temporal.PlainDateTime.from({ era: "ce", eraYear: 5, monthCode: "M06", day: 15, hour: 12, minute: 34, calendar }, options); + +const tests = [ + // From 5 BCE to 5 CE + [ + bce5, ce5, + [9, 0, 0, 0, "9y from 5 BCE to 5 CE (no year 0)"], + [0, 108, 0, 0, 0, "108mo from 5 BCE to 5 CE (no year 0)"], + ], + [ + ce5, bce5, + [-9, 0, 0, 0, "-9y backwards from 5 BCE to 5 CE (no year 0)"], + [0, -108, 0, 0, 0, "-108mo backwards from 5 BCE to 5 CE (no year 0)"], + ], + // CE-BCE boundary + [ + bce1, ce1, + [1, 0, 0, 0, "1y from 1 BCE to 1 CE"], + [0, 12, 0, 0, "12mo from 1 BCE to 1 CE"], + ], + [ + ce1, bce1, + [-1, 0, 0, 0, "-1y backwards from 1 BCE to 1 CE"], + [0, -12, 0, 0, "-12mo backwards from 1 BCE to 1 CE"], + ], + [ + bce2, ce2, + [2, 1, 0, 0, "2y 1mo from 2 BCE Dec to 2 CE Jan"], + [0, 25, 0, 0, "25mo from 2 BCE Dec to 2 CE Jan"], + ], + [ + ce2, bce2, + [-2, -1, 0, 0, "-2y -1mo backwards from 2 BCE Dec to 2 CE Jan"], + [0, -25, 0, 0, "-25mo backwards from 2 BCE Dec to 2 CE Jan"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.until(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.until(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.until(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.until(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.until(two); + const resultDaysISO = oneISO.until(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/until/era-boundary-japanese.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/until/era-boundary-japanese.js @@ -0,0 +1,172 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.until +description: Date difference works correctly across era boundaries +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "japanese"; +const options = { overflow: "reject" }; + +const bce1 = Temporal.PlainDateTime.from({ era: "bce", eraYear: 1, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options); +const ce1 = Temporal.PlainDateTime.from({ era: "ce", eraYear: 1, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options); +const ce1868 = Temporal.PlainDateTime.from({ era: "ce", eraYear: 1868, monthCode: "M10", day: 22, hour: 12, minute: 34, calendar }, options); +const meiji1 = Temporal.PlainDateTime.from({ era: "meiji", eraYear: 1, monthCode: "M10", day: 23, hour: 12, minute: 34, calendar}, options); +const meiji5 = Temporal.PlainDateTime.from({ era: "meiji", eraYear: 5, monthCode: "M01", day: 15, hour: 12, minute: 34, calendar }, options); +const meiji45 = Temporal.PlainDateTime.from({ era: "meiji", eraYear: 45, monthCode: "M05", day: 19, hour: 12, minute: 34, calendar }, options); +const taisho1 = Temporal.PlainDateTime.from({ era: "taisho", eraYear: 1, monthCode: "M08", day: 9, hour: 12, minute: 34, calendar }, options); +const taisho6 = Temporal.PlainDateTime.from({ era: "taisho", eraYear: 6, monthCode: "M03", day: 15, hour: 12, minute: 34, calendar }, options); +const taisho15 = Temporal.PlainDateTime.from({ era: "taisho", eraYear: 15, monthCode: "M05", day: 20, hour: 12, minute: 34, calendar }, options); +const showa1 = Temporal.PlainDateTime.from({ era: "showa", eraYear: 1, monthCode: "M12", day: 30, hour: 12, minute: 34, calendar }, options); +const showa55 = Temporal.PlainDateTime.from({ era: "showa", eraYear: 55, monthCode: "M03", day: 15, hour: 12, minute: 34, calendar }, options); +const showa64 = Temporal.PlainDateTime.from({ era: "showa", eraYear: 64, monthCode: "M01", day: 3, hour: 12, minute: 34, calendar }, options); +const heisei1 = Temporal.PlainDateTime.from({ era: "heisei", eraYear: 1, monthCode: "M02", day: 27, hour: 12, minute: 34, calendar }, options); +const heisei30 = Temporal.PlainDateTime.from({ era: "heisei", eraYear: 30, monthCode: "M03", day: 15, hour: 12, minute: 34, calendar }, options); +const heisei31 = Temporal.PlainDateTime.from({ era: "heisei", eraYear: 31, monthCode: "M04", day: 1, hour: 12, minute: 34, calendar }, options); +const reiwa1 = Temporal.PlainDateTime.from({ era: "reiwa", eraYear: 1, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options); +const reiwa2 = Temporal.PlainDateTime.from({ era: "reiwa", eraYear: 2, monthCode: "M03", day: 15, hour: 12, minute: 34, calendar }, options); + +const tests = [ + // From Heisei 30 (2018) to Reiwa 2 (2020) - crossing era boundary + [ + heisei30, reiwa2, + [2, 0, 0, 0, "2y from Heisei 30 March to Reiwa 2 March"], + [0, 24, 0, 0, "24mo from Heisei 30 March to Reiwa 2 March"], + ], + [ + reiwa2, heisei30, + [-2, 0, 0, 0, "-2y backwards from Heisei 30 March to Reiwa 2 March"], + [0, -24, 0, 0, "-24mo backwards from Heisei 30 March to Reiwa 2 March"], + + ], + // Within same year but different eras + [ + heisei31, reiwa1, + [0, 2, 0, 0, "2mo from Heisei 31 April to Reiwa 1 June"], + [0, 2, 0, 0, "2mo from Heisei 31 April to Reiwa 1 June"], + ], + [ + reiwa1, heisei31, + [0, -2, 0, 0, "-2mo backwards from Heisei 31 April to Reiwa 1 June"], + [0, -2, 0, 0, "-2mo backwards from Heisei 31 April to Reiwa 1 June"], + ], + // From Showa 55 (1980) to Heisei 30 (2018) - crossing era boundary + [ + showa55, heisei30, + [38, 0, 0, 0, "38y from Showa 55 March to Heisei 30 March"], + [0, 456, 0, 0, "456mo from Showa 55 March to Heisei 30 March"], + ], + [ + heisei30, showa55, + [-38, 0, 0, 0, "-38y backwards from Showa 55 March to Heisei 30 March"], + [0, -456, 0, 0, "-456mo backwards from Showa 55 March to Heisei 30 March"], + ], + // Within same year but different eras + [ + showa64, heisei1, + [0, 1, 0, 24, "1mo 24d from Showa 64 January 3 to Heisei 1 February 27"], + [0, 1, 0, 24, "1mo 24d from Showa 64 January 3 to Heisei 1 February 27"], + ], + [ + heisei1, showa64, + [0, -1, 0, -24, "-1mo -24d from Showa 64 January 3 to Heisei 1 February 27"], + [0, -1, 0, -24, "-1mo -24d from Showa 64 January 3 to Heisei 1 February 27"], + ], + // From Taisho 6 (1917) to Showa 55 (1980) - crossing era boundary + [ + taisho6, showa55, + [63, 0, 0, 0, "63y from Taisho 6 March to Showa 55 March"], + [0, 756, 0, 0, "756mo from Taisho 6 March to Showa 55 March"], + ], + [ + showa55, taisho6, + [-63, 0, 0, 0, "-63y backwards from Taisho 6 March to Showa 55 March"], + [0, -756, 0, 0, "-756mo backwards from Taisho 6 March to Showa 55 March"], + ], + // Within same year but different eras + [ + taisho15, showa1, + [0, 7, 0, 10, "7mo 10d from Taisho 15 July 20 to Showa 1 December 30"], + [0, 7, 0, 10, "7mo 10d from Taisho 15 July 20 to Showa 1 December 30"], + ], + [ + showa1, taisho15, + [0, -7, 0, -10, "-7mo -10d backwards from Taisho 15 July 20 to Showa 1 December 30"], + [0, -7, 0, -10, "-7mo -10d backwards from Taisho 15 July 20 to Showa 1 December 30"], + ], + // From Meiji 5 (1872) to Taisho 6 (1917) - crossing era boundary + // Note that contemporarily January 15 1872 would have been Meiji 4 in the + // pre-1873 lunisolar calendar, but the spec-mandated behaviour is proleptic + [ + meiji5, taisho6, + [45, 2, 0, 0, "45y 2mo from Meiji 5 January to Taisho 6 March"], + [0, 542, 0, 0, "542mo from Meiji 5 January to Taisho 6 March"], + ], + [ + taisho6, meiji5, + [-45, -2, 0, 0, "-45y -2mo backwards from Meiji 5 January to Taisho 6 March"], + [0, -542, 0, 0, "-542mo backwards from Meiji 5 January to Taisho 6 March"], + ], + // Within same year but different eras + [ + meiji45, taisho1, + [0, 2, 0, 21, "2mo 21d from Meiji 45 May 19 to Taisho 1 August 9"], + [0, 2, 0, 21, "2mo 21d from Meiji 45 May 19 to Taisho 1 August 9"], + ], + [ + taisho1, meiji45, + [0, -2, 0, -21, "-2mo -21d backwards from Meiji 45 May 19 to Taisho 1 August 9"], + [0, -2, 0, -21, "-2mo -21d backwards from Meiji 45 May 19 to Taisho 1 August 9"], + ], + // Last pre-Meiji day to first day of Meiji era + [ + ce1868, meiji1, + [0, 0, 0, 1, "from day before Meiji era to first day"], + [0, 0, 0, 1, "from day before Meiji era to first day"], + ], + [ + meiji1, ce1868, + [0, 0, 0, -1, "backwards from day before Meiji era to first day"], + [0, 0, 0, -1, "backwards from day before Meiji era to first day"], + ], + // CE-BCE boundary + [ + bce1, ce1, + [1, 0, 0, 0, "1y from 1 BCE to 1 CE"], + [0, 12, 0, 0, "12mo from 1 BCE to 1 CE"], + ], + [ + ce1, bce1, + [-1, 0, 0, 0, "-1y backwards from 1 BCE to 1 CE"], + [0, -12, 0, 0, "-12mo backwards from 1 BCE to 1 CE"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.until(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.until(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.until(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.until(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.until(two); + const resultDaysISO = oneISO.until(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/until/era-boundary-roc.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/until/era-boundary-roc.js @@ -0,0 +1,80 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.until +description: Date difference works correctly across era boundaries +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "roc"; +const options = { overflow: "reject" }; + +const broc5 = Temporal.PlainDateTime.from({ era: "broc", eraYear: 5, monthCode: "M03", day: 1, hour: 12, minute: 34, calendar }, options); +const broc3 = Temporal.PlainDateTime.from({ era: "broc", eraYear: 3, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options); +const broc1 = Temporal.PlainDateTime.from({ era: "broc", eraYear: 1, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options); +const roc1 = Temporal.PlainDateTime.from({ era: "roc", eraYear: 1, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options); +const roc5 = Temporal.PlainDateTime.from({ era: "roc", eraYear: 5, monthCode: "M03", day: 1, hour: 12, minute: 34, calendar }, options); +const roc10 = Temporal.PlainDateTime.from({ era: "roc", eraYear: 10, monthCode: "M01", day: 1, hour: 12, minute: 34, calendar }, options); + +const tests = [ + // From BROC 5 to ROC 5 + [ + broc5, roc5, + [9, 0, 0, 0, "9y from BROC 5 to ROC 5 (no year 0)"], + [0, 108, 0, 0, 0, "108mo from BROC 5 to ROC 5 (no year 0)"], + ], + [ + roc5, broc5, + [-9, 0, 0, 0, "-9y backwards from BROC 5 to ROC 5 (no year 0)"], + [0, -108, 0, 0, 0, "-108mo backwards from BROC 5 to ROC 5 (no year 0)"], + ], + // Era boundary + [ + broc1, roc1, + [1, 0, 0, 0, "1y from BROC 1 to ROC 1"], + [0, 12, 0, 0, "12mo from BROC 1 to ROC 1"], + ], + [ + roc1, broc1, + [-1, 0, 0, 0, "-1y backwards from BROC 1 to ROC 1"], + [0, -12, 0, 0, "-12mo backwards from BROC 1 to ROC 1"], + ], + [ + broc3, roc10, + [12, 0, 0, 0, "12y from BROC 3 to ROC 10"], + [0, 144, 0, 0, "144mo from BROC 3 to ROC 10"], + ], + [ + roc10, broc3, + [-12, 0, 0, 0, "-12y backwards from BROC 3 to ROC 10"], + [0, -144, 0, 0, "-144mo backwards from BROC 3 to ROC 10"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.until(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.until(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.until(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.until(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.until(two); + const resultDaysISO = oneISO.until(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/until/leap-months-chinese.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/until/leap-months-chinese.js @@ -0,0 +1,167 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Difference across leap months in chinese calendar +esid: sec-temporal.plaindatetime.prototype.until +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +// 2001 is a leap year with a M04L leap month. + +const calendar = "chinese"; +const options = { overflow: "reject" }; + +const common1Month4 = Temporal.PlainDateTime.from({ year: 2000, monthCode: "M04", day: 1, hour: 12, minute: 34, calendar }, options); +const common1Month5 = Temporal.PlainDateTime.from({ year: 2000, monthCode: "M05", day: 1, hour: 12, minute: 34, calendar }, options); +const common1Month6 = Temporal.PlainDateTime.from({ year: 2000, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options); +const leapMonth4 = Temporal.PlainDateTime.from({ year: 2001, monthCode: "M04", day: 1, hour: 12, minute: 34, calendar }, options); +const leapMonth4L = Temporal.PlainDateTime.from({ year: 2001, monthCode: "M04L", day: 1, hour: 12, minute: 34, calendar }, options); +const leapMonth5 = Temporal.PlainDateTime.from({ year: 2001, monthCode: "M05", day: 1, hour: 12, minute: 34, calendar }, options); +const common2Month4 = Temporal.PlainDateTime.from({ year: 2002, monthCode: "M04", day: 1, hour: 12, minute: 34, calendar }, options); +const common2Month5 = Temporal.PlainDateTime.from({ year: 2002, monthCode: "M05", day: 1, hour: 12, minute: 34, calendar }, options); + +// (receiver, argument, years test data, months test data) +// test data: expected years, months, weeks, days, description +// largestUnit years: make sure some cases where the answer is 12 months do not +// balance up to 1 year +// largestUnit months: similar to years, but make sure number of months in year +// is computed correctly +// For largestUnit of days and weeks, the results should be identical to what +// the ISO calendar gives for the corresponding ISO dates +const tests = [ + [ + common1Month4, leapMonth4, + [1, 0, 0, 0, "M04-M04 common-leap is 1y"], + [0, 12, 0, 0, "M04-M04 common-leap is 12mo"], + ], + [ + leapMonth4, common2Month4, + [1, 0, 0, 0, "M04-M04 leap-common is 1y"], + [0, 13, 0, 0, "M04-M04 leap-common is 13mo not 12mo"], + ], + [ + common1Month4, common2Month4, + [2, 0, 0, 0, "M04-M04 common-common is 2y"], + [0, 25, 0, 0, "M04-M04 common-common is 25mo not 24mo"], + ], + [ + common1Month5, leapMonth5, + [1, 0, 0, 0, "M05-M05 common-leap is 1y"], + [0, 13, 0, 0, "M05-M05 common-leap is 13mo not 12mo"], + ], + [ + leapMonth5, common2Month5, + [1, 0, 0, 0, "M05-M05 leap-common is 1y"], + [0, 12, 0, 0, "M05-M05 leap-common is 12mo"], + ], + [ + common1Month5, common2Month5, + [2, 0, 0, 0, "M05-M05 common-common is 2y"], + [0, 25, 0, 0, "M05-M05 common-common is 25mo not 24mo"], + ], + [ + common1Month4, leapMonth4L, + [1, 1, 0, 0, "M04-M04L is 1y 1mo"], + [0, 13, 0, 0, "M04-M04L is 13mo"], + ], + [ + leapMonth4L, common2Month4, + [0, 12, 0, 0, "M04L-M04 is 12mo not 1y"], + [0, 12, 0, 0, "M04L-M04 is 12mo"], + ], + [ + common1Month5, leapMonth4L, + [0, 12, 0, 0, "M05-M04L is 12mo not 1y"], + [0, 12, 0, 0, "M05-M04L is 12mo"], + ], + [ + leapMonth4L, common2Month5, + [1, 1, 0, 0, "M04L-M05 is 1y 1mo (exhibits calendar-specific constraining)"], + [0, 13, 0, 0, "M04L-M05 is 13mo"], + ], + [ + common1Month6, leapMonth5, + [0, 12, 0, 0, "M06-M05 common-leap is 12mo not 11mo"], + [0, 12, 0, 0, "M06-M05 common-leap is 12mo not 11mo"], + ], + + // Negative + [ + common2Month4, leapMonth4, + [-1, 0, 0, 0, "M04-M04 common-leap backwards is -1y"], + [0, -13, 0, 0, "M04-M04 common-leap backwards is -13mo not -12mo"], + ], + [ + leapMonth4, common1Month4, + [-1, 0, 0, 0, "M04-M04 leap-common backwards is -1y"], + [0, -12, 0, 0, "M04-M04 leap-common backwards is -12mo not -13mo"], + ], + [ + common2Month4, common1Month4, + [-2, 0, 0, 0, "M04-M04 common-common backwards is -2y"], + [0, -25, 0, 0, "M04-M04 common-common backwards is -25mo not -24mo"], + ], + [ + common2Month5, leapMonth5, + [-1, 0, 0, 0, "M05-M05 common-leap backwards is -1y"], + [0, -12, 0, 0, "M05-M05 common-leap backwards is -12mo not -13mo"], + ], + [ + leapMonth5, common1Month5, + [-1, 0, 0, 0, "M05-M05 leap-common backwards is -1y"], + [0, -13, 0, 0, "M05-M05 leap-common backwards is -13mo not -12mo"], + ], + [ + common2Month5, common1Month5, + [-2, 0, 0, 0, "M05-M05 common-common backwards is -2y"], + [0, -25, 0, 0, "M05-M05 common-common backwards is -25mo not -24mo"], + ], + [ + common2Month4, leapMonth4L, + [0, -12, 0, 0, "M04-M04L backwards is -12mo not -1y"], + [0, -12, 0, 0, "M04-M04L backwards is -12mo"], + ], + [ + leapMonth4L, common1Month4, + [-1, 0, 0, 0, "M04L-M04 backwards is -1y not -1y -1mo (exhibits calendar-specific constraining)"], + [0, -13, 0, 0, "M04L-M04 backwards is -13mo"], + ], + [ + common2Month5, leapMonth4L, + [-1, -1, 0, 0, "M05-M04L backwards is -1y -1mo"], + [0, -13, 0, 0, "M05-M04L backwards is -13mo"], + ], + [ + leapMonth4L, common1Month5, + [0, -12, 0, 0, "M04L-M05 backwards is -12mo not -1y"], + [0, -12, 0, 0, "M04L-M05 backwards is -12mo"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.until(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.until(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.until(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.until(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.until(two); + const resultDaysISO = oneISO.until(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/until/leap-months-dangi.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/until/leap-months-dangi.js @@ -0,0 +1,167 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Difference across leap months in dangi calendar +esid: sec-temporal.plaindatetime.prototype.until +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +// 2001 is a leap year with a M04L leap month. + +const calendar = "dangi"; +const options = { overflow: "reject" }; + +const common1Month4 = Temporal.PlainDateTime.from({ year: 2000, monthCode: "M04", day: 1, hour: 12, minute: 34, calendar }, options); +const common1Month5 = Temporal.PlainDateTime.from({ year: 2000, monthCode: "M05", day: 1, hour: 12, minute: 34, calendar }, options); +const common1Month6 = Temporal.PlainDateTime.from({ year: 2000, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options); +const leapMonth4 = Temporal.PlainDateTime.from({ year: 2001, monthCode: "M04", day: 1, hour: 12, minute: 34, calendar }, options); +const leapMonth4L = Temporal.PlainDateTime.from({ year: 2001, monthCode: "M04L", day: 1, hour: 12, minute: 34, calendar }, options); +const leapMonth5 = Temporal.PlainDateTime.from({ year: 2001, monthCode: "M05", day: 1, hour: 12, minute: 34, calendar }, options); +const common2Month4 = Temporal.PlainDateTime.from({ year: 2002, monthCode: "M04", day: 1, hour: 12, minute: 34, calendar }, options); +const common2Month5 = Temporal.PlainDateTime.from({ year: 2002, monthCode: "M05", day: 1, hour: 12, minute: 34, calendar }, options); + +// (receiver, argument, years test data, months test data) +// test data: expected years, months, weeks, days, description +// largestUnit years: make sure some cases where the answer is 12 months do not +// balance up to 1 year +// largestUnit months: similar to years, but make sure number of months in year +// is computed correctly +// For largestUnit of days and weeks, the results should be identical to what +// the ISO calendar gives for the corresponding ISO dates +const tests = [ + [ + common1Month4, leapMonth4, + [1, 0, 0, 0, "M04-M04 common-leap is 1y"], + [0, 12, 0, 0, "M04-M04 common-leap is 12mo"], + ], + [ + leapMonth4, common2Month4, + [1, 0, 0, 0, "M04-M04 leap-common is 1y"], + [0, 13, 0, 0, "M04-M04 leap-common is 13mo not 12mo"], + ], + [ + common1Month4, common2Month4, + [2, 0, 0, 0, "M04-M04 common-common is 2y"], + [0, 25, 0, 0, "M04-M04 common-common is 25mo not 24mo"], + ], + [ + common1Month5, leapMonth5, + [1, 0, 0, 0, "M05-M05 common-leap is 1y"], + [0, 13, 0, 0, "M05-M05 common-leap is 13mo not 12mo"], + ], + [ + leapMonth5, common2Month5, + [1, 0, 0, 0, "M05-M05 leap-common is 1y"], + [0, 12, 0, 0, "M05-M05 leap-common is 12mo"], + ], + [ + common1Month5, common2Month5, + [2, 0, 0, 0, "M05-M05 common-common is 2y"], + [0, 25, 0, 0, "M05-M05 common-common is 25mo not 24mo"], + ], + [ + common1Month4, leapMonth4L, + [1, 1, 0, 0, "M04-M04L is 1y 1mo"], + [0, 13, 0, 0, "M04-M04L is 13mo"], + ], + [ + leapMonth4L, common2Month4, + [0, 12, 0, 0, "M04L-M04 is 12mo not 1y"], + [0, 12, 0, 0, "M04L-M04 is 12mo"], + ], + [ + common1Month5, leapMonth4L, + [0, 12, 0, 0, "M05-M04L is 12mo not 1y"], + [0, 12, 0, 0, "M05-M04L is 12mo"], + ], + [ + leapMonth4L, common2Month5, + [1, 1, 0, 0, "M04L-M05 is 1y 1mo (exhibits calendar-specific constraining)"], + [0, 13, 0, 0, "M04L-M05 is 13mo"], + ], + [ + common1Month6, leapMonth5, + [0, 12, 0, 0, "M06-M05 common-leap is 12mo not 11mo"], + [0, 12, 0, 0, "M06-M05 common-leap is 12mo not 11mo"], + ], + + // Negative + [ + common2Month4, leapMonth4, + [-1, 0, 0, 0, "M04-M04 common-leap backwards is -1y"], + [0, -13, 0, 0, "M04-M04 common-leap backwards is -13mo not -12mo"], + ], + [ + leapMonth4, common1Month4, + [-1, 0, 0, 0, "M04-M04 leap-common backwards is -1y"], + [0, -12, 0, 0, "M04-M04 leap-common backwards is -12mo not -13mo"], + ], + [ + common2Month4, common1Month4, + [-2, 0, 0, 0, "M04-M04 common-common backwards is -2y"], + [0, -25, 0, 0, "M04-M04 common-common backwards is -25mo not -24mo"], + ], + [ + common2Month5, leapMonth5, + [-1, 0, 0, 0, "M05-M05 common-leap backwards is -1y"], + [0, -12, 0, 0, "M05-M05 common-leap backwards is -12mo not -13mo"], + ], + [ + leapMonth5, common1Month5, + [-1, 0, 0, 0, "M05-M05 leap-common backwards is -1y"], + [0, -13, 0, 0, "M05-M05 leap-common backwards is -13mo not -12mo"], + ], + [ + common2Month5, common1Month5, + [-2, 0, 0, 0, "M05-M05 common-common backwards is -2y"], + [0, -25, 0, 0, "M05-M05 common-common backwards is -25mo not -24mo"], + ], + [ + common2Month4, leapMonth4L, + [0, -12, 0, 0, "M04-M04L backwards is -12mo not -1y"], + [0, -12, 0, 0, "M04-M04L backwards is -12mo"], + ], + [ + leapMonth4L, common1Month4, + [-1, 0, 0, 0, "M04L-M04 backwards is -1y not -1y -1mo (exhibits calendar-specific constraining)"], + [0, -13, 0, 0, "M04L-M04 backwards is -13mo"], + ], + [ + common2Month5, leapMonth4L, + [-1, -1, 0, 0, "M05-M04L backwards is -1y -1mo"], + [0, -13, 0, 0, "M05-M04L backwards is -13mo"], + ], + [ + leapMonth4L, common1Month5, + [0, -12, 0, 0, "M04L-M05 backwards is -12mo not -1y"], + [0, -12, 0, 0, "M04L-M05 backwards is -12mo"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.until(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.until(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.until(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.until(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.until(two); + const resultDaysISO = oneISO.until(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/until/leap-months-hebrew.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/until/leap-months-hebrew.js @@ -0,0 +1,171 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Difference across leap months in hebrew calendar +esid: sec-temporal.plaindatetime.prototype.until +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +// 5784 is a leap year. +// M05 - Shevat +// M05L - Adar I +// M06 - Adar II +// M07 - Nisan + +const calendar = "hebrew"; +const options = { overflow: "reject" }; + +const common1Shevat = Temporal.PlainDateTime.from({ year: 5783, monthCode: "M05", day: 1, hour: 12, minute: 34, calendar }, options); +const common1Adar = Temporal.PlainDateTime.from({ year: 5783, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options); +const common1Nisan = Temporal.PlainDateTime.from({ year: 5783, monthCode: "M07", day: 1, hour: 12, minute: 34, calendar }, options); +const leapShevat = Temporal.PlainDateTime.from({ year: 5784, monthCode: "M05", day: 1, hour: 12, minute: 34, calendar }, options); +const leapAdarI = Temporal.PlainDateTime.from({ year: 5784, monthCode: "M05L", day: 1, hour: 12, minute: 34, calendar }, options); +const leapAdarII = Temporal.PlainDateTime.from({ year: 5784, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options); +const common2Shevat = Temporal.PlainDateTime.from({ year: 5785, monthCode: "M05", day: 1, hour: 12, minute: 34, calendar }, options); +const common2Adar = Temporal.PlainDateTime.from({ year: 5785, monthCode: "M06", day: 1, hour: 12, minute: 34, calendar }, options); + +// (receiver, argument, years test data, months test data) +// test data: expected years, months, weeks, days, description +// largestUnit years: make sure some cases where the answer is 12 months do not +// balance up to 1 year +// largestUnit months: similar to years, but make sure number of months in year +// is computed correctly +// For largestUnit of days and weeks, the results should be identical to what +// the ISO calendar gives for the corresponding ISO dates +const tests = [ + [ + common1Shevat, leapShevat, + [1, 0, 0, 0, "M05-M05 common-leap is 1y"], + [0, 12, 0, 0, "M05-M05 common-leap is 12mo"], + ], + [ + leapShevat, common2Shevat, + [1, 0, 0, 0, "M05-M05 leap-common is 1y"], + [0, 13, 0, 0, "M05-M05 leap-common is 13mo not 12mo"], + ], + [ + common1Shevat, common2Shevat, + [2, 0, 0, 0, "M05-M05 common-common is 2y"], + [0, 25, 0, 0, "M05-M05 common-common is 25mo not 24mo"], + ], + [ + common1Adar, leapAdarII, + [1, 0, 0, 0, "M06-M06 common-leap is 1y"], + [0, 13, 0, 0, "M06-M06 common-leap is 13mo not 12mo"], + ], + [ + leapAdarII, common2Adar, + [1, 0, 0, 0, "M06-M06 leap-common is 1y"], + [0, 12, 0, 0, "M06-M06 leap-common is 12mo"], + ], + [ + common1Adar, common2Adar, + [2, 0, 0, 0, "M06-M06 common-common is 2y"], + [0, 25, 0, 0, "M06-M06 common-common is 25mo not 24mo"], + ], + [ + common1Shevat, leapAdarI, + [1, 1, 0, 0, "M05-M05L is 1y 1mo"], + [0, 13, 0, 0, "M05-M05L is 13mo"], + ], + [ + leapAdarI, common2Shevat, + [0, 12, 0, 0, "M05L-M05 is 12mo not 1y"], + [0, 12, 0, 0, "M05L-M05 is 12mo"], + ], + [ + common1Adar, leapAdarI, + [0, 12, 0, 0, "M06-M05L is 12mo not 1y"], + [0, 12, 0, 0, "M06-M05L is 12mo"], + ], + [ + leapAdarI, common2Adar, + [1, 0, 0, 0, "M05L-M06 is 1y (exhibits calendar-specific constraining)"], + [0, 13, 0, 0, "M05L-M06 is 13mo"], + ], + [ + common1Nisan, leapAdarII, + [0, 12, 0, 0, "M07-M06 common-leap is 12mo not 11mo"], + [0, 12, 0, 0, "M07-M06 common-leap is 12mo not 11mo"], + ], + + // Negative + [ + common2Shevat, leapShevat, + [-1, 0, 0, 0, "M05-M05 common-leap backwards is -1y"], + [0, -13, 0, 0, "M05-M05 common-leap backwards is -13mo not -12mo"], + ], + [ + leapShevat, common1Shevat, + [-1, 0, 0, 0, "M05-M05 leap-common backwards is -1y"], + [0, -12, 0, 0, "M05-M05 leap-common backwards is -12mo not -13mo"], + ], + [ + common2Shevat, common1Shevat, + [-2, 0, 0, 0, "M05-M05 common-common backwards is -2y"], + [0, -25, 0, 0, "M05-M05 common-common backwards is -25mo not -24mo"], + ], + [ + common2Adar, leapAdarII, + [-1, 0, 0, 0, "M06-M06 common-leap backwards is -1y"], + [0, -12, 0, 0, "M06-M06 common-leap backwards is -12mo not -13mo"], + ], + [ + leapAdarII, common1Adar, + [-1, 0, 0, 0, "M06-M06 leap-common backwards is -1y"], + [0, -13, 0, 0, "M06-M06 leap-common backwards is -13mo not -12mo"], + ], + [ + common2Adar, common1Adar, + [-2, 0, 0, 0, "M06-M06 common-common backwards is -2y"], + [0, -25, 0, 0, "M06-M06 common-common backwards is -25mo not -24mo"], + ], + [ + common2Shevat, leapAdarI, + [0, -12, 0, 0, "M05-M05L backwards is -12mo not -1y"], + [0, -12, 0, 0, "M05-M05L backwards is -12mo"], + ], + [ + leapAdarI, common1Shevat, + [-1, -1, 0, 0, "M05L-M05 backwards is -1y -1mo (exhibits calendar-specific constraining)"], + [0, -13, 0, 0, "M05L-M05 backwards is -13mo"], + ], + [ + common2Adar, leapAdarI, + [-1, -1, 0, 0, "M06-M05L backwards is -1y -1mo"], + [0, -13, 0, 0, "M06-M05L backwards is -13mo"], + ], + [ + leapAdarI, common1Adar, + [0, -12, 0, 0, "M05L-M06 backwards is -12mo not -1y"], + [0, -12, 0, 0, "M05L-M06 backwards is -12mo"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.until(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.until(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.until(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.until(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.until(two); + const resultDaysISO = oneISO.until(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/from/remapping-era.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/from/remapping-era.js @@ -23,7 +23,7 @@ info: | calendars with regnal eras. includes: [temporalHelpers.js] -features: [Temporal] +features: [Temporal, Intl.Era-monthcode] ---*/ // Based on a test originally by André Bargull <andre.bargull@gmail.com> diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/basic-chinese.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/basic-chinese.js @@ -0,0 +1,64 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.add +description: Basic addition and subtraction in the chinese calendar +features: [Temporal, Intl.Era-monthcode] +includes: [temporalHelpers.js] +---*/ + +const calendar = "chinese"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const duration1 = new Temporal.Duration(0, 1); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2019, monthCode: "M11", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(duration1).toPlainDateTime(), + 2019, 12, "M12", 1, 12, 34, 0, 0, 0, 0, "add 1 month, with result in same year" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2019, monthCode: "M12", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(duration1).toPlainDateTime(), + 2020, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "add 1 month, with result in next year" +); + +// Weeks + +const months2weeks3 = new Temporal.Duration(0, /* months = */ 2, /* weeks = */ 3); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2021, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(months2weeks3).toPlainDateTime(), + 2021, 3, "M03", 22, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from non-leap day/month, ending in same year" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2021, monthCode: "M12", day: 29, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(months2weeks3).toPlainDateTime(), + 2022, 3, "M03", 21, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from end of year to next year" +); + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ 10); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2021, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(days10).toPlainDateTime(), + 2021, 1, "M01", 11, 12, 34, 0, 0, 0, 0, "add 10 days, ending in same month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2021, monthCode: "M01", day: 29, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(days10).toPlainDateTime(), + 2021, 2, "M02", 10, 12, 34, 0, 0, 0, 0, "add 10 days, ending in following month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2021, monthCode: "M12", day: 29, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(days10).toPlainDateTime(), + 2022, 1, "M01", 10, 12, 34, 0, 0, 0, 0, "add 10 days, ending in following year" +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/basic-dangi.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/basic-dangi.js @@ -0,0 +1,64 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.add +description: Basic addition and subtraction in the dangi calendar +features: [Temporal, Intl.Era-monthcode] +includes: [temporalHelpers.js] +---*/ + +const calendar = "dangi"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const duration1 = new Temporal.Duration(0, 1); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2019, monthCode: "M11", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(duration1).toPlainDateTime(), + 2019, 12, "M12", 1, 12, 34, 0, 0, 0, 0, "add 1 month, with result in same year" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2019, monthCode: "M12", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(duration1).toPlainDateTime(), + 2020, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "add 1 month, with result in next year" +); + +// Weeks + +const months2weeks3 = new Temporal.Duration(0, /* months = */ 2, /* weeks = */ 3); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2021, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(months2weeks3).toPlainDateTime(), + 2021, 3, "M03", 22, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from non-leap day/month, ending in same year" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2021, monthCode: "M12", day: 29, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(months2weeks3).toPlainDateTime(), + 2022, 3, "M03", 21, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from end of year to next year" +); + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ 10); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2021, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(days10).toPlainDateTime(), + 2021, 1, "M01", 11, 12, 34, 0, 0, 0, 0, "add 10 days, ending in same month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2021, monthCode: "M01", day: 29, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(days10).toPlainDateTime(), + 2021, 2, "M02", 10, 12, 34, 0, 0, 0, 0, "add 10 days, ending in following month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2021, monthCode: "M12", day: 29, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(days10).toPlainDateTime(), + 2022, 1, "M01", 10, 12, 34, 0, 0, 0, 0, "add 10 days, ending in following year" +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/basic-hebrew.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/basic-hebrew.js @@ -0,0 +1,30 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.add +description: Basic addition and subtraction in the hebrew calendar +features: [Temporal] +includes: [temporalHelpers.js] +---*/ + +const calendar = "hebrew"; +const options = { overflow: "reject" }; + +// Years + +// Months + +// Weeks + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ 10); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 5785, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(days10).toPlainDateTime(), + 5785, 1, "M01", 11, 12, 34, 0, 0, 0, 0, "adding 10 days", "am", 5785 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/basic-islamic-civil.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/basic-islamic-civil.js @@ -0,0 +1,84 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.add +description: Basic addition and subtraction in the islamic-civil calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-civil"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const date1 = Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(0, 8)).toPlainDateTime(), + 1445, 9, "M09", 1, 12, 34, 0, 0, 0, 0, "Adding 8 months to Muharram 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(0, 11)).toPlainDateTime(), + 1445, 12, "M12", 1, 12, 34, 0, 0, 0, 0, "Adding 11 months to Muharram 1445 lands in Dhu al-Hijjah", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(0, 12)).toPlainDateTime(), + 1446, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Adding 12 months to Muharram 1445 lands in Muharram 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }).add(new Temporal.Duration(0, 13)).toPlainDateTime(), + 1446, 7, "M07", 15, 12, 34, 0, 0, 0, 0, "Adding 13 months to Jumada II 1445 lands in Rajab 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M03", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(new Temporal.Duration(0, 6)).toPlainDateTime(), + 1445, 9, "M09", 15, 12, 34, 0, 0, 0, 0, "Adding 6 months to Rabi I 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1444, monthCode: "M10", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }).add(new Temporal.Duration(0, 5)).toPlainDateTime(), + 1445, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "Adding 5 months to Shawwal 1444 crosses to 1445", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1400, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }).add(new Temporal.Duration(0, 100)).toPlainDateTime(), + 1408, 5, "M05", 1, 12, 34, 0, 0, 0, 0, "Adding a large number of months", + "ah", 1408 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M09", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(new Temporal.Duration(0, -8)).toPlainDateTime(), + 1445, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Subtracting 8 months from Ramadan 1445 lands in Muharram", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(new Temporal.Duration(0, -12)).toPlainDateTime(), + 1444, 6, "M06", 1, 12, 34, 0, 0, 0, 0, "Subtracting 12 months from Jumada II 1445 lands in Jumada II 1444", + "ah", 1444 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M02", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(new Temporal.Duration(0, -5)).toPlainDateTime(), + 1444, 9, "M09", 15, 12, 34, 0, 0, 0, 0, "Subtracting 5 months from Safar 1445 crosses to Ramadan 1444", + "ah", 1444 +); + +// Weeks + +// Days + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/basic-islamic-tbla.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/basic-islamic-tbla.js @@ -0,0 +1,84 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.add +description: Basic addition and subtraction in the islamic-tbla calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-tbla"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const date1 = Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(0, 8)).toPlainDateTime(), + 1445, 9, "M09", 1, 12, 34, 0, 0, 0, 0, "Adding 8 months to Muharram 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(0, 11)).toPlainDateTime(), + 1445, 12, "M12", 1, 12, 34, 0, 0, 0, 0, "Adding 11 months to Muharram 1445 lands in Dhu al-Hijjah", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(0, 12)).toPlainDateTime(), + 1446, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Adding 12 months to Muharram 1445 lands in Muharram 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }).add(new Temporal.Duration(0, 13)).toPlainDateTime(), + 1446, 7, "M07", 15, 12, 34, 0, 0, 0, 0, "Adding 13 months to Jumada II 1445 lands in Rajab 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M03", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(new Temporal.Duration(0, 6)).toPlainDateTime(), + 1445, 9, "M09", 15, 12, 34, 0, 0, 0, 0, "Adding 6 months to Rabi I 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1444, monthCode: "M10", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }).add(new Temporal.Duration(0, 5)).toPlainDateTime(), + 1445, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "Adding 5 months to Shawwal 1444 crosses to 1445", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1400, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }).add(new Temporal.Duration(0, 100)).toPlainDateTime(), + 1408, 5, "M05", 1, 12, 34, 0, 0, 0, 0, "Adding a large number of months", + "ah", 1408 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M09", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(new Temporal.Duration(0, -8)).toPlainDateTime(), + 1445, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Subtracting 8 months from Ramadan 1445 lands in Muharram", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(new Temporal.Duration(0, -12)).toPlainDateTime(), + 1444, 6, "M06", 1, 12, 34, 0, 0, 0, 0, "Subtracting 12 months from Jumada II 1445 lands in Jumada II 1444", + "ah", 1444 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M02", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(new Temporal.Duration(0, -5)).toPlainDateTime(), + 1444, 9, "M09", 15, 12, 34, 0, 0, 0, 0, "Subtracting 5 months from Safar 1445 crosses to Ramadan 1444", + "ah", 1444 +); + +// Weeks + +// Days + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/basic-islamic-umalqura.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/basic-islamic-umalqura.js @@ -0,0 +1,84 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.add +description: Basic addition and subtraction in the islamic-umalqura calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-umalqura"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const date1 = Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(0, 8)).toPlainDateTime(), + 1445, 9, "M09", 1, 12, 34, 0, 0, 0, 0, "Adding 8 months to Muharram 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(0, 11)).toPlainDateTime(), + 1445, 12, "M12", 1, 12, 34, 0, 0, 0, 0, "Adding 11 months to Muharram 1445 lands in Dhu al-Hijjah", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(0, 12)).toPlainDateTime(), + 1446, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Adding 12 months to Muharram 1445 lands in Muharram 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }).add(new Temporal.Duration(0, 13)).toPlainDateTime(), + 1446, 7, "M07", 15, 12, 34, 0, 0, 0, 0, "Adding 13 months to Jumada II 1445 lands in Rajab 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M03", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(new Temporal.Duration(0, 6)).toPlainDateTime(), + 1445, 9, "M09", 15, 12, 34, 0, 0, 0, 0, "Adding 6 months to Rabi I 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1444, monthCode: "M10", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }).add(new Temporal.Duration(0, 5)).toPlainDateTime(), + 1445, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "Adding 5 months to Shawwal 1444 crosses to 1445", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1400, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }).add(new Temporal.Duration(0, 100)).toPlainDateTime(), + 1408, 5, "M05", 1, 12, 34, 0, 0, 0, 0, "Adding a large number of months", + "ah", 1408 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M09", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(new Temporal.Duration(0, -8)).toPlainDateTime(), + 1445, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Subtracting 8 months from Ramadan 1445 lands in Muharram", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(new Temporal.Duration(0, -12)).toPlainDateTime(), + 1444, 6, "M06", 1, 12, 34, 0, 0, 0, 0, "Subtracting 12 months from Jumada II 1445 lands in Jumada II 1444", + "ah", 1444 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M02", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(new Temporal.Duration(0, -5)).toPlainDateTime(), + 1444, 9, "M09", 15, 12, 34, 0, 0, 0, 0, "Subtracting 5 months from Safar 1445 crosses to Ramadan 1444", + "ah", 1444 +); + +// Weeks + +// Days + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/browser.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/browser.js diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/constrain-day-islamic-civil.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/constrain-day-islamic-civil.js @@ -0,0 +1,43 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.add +description: Constraining the day for 29/30-day months in islamic-civil calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-civil"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const months1 = new Temporal.Duration(0, 1); +const months1n = new Temporal.Duration(0, -1); + +const date1 = Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M01", day: 30, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.add(months1).toPlainDateTime(), + 1445, 2, "M02", 29, 12, 34, 0, 0, 0, 0, "Day is constrained when adding months to a 30-day month and landing in a 29-day month", + "ah", 1445 +); + +assert.throws(RangeError, function () { + date1.add(months1, options); +}, "Adding months to a 30-day month and landing in a 29-day month rejects"); + +TemporalHelpers.assertPlainDateTime( + date1.add(months1n).toPlainDateTime(), + 1444, 12, "M12", 29, 12, 34, 0, 0, 0, 0, "Day is constrained when subtracting months from a 30-day month and landing in a 29-day month", + "ah", 1444 +); + +assert.throws(RangeError, function () { + date1.add(months1n, options); +}, "Subtracting months from a 30-day month and landing in a 29-day month rejects"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/constrain-day-islamic-tbla.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/constrain-day-islamic-tbla.js @@ -0,0 +1,43 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.add +description: Constraining the day for 29/30-day months in islamic-tbla calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-tbla"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const months1 = new Temporal.Duration(0, 1); +const months1n = new Temporal.Duration(0, -1); + +const date1 = Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M01", day: 30, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.add(months1).toPlainDateTime(), + 1445, 2, "M02", 29, 12, 34, 0, 0, 0, 0, "Day is constrained when adding months to a 30-day month and landing in a 29-day month", + "ah", 1445 +); + +assert.throws(RangeError, function () { + date1.add(months1, options); +}, "Adding months to a 30-day month and landing in a 29-day month rejects"); + +TemporalHelpers.assertPlainDateTime( + date1.add(months1n).toPlainDateTime(), + 1444, 12, "M12", 29, 12, 34, 0, 0, 0, 0, "Day is constrained when subtracting months from a 30-day month and landing in a 29-day month", + "ah", 1444 +); + +assert.throws(RangeError, function () { + date1.add(months1n, options); +}, "Subtracting months from a 30-day month and landing in a 29-day month rejects"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/constrain-day-islamic-umalqura.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/constrain-day-islamic-umalqura.js @@ -0,0 +1,43 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.add +description: Constraining the day for 29/30-day months in islamic-umalqura calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-umalqura"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const months1 = new Temporal.Duration(0, /* months = */ 1); +const months1n = new Temporal.Duration(0, -1); + +const date1 = Temporal.ZonedDateTime.from({ year: 1447, monthCode: "M01", day: 30, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.add(months1).toPlainDateTime(), + 1447, 2, "M02", 29, 12, 34, 0, 0, 0, 0, "Day is constrained when adding months to a 30-day month and landing in a 29-day month", + "ah", 1447 +); + +assert.throws(RangeError, function () { + date1.add(months1, options); +}, "Adding months to a 30-day month and landing in a 29-day month rejects"); + +TemporalHelpers.assertPlainDateTime( + date1.add(months1n).toPlainDateTime(), + 1446, 12, "M12", 29, 12, 34, 0, 0, 0, 0, "Day is constrained when subtracting months from a 30-day month and landing in a 29-day month", + "ah", 1446 +); + +assert.throws(RangeError, function () { + date1.add(months1n, options); +}, "Subtracting months from a 30-day month and landing in a 29-day month rejects"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/era-boundary-ethiopic.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/era-boundary-ethiopic.js @@ -0,0 +1,51 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.add +description: Adding years works correctly across era boundaries in ethiopic calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const duration5 = new Temporal.Duration(5); +const duration5n = new Temporal.Duration(-5); +const calendar = "ethiopic"; +const options = { overflow: "reject" }; + +const date1 = Temporal.ZonedDateTime.from({ era: "aa", eraYear: 5500, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(1)).toPlainDateTime(), + 1, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Adding 1 year to last year of Amete Alem era lands in year 1 of incarnation era", + "am", 1 +); + +const date2 = Temporal.ZonedDateTime.from({ era: "am", eraYear: 2000, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date2.add(duration5).toPlainDateTime(), + 2005, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Adding 5 years within incarnation era", + "am", 2005 +); + +const date3 = Temporal.ZonedDateTime.from({ era: "aa", eraYear: 5450, monthCode: "M07", day: 12, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date3.add(duration5).toPlainDateTime(), + -45, 7, "M07", 12, 12, 34, 0, 0, 0, 0, "Adding 5 years within Amete Alem era", + "aa", 5455 +); + +TemporalHelpers.assertPlainDateTime( + date2.add(duration5n).toPlainDateTime(), + 1995, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Subtracting 5 years within incarnation era", + "am", 1995 +); + +const date4 = Temporal.ZonedDateTime.from({ era: "am", eraYear: 5, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date4.add(duration5n).toPlainDateTime(), + 0, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Subtracting 5 years from year 5 lands in last year of Amete Alem era, arithmetic year 0", + "aa", 5500 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/era-boundary-gregory.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/era-boundary-gregory.js @@ -0,0 +1,71 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.add +description: Adding years works correctly across era boundaries in gregory calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const duration1 = new Temporal.Duration(1); +const duration1n = new Temporal.Duration(-1); +const calendar = "gregory"; +const options = { overflow: "reject" }; + +const date1 = Temporal.ZonedDateTime.from({ era: "bce", eraYear: 2, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.add(duration1).toPlainDateTime(), + 0, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to 2 BCE lands in 1 BCE (counts backwards)", + "bce", 1 +); + +const date2 = Temporal.ZonedDateTime.from({ era: "bce", eraYear: 1, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date2.add(duration1).toPlainDateTime(), + 1, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to 1 BCE lands in 1 CE (no year zero)", + "ce", 1 +); + +const date3 = Temporal.ZonedDateTime.from({ era: "ce", eraYear: 1, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date3.add(duration1).toPlainDateTime(), + 2, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to 1 CE lands in 2 CE", + "ce", 2 +); + +const date4 = Temporal.ZonedDateTime.from({ era: "bce", eraYear: 5, monthCode: "M03", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date4.add(new Temporal.Duration(10)).toPlainDateTime(), + 6, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "Adding 10 years to 5 BCE lands in 6 CE (no year zero)", + "ce", 6 +); + +const date5 = Temporal.ZonedDateTime.from({ era: "ce", eraYear: 2, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date5.add(duration1n).toPlainDateTime(), + 1, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from 2 CE lands in 1 CE", + "ce", 1 +); + +TemporalHelpers.assertPlainDateTime( + date3.add(duration1n).toPlainDateTime(), + 0, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from 1 CE lands in 1 BCE", + "bce", 1 +); + +TemporalHelpers.assertPlainDateTime( + date2.add(duration1n).toPlainDateTime(), + -1, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from 1 BCE lands in 2 BCE", + "bce", 2 +); + +const date6 = Temporal.ZonedDateTime.from({ era: "ce", eraYear: 5, monthCode: "M03", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date6.add(new Temporal.Duration(-10)).toPlainDateTime(), + -5, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "Subtracting 10 years from 5 CE lands in 6 BCE", + "bce", 6 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/era-boundary-japanese.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/era-boundary-japanese.js @@ -0,0 +1,84 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.add +description: > + Adding years works correctly across era boundaries in calendars with eras +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +// Reiwa era started on May 1, 2019 (Reiwa 1 = 2019) +// Heisei era: 1989-2019 (Heisei 31 ended April 30, 2019) + +const duration1 = new Temporal.Duration(1); +const duration1n = new Temporal.Duration(-1); +const calendar = "japanese"; +const options = { overflow: "reject" }; + +const date1 = Temporal.ZonedDateTime.from({ era: "heisei", eraYear: 30, monthCode: "M03", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.add(duration1).toPlainDateTime(), + 2019, 3, "M03", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to Heisei 30 March (before May 1) lands in Heisei 31 March", + "heisei", 31 +); + +const date2 = Temporal.ZonedDateTime.from({ era: "heisei", eraYear: 31, monthCode: "M04", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date2.add(duration1).toPlainDateTime(), + 2020, 4, "M04", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to Heisei 31 April (before May 1) lands in Reiwa 2 April", + "reiwa", 2 +); + +const date3 = Temporal.ZonedDateTime.from({ era: "heisei", eraYear: 30, monthCode: "M06", day: 12, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date3.add(duration1).toPlainDateTime(), + 2019, 6, "M06", 12, 12, 34, 0, 0, 0, 0, "Adding 1 year to Heisei 30 June (after May 1) lands in Reiwa 1 June", + "reiwa", 1 +); + +const date4 = Temporal.ZonedDateTime.from({ era: "reiwa", eraYear: 1, monthCode: "M06", day: 10, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date4.add(duration1).toPlainDateTime(), + 2020, 6, "M06", 10, 12, 34, 0, 0, 0, 0, "Adding 1 year to Reiwa 1 June lands in Reiwa 2 June", + "reiwa", 2 +); + +const date5 = Temporal.ZonedDateTime.from({ era: "heisei", eraYear: 28, monthCode: "M07", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date5.add(new Temporal.Duration(3)).toPlainDateTime(), + 2019, 7, "M07", 1, 12, 34, 0, 0, 0, 0, "Multiple years across era boundary: Adding 3 years to Heisei 28 July lands in Reiwa 1 July", + "reiwa", 1 +); + +const date6 = Temporal.ZonedDateTime.from({ era: "reiwa", eraYear: 2, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date6.add(duration1n).toPlainDateTime(), + 2019, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from Reiwa 2 June lands in Reiwa 1 June", + "reiwa", 1 +); + +const date7 = Temporal.ZonedDateTime.from({ era: "reiwa", eraYear: 2, monthCode: "M03", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date7.add(duration1n).toPlainDateTime(), + 2019, 3, "M03", 15, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from Reiwa 2 March lands in Heisei 31 March", + "heisei", 31 +); + +const date8 = Temporal.ZonedDateTime.from({ era: "reiwa", eraYear: 1, monthCode: "M07", day: 10, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date8.add(duration1n).toPlainDateTime(), + 2018, 7, "M07", 10, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from Reiwa 1 July lands in Heisei 30 July", + "heisei", 30 +); + +const date9 = Temporal.ZonedDateTime.from({ era: "reiwa", eraYear: 4, monthCode: "M02", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date9.add(new Temporal.Duration(-5)).toPlainDateTime(), + 2017, 2, "M02", 1, 12, 34, 0, 0, 0, 0, "Subtracting 5 years from Reiwa 4 February lands in Heisei 29 February", + "heisei", 29 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/era-boundary-roc.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/era-boundary-roc.js @@ -0,0 +1,66 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.add +description: > + Adding years works correctly across era boundaries in calendars with eras +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const duration1 = new Temporal.Duration(1); +const duration1n = new Temporal.Duration(-1); +const calendar = "roc"; +const options = { overflow: "reject" }; + +const date1 = Temporal.ZonedDateTime.from({ era: "broc", eraYear: 2, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.add(duration1).toPlainDateTime(), + 0, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to 2 BROC lands in 1 BROC (counts backwards)", + "broc", 1 +); + +const date2 = Temporal.ZonedDateTime.from({ era: "broc", eraYear: 1, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date2.add(duration1).toPlainDateTime(), + 1, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to 1 BROC lands in 1 ROC (no year zero)", + "roc", 1 +); + +const date3 = Temporal.ZonedDateTime.from({ era: "roc", eraYear: 1, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date3.add(duration1).toPlainDateTime(), + 2, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to 1 ROC lands in 2 ROC", + "roc", 2 +); + +const date4 = Temporal.ZonedDateTime.from({ era: "broc", eraYear: 5, monthCode: "M03", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date4.add(new Temporal.Duration(10)).toPlainDateTime(), + 6, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "Adding 10 years to 5 BROC lands in 6 ROC (no year zero)", + "roc", 6 +); + +const date5 = Temporal.ZonedDateTime.from({ era: "roc", eraYear: 5, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date5.add(duration1n).toPlainDateTime(), + 4, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from ROC 5 lands in ROC 4", + "roc", 4 +); + +TemporalHelpers.assertPlainDateTime( + date3.add(duration1n).toPlainDateTime(), + 0, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from ROC 1 lands in BROC 1", + "broc", 1 +); + +const date6 = Temporal.ZonedDateTime.from({ era: "roc", eraYear: 10, monthCode: "M03", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date6.add(new Temporal.Duration(-15)).toPlainDateTime(), + -5, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "Subtracting 15 years from ROC 10 lands in BROC 6", + "broc", 6 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/leap-months-chinese.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/leap-months-chinese.js @@ -0,0 +1,129 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.add +description: Arithmetic around leap months in the chinese calendar +features: [Temporal, Intl.Era-monthcode] +includes: [temporalHelpers.js] +---*/ + +const calendar = "chinese"; +const options = { overflow: "reject" }; + +// Years + +const years1 = new Temporal.Duration(1); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2019, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(years1).toPlainDateTime(), + 2020, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "add 1 year from non-leap day" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1966, monthCode: "M03L", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(years1).toPlainDateTime(), + 1967, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "add 1 year from leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1938, monthCode: "M07L", day: 30, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(years1).toPlainDateTime(), + 1939, 7, "M07", 29, 12, 34, 0, 0, 0, 0, "add 1 year from leap day in leap month" +); + +// Months + +const months1 = new Temporal.Duration(0, 1); +const months1n = new Temporal.Duration(0, -1); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1947, monthCode: "M02L", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(months1).toPlainDateTime(), + 1947, 4, "M03", 1, 12, 34, 0, 0, 0, 0, "add 1 month, starting at start of leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1955, monthCode: "M03L", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(months1).toPlainDateTime(), + 1955, 5, "M04", 1, 12, 34, 0, 0, 0, 0, "add 1 month, starting at start of leap month with 30 days" +); + +const date1 = Temporal.ZonedDateTime.from({ year: 2020, monthCode: "M03", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.add(months1).toPlainDateTime(), + 2020, 4, "M04", 1, 12, 34, 0, 0, 0, 0, "adding 1 month to M03 in leap year lands in M04 (not M04L)" +); + +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(0, 2)).toPlainDateTime(), + 2020, 5, "M04L", 1, 12, 34, 0, 0, 0, 0, "adding 2 months to M03 in leap year lands in M04L (leap month)" +); + +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(0, 3)).toPlainDateTime(), + 2020, 6, "M05", 1, 12, 34, 0, 0, 0, 0, "adding 3 months to M03 in leap year lands in M05 (not M06)" +); + +const date2 = Temporal.ZonedDateTime.from({ year: 2020, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date2.add(months1n).toPlainDateTime(), + 2020, 6, "M05", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M06 in leap year lands in M05" +); + +TemporalHelpers.assertPlainDateTime( + date2.add(new Temporal.Duration(0, -2)).toPlainDateTime(), + 2020, 5, "M04L", 1, 12, 34, 0, 0, 0, 0, "Subtracting 2 months from M06 in leap year lands in M04L (leap month)" +); + +TemporalHelpers.assertPlainDateTime( + date2.add(new Temporal.Duration(0, -3)).toPlainDateTime(), + 2020, 4, "M04", 1, 12, 34, 0, 0, 0, 0, "Subtracting 3 months from M06 in leap year lands in M04 (not M03)" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2020, monthCode: "M05", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(months1n).toPlainDateTime(), + 2020, 5, "M04L", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M05 in leap year lands in M04L" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2020, monthCode: "M04L", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(months1n).toPlainDateTime(), + 2020, 4, "M04", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M04L in calendar lands in M04" +); + +// Weeks + +const months2weeks3 = new Temporal.Duration(0, /* months = */ 2, /* weeks = */ 3); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1947, monthCode: "M02L", day: 29, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(months2weeks3).toPlainDateTime(), + 1947, 6, "M05", 20, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from last day leap month without leap day" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1955, monthCode: "M03L", day: 30, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(months2weeks3).toPlainDateTime(), + 1955, 7, "M06", 21, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from leap day in leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1947, monthCode: "M01", day: 29, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(months2weeks3).toPlainDateTime(), + 1947, 4, "M03", 21, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from immediately before a leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1955, monthCode: "M06", day: 29, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(months2weeks3).toPlainDateTime(), + 1955, 10, "M09", 20, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from immediately before a leap month" +); + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ 10); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1955, monthCode: "M03L", day: 30, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(days10).toPlainDateTime(), + 1955, 5, "M04", 10, 12, 34, 0, 0, 0, 0, "add 10 days from leap day in leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1947, monthCode: "M02L", day: 29, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(days10).toPlainDateTime(), + 1947, 4, "M03", 10, 12, 34, 0, 0, 0, 0, "add 10 days from last day of leap month" +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/leap-months-dangi.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/leap-months-dangi.js @@ -0,0 +1,129 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.add +description: Arithmetic around leap months in the dangi calendar +features: [Temporal, Intl.Era-monthcode] +includes: [temporalHelpers.js] +---*/ + +const calendar = "dangi"; +const options = { overflow: "reject" }; + +// Years + +const years1 = new Temporal.Duration(1); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2019, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(years1).toPlainDateTime(), + 2020, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "add 1 year from non-leap day" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1966, monthCode: "M03L", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(years1).toPlainDateTime(), + 1967, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "add 1 year from leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1938, monthCode: "M07L", day: 30, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(years1).toPlainDateTime(), + 1939, 7, "M07", 29, 12, 34, 0, 0, 0, 0, "add 1 year from leap day in leap month" +); + +// Months + +const months1 = new Temporal.Duration(0, 1); +const months1n = new Temporal.Duration(0, -1); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1947, monthCode: "M02L", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(months1).toPlainDateTime(), + 1947, 4, "M03", 1, 12, 34, 0, 0, 0, 0, "add 1 month, starting at start of leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1955, monthCode: "M03L", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(months1).toPlainDateTime(), + 1955, 5, "M04", 1, 12, 34, 0, 0, 0, 0, "add 1 month, starting at start of leap month with 30 days" +); + +const date1 = Temporal.ZonedDateTime.from({ year: 2020, monthCode: "M03", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.add(months1).toPlainDateTime(), + 2020, 4, "M04", 1, 12, 34, 0, 0, 0, 0, "adding 1 month to M03 in leap year lands in M04 (not M04L)" +); + +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(0, 2)).toPlainDateTime(), + 2020, 5, "M04L", 1, 12, 34, 0, 0, 0, 0, "adding 2 months to M03 in leap year lands in M04L (leap month)" +); + +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(0, 3)).toPlainDateTime(), + 2020, 6, "M05", 1, 12, 34, 0, 0, 0, 0, "adding 3 months to M03 in leap year lands in M05 (not M06)" +); + +const date2 = Temporal.ZonedDateTime.from({ year: 2020, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date2.add(months1n).toPlainDateTime(), + 2020, 6, "M05", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M06 in leap year lands in M05" +); + +TemporalHelpers.assertPlainDateTime( + date2.add(new Temporal.Duration(0, -2)).toPlainDateTime(), + 2020, 5, "M04L", 1, 12, 34, 0, 0, 0, 0, "Subtracting 2 months from M06 in leap year lands in M04L (leap month)" +); + +TemporalHelpers.assertPlainDateTime( + date2.add(new Temporal.Duration(0, -3)).toPlainDateTime(), + 2020, 4, "M04", 1, 12, 34, 0, 0, 0, 0, "Subtracting 3 months from M06 in leap year lands in M04 (not M03)" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2020, monthCode: "M05", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(months1n).toPlainDateTime(), + 2020, 5, "M04L", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M05 in leap year lands in M04L" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2020, monthCode: "M04L", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(months1n).toPlainDateTime(), + 2020, 4, "M04", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M04L in calendar lands in M04" +); + +// Weeks + +const months2weeks3 = new Temporal.Duration(0, /* months = */ 2, /* weeks = */ 3); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1947, monthCode: "M02L", day: 29, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(months2weeks3).toPlainDateTime(), + 1947, 6, "M05", 20, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from last day leap month without leap day" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1955, monthCode: "M03L", day: 30, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(months2weeks3).toPlainDateTime(), + 1955, 7, "M06", 21, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from leap day in leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1947, monthCode: "M01", day: 29, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(months2weeks3).toPlainDateTime(), + 1947, 4, "M03", 21, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from immediately before a leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1955, monthCode: "M06", day: 29, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(months2weeks3).toPlainDateTime(), + 1955, 10, "M09", 20, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from immediately before a leap month" +); + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ 10); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1955, monthCode: "M03L", day: 30, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(days10).toPlainDateTime(), + 1955, 5, "M04", 10, 12, 34, 0, 0, 0, 0, "add 10 days from leap day in leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1947, monthCode: "M02L", day: 29, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(days10).toPlainDateTime(), + 1947, 4, "M03", 10, 12, 34, 0, 0, 0, 0, "add 10 days from last day of leap month" +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/leap-months-hebrew.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/leap-months-hebrew.js @@ -0,0 +1,104 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.add +description: Arithmetic around leap months in the hebrew calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "hebrew"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const months1 = new Temporal.Duration(0, 1); +const months1n = new Temporal.Duration(0, -1); +const months2 = new Temporal.Duration(0, 2); +const months2n = new Temporal.Duration(0, -2); + +const date1 = Temporal.ZonedDateTime.from({ year: 5784, monthCode: "M04", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.add(months1).toPlainDateTime(), + 5784, 5, "M05", 1, 12, 34, 0, 0, 0, 0, "Adding 1 month to M04 in leap year lands in M05 (Shevat)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + date1.add(months2).toPlainDateTime(), + 5784, 6, "M05L", 1, 12, 34, 0, 0, 0, 0, "Adding 2 months to M04 in leap year lands in M05L (Adar I)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + date1.add(new Temporal.Duration(0, 3)).toPlainDateTime(), + 5784, 7, "M06", 1, 12, 34, 0, 0, 0, 0, "Adding 3 months to M04 in leap year lands in M06 (Adar II)", + "am", 5784 +); + +const date2 = Temporal.ZonedDateTime.from({ year: 5784, monthCode: "M05L", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date2.add(months1).toPlainDateTime(), + 5784, 7, "M06", 1, 12, 34, 0, 0, 0, 0, "Adding 1 month to M05L (Adar I) lands in M06 (Adar II)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 5783, monthCode: "M04", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(months2).toPlainDateTime(), + 5783, 6, "M06", 1, 12, 34, 0, 0, 0, 0, "Adding 2 months to M04 in non-leap year lands in M06 (no M05L)", + "am", 5783 +); + +const date3 = Temporal.ZonedDateTime.from({ year: 5784, monthCode: "M07", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date3.add(months1n).toPlainDateTime(), + 5784, 7, "M06", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M07 in leap year lands in M06 (Adar II)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + date3.add(months2n).toPlainDateTime(), + 5784, 6, "M05L", 1, 12, 34, 0, 0, 0, 0, "Subtracting 2 months from M07 in leap year lands in M05L (Adar I)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + date3.add(new Temporal.Duration(0, -3)).toPlainDateTime(), + 5784, 5, "M05", 1, 12, 34, 0, 0, 0, 0, "Subtracting 3 months from M07 in leap year lands in M05 (Shevat)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 5784, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }).add(months1n).toPlainDateTime(), + 5784, 6, "M05L", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M06 (Adar II) in leap year lands in M05L (Adar I)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + date2.add(months1n).toPlainDateTime(), + 5784, 5, "M05", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M05L (Adar I) lands in M05 (Shevat)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 5783, monthCode: "M07", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }).add(months2n).toPlainDateTime(), + 5783, 5, "M05", 1, 12, 34, 0, 0, 0, 0, "Subtracting 2 months from M07 in non-leap year lands in M05 (no M05L)", + "am", 5783 +); + +// Weeks + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ 10); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 5784, monthCode: "M05L", day: 30, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).add(days10).toPlainDateTime(), + 5784, 7, "M06", 10, 12, 34, 0, 0, 0, 0, "add 10 days to leap day", "am", 5784 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/shell.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/add/shell.js diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/era-boundary-gregory.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/era-boundary-gregory.js @@ -0,0 +1,80 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.since +description: Date difference works correctly across era boundaries +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "gregory"; +const options = { overflow: "reject" }; + +const bce5 = Temporal.ZonedDateTime.from({ era: "bce", eraYear: 5, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const bce2 = Temporal.ZonedDateTime.from({ era: "bce", eraYear: 2, monthCode: "M12", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const bce1 = Temporal.ZonedDateTime.from({ era: "bce", eraYear: 1, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const ce1 = Temporal.ZonedDateTime.from({ era: "ce", eraYear: 1, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const ce2 = Temporal.ZonedDateTime.from({ era: "ce", eraYear: 2, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const ce5 = Temporal.ZonedDateTime.from({ era: "ce", eraYear: 5, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); + +const tests = [ + // From 5 BCE to 5 CE + [ + bce5, ce5, + [-9, 0, 0, 0, "-9y backwards from 5 BCE to 5 CE (no year 0)"], + [0, -108, 0, 0, 0, "-108mo backwards from 5 BCE to 5 CE (no year 0)"], + ], + [ + ce5, bce5, + [9, 0, 0, 0, "9y from 5 BCE to 5 CE (no year 0)"], + [0, 108, 0, 0, 0, "108mo from 5 BCE to 5 CE (no year 0)"], + ], + // CE-BCE boundary + [ + bce1, ce1, + [-1, 0, 0, 0, "-1y backwards from 1 BCE to 1 CE"], + [0, -12, 0, 0, "-12mo backwards from 1 BCE to 1 CE"], + ], + [ + ce1, bce1, + [1, 0, 0, 0, "1y from 1 BCE to 1 CE"], + [0, 12, 0, 0, "12mo from 1 BCE to 1 CE"], + ], + [ + bce2, ce2, + [-2, -1, 0, 0, "-2y -1mo backwards from 2 BCE Dec to 2 CE Jan"], + [0, -25, 0, 0, "-25mo backwards from 2 BCE Dec to 2 CE Jan"], + ], + [ + ce2, bce2, + [2, 1, 0, 0, "2y 1mo from 2 BCE Dec to 2 CE Jan"], + [0, 25, 0, 0, "25mo from 2 BCE Dec to 2 CE Jan"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.since(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.since(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.since(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.since(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.since(two); + const resultDaysISO = oneISO.since(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/era-boundary-japanese.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/era-boundary-japanese.js @@ -0,0 +1,172 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.since +description: Date difference works correctly across era boundaries +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "japanese"; +const options = { overflow: "reject" }; + +const bce1 = Temporal.ZonedDateTime.from({ era: "bce", eraYear: 1, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const ce1 = Temporal.ZonedDateTime.from({ era: "ce", eraYear: 1, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const ce1868 = Temporal.ZonedDateTime.from({ era: "ce", eraYear: 1868, monthCode: "M10", day: 22, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const meiji1 = Temporal.ZonedDateTime.from({ era: "meiji", eraYear: 1, monthCode: "M10", day: 23, hour: 12, minute: 34, timeZone: "UTC", calendar}, options); +const meiji5 = Temporal.ZonedDateTime.from({ era: "meiji", eraYear: 5, monthCode: "M01", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const meiji45 = Temporal.ZonedDateTime.from({ era: "meiji", eraYear: 45, monthCode: "M05", day: 19, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const taisho1 = Temporal.ZonedDateTime.from({ era: "taisho", eraYear: 1, monthCode: "M08", day: 9, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const taisho6 = Temporal.ZonedDateTime.from({ era: "taisho", eraYear: 6, monthCode: "M03", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const taisho15 = Temporal.ZonedDateTime.from({ era: "taisho", eraYear: 15, monthCode: "M05", day: 20, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const showa1 = Temporal.ZonedDateTime.from({ era: "showa", eraYear: 1, monthCode: "M12", day: 30, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const showa55 = Temporal.ZonedDateTime.from({ era: "showa", eraYear: 55, monthCode: "M03", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const showa64 = Temporal.ZonedDateTime.from({ era: "showa", eraYear: 64, monthCode: "M01", day: 3, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const heisei1 = Temporal.ZonedDateTime.from({ era: "heisei", eraYear: 1, monthCode: "M02", day: 27, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const heisei30 = Temporal.ZonedDateTime.from({ era: "heisei", eraYear: 30, monthCode: "M03", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const heisei31 = Temporal.ZonedDateTime.from({ era: "heisei", eraYear: 31, monthCode: "M04", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const reiwa1 = Temporal.ZonedDateTime.from({ era: "reiwa", eraYear: 1, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const reiwa2 = Temporal.ZonedDateTime.from({ era: "reiwa", eraYear: 2, monthCode: "M03", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); + +const tests = [ + // From Heisei 30 (2018) to Reiwa 2 (2020) - crossing era boundary + [ + heisei30, reiwa2, + [-2, 0, 0, 0, "-2y backwards from Heisei 30 March to Reiwa 2 March"], + [0, -24, 0, 0, "-24mo backwards from Heisei 30 March to Reiwa 2 March"], + ], + [ + reiwa2, heisei30, + [2, 0, 0, 0, "2y from Heisei 30 March to Reiwa 2 March"], + [0, 24, 0, 0, "24mo from Heisei 30 March to Reiwa 2 March"], + + ], + // Within same year but different eras + [ + heisei31, reiwa1, + [0, -2, 0, 0, "-2mo backwards from Heisei 31 April to Reiwa 1 June"], + [0, -2, 0, 0, "-2mo backwards from Heisei 31 April to Reiwa 1 June"], + ], + [ + reiwa1, heisei31, + [0, 2, 0, 0, "2mo from Heisei 31 April to Reiwa 1 June"], + [0, 2, 0, 0, "2mo from Heisei 31 April to Reiwa 1 June"], + ], + // From Showa 55 (1980) to Heisei 30 (2018) - crossing era boundary + [ + showa55, heisei30, + [-38, 0, 0, 0, "-38y backwards from Showa 55 March to Heisei 30 March"], + [0, -456, 0, 0, "-456mo backwards from Showa 55 March to Heisei 30 March"], + ], + [ + heisei30, showa55, + [38, 0, 0, 0, "38y from Showa 55 March to Heisei 30 March"], + [0, 456, 0, 0, "456mo from Showa 55 March to Heisei 30 March"], + ], + // Within same year but different eras + [ + showa64, heisei1, + [0, -1, 0, -24, "-1mo -24d from Showa 64 January 3 to Heisei 1 February 27"], + [0, -1, 0, -24, "-1mo -24d from Showa 64 January 3 to Heisei 1 February 27"], + ], + [ + heisei1, showa64, + [0, 1, 0, 24, "1mo 24d from Showa 64 January 3 to Heisei 1 February 27"], + [0, 1, 0, 24, "1mo 24d from Showa 64 January 3 to Heisei 1 February 27"], + ], + // From Taisho 6 (1917) to Showa 55 (1980) - crossing era boundary + [ + taisho6, showa55, + [-63, 0, 0, 0, "-63y backwards from Taisho 6 March to Showa 55 March"], + [0, -756, 0, 0, "-756mo backwards from Taisho 6 March to Showa 55 March"], + ], + [ + showa55, taisho6, + [63, 0, 0, 0, "63y from Taisho 6 March to Showa 55 March"], + [0, 756, 0, 0, "756mo from Taisho 6 March to Showa 55 March"], + ], + // Within same year but different eras + [ + taisho15, showa1, + [0, -7, 0, -10, "-7mo -10d backwards from Taisho 15 July 20 to Showa 1 December 30"], + [0, -7, 0, -10, "-7mo -10d backwards from Taisho 15 July 20 to Showa 1 December 30"], + ], + [ + showa1, taisho15, + [0, 7, 0, 10, "7mo 10d from Taisho 15 July 20 to Showa 1 December 30"], + [0, 7, 0, 10, "7mo 10d from Taisho 15 July 20 to Showa 1 December 30"], + ], + // From Meiji 5 (1872) to Taisho 6 (1917) - crossing era boundary + // Note that contemporarily January 15 1872 would have been Meiji 4 in the + // pre-1873 lunisolar calendar, but the spec-mandated behaviour is proleptic + [ + meiji5, taisho6, + [-45, -2, 0, 0, "-45y -2mo backwards from Meiji 5 January to Taisho 6 March"], + [0, -542, 0, 0, "-542mo backwards from Meiji 5 January to Taisho 6 March"], + ], + [ + taisho6, meiji5, + [45, 2, 0, 0, "45y 2mo from Meiji 5 January to Taisho 6 March"], + [0, 542, 0, 0, "542mo from Meiji 5 January to Taisho 6 March"], + ], + // Within same year but different eras + [ + meiji45, taisho1, + [0, -2, 0, -21, "-2mo -21d backwards from Meiji 45 May 19 to Taisho 1 August 9"], + [0, -2, 0, -21, "-2mo -21d backwards from Meiji 45 May 19 to Taisho 1 August 9"], + ], + [ + taisho1, meiji45, + [0, 2, 0, 21, "2mo 21d from Meiji 45 May 19 to Taisho 1 August 9"], + [0, 2, 0, 21, "2mo 21d from Meiji 45 May 19 to Taisho 1 August 9"], + ], + // Last pre-Meiji day to first day of Meiji era + [ + ce1868, meiji1, + [0, 0, 0, -1, "backwards from day before Meiji era to first day"], + [0, 0, 0, -1, "backwards from day before Meiji era to first day"], + ], + [ + meiji1, ce1868, + [0, 0, 0, 1, "from day before Meiji era to first day"], + [0, 0, 0, 1, "from day before Meiji era to first day"], + ], + // CE-BCE boundary + [ + bce1, ce1, + [-1, 0, 0, 0, "-1y backwards from 1 BCE to 1 CE"], + [0, -12, 0, 0, "-12mo backwards from 1 BCE to 1 CE"], + ], + [ + ce1, bce1, + [1, 0, 0, 0, "1y from 1 BCE to 1 CE"], + [0, 12, 0, 0, "12mo from 1 BCE to 1 CE"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.since(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.since(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.since(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.since(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.since(two); + const resultDaysISO = oneISO.since(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/era-boundary-roc.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/era-boundary-roc.js @@ -0,0 +1,80 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.since +description: Date difference works correctly across era boundaries +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "roc"; +const options = { overflow: "reject" }; + +const broc5 = Temporal.ZonedDateTime.from({ era: "broc", eraYear: 5, monthCode: "M03", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const broc3 = Temporal.ZonedDateTime.from({ era: "broc", eraYear: 3, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const broc1 = Temporal.ZonedDateTime.from({ era: "broc", eraYear: 1, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const roc1 = Temporal.ZonedDateTime.from({ era: "roc", eraYear: 1, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const roc5 = Temporal.ZonedDateTime.from({ era: "roc", eraYear: 5, monthCode: "M03", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const roc10 = Temporal.ZonedDateTime.from({ era: "roc", eraYear: 10, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); + +const tests = [ + // From BROC 5 to ROC 5 + [ + broc5, roc5, + [-9, 0, 0, 0, "-9y backwards from BROC 5 to ROC 5 (no year 0)"], + [0, -108, 0, 0, 0, "-108mo backwards from BROC 5 to ROC 5 (no year 0)"], + ], + [ + roc5, broc5, + [9, 0, 0, 0, "9y from BROC 5 to ROC 5 (no year 0)"], + [0, 108, 0, 0, 0, "108mo from BROC 5 to ROC 5 (no year 0)"], + ], + // Era boundary + [ + broc1, roc1, + [-1, 0, 0, 0, "-1y backwards from BROC 1 to ROC 1"], + [0, -12, 0, 0, "-12mo backwards from BROC 1 to ROC 1"], + ], + [ + roc1, broc1, + [1, 0, 0, 0, "1y from BROC 1 to ROC 1"], + [0, 12, 0, 0, "12mo from BROC 1 to ROC 1"], + ], + [ + broc3, roc10, + [-12, 0, 0, 0, "-12y backwards from BROC 3 to ROC 10"], + [0, -144, 0, 0, "-144mo backwards from BROC 3 to ROC 10"], + ], + [ + roc10, broc3, + [12, 0, 0, 0, "12y from BROC 3 to ROC 10"], + [0, 144, 0, 0, "144mo from BROC 3 to ROC 10"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.since(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.since(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.since(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.since(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.since(two); + const resultDaysISO = oneISO.since(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/leap-months-chinese.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/leap-months-chinese.js @@ -0,0 +1,167 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Difference across leap months in chinese calendar +esid: sec-temporal.zoneddatetime.prototype.since +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +// 2001 is a leap year with a M04L leap month. + +const calendar = "chinese"; +const options = { overflow: "reject" }; + +const common1Month4 = Temporal.ZonedDateTime.from({ year: 2000, monthCode: "M04", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const common1Month5 = Temporal.ZonedDateTime.from({ year: 2000, monthCode: "M05", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const common1Month6 = Temporal.ZonedDateTime.from({ year: 2000, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const leapMonth4 = Temporal.ZonedDateTime.from({ year: 2001, monthCode: "M04", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const leapMonth4L = Temporal.ZonedDateTime.from({ year: 2001, monthCode: "M04L", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const leapMonth5 = Temporal.ZonedDateTime.from({ year: 2001, monthCode: "M05", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const common2Month4 = Temporal.ZonedDateTime.from({ year: 2002, monthCode: "M04", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const common2Month5 = Temporal.ZonedDateTime.from({ year: 2002, monthCode: "M05", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); + +// (receiver, argument, years test data, months test data) +// test data: expected years, months, weeks, days, description +// largestUnit years: make sure some cases where the answer is 12 months do not +// balance up to 1 year +// largestUnit months: similar to years, but make sure number of months in year +// is computed correctly +// For largestUnit of days and weeks, the results should be identical to what +// the ISO calendar gives for the corresponding ISO dates +const tests = [ + [ + common1Month4, leapMonth4, + [-1, 0, 0, 0, "M04-M04 common-leap backwards is -1y"], + [0, -12, 0, 0, "M04-M04 common-leap backwards is -12mo"], + ], + [ + leapMonth4, common2Month4, + [-1, 0, 0, 0, "M04-M04 leap-common backwards is -1y"], + [0, -13, 0, 0, "M04-M04 leap-common backwards is -13mo not -12mo"], + ], + [ + common1Month4, common2Month4, + [-2, 0, 0, 0, "M04-M04 common-common backwards is -2y"], + [0, -25, 0, 0, "M04-M04 common-common backwards is -25mo not -24mo"], + ], + [ + common1Month5, leapMonth5, + [-1, 0, 0, 0, "M05-M05 common-leap backwards is -1y"], + [0, -13, 0, 0, "M05-M05 common-leap backwards is -13mo not -12mo"], + ], + [ + leapMonth5, common2Month5, + [-1, 0, 0, 0, "M05-M05 leap-common backwards is -1y"], + [0, -12, 0, 0, "M05-M05 leap-common backwards is -12mo"], + ], + [ + common1Month5, common2Month5, + [-2, 0, 0, 0, "M05-M05 common-common backwards is -2y"], + [0, -25, 0, 0, "M05-M05 common-common backwards is -25mo not -24mo"], + ], + [ + common1Month4, leapMonth4L, + [-1, -1, 0, 0, "M04-M04L backwards is -1y -1mo"], + [0, -13, 0, 0, "M04-M04L backwards is -13mo"], + ], + [ + leapMonth4L, common2Month4, + [0, -12, 0, 0, "M04L-M04 backwards is -12mo not -1y"], + [0, -12, 0, 0, "M04L-M04 backwards is -12mo"], + ], + [ + common1Month5, leapMonth4L, + [0, -12, 0, 0, "M05-M04L backwards is -12mo not -1y"], + [0, -12, 0, 0, "M05-M04L backwards is -12mo"], + ], + [ + leapMonth4L, common2Month5, + [-1, -1, 0, 0, "M04L-M05 backwards is -1y -1mo (exhibits calendar-specific constraining)"], + [0, -13, 0, 0, "M04L-M05 backwards is -13mo"], + ], + [ + common1Month6, leapMonth5, + [0, -12, 0, 0, "M06-M05 common-leap backwards is -12mo not -11mo"], + [0, -12, 0, 0, "M06-M05 common-leap backwards is -12mo not -11mo"], + ], + + // Negative + [ + common2Month4, leapMonth4, + [1, 0, 0, 0, "M04-M04 common-leap is 1y"], + [0, 13, 0, 0, "M04-M04 common-leap is 13mo not 12mo"], + ], + [ + leapMonth4, common1Month4, + [1, 0, 0, 0, "M04-M04 leap-common is 1y"], + [0, 12, 0, 0, "M04-M04 leap-common is 12mo not 13mo"], + ], + [ + common2Month4, common1Month4, + [2, 0, 0, 0, "M04-M04 common-common is 2y"], + [0, 25, 0, 0, "M04-M04 common-common is 25mo not 24mo"], + ], + [ + common2Month5, leapMonth5, + [1, 0, 0, 0, "M05-M05 common-leap is 1y"], + [0, 12, 0, 0, "M05-M05 common-leap is 12mo not 13mo"], + ], + [ + leapMonth5, common1Month5, + [1, 0, 0, 0, "M05-M05 leap-common is 1y"], + [0, 13, 0, 0, "M05-M05 leap-common is 13mo not 12mo"], + ], + [ + common2Month5, common1Month5, + [2, 0, 0, 0, "M05-M05 common-common is 2y"], + [0, 25, 0, 0, "M05-M05 common-common is 25mo not 24mo"], + ], + [ + common2Month4, leapMonth4L, + [0, 12, 0, 0, "M04-M04L is 12mo not 1y"], + [0, 12, 0, 0, "M04-M04L is 12mo"], + ], + [ + leapMonth4L, common1Month4, + [1, 0, 0, 0, "M04L-M04 is 1y not 1y 1mo (exhibits calendar-specific constraining)"], + [0, 13, 0, 0, "M04L-M04 is 13mo"], + ], + [ + common2Month5, leapMonth4L, + [1, 1, 0, 0, "M05-M04L is 1y 1mo"], + [0, 13, 0, 0, "M05-M04L is 13mo"], + ], + [ + leapMonth4L, common1Month5, + [0, 12, 0, 0, "M04L-M05 is 12mo not 1y"], + [0, 12, 0, 0, "M04L-M05 is 12mo"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.since(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.since(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.since(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.since(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.since(two); + const resultDaysISO = oneISO.since(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/leap-months-dangi.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/leap-months-dangi.js @@ -0,0 +1,167 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Difference across leap months in dangi calendar +esid: sec-temporal.zoneddatetime.prototype.since +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +// 2001 is a leap year with a M04L leap month. + +const calendar = "dangi"; +const options = { overflow: "reject" }; + +const common1Month4 = Temporal.ZonedDateTime.from({ year: 2000, monthCode: "M04", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const common1Month5 = Temporal.ZonedDateTime.from({ year: 2000, monthCode: "M05", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const common1Month6 = Temporal.ZonedDateTime.from({ year: 2000, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const leapMonth4 = Temporal.ZonedDateTime.from({ year: 2001, monthCode: "M04", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const leapMonth4L = Temporal.ZonedDateTime.from({ year: 2001, monthCode: "M04L", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const leapMonth5 = Temporal.ZonedDateTime.from({ year: 2001, monthCode: "M05", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const common2Month4 = Temporal.ZonedDateTime.from({ year: 2002, monthCode: "M04", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const common2Month5 = Temporal.ZonedDateTime.from({ year: 2002, monthCode: "M05", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); + +// (receiver, argument, years test data, months test data) +// test data: expected years, months, weeks, days, description +// largestUnit years: make sure some cases where the answer is 12 months do not +// balance up to 1 year +// largestUnit months: similar to years, but make sure number of months in year +// is computed correctly +// For largestUnit of days and weeks, the results should be identical to what +// the ISO calendar gives for the corresponding ISO dates +const tests = [ + [ + common1Month4, leapMonth4, + [-1, 0, 0, 0, "M04-M04 common-leap backwards is -1y"], + [0, -12, 0, 0, "M04-M04 common-leap backwards is -12mo"], + ], + [ + leapMonth4, common2Month4, + [-1, 0, 0, 0, "M04-M04 leap-common backwards is -1y"], + [0, -13, 0, 0, "M04-M04 leap-common backwards is -13mo not -12mo"], + ], + [ + common1Month4, common2Month4, + [-2, 0, 0, 0, "M04-M04 common-common backwards is -2y"], + [0, -25, 0, 0, "M04-M04 common-common backwards is -25mo not -24mo"], + ], + [ + common1Month5, leapMonth5, + [-1, 0, 0, 0, "M05-M05 common-leap backwards is -1y"], + [0, -13, 0, 0, "M05-M05 common-leap backwards is -13mo not -12mo"], + ], + [ + leapMonth5, common2Month5, + [-1, 0, 0, 0, "M05-M05 leap-common backwards is -1y"], + [0, -12, 0, 0, "M05-M05 leap-common backwards is -12mo"], + ], + [ + common1Month5, common2Month5, + [-2, 0, 0, 0, "M05-M05 common-common backwards is -2y"], + [0, -25, 0, 0, "M05-M05 common-common backwards is -25mo not -24mo"], + ], + [ + common1Month4, leapMonth4L, + [-1, -1, 0, 0, "M04-M04L backwards is -1y -1mo"], + [0, -13, 0, 0, "M04-M04L backwards is -13mo"], + ], + [ + leapMonth4L, common2Month4, + [0, -12, 0, 0, "M04L-M04 backwards is -12mo not -1y"], + [0, -12, 0, 0, "M04L-M04 backwards is -12mo"], + ], + [ + common1Month5, leapMonth4L, + [0, -12, 0, 0, "M05-M04L backwards is -12mo not -1y"], + [0, -12, 0, 0, "M05-M04L backwards is -12mo"], + ], + [ + leapMonth4L, common2Month5, + [-1, -1, 0, 0, "M04L-M05 backwards is -1y -1mo (exhibits calendar-specific constraining)"], + [0, -13, 0, 0, "M04L-M05 backwards is -13mo"], + ], + [ + common1Month6, leapMonth5, + [0, -12, 0, 0, "M06-M05 common-leap backwards is -12mo not -11mo"], + [0, -12, 0, 0, "M06-M05 common-leap backwards is -12mo not -11mo"], + ], + + // Negative + [ + common2Month4, leapMonth4, + [1, 0, 0, 0, "M04-M04 common-leap is 1y"], + [0, 13, 0, 0, "M04-M04 common-leap is 13mo not 12mo"], + ], + [ + leapMonth4, common1Month4, + [1, 0, 0, 0, "M04-M04 leap-common is 1y"], + [0, 12, 0, 0, "M04-M04 leap-common is 12mo not 13mo"], + ], + [ + common2Month4, common1Month4, + [2, 0, 0, 0, "M04-M04 common-common is 2y"], + [0, 25, 0, 0, "M04-M04 common-common is 25mo not 24mo"], + ], + [ + common2Month5, leapMonth5, + [1, 0, 0, 0, "M05-M05 common-leap is 1y"], + [0, 12, 0, 0, "M05-M05 common-leap is 12mo not 13mo"], + ], + [ + leapMonth5, common1Month5, + [1, 0, 0, 0, "M05-M05 leap-common is 1y"], + [0, 13, 0, 0, "M05-M05 leap-common is 13mo not 12mo"], + ], + [ + common2Month5, common1Month5, + [2, 0, 0, 0, "M05-M05 common-common is 2y"], + [0, 25, 0, 0, "M05-M05 common-common is 25mo not 24mo"], + ], + [ + common2Month4, leapMonth4L, + [0, 12, 0, 0, "M04-M04L is 12mo not 1y"], + [0, 12, 0, 0, "M04-M04L is 12mo"], + ], + [ + leapMonth4L, common1Month4, + [1, 0, 0, 0, "M04L-M04 is 1y not 1y 1mo (exhibits calendar-specific constraining)"], + [0, 13, 0, 0, "M04L-M04 is 13mo"], + ], + [ + common2Month5, leapMonth4L, + [1, 1, 0, 0, "M05-M04L is 1y 1mo"], + [0, 13, 0, 0, "M05-M04L is 13mo"], + ], + [ + leapMonth4L, common1Month5, + [0, 12, 0, 0, "M04L-M05 is 12mo not 1y"], + [0, 12, 0, 0, "M04L-M05 is 12mo"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.since(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.since(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.since(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.since(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.since(two); + const resultDaysISO = oneISO.since(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/leap-months-hebrew.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/leap-months-hebrew.js @@ -0,0 +1,171 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Difference across leap months in hebrew calendar +esid: sec-temporal.zoneddatetime.prototype.since +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +// 5784 is a leap year. +// M05 - Shevat +// M05L - Adar I +// M06 - Adar II +// M07 - Nisan + +const calendar = "hebrew"; +const options = { overflow: "reject" }; + +const common1Shevat = Temporal.ZonedDateTime.from({ year: 5783, monthCode: "M05", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const common1Adar = Temporal.ZonedDateTime.from({ year: 5783, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const common1Nisan = Temporal.ZonedDateTime.from({ year: 5783, monthCode: "M07", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const leapShevat = Temporal.ZonedDateTime.from({ year: 5784, monthCode: "M05", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const leapAdarI = Temporal.ZonedDateTime.from({ year: 5784, monthCode: "M05L", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const leapAdarII = Temporal.ZonedDateTime.from({ year: 5784, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const common2Shevat = Temporal.ZonedDateTime.from({ year: 5785, monthCode: "M05", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const common2Adar = Temporal.ZonedDateTime.from({ year: 5785, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); + +// (receiver, argument, years test data, months test data) +// test data: expected years, months, weeks, days, description +// largestUnit years: make sure some cases where the answer is 12 months do not +// balance up to 1 year +// largestUnit months: similar to years, but make sure number of months in year +// is computed correctly +// For largestUnit of days and weeks, the results should be identical to what +// the ISO calendar gives for the corresponding ISO dates +const tests = [ + [ + common1Shevat, leapShevat, + [-1, 0, 0, 0, "M05-M05 common-leap backwards is -1y"], + [0, -12, 0, 0, "M05-M05 common-leap backwards is -12mo"], + ], + [ + leapShevat, common2Shevat, + [-1, 0, 0, 0, "M05-M05 leap-common backwards is -1y"], + [0, -13, 0, 0, "M05-M05 leap-common backwards is -13mo not -12mo"], + ], + [ + common1Shevat, common2Shevat, + [-2, 0, 0, 0, "M05-M05 common-common backwards is -2y"], + [0, -25, 0, 0, "M05-M05 common-common backwards is -25mo not -24mo"], + ], + [ + common1Adar, leapAdarII, + [-1, 0, 0, 0, "M06-M06 common-leap backwards is -1y"], + [0, -13, 0, 0, "M06-M06 common-leap backwards is -13mo not -12mo"], + ], + [ + leapAdarII, common2Adar, + [-1, 0, 0, 0, "M06-M06 leap-common backwards is -1y"], + [0, -12, 0, 0, "M06-M06 leap-common backwards is -12mo"], + ], + [ + common1Adar, common2Adar, + [-2, 0, 0, 0, "M06-M06 common-common backwards is -2y"], + [0, -25, 0, 0, "M06-M06 common-common backwards is -25mo not -24mo"], + ], + [ + common1Shevat, leapAdarI, + [-1, -1, 0, 0, "M05-M05L backwards is -1y -1mo"], + [0, -13, 0, 0, "M05-M05L backwards is -13mo"], + ], + [ + leapAdarI, common2Shevat, + [0, -12, 0, 0, "M05L-M05 backwards is -12mo not -1y"], + [0, -12, 0, 0, "M05L-M05 backwards is -12mo"], + ], + [ + common1Adar, leapAdarI, + [0, -12, 0, 0, "M06-M05L backwards is -12mo not -1y"], + [0, -12, 0, 0, "M06-M05L backwards is -12mo"], + ], + [ + leapAdarI, common2Adar, + [-1, 0, 0, 0, "M05L-M06 backwards is -1y (exhibits calendar-specific constraining)"], + [0, -13, 0, 0, "M05L-M06 backwards is -13mo"], + ], + [ + common1Nisan, leapAdarII, + [0, -12, 0, 0, "M07-M06 common-leap backwards is -12mo not -11mo"], + [0, -12, 0, 0, "M07-M06 common-leap backwards is -12mo not -11mo"], + ], + + // Negative + [ + common2Shevat, leapShevat, + [1, 0, 0, 0, "M05-M05 common-leap is 1y"], + [0, 13, 0, 0, "M05-M05 common-leap is 13mo not 12mo"], + ], + [ + leapShevat, common1Shevat, + [1, 0, 0, 0, "M05-M05 leap-common is 1y"], + [0, 12, 0, 0, "M05-M05 leap-common is 12mo not 13mo"], + ], + [ + common2Shevat, common1Shevat, + [2, 0, 0, 0, "M05-M05 common-common is 2y"], + [0, 25, 0, 0, "M05-M05 common-common is 25mo not 24mo"], + ], + [ + common2Adar, leapAdarII, + [1, 0, 0, 0, "M06-M06 common-leap is 1y"], + [0, 12, 0, 0, "M06-M06 common-leap is 12mo not 13mo"], + ], + [ + leapAdarII, common1Adar, + [1, 0, 0, 0, "M06-M06 leap-common is 1y"], + [0, 13, 0, 0, "M06-M06 leap-common is 13mo not 12mo"], + ], + [ + common2Adar, common1Adar, + [2, 0, 0, 0, "M06-M06 common-common is 2y"], + [0, 25, 0, 0, "M06-M06 common-common is 25mo not 24mo"], + ], + [ + common2Shevat, leapAdarI, + [0, 12, 0, 0, "M05-M05L is 12mo not 1y"], + [0, 12, 0, 0, "M05-M05L is 12mo"], + ], + [ + leapAdarI, common1Shevat, + [1, 1, 0, 0, "M05L-M05 is 1y 1mo (exhibits calendar-specific constraining)"], + [0, 13, 0, 0, "M05L-M05 is 13mo"], + ], + [ + common2Adar, leapAdarI, + [1, 1, 0, 0, "M06-M05L is 1y 1mo"], + [0, 13, 0, 0, "M06-M05L is 13mo"], + ], + [ + leapAdarI, common1Adar, + [0, 12, 0, 0, "M05L-M06 is 12mo not 1y"], + [0, 12, 0, 0, "M05L-M06 is 12mo"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.since(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.since(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.since(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.since(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.since(two); + const resultDaysISO = oneISO.since(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/basic-chinese.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/basic-chinese.js @@ -0,0 +1,64 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.subtract +description: Basic addition and subtraction in the chinese calendar +features: [Temporal, Intl.Era-monthcode] +includes: [temporalHelpers.js] +---*/ + +const calendar = "chinese"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const duration1 = new Temporal.Duration(0, -1); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2019, monthCode: "M11", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(duration1).toPlainDateTime(), + 2019, 12, "M12", 1, 12, 34, 0, 0, 0, 0, "add 1 month, with result in same year" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2019, monthCode: "M12", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(duration1).toPlainDateTime(), + 2020, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "add 1 month, with result in next year" +); + +// Weeks + +const months2weeks3 = new Temporal.Duration(0, /* months = */ -2, /* weeks = */ -3); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2021, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(months2weeks3).toPlainDateTime(), + 2021, 3, "M03", 22, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from non-leap day/month, ending in same year" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2021, monthCode: "M12", day: 29, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(months2weeks3).toPlainDateTime(), + 2022, 3, "M03", 21, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from end of year to next year" +); + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ -10); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2021, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(days10).toPlainDateTime(), + 2021, 1, "M01", 11, 12, 34, 0, 0, 0, 0, "add 10 days, ending in same month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2021, monthCode: "M01", day: 29, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(days10).toPlainDateTime(), + 2021, 2, "M02", 10, 12, 34, 0, 0, 0, 0, "add 10 days, ending in following month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2021, monthCode: "M12", day: 29, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(days10).toPlainDateTime(), + 2022, 1, "M01", 10, 12, 34, 0, 0, 0, 0, "add 10 days, ending in following year" +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/basic-dangi.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/basic-dangi.js @@ -0,0 +1,64 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.subtract +description: Basic addition and subtraction in the dangi calendar +features: [Temporal, Intl.Era-monthcode] +includes: [temporalHelpers.js] +---*/ + +const calendar = "dangi"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const duration1 = new Temporal.Duration(0, -1); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2019, monthCode: "M11", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(duration1).toPlainDateTime(), + 2019, 12, "M12", 1, 12, 34, 0, 0, 0, 0, "add 1 month, with result in same year" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2019, monthCode: "M12", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(duration1).toPlainDateTime(), + 2020, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "add 1 month, with result in next year" +); + +// Weeks + +const months2weeks3 = new Temporal.Duration(0, /* months = */ -2, /* weeks = */ -3); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2021, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(months2weeks3).toPlainDateTime(), + 2021, 3, "M03", 22, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from non-leap day/month, ending in same year" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2021, monthCode: "M12", day: 29, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(months2weeks3).toPlainDateTime(), + 2022, 3, "M03", 21, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from end of year to next year" +); + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ -10); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2021, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(days10).toPlainDateTime(), + 2021, 1, "M01", 11, 12, 34, 0, 0, 0, 0, "add 10 days, ending in same month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2021, monthCode: "M01", day: 29, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(days10).toPlainDateTime(), + 2021, 2, "M02", 10, 12, 34, 0, 0, 0, 0, "add 10 days, ending in following month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2021, monthCode: "M12", day: 29, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(days10).toPlainDateTime(), + 2022, 1, "M01", 10, 12, 34, 0, 0, 0, 0, "add 10 days, ending in following year" +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/basic-hebrew.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/basic-hebrew.js @@ -0,0 +1,30 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.subtract +description: Basic addition and subtraction in the hebrew calendar +features: [Temporal] +includes: [temporalHelpers.js] +---*/ + +const calendar = "hebrew"; +const options = { overflow: "reject" }; + +// Years + +// Months + +// Weeks + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ -10); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 5785, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(days10).toPlainDateTime(), + 5785, 1, "M01", 11, 12, 34, 0, 0, 0, 0, "adding 10 days", "am", 5785 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/basic-islamic-civil.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/basic-islamic-civil.js @@ -0,0 +1,84 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.subtract +description: Basic addition and subtraction in the islamic-civil calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-civil"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const date1 = Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(0, -8)).toPlainDateTime(), + 1445, 9, "M09", 1, 12, 34, 0, 0, 0, 0, "Adding 8 months to Muharram 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(0, -11)).toPlainDateTime(), + 1445, 12, "M12", 1, 12, 34, 0, 0, 0, 0, "Adding 11 months to Muharram 1445 lands in Dhu al-Hijjah", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(0, -12)).toPlainDateTime(), + 1446, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Adding 12 months to Muharram 1445 lands in Muharram 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }).subtract(new Temporal.Duration(0, -13)).toPlainDateTime(), + 1446, 7, "M07", 15, 12, 34, 0, 0, 0, 0, "Adding 13 months to Jumada II 1445 lands in Rajab 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M03", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(new Temporal.Duration(0, -6)).toPlainDateTime(), + 1445, 9, "M09", 15, 12, 34, 0, 0, 0, 0, "Adding 6 months to Rabi I 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1444, monthCode: "M10", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }).subtract(new Temporal.Duration(0, -5)).toPlainDateTime(), + 1445, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "Adding 5 months to Shawwal 1444 crosses to 1445", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1400, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }).subtract(new Temporal.Duration(0, -100)).toPlainDateTime(), + 1408, 5, "M05", 1, 12, 34, 0, 0, 0, 0, "Adding a large number of months", + "ah", 1408 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M09", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(new Temporal.Duration(0, 8)).toPlainDateTime(), + 1445, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Subtracting 8 months from Ramadan 1445 lands in Muharram", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(new Temporal.Duration(0, 12)).toPlainDateTime(), + 1444, 6, "M06", 1, 12, 34, 0, 0, 0, 0, "Subtracting 12 months from Jumada II 1445 lands in Jumada II 1444", + "ah", 1444 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M02", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(new Temporal.Duration(0, 5)).toPlainDateTime(), + 1444, 9, "M09", 15, 12, 34, 0, 0, 0, 0, "Subtracting 5 months from Safar 1445 crosses to Ramadan 1444", + "ah", 1444 +); + +// Weeks + +// Days + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/basic-islamic-tbla.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/basic-islamic-tbla.js @@ -0,0 +1,84 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.subtract +description: Basic addition and subtraction in the islamic-tbla calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-tbla"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const date1 = Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(0, -8)).toPlainDateTime(), + 1445, 9, "M09", 1, 12, 34, 0, 0, 0, 0, "Adding 8 months to Muharram 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(0, -11)).toPlainDateTime(), + 1445, 12, "M12", 1, 12, 34, 0, 0, 0, 0, "Adding 11 months to Muharram 1445 lands in Dhu al-Hijjah", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(0, -12)).toPlainDateTime(), + 1446, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Adding 12 months to Muharram 1445 lands in Muharram 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }).subtract(new Temporal.Duration(0, -13)).toPlainDateTime(), + 1446, 7, "M07", 15, 12, 34, 0, 0, 0, 0, "Adding 13 months to Jumada II 1445 lands in Rajab 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M03", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(new Temporal.Duration(0, -6)).toPlainDateTime(), + 1445, 9, "M09", 15, 12, 34, 0, 0, 0, 0, "Adding 6 months to Rabi I 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1444, monthCode: "M10", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }).subtract(new Temporal.Duration(0, -5)).toPlainDateTime(), + 1445, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "Adding 5 months to Shawwal 1444 crosses to 1445", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1400, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }).subtract(new Temporal.Duration(0, -100)).toPlainDateTime(), + 1408, 5, "M05", 1, 12, 34, 0, 0, 0, 0, "Adding a large number of months", + "ah", 1408 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M09", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(new Temporal.Duration(0, 8)).toPlainDateTime(), + 1445, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Subtracting 8 months from Ramadan 1445 lands in Muharram", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(new Temporal.Duration(0, 12)).toPlainDateTime(), + 1444, 6, "M06", 1, 12, 34, 0, 0, 0, 0, "Subtracting 12 months from Jumada II 1445 lands in Jumada II 1444", + "ah", 1444 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M02", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(new Temporal.Duration(0, 5)).toPlainDateTime(), + 1444, 9, "M09", 15, 12, 34, 0, 0, 0, 0, "Subtracting 5 months from Safar 1445 crosses to Ramadan 1444", + "ah", 1444 +); + +// Weeks + +// Days + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/basic-islamic-umalqura.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/basic-islamic-umalqura.js @@ -0,0 +1,84 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.subtract +description: Basic addition and subtraction in the islamic-umalqura calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-umalqura"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const date1 = Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(0, -8)).toPlainDateTime(), + 1445, 9, "M09", 1, 12, 34, 0, 0, 0, 0, "Adding 8 months to Muharram 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(0, -11)).toPlainDateTime(), + 1445, 12, "M12", 1, 12, 34, 0, 0, 0, 0, "Adding 11 months to Muharram 1445 lands in Dhu al-Hijjah", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(0, -12)).toPlainDateTime(), + 1446, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Adding 12 months to Muharram 1445 lands in Muharram 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }).subtract(new Temporal.Duration(0, -13)).toPlainDateTime(), + 1446, 7, "M07", 15, 12, 34, 0, 0, 0, 0, "Adding 13 months to Jumada II 1445 lands in Rajab 1446", + "ah", 1446 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M03", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(new Temporal.Duration(0, -6)).toPlainDateTime(), + 1445, 9, "M09", 15, 12, 34, 0, 0, 0, 0, "Adding 6 months to Rabi I 1445 lands in Ramadan", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1444, monthCode: "M10", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }).subtract(new Temporal.Duration(0, -5)).toPlainDateTime(), + 1445, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "Adding 5 months to Shawwal 1444 crosses to 1445", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1400, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }).subtract(new Temporal.Duration(0, -100)).toPlainDateTime(), + 1408, 5, "M05", 1, 12, 34, 0, 0, 0, 0, "Adding a large number of months", + "ah", 1408 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M09", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(new Temporal.Duration(0, 8)).toPlainDateTime(), + 1445, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Subtracting 8 months from Ramadan 1445 lands in Muharram", + "ah", 1445 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(new Temporal.Duration(0, 12)).toPlainDateTime(), + 1444, 6, "M06", 1, 12, 34, 0, 0, 0, 0, "Subtracting 12 months from Jumada II 1445 lands in Jumada II 1444", + "ah", 1444 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M02", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(new Temporal.Duration(0, 5)).toPlainDateTime(), + 1444, 9, "M09", 15, 12, 34, 0, 0, 0, 0, "Subtracting 5 months from Safar 1445 crosses to Ramadan 1444", + "ah", 1444 +); + +// Weeks + +// Days + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/browser.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/browser.js diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/constrain-day-islamic-civil.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/constrain-day-islamic-civil.js @@ -0,0 +1,43 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.subtract +description: Constraining the day for 29/30-day months in islamic-civil calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-civil"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const months1 = new Temporal.Duration(0, -1); +const months1n = new Temporal.Duration(0, 1); + +const date1 = Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M01", day: 30, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.subtract(months1).toPlainDateTime(), + 1445, 2, "M02", 29, 12, 34, 0, 0, 0, 0, "Day is constrained when adding months to a 30-day month and landing in a 29-day month", + "ah", 1445 +); + +assert.throws(RangeError, function () { + date1.subtract(months1, options); +}, "Adding months to a 30-day month and landing in a 29-day month rejects"); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(months1n).toPlainDateTime(), + 1444, 12, "M12", 29, 12, 34, 0, 0, 0, 0, "Day is constrained when subtracting months from a 30-day month and landing in a 29-day month", + "ah", 1444 +); + +assert.throws(RangeError, function () { + date1.subtract(months1n, options); +}, "Subtracting months from a 30-day month and landing in a 29-day month rejects"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/constrain-day-islamic-tbla.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/constrain-day-islamic-tbla.js @@ -0,0 +1,43 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.subtract +description: Constraining the day for 29/30-day months in islamic-tbla calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-tbla"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const months1 = new Temporal.Duration(0, -1); +const months1n = new Temporal.Duration(0, 1); + +const date1 = Temporal.ZonedDateTime.from({ year: 1445, monthCode: "M01", day: 30, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.subtract(months1).toPlainDateTime(), + 1445, 2, "M02", 29, 12, 34, 0, 0, 0, 0, "Day is constrained when adding months to a 30-day month and landing in a 29-day month", + "ah", 1445 +); + +assert.throws(RangeError, function () { + date1.subtract(months1, options); +}, "Adding months to a 30-day month and landing in a 29-day month rejects"); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(months1n).toPlainDateTime(), + 1444, 12, "M12", 29, 12, 34, 0, 0, 0, 0, "Day is constrained when subtracting months from a 30-day month and landing in a 29-day month", + "ah", 1444 +); + +assert.throws(RangeError, function () { + date1.subtract(months1n, options); +}, "Subtracting months from a 30-day month and landing in a 29-day month rejects"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/constrain-day-islamic-umalqura.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/constrain-day-islamic-umalqura.js @@ -0,0 +1,43 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.subtract +description: Constraining the day for 29/30-day months in islamic-umalqura calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "islamic-umalqura"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const months1 = new Temporal.Duration(0, /* months = */ -1); +const months1n = new Temporal.Duration(0, 1); + +const date1 = Temporal.ZonedDateTime.from({ year: 1447, monthCode: "M01", day: 30, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.subtract(months1).toPlainDateTime(), + 1447, 2, "M02", 29, 12, 34, 0, 0, 0, 0, "Day is constrained when adding months to a 30-day month and landing in a 29-day month", + "ah", 1447 +); + +assert.throws(RangeError, function () { + date1.subtract(months1, options); +}, "Adding months to a 30-day month and landing in a 29-day month rejects"); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(months1n).toPlainDateTime(), + 1446, 12, "M12", 29, 12, 34, 0, 0, 0, 0, "Day is constrained when subtracting months from a 30-day month and landing in a 29-day month", + "ah", 1446 +); + +assert.throws(RangeError, function () { + date1.subtract(months1n, options); +}, "Subtracting months from a 30-day month and landing in a 29-day month rejects"); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/era-boundary-ethiopic.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/era-boundary-ethiopic.js @@ -0,0 +1,51 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.subtract +description: Adding years works correctly across era boundaries in ethiopic calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const duration5 = new Temporal.Duration(-5); +const duration5n = new Temporal.Duration(5); +const calendar = "ethiopic"; +const options = { overflow: "reject" }; + +const date1 = Temporal.ZonedDateTime.from({ era: "aa", eraYear: 5500, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(-1)).toPlainDateTime(), + 1, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Adding 1 year to last year of Amete Alem era lands in year 1 of incarnation era", + "am", 1 +); + +const date2 = Temporal.ZonedDateTime.from({ era: "am", eraYear: 2000, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date2.subtract(duration5).toPlainDateTime(), + 2005, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Adding 5 years within incarnation era", + "am", 2005 +); + +const date3 = Temporal.ZonedDateTime.from({ era: "aa", eraYear: 5450, monthCode: "M07", day: 12, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date3.subtract(duration5).toPlainDateTime(), + -45, 7, "M07", 12, 12, 34, 0, 0, 0, 0, "Adding 5 years within Amete Alem era", + "aa", 5455 +); + +TemporalHelpers.assertPlainDateTime( + date2.subtract(duration5n).toPlainDateTime(), + 1995, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Subtracting 5 years within incarnation era", + "am", 1995 +); + +const date4 = Temporal.ZonedDateTime.from({ era: "am", eraYear: 5, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date4.subtract(duration5n).toPlainDateTime(), + 0, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "Subtracting 5 years from year 5 lands in last year of Amete Alem era, arithmetic year 0", + "aa", 5500 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/era-boundary-gregory.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/era-boundary-gregory.js @@ -0,0 +1,71 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.subtract +description: Adding years works correctly across era boundaries in gregory calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const duration1 = new Temporal.Duration(-1); +const duration1n = new Temporal.Duration(1); +const calendar = "gregory"; +const options = { overflow: "reject" }; + +const date1 = Temporal.ZonedDateTime.from({ era: "bce", eraYear: 2, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.subtract(duration1).toPlainDateTime(), + 0, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to 2 BCE lands in 1 BCE (counts backwards)", + "bce", 1 +); + +const date2 = Temporal.ZonedDateTime.from({ era: "bce", eraYear: 1, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date2.subtract(duration1).toPlainDateTime(), + 1, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to 1 BCE lands in 1 CE (no year zero)", + "ce", 1 +); + +const date3 = Temporal.ZonedDateTime.from({ era: "ce", eraYear: 1, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date3.subtract(duration1).toPlainDateTime(), + 2, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to 1 CE lands in 2 CE", + "ce", 2 +); + +const date4 = Temporal.ZonedDateTime.from({ era: "bce", eraYear: 5, monthCode: "M03", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date4.subtract(new Temporal.Duration(-10)).toPlainDateTime(), + 6, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "Adding 10 years to 5 BCE lands in 6 CE (no year zero)", + "ce", 6 +); + +const date5 = Temporal.ZonedDateTime.from({ era: "ce", eraYear: 2, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date5.subtract(duration1n).toPlainDateTime(), + 1, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from 2 CE lands in 1 CE", + "ce", 1 +); + +TemporalHelpers.assertPlainDateTime( + date3.subtract(duration1n).toPlainDateTime(), + 0, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from 1 CE lands in 1 BCE", + "bce", 1 +); + +TemporalHelpers.assertPlainDateTime( + date2.subtract(duration1n).toPlainDateTime(), + -1, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from 1 BCE lands in 2 BCE", + "bce", 2 +); + +const date6 = Temporal.ZonedDateTime.from({ era: "ce", eraYear: 5, monthCode: "M03", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date6.subtract(new Temporal.Duration(10)).toPlainDateTime(), + -5, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "Subtracting 10 years from 5 CE lands in 6 BCE", + "bce", 6 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/era-boundary-japanese.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/era-boundary-japanese.js @@ -0,0 +1,84 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.subtract +description: > + Adding years works correctly across era boundaries in calendars with eras +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +// Reiwa era started on May 1, 2019 (Reiwa 1 = 2019) +// Heisei era: 1989-2019 (Heisei 31 ended April 30, 2019) + +const duration1 = new Temporal.Duration(-1); +const duration1n = new Temporal.Duration(1); +const calendar = "japanese"; +const options = { overflow: "reject" }; + +const date1 = Temporal.ZonedDateTime.from({ era: "heisei", eraYear: 30, monthCode: "M03", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.subtract(duration1).toPlainDateTime(), + 2019, 3, "M03", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to Heisei 30 March (before May 1) lands in Heisei 31 March", + "heisei", 31 +); + +const date2 = Temporal.ZonedDateTime.from({ era: "heisei", eraYear: 31, monthCode: "M04", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date2.subtract(duration1).toPlainDateTime(), + 2020, 4, "M04", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to Heisei 31 April (before May 1) lands in Reiwa 2 April", + "reiwa", 2 +); + +const date3 = Temporal.ZonedDateTime.from({ era: "heisei", eraYear: 30, monthCode: "M06", day: 12, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date3.subtract(duration1).toPlainDateTime(), + 2019, 6, "M06", 12, 12, 34, 0, 0, 0, 0, "Adding 1 year to Heisei 30 June (after May 1) lands in Reiwa 1 June", + "reiwa", 1 +); + +const date4 = Temporal.ZonedDateTime.from({ era: "reiwa", eraYear: 1, monthCode: "M06", day: 10, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date4.subtract(duration1).toPlainDateTime(), + 2020, 6, "M06", 10, 12, 34, 0, 0, 0, 0, "Adding 1 year to Reiwa 1 June lands in Reiwa 2 June", + "reiwa", 2 +); + +const date5 = Temporal.ZonedDateTime.from({ era: "heisei", eraYear: 28, monthCode: "M07", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date5.subtract(new Temporal.Duration(-3)).toPlainDateTime(), + 2019, 7, "M07", 1, 12, 34, 0, 0, 0, 0, "Multiple years across era boundary: Adding 3 years to Heisei 28 July lands in Reiwa 1 July", + "reiwa", 1 +); + +const date6 = Temporal.ZonedDateTime.from({ era: "reiwa", eraYear: 2, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date6.subtract(duration1n).toPlainDateTime(), + 2019, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from Reiwa 2 June lands in Reiwa 1 June", + "reiwa", 1 +); + +const date7 = Temporal.ZonedDateTime.from({ era: "reiwa", eraYear: 2, monthCode: "M03", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date7.subtract(duration1n).toPlainDateTime(), + 2019, 3, "M03", 15, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from Reiwa 2 March lands in Heisei 31 March", + "heisei", 31 +); + +const date8 = Temporal.ZonedDateTime.from({ era: "reiwa", eraYear: 1, monthCode: "M07", day: 10, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date8.subtract(duration1n).toPlainDateTime(), + 2018, 7, "M07", 10, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from Reiwa 1 July lands in Heisei 30 July", + "heisei", 30 +); + +const date9 = Temporal.ZonedDateTime.from({ era: "reiwa", eraYear: 4, monthCode: "M02", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date9.subtract(new Temporal.Duration(5)).toPlainDateTime(), + 2017, 2, "M02", 1, 12, 34, 0, 0, 0, 0, "Subtracting 5 years from Reiwa 4 February lands in Heisei 29 February", + "heisei", 29 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/era-boundary-roc.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/era-boundary-roc.js @@ -0,0 +1,66 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.subtract +description: > + Adding years works correctly across era boundaries in calendars with eras +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const duration1 = new Temporal.Duration(-1); +const duration1n = new Temporal.Duration(1); +const calendar = "roc"; +const options = { overflow: "reject" }; + +const date1 = Temporal.ZonedDateTime.from({ era: "broc", eraYear: 2, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.subtract(duration1).toPlainDateTime(), + 0, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to 2 BROC lands in 1 BROC (counts backwards)", + "broc", 1 +); + +const date2 = Temporal.ZonedDateTime.from({ era: "broc", eraYear: 1, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date2.subtract(duration1).toPlainDateTime(), + 1, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to 1 BROC lands in 1 ROC (no year zero)", + "roc", 1 +); + +const date3 = Temporal.ZonedDateTime.from({ era: "roc", eraYear: 1, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date3.subtract(duration1).toPlainDateTime(), + 2, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Adding 1 year to 1 ROC lands in 2 ROC", + "roc", 2 +); + +const date4 = Temporal.ZonedDateTime.from({ era: "broc", eraYear: 5, monthCode: "M03", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date4.subtract(new Temporal.Duration(-10)).toPlainDateTime(), + 6, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "Adding 10 years to 5 BROC lands in 6 ROC (no year zero)", + "roc", 6 +); + +const date5 = Temporal.ZonedDateTime.from({ era: "roc", eraYear: 5, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date5.subtract(duration1n).toPlainDateTime(), + 4, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from ROC 5 lands in ROC 4", + "roc", 4 +); + +TemporalHelpers.assertPlainDateTime( + date3.subtract(duration1n).toPlainDateTime(), + 0, 6, "M06", 15, 12, 34, 0, 0, 0, 0, "Subtracting 1 year from ROC 1 lands in BROC 1", + "broc", 1 +); + +const date6 = Temporal.ZonedDateTime.from({ era: "roc", eraYear: 10, monthCode: "M03", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date6.subtract(new Temporal.Duration(15)).toPlainDateTime(), + -5, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "Subtracting 15 years from ROC 10 lands in BROC 6", + "broc", 6 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/leap-months-chinese.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/leap-months-chinese.js @@ -0,0 +1,129 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.subtract +description: Arithmetic around leap months in the chinese calendar +features: [Temporal, Intl.Era-monthcode] +includes: [temporalHelpers.js] +---*/ + +const calendar = "chinese"; +const options = { overflow: "reject" }; + +// Years + +const years1 = new Temporal.Duration(-1); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2019, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(years1).toPlainDateTime(), + 2020, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "add 1 year from non-leap day" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1966, monthCode: "M03L", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(years1).toPlainDateTime(), + 1967, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "add 1 year from leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1938, monthCode: "M07L", day: 30, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(years1).toPlainDateTime(), + 1939, 7, "M07", 29, 12, 34, 0, 0, 0, 0, "add 1 year from leap day in leap month" +); + +// Months + +const months1 = new Temporal.Duration(0, -1); +const months1n = new Temporal.Duration(0, 1); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1947, monthCode: "M02L", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(months1).toPlainDateTime(), + 1947, 4, "M03", 1, 12, 34, 0, 0, 0, 0, "add 1 month, starting at start of leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1955, monthCode: "M03L", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(months1).toPlainDateTime(), + 1955, 5, "M04", 1, 12, 34, 0, 0, 0, 0, "add 1 month, starting at start of leap month with 30 days" +); + +const date1 = Temporal.ZonedDateTime.from({ year: 2020, monthCode: "M03", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.subtract(months1).toPlainDateTime(), + 2020, 4, "M04", 1, 12, 34, 0, 0, 0, 0, "adding 1 month to M03 in leap year lands in M04 (not M04L)" +); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(0, -2)).toPlainDateTime(), + 2020, 5, "M04L", 1, 12, 34, 0, 0, 0, 0, "adding 2 months to M03 in leap year lands in M04L (leap month)" +); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(0, -3)).toPlainDateTime(), + 2020, 6, "M05", 1, 12, 34, 0, 0, 0, 0, "adding 3 months to M03 in leap year lands in M05 (not M06)" +); + +const date2 = Temporal.ZonedDateTime.from({ year: 2020, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date2.subtract(months1n).toPlainDateTime(), + 2020, 6, "M05", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M06 in leap year lands in M05" +); + +TemporalHelpers.assertPlainDateTime( + date2.subtract(new Temporal.Duration(0, 2)).toPlainDateTime(), + 2020, 5, "M04L", 1, 12, 34, 0, 0, 0, 0, "Subtracting 2 months from M06 in leap year lands in M04L (leap month)" +); + +TemporalHelpers.assertPlainDateTime( + date2.subtract(new Temporal.Duration(0, 3)).toPlainDateTime(), + 2020, 4, "M04", 1, 12, 34, 0, 0, 0, 0, "Subtracting 3 months from M06 in leap year lands in M04 (not M03)" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2020, monthCode: "M05", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(months1n).toPlainDateTime(), + 2020, 5, "M04L", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M05 in leap year lands in M04L" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2020, monthCode: "M04L", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(months1n).toPlainDateTime(), + 2020, 4, "M04", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M04L in calendar lands in M04" +); + +// Weeks + +const months2weeks3 = new Temporal.Duration(0, /* months = */ -2, /* weeks = */ -3); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1947, monthCode: "M02L", day: 29, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(months2weeks3).toPlainDateTime(), + 1947, 6, "M05", 20, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from last day leap month without leap day" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1955, monthCode: "M03L", day: 30, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(months2weeks3).toPlainDateTime(), + 1955, 7, "M06", 21, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from leap day in leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1947, monthCode: "M01", day: 29, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(months2weeks3).toPlainDateTime(), + 1947, 4, "M03", 21, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from immediately before a leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1955, monthCode: "M06", day: 29, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(months2weeks3).toPlainDateTime(), + 1955, 10, "M09", 20, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from immediately before a leap month" +); + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ -10); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1955, monthCode: "M03L", day: 30, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(days10).toPlainDateTime(), + 1955, 5, "M04", 10, 12, 34, 0, 0, 0, 0, "add 10 days from leap day in leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1947, monthCode: "M02L", day: 29, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(days10).toPlainDateTime(), + 1947, 4, "M03", 10, 12, 34, 0, 0, 0, 0, "add 10 days from last day of leap month" +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/leap-months-dangi.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/leap-months-dangi.js @@ -0,0 +1,129 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.subtract +description: Arithmetic around leap months in the dangi calendar +features: [Temporal, Intl.Era-monthcode] +includes: [temporalHelpers.js] +---*/ + +const calendar = "dangi"; +const options = { overflow: "reject" }; + +// Years + +const years1 = new Temporal.Duration(-1); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2019, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(years1).toPlainDateTime(), + 2020, 1, "M01", 1, 12, 34, 0, 0, 0, 0, "add 1 year from non-leap day" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1966, monthCode: "M03L", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(years1).toPlainDateTime(), + 1967, 3, "M03", 1, 12, 34, 0, 0, 0, 0, "add 1 year from leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1938, monthCode: "M07L", day: 30, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(years1).toPlainDateTime(), + 1939, 7, "M07", 29, 12, 34, 0, 0, 0, 0, "add 1 year from leap day in leap month" +); + +// Months + +const months1 = new Temporal.Duration(0, -1); +const months1n = new Temporal.Duration(0, 1); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1947, monthCode: "M02L", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(months1).toPlainDateTime(), + 1947, 4, "M03", 1, 12, 34, 0, 0, 0, 0, "add 1 month, starting at start of leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1955, monthCode: "M03L", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(months1).toPlainDateTime(), + 1955, 5, "M04", 1, 12, 34, 0, 0, 0, 0, "add 1 month, starting at start of leap month with 30 days" +); + +const date1 = Temporal.ZonedDateTime.from({ year: 2020, monthCode: "M03", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.subtract(months1).toPlainDateTime(), + 2020, 4, "M04", 1, 12, 34, 0, 0, 0, 0, "adding 1 month to M03 in leap year lands in M04 (not M04L)" +); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(0, -2)).toPlainDateTime(), + 2020, 5, "M04L", 1, 12, 34, 0, 0, 0, 0, "adding 2 months to M03 in leap year lands in M04L (leap month)" +); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(0, -3)).toPlainDateTime(), + 2020, 6, "M05", 1, 12, 34, 0, 0, 0, 0, "adding 3 months to M03 in leap year lands in M05 (not M06)" +); + +const date2 = Temporal.ZonedDateTime.from({ year: 2020, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date2.subtract(months1n).toPlainDateTime(), + 2020, 6, "M05", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M06 in leap year lands in M05" +); + +TemporalHelpers.assertPlainDateTime( + date2.subtract(new Temporal.Duration(0, 2)).toPlainDateTime(), + 2020, 5, "M04L", 1, 12, 34, 0, 0, 0, 0, "Subtracting 2 months from M06 in leap year lands in M04L (leap month)" +); + +TemporalHelpers.assertPlainDateTime( + date2.subtract(new Temporal.Duration(0, 3)).toPlainDateTime(), + 2020, 4, "M04", 1, 12, 34, 0, 0, 0, 0, "Subtracting 3 months from M06 in leap year lands in M04 (not M03)" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2020, monthCode: "M05", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(months1n).toPlainDateTime(), + 2020, 5, "M04L", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M05 in leap year lands in M04L" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 2020, monthCode: "M04L", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(months1n).toPlainDateTime(), + 2020, 4, "M04", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M04L in calendar lands in M04" +); + +// Weeks + +const months2weeks3 = new Temporal.Duration(0, /* months = */ -2, /* weeks = */ -3); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1947, monthCode: "M02L", day: 29, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(months2weeks3).toPlainDateTime(), + 1947, 6, "M05", 20, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from last day leap month without leap day" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1955, monthCode: "M03L", day: 30, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(months2weeks3).toPlainDateTime(), + 1955, 7, "M06", 21, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from leap day in leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1947, monthCode: "M01", day: 29, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(months2weeks3).toPlainDateTime(), + 1947, 4, "M03", 21, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from immediately before a leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1955, monthCode: "M06", day: 29, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(months2weeks3).toPlainDateTime(), + 1955, 10, "M09", 20, 12, 34, 0, 0, 0, 0, "add 2 months 3 weeks from immediately before a leap month" +); + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ -10); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1955, monthCode: "M03L", day: 30, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(days10).toPlainDateTime(), + 1955, 5, "M04", 10, 12, 34, 0, 0, 0, 0, "add 10 days from leap day in leap month" +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 1947, monthCode: "M02L", day: 29, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(days10).toPlainDateTime(), + 1947, 4, "M03", 10, 12, 34, 0, 0, 0, 0, "add 10 days from last day of leap month" +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/leap-months-hebrew.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/leap-months-hebrew.js @@ -0,0 +1,104 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.subtract +description: Arithmetic around leap months in the hebrew calendar +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "hebrew"; +const options = { overflow: "reject" }; + +// Years + +// Months + +const months1 = new Temporal.Duration(0, -1); +const months1n = new Temporal.Duration(0, 1); +const months2 = new Temporal.Duration(0, -2); +const months2n = new Temporal.Duration(0, 2); + +const date1 = Temporal.ZonedDateTime.from({ year: 5784, monthCode: "M04", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date1.subtract(months1).toPlainDateTime(), + 5784, 5, "M05", 1, 12, 34, 0, 0, 0, 0, "Adding 1 month to M04 in leap year lands in M05 (Shevat)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(months2).toPlainDateTime(), + 5784, 6, "M05L", 1, 12, 34, 0, 0, 0, 0, "Adding 2 months to M04 in leap year lands in M05L (Adar I)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + date1.subtract(new Temporal.Duration(0, -3)).toPlainDateTime(), + 5784, 7, "M06", 1, 12, 34, 0, 0, 0, 0, "Adding 3 months to M04 in leap year lands in M06 (Adar II)", + "am", 5784 +); + +const date2 = Temporal.ZonedDateTime.from({ year: 5784, monthCode: "M05L", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date2.subtract(months1).toPlainDateTime(), + 5784, 7, "M06", 1, 12, 34, 0, 0, 0, 0, "Adding 1 month to M05L (Adar I) lands in M06 (Adar II)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 5783, monthCode: "M04", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(months2).toPlainDateTime(), + 5783, 6, "M06", 1, 12, 34, 0, 0, 0, 0, "Adding 2 months to M04 in non-leap year lands in M06 (no M05L)", + "am", 5783 +); + +const date3 = Temporal.ZonedDateTime.from({ year: 5784, monthCode: "M07", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +TemporalHelpers.assertPlainDateTime( + date3.subtract(months1n).toPlainDateTime(), + 5784, 7, "M06", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M07 in leap year lands in M06 (Adar II)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + date3.subtract(months2n).toPlainDateTime(), + 5784, 6, "M05L", 1, 12, 34, 0, 0, 0, 0, "Subtracting 2 months from M07 in leap year lands in M05L (Adar I)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + date3.subtract(new Temporal.Duration(0, 3)).toPlainDateTime(), + 5784, 5, "M05", 1, 12, 34, 0, 0, 0, 0, "Subtracting 3 months from M07 in leap year lands in M05 (Shevat)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 5784, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }).subtract(months1n).toPlainDateTime(), + 5784, 6, "M05L", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M06 (Adar II) in leap year lands in M05L (Adar I)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + date2.subtract(months1n).toPlainDateTime(), + 5784, 5, "M05", 1, 12, 34, 0, 0, 0, 0, "Subtracting 1 month from M05L (Adar I) lands in M05 (Shevat)", + "am", 5784 +); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 5783, monthCode: "M07", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }).subtract(months2n).toPlainDateTime(), + 5783, 5, "M05", 1, 12, 34, 0, 0, 0, 0, "Subtracting 2 months from M07 in non-leap year lands in M05 (no M05L)", + "am", 5783 +); + +// Weeks + +// Days + +const days10 = new Temporal.Duration(0, 0, 0, /* days = */ -10); + +TemporalHelpers.assertPlainDateTime( + Temporal.ZonedDateTime.from({ year: 5784, monthCode: "M05L", day: 30, hour: 12, minute: 34, timeZone: "UTC", calendar }, options).subtract(days10).toPlainDateTime(), + 5784, 7, "M06", 10, 12, 34, 0, 0, 0, 0, "add 10 days to leap day", "am", 5784 +); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/shell.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/subtract/shell.js diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/era-boundary-gregory.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/era-boundary-gregory.js @@ -0,0 +1,80 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.until +description: Date difference works correctly across era boundaries +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "gregory"; +const options = { overflow: "reject" }; + +const bce5 = Temporal.ZonedDateTime.from({ era: "bce", eraYear: 5, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const bce2 = Temporal.ZonedDateTime.from({ era: "bce", eraYear: 2, monthCode: "M12", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const bce1 = Temporal.ZonedDateTime.from({ era: "bce", eraYear: 1, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const ce1 = Temporal.ZonedDateTime.from({ era: "ce", eraYear: 1, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const ce2 = Temporal.ZonedDateTime.from({ era: "ce", eraYear: 2, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const ce5 = Temporal.ZonedDateTime.from({ era: "ce", eraYear: 5, monthCode: "M06", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); + +const tests = [ + // From 5 BCE to 5 CE + [ + bce5, ce5, + [9, 0, 0, 0, "9y from 5 BCE to 5 CE (no year 0)"], + [0, 108, 0, 0, 0, "108mo from 5 BCE to 5 CE (no year 0)"], + ], + [ + ce5, bce5, + [-9, 0, 0, 0, "-9y backwards from 5 BCE to 5 CE (no year 0)"], + [0, -108, 0, 0, 0, "-108mo backwards from 5 BCE to 5 CE (no year 0)"], + ], + // CE-BCE boundary + [ + bce1, ce1, + [1, 0, 0, 0, "1y from 1 BCE to 1 CE"], + [0, 12, 0, 0, "12mo from 1 BCE to 1 CE"], + ], + [ + ce1, bce1, + [-1, 0, 0, 0, "-1y backwards from 1 BCE to 1 CE"], + [0, -12, 0, 0, "-12mo backwards from 1 BCE to 1 CE"], + ], + [ + bce2, ce2, + [2, 1, 0, 0, "2y 1mo from 2 BCE Dec to 2 CE Jan"], + [0, 25, 0, 0, "25mo from 2 BCE Dec to 2 CE Jan"], + ], + [ + ce2, bce2, + [-2, -1, 0, 0, "-2y -1mo backwards from 2 BCE Dec to 2 CE Jan"], + [0, -25, 0, 0, "-25mo backwards from 2 BCE Dec to 2 CE Jan"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.until(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.until(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.until(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.until(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.until(two); + const resultDaysISO = oneISO.until(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/era-boundary-japanese.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/era-boundary-japanese.js @@ -0,0 +1,172 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.until +description: Date difference works correctly across era boundaries +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "japanese"; +const options = { overflow: "reject" }; + +const bce1 = Temporal.ZonedDateTime.from({ era: "bce", eraYear: 1, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const ce1 = Temporal.ZonedDateTime.from({ era: "ce", eraYear: 1, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const ce1868 = Temporal.ZonedDateTime.from({ era: "ce", eraYear: 1868, monthCode: "M10", day: 22, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const meiji1 = Temporal.ZonedDateTime.from({ era: "meiji", eraYear: 1, monthCode: "M10", day: 23, hour: 12, minute: 34, timeZone: "UTC", calendar}, options); +const meiji5 = Temporal.ZonedDateTime.from({ era: "meiji", eraYear: 5, monthCode: "M01", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const meiji45 = Temporal.ZonedDateTime.from({ era: "meiji", eraYear: 45, monthCode: "M05", day: 19, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const taisho1 = Temporal.ZonedDateTime.from({ era: "taisho", eraYear: 1, monthCode: "M08", day: 9, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const taisho6 = Temporal.ZonedDateTime.from({ era: "taisho", eraYear: 6, monthCode: "M03", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const taisho15 = Temporal.ZonedDateTime.from({ era: "taisho", eraYear: 15, monthCode: "M05", day: 20, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const showa1 = Temporal.ZonedDateTime.from({ era: "showa", eraYear: 1, monthCode: "M12", day: 30, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const showa55 = Temporal.ZonedDateTime.from({ era: "showa", eraYear: 55, monthCode: "M03", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const showa64 = Temporal.ZonedDateTime.from({ era: "showa", eraYear: 64, monthCode: "M01", day: 3, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const heisei1 = Temporal.ZonedDateTime.from({ era: "heisei", eraYear: 1, monthCode: "M02", day: 27, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const heisei30 = Temporal.ZonedDateTime.from({ era: "heisei", eraYear: 30, monthCode: "M03", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const heisei31 = Temporal.ZonedDateTime.from({ era: "heisei", eraYear: 31, monthCode: "M04", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const reiwa1 = Temporal.ZonedDateTime.from({ era: "reiwa", eraYear: 1, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const reiwa2 = Temporal.ZonedDateTime.from({ era: "reiwa", eraYear: 2, monthCode: "M03", day: 15, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); + +const tests = [ + // From Heisei 30 (2018) to Reiwa 2 (2020) - crossing era boundary + [ + heisei30, reiwa2, + [2, 0, 0, 0, "2y from Heisei 30 March to Reiwa 2 March"], + [0, 24, 0, 0, "24mo from Heisei 30 March to Reiwa 2 March"], + ], + [ + reiwa2, heisei30, + [-2, 0, 0, 0, "-2y backwards from Heisei 30 March to Reiwa 2 March"], + [0, -24, 0, 0, "-24mo backwards from Heisei 30 March to Reiwa 2 March"], + + ], + // Within same year but different eras + [ + heisei31, reiwa1, + [0, 2, 0, 0, "2mo from Heisei 31 April to Reiwa 1 June"], + [0, 2, 0, 0, "2mo from Heisei 31 April to Reiwa 1 June"], + ], + [ + reiwa1, heisei31, + [0, -2, 0, 0, "-2mo backwards from Heisei 31 April to Reiwa 1 June"], + [0, -2, 0, 0, "-2mo backwards from Heisei 31 April to Reiwa 1 June"], + ], + // From Showa 55 (1980) to Heisei 30 (2018) - crossing era boundary + [ + showa55, heisei30, + [38, 0, 0, 0, "38y from Showa 55 March to Heisei 30 March"], + [0, 456, 0, 0, "456mo from Showa 55 March to Heisei 30 March"], + ], + [ + heisei30, showa55, + [-38, 0, 0, 0, "-38y backwards from Showa 55 March to Heisei 30 March"], + [0, -456, 0, 0, "-456mo backwards from Showa 55 March to Heisei 30 March"], + ], + // Within same year but different eras + [ + showa64, heisei1, + [0, 1, 0, 24, "1mo 24d from Showa 64 January 3 to Heisei 1 February 27"], + [0, 1, 0, 24, "1mo 24d from Showa 64 January 3 to Heisei 1 February 27"], + ], + [ + heisei1, showa64, + [0, -1, 0, -24, "-1mo -24d from Showa 64 January 3 to Heisei 1 February 27"], + [0, -1, 0, -24, "-1mo -24d from Showa 64 January 3 to Heisei 1 February 27"], + ], + // From Taisho 6 (1917) to Showa 55 (1980) - crossing era boundary + [ + taisho6, showa55, + [63, 0, 0, 0, "63y from Taisho 6 March to Showa 55 March"], + [0, 756, 0, 0, "756mo from Taisho 6 March to Showa 55 March"], + ], + [ + showa55, taisho6, + [-63, 0, 0, 0, "-63y backwards from Taisho 6 March to Showa 55 March"], + [0, -756, 0, 0, "-756mo backwards from Taisho 6 March to Showa 55 March"], + ], + // Within same year but different eras + [ + taisho15, showa1, + [0, 7, 0, 10, "7mo 10d from Taisho 15 July 20 to Showa 1 December 30"], + [0, 7, 0, 10, "7mo 10d from Taisho 15 July 20 to Showa 1 December 30"], + ], + [ + showa1, taisho15, + [0, -7, 0, -10, "-7mo -10d backwards from Taisho 15 July 20 to Showa 1 December 30"], + [0, -7, 0, -10, "-7mo -10d backwards from Taisho 15 July 20 to Showa 1 December 30"], + ], + // From Meiji 5 (1872) to Taisho 6 (1917) - crossing era boundary + // Note that contemporarily January 15 1872 would have been Meiji 4 in the + // pre-1873 lunisolar calendar, but the spec-mandated behaviour is proleptic + [ + meiji5, taisho6, + [45, 2, 0, 0, "45y 2mo from Meiji 5 January to Taisho 6 March"], + [0, 542, 0, 0, "542mo from Meiji 5 January to Taisho 6 March"], + ], + [ + taisho6, meiji5, + [-45, -2, 0, 0, "-45y -2mo backwards from Meiji 5 January to Taisho 6 March"], + [0, -542, 0, 0, "-542mo backwards from Meiji 5 January to Taisho 6 March"], + ], + // Within same year but different eras + [ + meiji45, taisho1, + [0, 2, 0, 21, "2mo 21d from Meiji 45 May 19 to Taisho 1 August 9"], + [0, 2, 0, 21, "2mo 21d from Meiji 45 May 19 to Taisho 1 August 9"], + ], + [ + taisho1, meiji45, + [0, -2, 0, -21, "-2mo -21d backwards from Meiji 45 May 19 to Taisho 1 August 9"], + [0, -2, 0, -21, "-2mo -21d backwards from Meiji 45 May 19 to Taisho 1 August 9"], + ], + // Last pre-Meiji day to first day of Meiji era + [ + ce1868, meiji1, + [0, 0, 0, 1, "from day before Meiji era to first day"], + [0, 0, 0, 1, "from day before Meiji era to first day"], + ], + [ + meiji1, ce1868, + [0, 0, 0, -1, "backwards from day before Meiji era to first day"], + [0, 0, 0, -1, "backwards from day before Meiji era to first day"], + ], + // CE-BCE boundary + [ + bce1, ce1, + [1, 0, 0, 0, "1y from 1 BCE to 1 CE"], + [0, 12, 0, 0, "12mo from 1 BCE to 1 CE"], + ], + [ + ce1, bce1, + [-1, 0, 0, 0, "-1y backwards from 1 BCE to 1 CE"], + [0, -12, 0, 0, "-12mo backwards from 1 BCE to 1 CE"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.until(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.until(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.until(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.until(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.until(two); + const resultDaysISO = oneISO.until(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/era-boundary-roc.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/era-boundary-roc.js @@ -0,0 +1,80 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.until +description: Date difference works correctly across era boundaries +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +const calendar = "roc"; +const options = { overflow: "reject" }; + +const broc5 = Temporal.ZonedDateTime.from({ era: "broc", eraYear: 5, monthCode: "M03", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const broc3 = Temporal.ZonedDateTime.from({ era: "broc", eraYear: 3, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const broc1 = Temporal.ZonedDateTime.from({ era: "broc", eraYear: 1, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const roc1 = Temporal.ZonedDateTime.from({ era: "roc", eraYear: 1, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const roc5 = Temporal.ZonedDateTime.from({ era: "roc", eraYear: 5, monthCode: "M03", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const roc10 = Temporal.ZonedDateTime.from({ era: "roc", eraYear: 10, monthCode: "M01", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); + +const tests = [ + // From BROC 5 to ROC 5 + [ + broc5, roc5, + [9, 0, 0, 0, "9y from BROC 5 to ROC 5 (no year 0)"], + [0, 108, 0, 0, 0, "108mo from BROC 5 to ROC 5 (no year 0)"], + ], + [ + roc5, broc5, + [-9, 0, 0, 0, "-9y backwards from BROC 5 to ROC 5 (no year 0)"], + [0, -108, 0, 0, 0, "-108mo backwards from BROC 5 to ROC 5 (no year 0)"], + ], + // Era boundary + [ + broc1, roc1, + [1, 0, 0, 0, "1y from BROC 1 to ROC 1"], + [0, 12, 0, 0, "12mo from BROC 1 to ROC 1"], + ], + [ + roc1, broc1, + [-1, 0, 0, 0, "-1y backwards from BROC 1 to ROC 1"], + [0, -12, 0, 0, "-12mo backwards from BROC 1 to ROC 1"], + ], + [ + broc3, roc10, + [12, 0, 0, 0, "12y from BROC 3 to ROC 10"], + [0, 144, 0, 0, "144mo from BROC 3 to ROC 10"], + ], + [ + roc10, broc3, + [-12, 0, 0, 0, "-12y backwards from BROC 3 to ROC 10"], + [0, -144, 0, 0, "-144mo backwards from BROC 3 to ROC 10"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.until(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.until(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.until(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.until(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.until(two); + const resultDaysISO = oneISO.until(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/leap-months-chinese.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/leap-months-chinese.js @@ -0,0 +1,167 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Difference across leap months in chinese calendar +esid: sec-temporal.zoneddatetime.prototype.until +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +// 2001 is a leap year with a M04L leap month. + +const calendar = "chinese"; +const options = { overflow: "reject" }; + +const common1Month4 = Temporal.ZonedDateTime.from({ year: 2000, monthCode: "M04", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const common1Month5 = Temporal.ZonedDateTime.from({ year: 2000, monthCode: "M05", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const common1Month6 = Temporal.ZonedDateTime.from({ year: 2000, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const leapMonth4 = Temporal.ZonedDateTime.from({ year: 2001, monthCode: "M04", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const leapMonth4L = Temporal.ZonedDateTime.from({ year: 2001, monthCode: "M04L", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const leapMonth5 = Temporal.ZonedDateTime.from({ year: 2001, monthCode: "M05", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const common2Month4 = Temporal.ZonedDateTime.from({ year: 2002, monthCode: "M04", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const common2Month5 = Temporal.ZonedDateTime.from({ year: 2002, monthCode: "M05", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); + +// (receiver, argument, years test data, months test data) +// test data: expected years, months, weeks, days, description +// largestUnit years: make sure some cases where the answer is 12 months do not +// balance up to 1 year +// largestUnit months: similar to years, but make sure number of months in year +// is computed correctly +// For largestUnit of days and weeks, the results should be identical to what +// the ISO calendar gives for the corresponding ISO dates +const tests = [ + [ + common1Month4, leapMonth4, + [1, 0, 0, 0, "M04-M04 common-leap is 1y"], + [0, 12, 0, 0, "M04-M04 common-leap is 12mo"], + ], + [ + leapMonth4, common2Month4, + [1, 0, 0, 0, "M04-M04 leap-common is 1y"], + [0, 13, 0, 0, "M04-M04 leap-common is 13mo not 12mo"], + ], + [ + common1Month4, common2Month4, + [2, 0, 0, 0, "M04-M04 common-common is 2y"], + [0, 25, 0, 0, "M04-M04 common-common is 25mo not 24mo"], + ], + [ + common1Month5, leapMonth5, + [1, 0, 0, 0, "M05-M05 common-leap is 1y"], + [0, 13, 0, 0, "M05-M05 common-leap is 13mo not 12mo"], + ], + [ + leapMonth5, common2Month5, + [1, 0, 0, 0, "M05-M05 leap-common is 1y"], + [0, 12, 0, 0, "M05-M05 leap-common is 12mo"], + ], + [ + common1Month5, common2Month5, + [2, 0, 0, 0, "M05-M05 common-common is 2y"], + [0, 25, 0, 0, "M05-M05 common-common is 25mo not 24mo"], + ], + [ + common1Month4, leapMonth4L, + [1, 1, 0, 0, "M04-M04L is 1y 1mo"], + [0, 13, 0, 0, "M04-M04L is 13mo"], + ], + [ + leapMonth4L, common2Month4, + [0, 12, 0, 0, "M04L-M04 is 12mo not 1y"], + [0, 12, 0, 0, "M04L-M04 is 12mo"], + ], + [ + common1Month5, leapMonth4L, + [0, 12, 0, 0, "M05-M04L is 12mo not 1y"], + [0, 12, 0, 0, "M05-M04L is 12mo"], + ], + [ + leapMonth4L, common2Month5, + [1, 1, 0, 0, "M04L-M05 is 1y 1mo (exhibits calendar-specific constraining)"], + [0, 13, 0, 0, "M04L-M05 is 13mo"], + ], + [ + common1Month6, leapMonth5, + [0, 12, 0, 0, "M06-M05 common-leap is 12mo not 11mo"], + [0, 12, 0, 0, "M06-M05 common-leap is 12mo not 11mo"], + ], + + // Negative + [ + common2Month4, leapMonth4, + [-1, 0, 0, 0, "M04-M04 common-leap backwards is -1y"], + [0, -13, 0, 0, "M04-M04 common-leap backwards is -13mo not -12mo"], + ], + [ + leapMonth4, common1Month4, + [-1, 0, 0, 0, "M04-M04 leap-common backwards is -1y"], + [0, -12, 0, 0, "M04-M04 leap-common backwards is -12mo not -13mo"], + ], + [ + common2Month4, common1Month4, + [-2, 0, 0, 0, "M04-M04 common-common backwards is -2y"], + [0, -25, 0, 0, "M04-M04 common-common backwards is -25mo not -24mo"], + ], + [ + common2Month5, leapMonth5, + [-1, 0, 0, 0, "M05-M05 common-leap backwards is -1y"], + [0, -12, 0, 0, "M05-M05 common-leap backwards is -12mo not -13mo"], + ], + [ + leapMonth5, common1Month5, + [-1, 0, 0, 0, "M05-M05 leap-common backwards is -1y"], + [0, -13, 0, 0, "M05-M05 leap-common backwards is -13mo not -12mo"], + ], + [ + common2Month5, common1Month5, + [-2, 0, 0, 0, "M05-M05 common-common backwards is -2y"], + [0, -25, 0, 0, "M05-M05 common-common backwards is -25mo not -24mo"], + ], + [ + common2Month4, leapMonth4L, + [0, -12, 0, 0, "M04-M04L backwards is -12mo not -1y"], + [0, -12, 0, 0, "M04-M04L backwards is -12mo"], + ], + [ + leapMonth4L, common1Month4, + [-1, 0, 0, 0, "M04L-M04 backwards is -1y not -1y -1mo (exhibits calendar-specific constraining)"], + [0, -13, 0, 0, "M04L-M04 backwards is -13mo"], + ], + [ + common2Month5, leapMonth4L, + [-1, -1, 0, 0, "M05-M04L backwards is -1y -1mo"], + [0, -13, 0, 0, "M05-M04L backwards is -13mo"], + ], + [ + leapMonth4L, common1Month5, + [0, -12, 0, 0, "M04L-M05 backwards is -12mo not -1y"], + [0, -12, 0, 0, "M04L-M05 backwards is -12mo"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.until(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.until(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.until(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.until(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.until(two); + const resultDaysISO = oneISO.until(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/leap-months-dangi.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/leap-months-dangi.js @@ -0,0 +1,167 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Difference across leap months in dangi calendar +esid: sec-temporal.zoneddatetime.prototype.until +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +// 2001 is a leap year with a M04L leap month. + +const calendar = "dangi"; +const options = { overflow: "reject" }; + +const common1Month4 = Temporal.ZonedDateTime.from({ year: 2000, monthCode: "M04", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const common1Month5 = Temporal.ZonedDateTime.from({ year: 2000, monthCode: "M05", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const common1Month6 = Temporal.ZonedDateTime.from({ year: 2000, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const leapMonth4 = Temporal.ZonedDateTime.from({ year: 2001, monthCode: "M04", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const leapMonth4L = Temporal.ZonedDateTime.from({ year: 2001, monthCode: "M04L", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const leapMonth5 = Temporal.ZonedDateTime.from({ year: 2001, monthCode: "M05", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const common2Month4 = Temporal.ZonedDateTime.from({ year: 2002, monthCode: "M04", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const common2Month5 = Temporal.ZonedDateTime.from({ year: 2002, monthCode: "M05", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); + +// (receiver, argument, years test data, months test data) +// test data: expected years, months, weeks, days, description +// largestUnit years: make sure some cases where the answer is 12 months do not +// balance up to 1 year +// largestUnit months: similar to years, but make sure number of months in year +// is computed correctly +// For largestUnit of days and weeks, the results should be identical to what +// the ISO calendar gives for the corresponding ISO dates +const tests = [ + [ + common1Month4, leapMonth4, + [1, 0, 0, 0, "M04-M04 common-leap is 1y"], + [0, 12, 0, 0, "M04-M04 common-leap is 12mo"], + ], + [ + leapMonth4, common2Month4, + [1, 0, 0, 0, "M04-M04 leap-common is 1y"], + [0, 13, 0, 0, "M04-M04 leap-common is 13mo not 12mo"], + ], + [ + common1Month4, common2Month4, + [2, 0, 0, 0, "M04-M04 common-common is 2y"], + [0, 25, 0, 0, "M04-M04 common-common is 25mo not 24mo"], + ], + [ + common1Month5, leapMonth5, + [1, 0, 0, 0, "M05-M05 common-leap is 1y"], + [0, 13, 0, 0, "M05-M05 common-leap is 13mo not 12mo"], + ], + [ + leapMonth5, common2Month5, + [1, 0, 0, 0, "M05-M05 leap-common is 1y"], + [0, 12, 0, 0, "M05-M05 leap-common is 12mo"], + ], + [ + common1Month5, common2Month5, + [2, 0, 0, 0, "M05-M05 common-common is 2y"], + [0, 25, 0, 0, "M05-M05 common-common is 25mo not 24mo"], + ], + [ + common1Month4, leapMonth4L, + [1, 1, 0, 0, "M04-M04L is 1y 1mo"], + [0, 13, 0, 0, "M04-M04L is 13mo"], + ], + [ + leapMonth4L, common2Month4, + [0, 12, 0, 0, "M04L-M04 is 12mo not 1y"], + [0, 12, 0, 0, "M04L-M04 is 12mo"], + ], + [ + common1Month5, leapMonth4L, + [0, 12, 0, 0, "M05-M04L is 12mo not 1y"], + [0, 12, 0, 0, "M05-M04L is 12mo"], + ], + [ + leapMonth4L, common2Month5, + [1, 1, 0, 0, "M04L-M05 is 1y 1mo (exhibits calendar-specific constraining)"], + [0, 13, 0, 0, "M04L-M05 is 13mo"], + ], + [ + common1Month6, leapMonth5, + [0, 12, 0, 0, "M06-M05 common-leap is 12mo not 11mo"], + [0, 12, 0, 0, "M06-M05 common-leap is 12mo not 11mo"], + ], + + // Negative + [ + common2Month4, leapMonth4, + [-1, 0, 0, 0, "M04-M04 common-leap backwards is -1y"], + [0, -13, 0, 0, "M04-M04 common-leap backwards is -13mo not -12mo"], + ], + [ + leapMonth4, common1Month4, + [-1, 0, 0, 0, "M04-M04 leap-common backwards is -1y"], + [0, -12, 0, 0, "M04-M04 leap-common backwards is -12mo not -13mo"], + ], + [ + common2Month4, common1Month4, + [-2, 0, 0, 0, "M04-M04 common-common backwards is -2y"], + [0, -25, 0, 0, "M04-M04 common-common backwards is -25mo not -24mo"], + ], + [ + common2Month5, leapMonth5, + [-1, 0, 0, 0, "M05-M05 common-leap backwards is -1y"], + [0, -12, 0, 0, "M05-M05 common-leap backwards is -12mo not -13mo"], + ], + [ + leapMonth5, common1Month5, + [-1, 0, 0, 0, "M05-M05 leap-common backwards is -1y"], + [0, -13, 0, 0, "M05-M05 leap-common backwards is -13mo not -12mo"], + ], + [ + common2Month5, common1Month5, + [-2, 0, 0, 0, "M05-M05 common-common backwards is -2y"], + [0, -25, 0, 0, "M05-M05 common-common backwards is -25mo not -24mo"], + ], + [ + common2Month4, leapMonth4L, + [0, -12, 0, 0, "M04-M04L backwards is -12mo not -1y"], + [0, -12, 0, 0, "M04-M04L backwards is -12mo"], + ], + [ + leapMonth4L, common1Month4, + [-1, 0, 0, 0, "M04L-M04 backwards is -1y not -1y -1mo (exhibits calendar-specific constraining)"], + [0, -13, 0, 0, "M04L-M04 backwards is -13mo"], + ], + [ + common2Month5, leapMonth4L, + [-1, -1, 0, 0, "M05-M04L backwards is -1y -1mo"], + [0, -13, 0, 0, "M05-M04L backwards is -13mo"], + ], + [ + leapMonth4L, common1Month5, + [0, -12, 0, 0, "M04L-M05 backwards is -12mo not -1y"], + [0, -12, 0, 0, "M04L-M05 backwards is -12mo"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.until(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.until(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.until(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.until(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.until(two); + const resultDaysISO = oneISO.until(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/leap-months-hebrew.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/leap-months-hebrew.js @@ -0,0 +1,171 @@ +// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally +// Copyright (C) 2025 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Difference across leap months in hebrew calendar +esid: sec-temporal.zoneddatetime.prototype.until +includes: [temporalHelpers.js] +features: [Temporal, Intl.Era-monthcode] +---*/ + +// 5784 is a leap year. +// M05 - Shevat +// M05L - Adar I +// M06 - Adar II +// M07 - Nisan + +const calendar = "hebrew"; +const options = { overflow: "reject" }; + +const common1Shevat = Temporal.ZonedDateTime.from({ year: 5783, monthCode: "M05", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const common1Adar = Temporal.ZonedDateTime.from({ year: 5783, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const common1Nisan = Temporal.ZonedDateTime.from({ year: 5783, monthCode: "M07", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const leapShevat = Temporal.ZonedDateTime.from({ year: 5784, monthCode: "M05", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const leapAdarI = Temporal.ZonedDateTime.from({ year: 5784, monthCode: "M05L", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const leapAdarII = Temporal.ZonedDateTime.from({ year: 5784, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const common2Shevat = Temporal.ZonedDateTime.from({ year: 5785, monthCode: "M05", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); +const common2Adar = Temporal.ZonedDateTime.from({ year: 5785, monthCode: "M06", day: 1, hour: 12, minute: 34, timeZone: "UTC", calendar }, options); + +// (receiver, argument, years test data, months test data) +// test data: expected years, months, weeks, days, description +// largestUnit years: make sure some cases where the answer is 12 months do not +// balance up to 1 year +// largestUnit months: similar to years, but make sure number of months in year +// is computed correctly +// For largestUnit of days and weeks, the results should be identical to what +// the ISO calendar gives for the corresponding ISO dates +const tests = [ + [ + common1Shevat, leapShevat, + [1, 0, 0, 0, "M05-M05 common-leap is 1y"], + [0, 12, 0, 0, "M05-M05 common-leap is 12mo"], + ], + [ + leapShevat, common2Shevat, + [1, 0, 0, 0, "M05-M05 leap-common is 1y"], + [0, 13, 0, 0, "M05-M05 leap-common is 13mo not 12mo"], + ], + [ + common1Shevat, common2Shevat, + [2, 0, 0, 0, "M05-M05 common-common is 2y"], + [0, 25, 0, 0, "M05-M05 common-common is 25mo not 24mo"], + ], + [ + common1Adar, leapAdarII, + [1, 0, 0, 0, "M06-M06 common-leap is 1y"], + [0, 13, 0, 0, "M06-M06 common-leap is 13mo not 12mo"], + ], + [ + leapAdarII, common2Adar, + [1, 0, 0, 0, "M06-M06 leap-common is 1y"], + [0, 12, 0, 0, "M06-M06 leap-common is 12mo"], + ], + [ + common1Adar, common2Adar, + [2, 0, 0, 0, "M06-M06 common-common is 2y"], + [0, 25, 0, 0, "M06-M06 common-common is 25mo not 24mo"], + ], + [ + common1Shevat, leapAdarI, + [1, 1, 0, 0, "M05-M05L is 1y 1mo"], + [0, 13, 0, 0, "M05-M05L is 13mo"], + ], + [ + leapAdarI, common2Shevat, + [0, 12, 0, 0, "M05L-M05 is 12mo not 1y"], + [0, 12, 0, 0, "M05L-M05 is 12mo"], + ], + [ + common1Adar, leapAdarI, + [0, 12, 0, 0, "M06-M05L is 12mo not 1y"], + [0, 12, 0, 0, "M06-M05L is 12mo"], + ], + [ + leapAdarI, common2Adar, + [1, 0, 0, 0, "M05L-M06 is 1y (exhibits calendar-specific constraining)"], + [0, 13, 0, 0, "M05L-M06 is 13mo"], + ], + [ + common1Nisan, leapAdarII, + [0, 12, 0, 0, "M07-M06 common-leap is 12mo not 11mo"], + [0, 12, 0, 0, "M07-M06 common-leap is 12mo not 11mo"], + ], + + // Negative + [ + common2Shevat, leapShevat, + [-1, 0, 0, 0, "M05-M05 common-leap backwards is -1y"], + [0, -13, 0, 0, "M05-M05 common-leap backwards is -13mo not -12mo"], + ], + [ + leapShevat, common1Shevat, + [-1, 0, 0, 0, "M05-M05 leap-common backwards is -1y"], + [0, -12, 0, 0, "M05-M05 leap-common backwards is -12mo not -13mo"], + ], + [ + common2Shevat, common1Shevat, + [-2, 0, 0, 0, "M05-M05 common-common backwards is -2y"], + [0, -25, 0, 0, "M05-M05 common-common backwards is -25mo not -24mo"], + ], + [ + common2Adar, leapAdarII, + [-1, 0, 0, 0, "M06-M06 common-leap backwards is -1y"], + [0, -12, 0, 0, "M06-M06 common-leap backwards is -12mo not -13mo"], + ], + [ + leapAdarII, common1Adar, + [-1, 0, 0, 0, "M06-M06 leap-common backwards is -1y"], + [0, -13, 0, 0, "M06-M06 leap-common backwards is -13mo not -12mo"], + ], + [ + common2Adar, common1Adar, + [-2, 0, 0, 0, "M06-M06 common-common backwards is -2y"], + [0, -25, 0, 0, "M06-M06 common-common backwards is -25mo not -24mo"], + ], + [ + common2Shevat, leapAdarI, + [0, -12, 0, 0, "M05-M05L backwards is -12mo not -1y"], + [0, -12, 0, 0, "M05-M05L backwards is -12mo"], + ], + [ + leapAdarI, common1Shevat, + [-1, -1, 0, 0, "M05L-M05 backwards is -1y -1mo (exhibits calendar-specific constraining)"], + [0, -13, 0, 0, "M05L-M05 backwards is -13mo"], + ], + [ + common2Adar, leapAdarI, + [-1, -1, 0, 0, "M06-M05L backwards is -1y -1mo"], + [0, -13, 0, 0, "M06-M05L backwards is -13mo"], + ], + [ + leapAdarI, common1Adar, + [0, -12, 0, 0, "M05L-M06 backwards is -12mo not -1y"], + [0, -12, 0, 0, "M05L-M06 backwards is -12mo"], + ], +]; + +for (const [one, two, yearsTest, monthsTest] of tests) { + let [years, months, weeks, days, descr] = yearsTest; + let result = one.until(two, { largestUnit: "years" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + [years, months, weeks, days, descr] = monthsTest; + result = one.until(two, { largestUnit: "months" }); + TemporalHelpers.assertDuration(result, years, months, weeks, days, 0, 0, 0, 0, 0, 0, descr); + + const oneISO = one.withCalendar("iso8601"); + const twoISO = two.withCalendar("iso8601"); + + const resultWeeks = one.until(two, { largestUnit: "weeks" }); + const resultWeeksISO = oneISO.until(twoISO, { largestUnit: "weeks" }); + TemporalHelpers.assertDurationsEqual(resultWeeks, resultWeeksISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit weeks`); + + const resultDays = one.until(two); + const resultDaysISO = oneISO.until(twoISO); + TemporalHelpers.assertDurationsEqual(resultDays, resultDaysISO, + `${one.year}-${one.monthCode}-${one.day} : ${two.year}-${two.monthCode}-${two.day} largestUnit days`); +} + +reportCompare(0, 0); diff --git a/js/src/tests/test262/intl402/shell.js b/js/src/tests/test262/intl402/shell.js @@ -2121,12 +2121,14 @@ function allNumberingSystems() { "ethi", "finance", "fullwide", + "gara", "geor", "gong", "gonm", "grek", "greklow", "gujr", + "gukh", "guru", "hanidays", "hanidec", @@ -2145,6 +2147,7 @@ function allNumberingSystems() { "kawi", "khmr", "knda", + "krai", "lana", "lanatham", "laoo", @@ -2162,6 +2165,8 @@ function allNumberingSystems() { "mroo", "mtei", "mymr", + "mymrepka", + "mymrpao", "mymrshan", "mymrtlng", "nagm", @@ -2169,8 +2174,10 @@ function allNumberingSystems() { "newa", "nkoo", "olck", + "onao", "orya", "osma", + "outlined", "rohg", "roman", "romanlow", @@ -2180,6 +2187,7 @@ function allNumberingSystems() { "sinh", "sora", "sund", + "sunu", "takr", "talu", "taml", diff --git a/js/src/tests/test262/language/statements/await-using/syntax/block-scope-syntax-await-using-declarations-mixed-with-without-initializer.js b/js/src/tests/test262/language/statements/await-using/syntax/block-scope-syntax-await-using-declarations-mixed-with-without-initializer.js @@ -1,4 +1,4 @@ -// |reftest| shell-option(--enable-explicit-resource-management) skip-if(!(this.hasOwnProperty('getBuildConfiguration')&&getBuildConfiguration('explicit-resource-management'))||!xulRuntime.shell) error:SyntaxError async -- explicit-resource-management is not enabled unconditionally, requires shell-options +// |reftest| shell-option(--enable-explicit-resource-management) skip-if(!(this.hasOwnProperty('getBuildConfiguration')&&getBuildConfiguration('explicit-resource-management'))||!xulRuntime.shell) error:SyntaxError -- explicit-resource-management is not enabled unconditionally, requires shell-options // Copyright (C) 2023 Ron Buckton. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. @@ -22,7 +22,6 @@ info: | negative: phase: parse type: SyntaxError -flags: [async] features: [explicit-resource-management] ---*/ diff --git a/js/src/tests/test262/language/statements/using/syntax/using-for-using-of-of.js b/js/src/tests/test262/language/statements/using/syntax/using-for-using-of-of.js @@ -19,7 +19,7 @@ for (using of of [0, 1, 2]) { result.push(using); } -asserts.sameValue(result.length, 1); -asserts.sameValue(result[0], 7); +assert.sameValue(result.length, 1); +assert.sameValue(result[0], 7); reportCompare(0, 0); diff --git a/js/src/tests/test262/language/statements/using/syntax/with-initializer-default-statement-list.js b/js/src/tests/test262/language/statements/using/syntax/with-initializer-default-statement-list.js @@ -1,4 +1,4 @@ -// |reftest| shell-option(--enable-explicit-resource-management) skip-if(!(this.hasOwnProperty('getBuildConfiguration')&&getBuildConfiguration('explicit-resource-management'))||!xulRuntime.shell) -- explicit-resource-management is not enabled unconditionally, requires shell-options +// |reftest| shell-option(--enable-explicit-resource-management) skip-if(!(this.hasOwnProperty('getBuildConfiguration')&&getBuildConfiguration('explicit-resource-management'))||!xulRuntime.shell) error:SyntaxError -- explicit-resource-management is not enabled unconditionally, requires shell-options // Copyright (C) 2023 Ron Buckton. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. @@ -12,10 +12,10 @@ info: | - It is a Syntax Error if AwaitUsingDeclaration is contained directly within the StatementList of either a CaseClause or DefaultClause. - +negative: + phase: parse + type: SyntaxError features: [explicit-resource-management] ---*/ $DONOTEVALUATE(); switch (true) { default: using x = null; } - -reportCompare(0, 0); diff --git a/js/src/tests/test262/staging/Intl402/Temporal/old/indian-calendar.js b/js/src/tests/test262/staging/Intl402/Temporal/old/indian-calendar.js @@ -9,12 +9,6 @@ features: [Temporal] ---*/ -// throws in Node 12 & 14 before 1 CE -var vulnerableToBceBug = new Date("0000-01-01T00:00Z").toLocaleDateString("en-US-u-ca-indian", { timeZone: "UTC" }) !== "10/11/-79 Saka"; -if (vulnerableToBceBug) { - assert.throws(RangeError, () => Temporal.PlainDate.from("0000-01-01").withCalendar("indian").day); -} - // handles leap days var leapYearFirstDay = Temporal.PlainDate.from("2004-03-21[u-ca=indian]"); assert.sameValue(leapYearFirstDay.year, 2004 - 78);