From 60faf4f2bb05d8bd08d70feabdfcf878cbc485a9 Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Mon, 15 Sep 2025 17:15:10 -0700 Subject: [PATCH] Temporal: Add tests covering end-of-month behaviour for ISO-like non-ISO calendars Same tests as added in #4004, but for the four non-ISO calendars which use ISO-like months and days. --- .../wrapping-at-end-of-month-gregorian.js | 122 +++++++++++++++ .../wrapping-at-end-of-month-gregorian.js | 122 +++++++++++++++ .../wrapping-at-end-of-month-gregorian.js | 146 ++++++++++++++++++ .../wrapping-at-end-of-month-gregorian.js | 146 ++++++++++++++++++ .../wrapping-at-end-of-month-gregorian.js | 122 +++++++++++++++ .../wrapping-at-end-of-month-gregorian.js | 120 ++++++++++++++ 6 files changed, 778 insertions(+) create mode 100644 test/intl402/Temporal/PlainDate/prototype/since/wrapping-at-end-of-month-gregorian.js create mode 100644 test/intl402/Temporal/PlainDate/prototype/until/wrapping-at-end-of-month-gregorian.js create mode 100644 test/intl402/Temporal/PlainDateTime/prototype/since/wrapping-at-end-of-month-gregorian.js create mode 100644 test/intl402/Temporal/PlainDateTime/prototype/until/wrapping-at-end-of-month-gregorian.js create mode 100644 test/intl402/Temporal/ZonedDateTime/prototype/since/wrapping-at-end-of-month-gregorian.js create mode 100644 test/intl402/Temporal/ZonedDateTime/prototype/until/wrapping-at-end-of-month-gregorian.js diff --git a/test/intl402/Temporal/PlainDate/prototype/since/wrapping-at-end-of-month-gregorian.js b/test/intl402/Temporal/PlainDate/prototype/since/wrapping-at-end-of-month-gregorian.js new file mode 100644 index 0000000000..b4585cf2f9 --- /dev/null +++ b/test/intl402/Temporal/PlainDate/prototype/since/wrapping-at-end-of-month-gregorian.js @@ -0,0 +1,122 @@ +// Copyright (C) 2024 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.since +description: > + Tests balancing of days to months at end of month (ISO-like non-ISO calendars) +includes: [temporalHelpers.js] +features: [Temporal] +---*/ + +for (const calendar of ["buddhist", "gregory", "japanese", "roc"]) { + // Difference between end of longer month to end of following shorter month + { + const end = new Temporal.PlainDate(1970, 2, 28, calendar); + for (const largestUnit of ["years", "months"]) { + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1970, 1, 28, calendar).since(end, { largestUnit }), + 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, + `Jan 28th to Feb 28th is one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1970, 1, 29, calendar).since(end, { largestUnit }), + 0, 0, 0, -30, 0, 0, 0, 0, 0, 0, + `Jan 29th to Feb 28th is 30 days, not one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1970, 1, 30, calendar).since(end, { largestUnit }), + 0, 0, 0, -29, 0, 0, 0, 0, 0, 0, + `Jan 30th to Feb 28th is 29 days, not one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1970, 1, 31, calendar).since(end, { largestUnit }), + 0, 0, 0, -28, 0, 0, 0, 0, 0, 0, + `Jan 31st to Feb 28th is 28 days, not one month (${calendar}, ${largestUnit})` + ); + } + } + + // Difference between end of leap-year January to end of leap-year February + { + const end = new Temporal.PlainDate(1972, 2, 29, calendar); + for (const largestUnit of ["years", "months"]) { + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1972, 1, 29, calendar).since(end, { largestUnit }), + 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, + `Jan 29th to Feb 29th is one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1972, 1, 30, calendar).since(end, { largestUnit }), + 0, 0, 0, -30, 0, 0, 0, 0, 0, 0, + `Jan 30th to Feb 29th is 30 days, not one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1972, 1, 31, calendar).since(end, { largestUnit }), + 0, 0, 0, -29, 0, 0, 0, 0, 0, 0, + `Jan 31st to Feb 29th is 29 days, not one month (${calendar}, ${largestUnit})` + ); + } + } + + // Difference between end of longer month to end of not-immediately-following + // shorter month + { + const end = new Temporal.PlainDate(1970, 11, 30, calendar); + for (const largestUnit of ["years", "months"]) { + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1970, 8, 30, calendar).since(end, { largestUnit }), + 0, -3, 0, 0, 0, 0, 0, 0, 0, 0, + `Aug 30th to Nov 30th is 3 months (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1970, 8, 31, calendar).since(end, { largestUnit }), + 0, -2, 0, -30, 0, 0, 0, 0, 0, 0, + `Aug 31st to Nov 30th is 2 months 30 days, not 3 months (${calendar}, ${largestUnit})` + ); + } + } + + // Difference between end of longer month in one year to shorter month in + // later year + { + const end = new Temporal.PlainDate(1973, 4, 30, calendar); + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1970, 12, 30, calendar).since(end, { largestUnit: "months" }), + 0, -28, 0, 0, 0, 0, 0, 0, 0, 0, + `Dec 30th 1970 to Apr 30th 1973 is 28 months (${calendar})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1970, 12, 30, calendar).since(end, { largestUnit: "years" }), + -2, -4, 0, 0, 0, 0, 0, 0, 0, 0, + `Dec 30th 1970 to Apr 30th 1973 is 2 years, 4 months (${calendar})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1970, 12, 31, calendar).since(end, { largestUnit: "months" }), + 0, -27, 0, -30, 0, 0, 0, 0, 0, 0, + `Dec 30th 1970 to Apr 30th 1973 is 27 months, 30 days, not 28 months (${calendar})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1970, 12, 31, calendar).since(end, { largestUnit: "years" }), + -2, -3, 0, -30, 0, 0, 0, 0, 0, 0, + `Dec 30th 1970 to Apr 30th 1973 is 2 years, 3 months, 30 days, not 2 years 4 months (${calendar})` + ); + } + + // Difference where months passes through a month that's the same length or + // shorter than either the start or end month + { + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1970, 1, 29, calendar) + .since(new Temporal.PlainDate(1970, 3, 28, calendar), { largestUnit: "months" }), + 0, -1, 0, -28, 0, 0, 0, 0, 0, 0, + `Jan 29th to Mar 28th is 1 month 28 days, not 58 days (${calendar})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1970, 1, 31, calendar) + .since(new Temporal.PlainDate(1971, 5, 30, calendar), { largestUnit: "years" }), + -1, -3, 0, -30, 0, 0, 0, 0, 0, 0, + `Jan 31st 1970 to May 30th 1971 is 1 year, 3 months, 30 days, not 1 year, 2 months, 60 days (${calendar})` + ); + } +} diff --git a/test/intl402/Temporal/PlainDate/prototype/until/wrapping-at-end-of-month-gregorian.js b/test/intl402/Temporal/PlainDate/prototype/until/wrapping-at-end-of-month-gregorian.js new file mode 100644 index 0000000000..166a58516a --- /dev/null +++ b/test/intl402/Temporal/PlainDate/prototype/until/wrapping-at-end-of-month-gregorian.js @@ -0,0 +1,122 @@ +// Copyright (C) 2024 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindate.prototype.until +description: > + Tests balancing of days to months at end of month (ISO-like non-ISO calendars) +includes: [temporalHelpers.js] +features: [Temporal] +---*/ + +for (const calendar of ["buddhist", "gregory", "japanese", "roc"]) { + // Difference between end of longer month to end of following shorter month + { + const end = new Temporal.PlainDate(1970, 2, 28, calendar); + for (const largestUnit of ["years", "months"]) { + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1970, 1, 28, calendar).until(end, { largestUnit }), + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + `Jan 28th to Feb 28th is one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1970, 1, 29, calendar).until(end, { largestUnit }), + 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, + `Jan 29th to Feb 28th is 30 days, not one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1970, 1, 30, calendar).until(end, { largestUnit }), + 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, + `Jan 30th to Feb 28th is 29 days, not one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1970, 1, 31, calendar).until(end, { largestUnit }), + 0, 0, 0, 28, 0, 0, 0, 0, 0, 0, + `Jan 31st to Feb 28th is 28 days, not one month (${calendar}, ${largestUnit})` + ); + } + } + + // Difference between end of leap-year January to end of leap-year February + { + const end = new Temporal.PlainDate(1972, 2, 29, calendar); + for (const largestUnit of ["years", "months"]) { + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1972, 1, 29, calendar).until(end, { largestUnit }), + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + `Jan 29th to Feb 29th is one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1972, 1, 30, calendar).until(end, { largestUnit }), + 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, + `Jan 30th to Feb 29th is 30 days, not one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1972, 1, 31, calendar).until(end, { largestUnit }), + 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, + `Jan 31st to Feb 29th is 29 days, not one month (${calendar}, ${largestUnit})` + ); + } + } + + // Difference between end of longer month to end of not-immediately-following + // shorter month + { + const end = new Temporal.PlainDate(1970, 11, 30, calendar); + for (const largestUnit of ["years", "months"]) { + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1970, 8, 30, calendar).until(end, { largestUnit }), + 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, + `Aug 30th to Nov 30th is 3 months (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1970, 8, 31, calendar).until(end, { largestUnit }), + 0, 2, 0, 30, 0, 0, 0, 0, 0, 0, + `Aug 31st to Nov 30th is 2 months 30 days, not 3 months (${calendar}, ${largestUnit})` + ); + } + } + + // Difference between end of longer month in one year to shorter month in + // later year + { + const end = new Temporal.PlainDate(1973, 4, 30, calendar); + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1970, 12, 30, calendar).until(end, { largestUnit: "months" }), + 0, 28, 0, 0, 0, 0, 0, 0, 0, 0, + `Dec 30th 1970 to Apr 30th 1973 is 28 months (${calendar})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1970, 12, 30, calendar).until(end, { largestUnit: "years" }), + 2, 4, 0, 0, 0, 0, 0, 0, 0, 0, + `Dec 30th 1970 to Apr 30th 1973 is 2 years, 4 months (${calendar})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1970, 12, 31, calendar).until(end, { largestUnit: "months" }), + 0, 27, 0, 30, 0, 0, 0, 0, 0, 0, + `Dec 30th 1970 to Apr 30th 1973 is 27 months, 30 days, not 28 months (${calendar})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1970, 12, 31, calendar).until(end, { largestUnit: "years" }), + 2, 3, 0, 30, 0, 0, 0, 0, 0, 0, + `Dec 30th 1970 to Apr 30th 1973 is 2 years, 3 months, 30 days, not 2 years 4 months (${calendar})` + ); + } + + // Difference where months passes through a month that's the same length or + // shorter than either the start or end month + { + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1970, 1, 29, calendar) + .until(new Temporal.PlainDate(1970, 3, 28, calendar), { largestUnit: "months" }), + 0, 1, 0, 28, 0, 0, 0, 0, 0, 0, + `Jan 29th to Mar 28th is 1 month 28 days, not 58 days (${calendar})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDate(1970, 1, 31, calendar) + .until(new Temporal.PlainDate(1971, 5, 30, calendar), { largestUnit: "years" }), + 1, 3, 0, 30, 0, 0, 0, 0, 0, 0, + `Jan 31st 1970 to May 30th 1971 is 1 year, 3 months, 30 days, not 1 year, 2 months, 60 days (${calendar})` + ); + } +} diff --git a/test/intl402/Temporal/PlainDateTime/prototype/since/wrapping-at-end-of-month-gregorian.js b/test/intl402/Temporal/PlainDateTime/prototype/since/wrapping-at-end-of-month-gregorian.js new file mode 100644 index 0000000000..6206937489 --- /dev/null +++ b/test/intl402/Temporal/PlainDateTime/prototype/since/wrapping-at-end-of-month-gregorian.js @@ -0,0 +1,146 @@ +// Copyright (C) 2024 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.since +description: > + Tests balancing of days to months at end of month (ISO-like non-ISO calendars) +includes: [temporalHelpers.js] +features: [Temporal] +---*/ + +for (const calendar of ["buddhist", "gregory", "japanese", "roc"]) { + // Difference between end of longer month to end of following shorter month + { + const end = new Temporal.PlainDateTime(1970, 2, 28, 0, 0, 0, 0, 0, 0, calendar); + for (const largestUnit of ["years", "months"]) { + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1970, 1, 28, 0, 0, 0, 0, 0, 0, calendar).since(end, { largestUnit }), + 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, + `Jan 28th to Feb 28th is one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1970, 1, 29, 0, 0, 0, 0, 0, 0, calendar).since(end, { largestUnit }), + 0, 0, 0, -30, 0, 0, 0, 0, 0, 0, + `Jan 29th to Feb 28th is 30 days, not one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1970, 1, 30, 0, 0, 0, 0, 0, 0, calendar).since(end, { largestUnit }), + 0, 0, 0, -29, 0, 0, 0, 0, 0, 0, + `Jan 30th to Feb 28th is 29 days, not one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1970, 1, 31, 0, 0, 0, 0, 0, 0, calendar).since(end, { largestUnit }), + 0, 0, 0, -28, 0, 0, 0, 0, 0, 0, + `Jan 31st to Feb 28th is 28 days, not one month (${calendar}, ${largestUnit})` + ); + } + } + + // Difference between end of leap-year January to end of leap-year February + { + const end = new Temporal.PlainDateTime(1972, 2, 29, 0, 0, 0, 0, 0, 0, calendar); + for (const largestUnit of ["years", "months"]) { + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1972, 1, 29, 0, 0, 0, 0, 0, 0, calendar).since(end, { largestUnit }), + 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, + `Jan 29th to Feb 29th is one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1972, 1, 30, 0, 0, 0, 0, 0, 0, calendar).since(end, { largestUnit }), + 0, 0, 0, -30, 0, 0, 0, 0, 0, 0, + `Jan 30th to Feb 29th is 30 days, not one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1972, 1, 31, 0, 0, 0, 0, 0, 0, calendar).since(end, { largestUnit }), + 0, 0, 0, -29, 0, 0, 0, 0, 0, 0, + `Jan 31st to Feb 29th is 29 days, not one month (${calendar}, ${largestUnit})` + ); + } + } + + // Difference between end of longer month to end of not-immediately-following + // shorter month + { + const end = new Temporal.PlainDateTime(1970, 11, 30, 0, 0, 0, 0, 0, 0, calendar); + for (const largestUnit of ["years", "months"]) { + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1970, 8, 30, 0, 0, 0, 0, 0, 0, calendar).since(end, { largestUnit }), + 0, -3, 0, 0, 0, 0, 0, 0, 0, 0, + `Aug 30th to Nov 30th is 3 months (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1970, 8, 31, 0, 0, 0, 0, 0, 0, calendar).since(end, { largestUnit }), + 0, -2, 0, -30, 0, 0, 0, 0, 0, 0, + `Aug 31st to Nov 30th is 2 months 30 days, not 3 months (${calendar}, ${largestUnit})` + ); + } + } + + // Difference between end of longer month in one year to shorter month in + // later year + { + const end = new Temporal.PlainDateTime(1973, 4, 30, 0, 0, 0, 0, 0, 0, calendar); + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1970, 12, 30, 0, 0, 0, 0, 0, 0, calendar).since(end, { largestUnit: "months" }), + 0, -28, 0, 0, 0, 0, 0, 0, 0, 0, + `Dec 30th 1970 to Apr 30th 1973 is 28 months (${calendar})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1970, 12, 30, 0, 0, 0, 0, 0, 0, calendar).since(end, { largestUnit: "years" }), + -2, -4, 0, 0, 0, 0, 0, 0, 0, 0, + `Dec 30th 1970 to Apr 30th 1973 is 2 years, 4 months (${calendar})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1970, 12, 31, 0, 0, 0, 0, 0, 0, calendar).since(end, { largestUnit: "months" }), + 0, -27, 0, -30, 0, 0, 0, 0, 0, 0, + `Dec 30th 1970 to Apr 30th 1973 is 27 months, 30 days, not 28 months (${calendar})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1970, 12, 31, 0, 0, 0, 0, 0, 0, calendar).since(end, { largestUnit: "years" }), + -2, -3, 0, -30, 0, 0, 0, 0, 0, 0, + `Dec 30th 1970 to Apr 30th 1973 is 2 years, 3 months, 30 days, not 2 years 4 months (${calendar})` + ); + } + + // Difference where months passes through a month that's the same length or + // shorter than either the start or end month + { + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1970, 1, 29, 0, 0, 0, 0, 0, 0, calendar) + .since(new Temporal.PlainDateTime(1970, 3, 28, 0, 0, 0, 0, 0, 0, calendar), { largestUnit: "months" }), + 0, -1, 0, -28, 0, 0, 0, 0, 0, 0, + `Jan 29th to Mar 28th is 1 month 28 days, not 58 days (${calendar})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1970, 1, 31, 0, 0, 0, 0, 0, 0, calendar) + .since(new Temporal.PlainDateTime(1971, 5, 30, 0, 0, 0, 0, 0, 0, calendar), { largestUnit: "years" }), + -1, -3, 0, -30, 0, 0, 0, 0, 0, 0, + `Jan 31st 1970 to May 30th 1971 is 1 year, 3 months, 30 days, not 1 year, 2 months, 60 days (${calendar})` + ); + } + + // Test that 1-day backoff to maintain date/time sign compatibility backs-off from correct end + // while moving *forwards* in time and does not interfere with month boundaries + // https://github.com/tc39/proposal-temporal/issues/2820 + { + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(2023, 2, 28, 3, 0, 0, 0, 0, 0, calendar) + .since(new Temporal.PlainDateTime(2023, 4, 1, 2, 0, 0, 0, 0, 0, calendar), { largestUnit: "years" }), + 0, -1, 0, -3, -23, 0, 0, 0, 0, 0, + `Feb 28th (3am) to Apr 1st (2am) is -1 month, -3 days, and -23 hours (${calendar})` + ); + } + + // Test that 1-day backoff to maintain date/time sign compatibility backs-off from correct end + // while moving *backwards* in time and does not interfere with month boundaries + // https://github.com/tc39/proposal-temporal/issues/2820 + { + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(2023, 3, 1, 2, 0, 0, 0, 0, 0, calendar) + .since(new Temporal.PlainDateTime(2023, 1, 1, 3, 0, 0, 0, 0, 0, calendar), { largestUnit: "years" }), + 0, 1, 0, 30, 23, 0, 0, 0, 0, 0, + `Mar 1st (2am) to Jan 1st (3am) is 1 month, 30 days, and 23 hours (${calendar})` + ); + } +} diff --git a/test/intl402/Temporal/PlainDateTime/prototype/until/wrapping-at-end-of-month-gregorian.js b/test/intl402/Temporal/PlainDateTime/prototype/until/wrapping-at-end-of-month-gregorian.js new file mode 100644 index 0000000000..8b4663bd1d --- /dev/null +++ b/test/intl402/Temporal/PlainDateTime/prototype/until/wrapping-at-end-of-month-gregorian.js @@ -0,0 +1,146 @@ +// Copyright (C) 2024 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.plaindatetime.prototype.until +description: > + Tests balancing of days to months at end of month (ISO-like non-ISO calendars) +includes: [temporalHelpers.js] +features: [Temporal] +---*/ + +for (const calendar of ["buddhist", "gregory", "japanese", "roc"]) { + // Difference between end of longer month to end of following shorter month + { + const end = new Temporal.PlainDateTime(1970, 2, 28, 0, 0, 0, 0, 0, 0, calendar); + for (const largestUnit of ["years", "months"]) { + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1970, 1, 28, 0, 0, 0, 0, 0, 0, calendar).until(end, { largestUnit }), + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + `Jan 28th to Feb 28th is one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1970, 1, 29, 0, 0, 0, 0, 0, 0, calendar).until(end, { largestUnit }), + 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, + `Jan 29th to Feb 28th is 30 days, not one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1970, 1, 30, 0, 0, 0, 0, 0, 0, calendar).until(end, { largestUnit }), + 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, + `Jan 30th to Feb 28th is 29 days, not one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1970, 1, 31, 0, 0, 0, 0, 0, 0, calendar).until(end, { largestUnit }), + 0, 0, 0, 28, 0, 0, 0, 0, 0, 0, + `Jan 31st to Feb 28th is 28 days, not one month (${calendar}, ${largestUnit})` + ); + } + } + + // Difference between end of leap-year January to end of leap-year February + { + const end = new Temporal.PlainDateTime(1972, 2, 29, 0, 0, 0, 0, 0, 0, calendar); + for (const largestUnit of ["years", "months"]) { + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1972, 1, 29, 0, 0, 0, 0, 0, 0, calendar).until(end, { largestUnit }), + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + `Jan 29th to Feb 29th is one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1972, 1, 30, 0, 0, 0, 0, 0, 0, calendar).until(end, { largestUnit }), + 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, + `Jan 30th to Feb 29th is 30 days, not one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1972, 1, 31, 0, 0, 0, 0, 0, 0, calendar).until(end, { largestUnit }), + 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, + `Jan 31st to Feb 29th is 29 days, not one month (${calendar}, ${largestUnit})` + ); + } + } + + // Difference between end of longer month to end of not-immediately-following + // shorter month + { + const end = new Temporal.PlainDateTime(1970, 11, 30, 0, 0, 0, 0, 0, 0, calendar); + for (const largestUnit of ["years", "months"]) { + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1970, 8, 30, 0, 0, 0, 0, 0, 0, calendar).until(end, { largestUnit }), + 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, + `Aug 30th to Nov 30th is 3 months (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1970, 8, 31, 0, 0, 0, 0, 0, 0, calendar).until(end, { largestUnit }), + 0, 2, 0, 30, 0, 0, 0, 0, 0, 0, + `Aug 31st to Nov 30th is 2 months 30 days, not 3 months (${calendar}, ${largestUnit})` + ); + } + } + + // Difference between end of longer month in one year to shorter month in + // later year + { + const end = new Temporal.PlainDateTime(1973, 4, 30, 0, 0, 0, 0, 0, 0, calendar); + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1970, 12, 30, 0, 0, 0, 0, 0, 0, calendar).until(end, { largestUnit: "months" }), + 0, 28, 0, 0, 0, 0, 0, 0, 0, 0, + `Dec 30th 1970 to Apr 30th 1973 is 28 months (${calendar})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1970, 12, 30, 0, 0, 0, 0, 0, 0, calendar).until(end, { largestUnit: "years" }), + 2, 4, 0, 0, 0, 0, 0, 0, 0, 0, + `Dec 30th 1970 to Apr 30th 1973 is 2 years, 4 months (${calendar})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1970, 12, 31, 0, 0, 0, 0, 0, 0, calendar).until(end, { largestUnit: "months" }), + 0, 27, 0, 30, 0, 0, 0, 0, 0, 0, + `Dec 30th 1970 to Apr 30th 1973 is 27 months, 30 days, not 28 months (${calendar})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1970, 12, 31, 0, 0, 0, 0, 0, 0, calendar).until(end, { largestUnit: "years" }), + 2, 3, 0, 30, 0, 0, 0, 0, 0, 0, + `Dec 30th 1970 to Apr 30th 1973 is 2 years, 3 months, 30 days, not 2 years 4 months (${calendar})` + ); + } + + // Difference where months passes through a month that's the same length or + // shorter than either the start or end month + { + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1970, 1, 29, 0, 0, 0, 0, 0, 0, calendar) + .until(new Temporal.PlainDateTime(1970, 3, 28, 0, 0, 0, 0, 0, 0, calendar), { largestUnit: "months" }), + 0, 1, 0, 28, 0, 0, 0, 0, 0, 0, + `Jan 29th to Mar 28th is 1 month 28 days, not 58 days (${calendar})` + ); + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(1970, 1, 31, 0, 0, 0, 0, 0, 0, calendar) + .until(new Temporal.PlainDateTime(1971, 5, 30, 0, 0, 0, 0, 0, 0, calendar), { largestUnit: "years" }), + 1, 3, 0, 30, 0, 0, 0, 0, 0, 0, + `Jan 31st 1970 to May 30th 1971 is 1 year, 3 months, 30 days, not 1 year, 2 months, 60 days (${calendar})` + ); + } + + // Test that 1-day backoff to maintain date/time sign compatibility backs-off from correct end + // while moving *backwards* in time and does not interfere with month boundaries + // https://github.com/tc39/proposal-temporal/issues/2820 + { + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(2023, 2, 28, 3, 0, 0, 0, 0, 0, calendar) + .until(new Temporal.PlainDateTime(2023, 4, 1, 2, 0, 0, 0, 0, 0, calendar), { largestUnit: "years" }), + 0, 1, 0, 3, 23, 0, 0, 0, 0, 0, + `Feb 28th (3am) to Apr 1st (2am) is 1 month, 3 days, and 23 hours (${calendar})` + ); + } + + // Test that 1-day backoff to maintain date/time sign compatibility backs-off from correct end + // while moving *forwards* in time and does not interfere with month boundaries + // https://github.com/tc39/proposal-temporal/issues/2820 + { + TemporalHelpers.assertDuration( + new Temporal.PlainDateTime(2023, 3, 1, 2, 0, 0, 0, 0, 0, calendar) + .until(new Temporal.PlainDateTime(2023, 1, 1, 3, 0, 0, 0, 0, 0, calendar), { largestUnit: "years" }), + 0, -1, 0, -30, -23, 0, 0, 0, 0, 0, + `Mar 1st (2am) to Jan 1st (3am) is -1 month, -30 days, and -23 hours (${calendar})` + ); + } +} diff --git a/test/intl402/Temporal/ZonedDateTime/prototype/since/wrapping-at-end-of-month-gregorian.js b/test/intl402/Temporal/ZonedDateTime/prototype/since/wrapping-at-end-of-month-gregorian.js new file mode 100644 index 0000000000..9c41728900 --- /dev/null +++ b/test/intl402/Temporal/ZonedDateTime/prototype/since/wrapping-at-end-of-month-gregorian.js @@ -0,0 +1,122 @@ +// Copyright (C) 2024 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.since +description: > + Tests balancing of days to months at end of month (ISO-like non-ISO calendars) +includes: [temporalHelpers.js] +features: [Temporal] +---*/ + +for (const calendar of ["buddhist", "gregory", "japanese", "roc"]) { + // Difference between end of longer month to end of following shorter month + { + const end = new Temporal.ZonedDateTime(5011200_000_000_000n /* = 1970-02-28T00Z */, "UTC", calendar); + for (const largestUnit of ["years", "months"]) { + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(2332800_000_000_000n /* = 1970-01-28T00Z */, "UTC", calendar).since(end, { largestUnit }), + 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, + `Jan 28th to Feb 28th is one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(2419200_000_000_000n /* = 1970-01-29T00Z */, "UTC", calendar).since(end, { largestUnit }), + 0, 0, 0, -30, 0, 0, 0, 0, 0, 0, + `Jan 29th to Feb 28th is 30 days, not one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(2505600_000_000_000n /* = 1970-01-30T00Z */, "UTC", calendar).since(end, { largestUnit }), + 0, 0, 0, -29, 0, 0, 0, 0, 0, 0, + `Jan 30th to Feb 28th is 29 days, not one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(2592000_000_000_000n /* = 1970-01-31T00Z */, "UTC", calendar).since(end, { largestUnit }), + 0, 0, 0, -28, 0, 0, 0, 0, 0, 0, + `Jan 31st to Feb 28th is 28 days, not one month (${calendar}, ${largestUnit})` + ); + } + } + + // Difference between end of leap-year January to end of leap-year February + { + const end = new Temporal.ZonedDateTime(68169600_000_000_000n /* = 1972-02-29T00Z */, "UTC", calendar); + for (const largestUnit of ["years", "months"]) { + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(65491200_000_000_000n /* = 1972-01-29T00Z */, "UTC", calendar).since(end, { largestUnit }), + 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, + `Jan 29th to Feb 29th is one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(65577600_000_000_000n /* = 1972-01-30T00Z */, "UTC", calendar).since(end, { largestUnit }), + 0, 0, 0, -30, 0, 0, 0, 0, 0, 0, + `Jan 30th to Feb 29th is 30 days, not one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(65664000_000_000_000n /* = 1972-01-31T00Z */, "UTC", calendar).since(end, { largestUnit }), + 0, 0, 0, -29, 0, 0, 0, 0, 0, 0, + `Jan 31st to Feb 29th is 29 days, not one month (${calendar}, ${largestUnit})` + ); + } + } + + // Difference between end of longer month to end of not-immediately-following + // shorter month + { + const end = new Temporal.ZonedDateTime(28771200_000_000_000n /* = 1970-11-30T00Z */, "UTC", calendar); + for (const largestUnit of ["years", "months"]) { + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(20822400_000_000_000n /* = 1970-08-30T00Z */, "UTC", calendar).since(end, { largestUnit }), + 0, -3, 0, 0, 0, 0, 0, 0, 0, 0, + `Aug 30th to Nov 30th is 3 months (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(20908800_000_000_000n /* = 1970-08-31T00Z */, "UTC", calendar).since(end, { largestUnit }), + 0, -2, 0, -30, 0, 0, 0, 0, 0, 0, + `Aug 31st to Nov 30th is 2 months 30 days, not 3 months (${calendar}, ${largestUnit})` + ); + } + } + + // Difference between end of longer month in one year to shorter month in + // later year + { + const end = new Temporal.ZonedDateTime(104976000_000_000_000n /* = 1973-04-30T00Z */, "UTC", calendar); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(31363200_000_000_000n /* = 1970-12-30T00Z */, "UTC", calendar).since(end, { largestUnit: "months" }), + 0, -28, 0, 0, 0, 0, 0, 0, 0, 0, + `Dec 30th 1970 to Apr 30th 1973 is 28 months (${calendar})` + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(31363200_000_000_000n /* = 1970-12-30T00Z */, "UTC", calendar).since(end, { largestUnit: "years" }), + -2, -4, 0, 0, 0, 0, 0, 0, 0, 0, + `Dec 30th 1970 to Apr 30th 1973 is 2 years, 4 months (${calendar})` + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(31449600_000_000_000n /* = 1970-12-31T00Z */, "UTC", calendar).since(end, { largestUnit: "months" }), + 0, -27, 0, -30, 0, 0, 0, 0, 0, 0, + `Dec 30th 1970 to Apr 30th 1973 is 27 months, 30 days, not 28 months (${calendar})` + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(31449600_000_000_000n /* = 1970-12-31T00Z */, "UTC", calendar).since(end, { largestUnit: "years" }), + -2, -3, 0, -30, 0, 0, 0, 0, 0, 0, + `Dec 30th 1970 to Apr 30th 1973 is 2 years, 3 months, 30 days, not 2 years 4 months (${calendar})` + ); + } + + // Difference where months passes through a month that's the same length or + // shorter than either the start or end month + { + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(2419200_000_000_000n /* = 1970-01-29T00Z */, "UTC", calendar) + .since(new Temporal.ZonedDateTime(7430400_000_000_000n /* = 1970-03-28T00Z */, "UTC", calendar), { largestUnit: "months" }), + 0, -1, 0, -28, 0, 0, 0, 0, 0, 0, + `Jan 29th to Mar 28th is 1 month 28 days, not 58 days (${calendar})` + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(2592000_000_000_000n /* = 1970-01-31T00Z */, "UTC", calendar) + .since(new Temporal.ZonedDateTime(44409600_000_000_000n /* = 1971-05-30T00Z */, "UTC", calendar), { largestUnit: "years" }), + -1, -3, 0, -30, 0, 0, 0, 0, 0, 0, + `Jan 31st 1970 to May 30th 1971 is 1 year, 3 months, 30 days, not 1 year, 2 months, 60 days (${calendar})` + ); + } +} diff --git a/test/intl402/Temporal/ZonedDateTime/prototype/until/wrapping-at-end-of-month-gregorian.js b/test/intl402/Temporal/ZonedDateTime/prototype/until/wrapping-at-end-of-month-gregorian.js new file mode 100644 index 0000000000..5607102b70 --- /dev/null +++ b/test/intl402/Temporal/ZonedDateTime/prototype/until/wrapping-at-end-of-month-gregorian.js @@ -0,0 +1,120 @@ +// Copyright (C) 2024 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-temporal.zoneddatetime.prototype.until +description: > + Tests balancing of days to months at end of month (ISO-like non-ISO calendars) +includes: [temporalHelpers.js] +features: [Temporal] +---*/ + +for (const calendar of ["buddhist", "gregory", "japanese", "roc"]) { + // Difference between end of longer month to end of following shorter month + { + const end = new Temporal.ZonedDateTime(5011200_000_000_000n /* = 1970-02-28T00Z */, "UTC", calendar); + for (const largestUnit of ["years", "months"]) { + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(2332800_000_000_000n /* = 1970-01-28T00Z */, "UTC", calendar).until(end, { largestUnit }), + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + `Jan 28th to Feb 28th is one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(2419200_000_000_000n /* = 1970-01-29T00Z */, "UTC", calendar).until(end, { largestUnit }), + 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, + `Jan 29th to Feb 28th is 30 days, not one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(2505600_000_000_000n /* = 1970-01-30T00Z */, "UTC", calendar).until(end, { largestUnit }), + 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, + `Jan 30th to Feb 28th is 29 days, not one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(2592000_000_000_000n /* = 1970-01-31T00Z */, "UTC", calendar).until(end, { largestUnit }), + 0, 0, 0, 28, 0, 0, 0, 0, 0, 0, + `Jan 31st to Feb 28th is 28 days, not one month (${calendar}, ${largestUnit})` + ); + } + } + + // Difference between end of leap-year January to end of leap-year February + { + const end = new Temporal.ZonedDateTime(68169600_000_000_000n /* = 1972-02-29T00Z */, "UTC", calendar); + for (const largestUnit of ["years", "months"]) { + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(65491200_000_000_000n /* = 1972-01-29T00Z */, "UTC", calendar).until(end, { largestUnit }), + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + `Jan 29th to Feb 29th is one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(65577600_000_000_000n /* = 1972-01-30T00Z */, "UTC", calendar).until(end, { largestUnit }), + 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, + `Jan 30th to Feb 29th is 30 days, not one month (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(65664000_000_000_000n /* = 1972-01-31T00Z */, "UTC", calendar).until(end, { largestUnit }), + 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, + `Jan 31st to Feb 29th is 29 days, not one month (${calendar}, ${largestUnit})` + ); + } + } + + // Difference between end of longer month to end of not-immediately-following + // shorter month + { + const end = new Temporal.ZonedDateTime(28771200_000_000_000n /* = 1970-11-30T00Z */, "UTC", calendar); + for (const largestUnit of ["years", "months"]) { + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(20822400_000_000_000n /* = 1970-08-30T00Z */, "UTC", calendar).until(end, { largestUnit }), + 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, + `Aug 30th to Nov 30th is 3 months (${calendar}, ${largestUnit})` + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(20908800_000_000_000n /* = 1970-08-31T00Z */, "UTC", calendar).until(end, { largestUnit }), + 0, 2, 0, 30, 0, 0, 0, 0, 0, 0, + `Aug 31st to Nov 30th is 2 months 30 days, not 3 months (${calendar}, ${largestUnit})` + ); + } + } + + // Difference between end of longer month in one year to shorter month in + // later year + { + const end = new Temporal.ZonedDateTime(104976000_000_000_000n /* = 1973-04-30T00Z */, "UTC", calendar); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(31363200_000_000_000n /* = 1970-12-30T00Z */, "UTC", calendar).until(end, { largestUnit: "months" }), + 0, 28, 0, 0, 0, 0, 0, 0, 0, 0, + `Dec 30th 1970 to Apr 30th 1973 is 28 months (${calendar})` + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(31363200_000_000_000n /* = 1970-12-30T00Z */, "UTC", calendar).until(end, { largestUnit: "years" }), + 2, 4, 0, 0, 0, 0, 0, 0, 0, 0, + `Dec 30th 1970 to Apr 30th 1973 is 2 years, 4 months (${calendar})` + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(31449600_000_000_000n /* = 1970-12-31T00Z */, "UTC", calendar).until(end, { largestUnit: "months" }), + 0, 27, 0, 30, 0, 0, 0, 0, 0, 0, + `Dec 30th 1970 to Apr 30th 1973 is 27 months, 30 days, not 28 months (${calendar})` + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(31449600_000_000_000n /* = 1970-12-31T00Z */, "UTC", calendar).until(end, { largestUnit: "years" }), + 2, 3, 0, 30, 0, 0, 0, 0, 0, 0, + `Dec 30th 1970 to Apr 30th 1973 is 2 years, 3 months, 30 days, not 2 years 4 months (${calendar})` + ); + } + + // Difference where months passes through a month that's the same length or + // shorter than either the start or end month + { + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(2419200_000_000_000n /* = 1970-01-29T00Z */, "UTC", calendar).until(new Temporal.ZonedDateTime(7430400_000_000_000n /* = 1970-03-28T00Z */, "UTC", calendar), { largestUnit: "months" }), + 0, 1, 0, 28, 0, 0, 0, 0, 0, 0, + `Jan 29th to Mar 28th is 1 month 28 days, not 58 days (${calendar})` + ); + TemporalHelpers.assertDuration( + new Temporal.ZonedDateTime(2592000_000_000_000n /* = 1970-01-31T00Z */, "UTC", calendar).until(new Temporal.ZonedDateTime(44409600_000_000_000n /* = 1971-05-30T00Z */, "UTC", calendar), { largestUnit: "years" }), + 1, 3, 0, 30, 0, 0, 0, 0, 0, 0, + `Jan 31st 1970 to May 30th 1971 is 1 year, 3 months, 30 days, not 1 year, 2 months, 60 days (${calendar})` + ); + } +}