Skip to content

Commit a6fb0dd

Browse files
committed
Intl Era/Month Code: Tests for PlainMonthDay reference years
The reference years for the Chinese and Dangi lunisolar calendars should follow a hardcoded table, which is tested in the various chinese-* and dangi-* tests in this commit. The behaviour of `{ overflow: 'constrain' }` has changed for leap months that are not known for certain to ever have had 30 days, which is tested in chinese-month-codes.js and dangi-month-codes.js. This also tightens the test expectations for creation of PlainMonthDay in all other calendars, to make sure the reference years are correctly determined from the deterministic steps listed in the spec text. See: tc39/proposal-intl-era-monthcode#108 This normative change was adopted in the January 2026 TC39 plenary.
1 parent 6c2b201 commit a6fb0dd

15 files changed

+386
-222
lines changed

test/intl402/Temporal/PlainMonthDay/from/chinese-30-day-leap-months.js

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,37 @@
33

44
/*---
55
esid: sec-temporal.plainmonthday.from
6-
features: [Temporal]
6+
features: [Temporal, Intl.Era-monthcode]
77
description: Check correct results for 30-day leap months
88
includes: [temporalHelpers.js]
99
---*/
1010

11-
// Common leap months should find a result not too far into the past.
11+
// Reference year for day 30 of leap months
1212
//
1313
// Month -> ISO year
1414
//
15-
// M01L <uncommon>
16-
// M02L 1765
15+
// M01L -
16+
// M02L -
1717
// M03L 1955
1818
// M04L 1944
1919
// M05L 1952
2020
// M06L 1941
2121
// M07L 1938
22-
// M08L 1718
23-
// M09L <uncommon>
24-
// M10L <uncommon>
25-
// M11L <uncommon>
26-
// M12L <uncommon>
22+
// M08L -
23+
// M09L -
24+
// M10L -
25+
// M11L -
26+
// M12L -
2727
//
28-
// M02L and M08L with 29 days is common, but with 30 is actually rather uncommon.
28+
// M02L and M08L with 29 days are common, but with 30 are actually rather
29+
// uncommon and are not known to have occurred in the range in which the Chinese
30+
// calendar can be accurately calculated.
2931
//
3032
// See also "The Mathematics of the Chinese Calendar", Table 21 [1] for a
3133
// distribution of leap months.
3234
//
3335
// [1] https://www.xirugu.com/CHI500/Dates_Time/Chinesecalender.pdf
3436

35-
// In this test, we skip the months whose most recent year is outside
36-
// the range 1900-2100.
3737
const monthCodesWithYears = [
3838
{ monthCode: "M03L", referenceYear: 1955 },
3939
{ monthCode: "M04L", referenceYear: 1944 },
@@ -44,19 +44,15 @@ const monthCodesWithYears = [
4444

4545
const calendar = "chinese";
4646

47-
// Months can have up to 30 days.
48-
const day = 30;
49-
5047
for (let {monthCode, referenceYear} of monthCodesWithYears) {
51-
let pmd = Temporal.PlainMonthDay.from({calendar, monthCode, day});
52-
TemporalHelpers.assertPlainMonthDay(pmd, monthCode, day, monthCode, referenceYear);
48+
const pmd = Temporal.PlainMonthDay.from({ calendar, monthCode, day: 30 });
49+
TemporalHelpers.assertPlainMonthDay(pmd, monthCode, 30, `${monthCode}-30`, referenceYear);
5350

54-
let constrain = Temporal.PlainMonthDay.from({calendar, monthCode, day: day + 1}, {overflow: "constrain"});
55-
TemporalHelpers.assertPlainMonthDay(constrain, monthCode, day, `${monthCode} (constrained)`, referenceYear);
51+
const constrain = Temporal.PlainMonthDay.from({ calendar, monthCode, day: 31 });
52+
TemporalHelpers.assertPlainMonthDay(constrain, monthCode, 30, `${monthCode} (constrained)`, referenceYear);
5653
assert.sameValue(constrain.equals(pmd), true);
5754

5855
assert.throws(RangeError, () => {
59-
Temporal.PlainMonthDay.from({calendar, monthCode, day: day + 1}, {overflow: "reject"});
56+
Temporal.PlainMonthDay.from({ calendar, monthCode, day: 31 }, { overflow: "reject" });
6057
});
6158
}
62-

test/intl402/Temporal/PlainMonthDay/from/chinese-leap-month-codes-common.js

Lines changed: 67 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
esid: sec-temporal.plainmonthday.from
66
description: PlainMonthDay can be created for common leap month codes in Chinese calendar
77
features: [Temporal, Intl.Era-monthcode]
8+
includes: [temporalHelpers.js]
89
---*/
910

1011
// Test common leap months in the Chinese calendar
@@ -14,37 +15,78 @@ features: [Temporal, Intl.Era-monthcode]
1415
// M02L, M03L, M04L, M05L, M06L, M07L, M08L
1516
//
1617
// The distribution of leap months follows astronomical calculations.
18+
19+
// Reference year for days 1-29 of leap months
20+
//
21+
// Month -> ISO year
22+
//
23+
// M01L -
24+
// M02L 1947
25+
// M03L 1966
26+
// M04L 1963
27+
// M05L 1971
28+
// M06L 1960
29+
// M07L 1968
30+
// M08L 1957
31+
// M09L 2014
32+
// M10L 1984
33+
// M11L 2033
34+
// M12L -
1735
//
18-
// Note: M01L, M02L and M08L through M12L are temporarily omitted from this
19-
// test because while any leap month can have 30 days, there isn't a year in the
20-
// supported range where these months do, and it's not yet well-defined what
21-
// reference ISO year should be used.
22-
// See https://github.com/tc39/proposal-intl-era-monthcode/issues/60
36+
// M01L and M12L are not known to have occurred in the range in which the
37+
// Chinese calendar can be accurately calculated. M09L, M10L, and M11L are
38+
// uncommon and did not occur between 1900-1972, so require reference years in
39+
// the more recent past or near future.
2340

24-
const calendar = "chinese";
41+
const monthCodesWithYears = [
42+
{ monthCode: "M02L", referenceYear1: 1947 },
43+
{ monthCode: "M03L", referenceYear1: 1966, has30: true },
44+
{ monthCode: "M04L", referenceYear1: 1963, has30: true },
45+
{ monthCode: "M05L", referenceYear1: 1971, has30: true },
46+
{ monthCode: "M06L", referenceYear1: 1960, has30: true },
47+
{ monthCode: "M07L", referenceYear1: 1968, has30: true },
48+
{ monthCode: "M08L", referenceYear1: 1957 },
49+
{ monthCode: "M09L", referenceYear1: 2014 },
50+
{ monthCode: "M10L", referenceYear1: 1984 },
51+
{ monthCode: "M11L", referenceYear1: 2033, referenceYear29: 2034 },
52+
];
2553

26-
// Test leap months M03L-M07L with day 30
27-
// These are well-established leap months that can have 30 days
28-
const leapMonthsWith30Days = ["M03L", "M04L", "M05L", "M06L", "M07L"];
54+
const calendar = "chinese";
2955

30-
for (const monthCode of leapMonthsWith30Days) {
56+
for (const { monthCode, referenceYear1, referenceYear29 = referenceYear1, has30 = false } of monthCodesWithYears) {
3157
// Test creation with monthCode
3258
const pmd = Temporal.PlainMonthDay.from({ calendar, monthCode, day: 1 });
33-
assert.sameValue(pmd.monthCode, monthCode, `leap monthCode ${monthCode} should be preserved`);
34-
assert.sameValue(pmd.day, 1, `day should be 1 for ${monthCode}`);
35-
36-
// These leap months can have up to 30 days (minimum for PlainMonthDay)
37-
const pmd30 = Temporal.PlainMonthDay.from({ calendar, monthCode, day: 30 });
38-
assert.sameValue(pmd30.monthCode, monthCode, `${monthCode} with day 30 should be valid`);
39-
assert.sameValue(pmd30.day, 30, `day should be 30 for ${monthCode}`);
40-
41-
// Test constrain overflow - day 31 should be constrained to 30
42-
const constrained = Temporal.PlainMonthDay.from(
43-
{ calendar, monthCode, day: 31 },
44-
{ overflow: "constrain" }
45-
);
46-
assert.sameValue(constrained.monthCode, monthCode, `${monthCode} should be preserved with constrain`);
47-
assert.sameValue(constrained.day, 30, `day 31 should be constrained to 30 for ${monthCode}`);
59+
TemporalHelpers.assertPlainMonthDay(pmd, monthCode, 1, `${monthCode}-01`, referenceYear1);
60+
61+
// These leap months can have at least 29 days
62+
const pmd29 = Temporal.PlainMonthDay.from({ calendar, monthCode, day: 29 });
63+
TemporalHelpers.assertPlainMonthDay(pmd29, monthCode, 29, `${monthCode}-29`, referenceYear29);
64+
65+
// Test constraining; leap months that never occurred with 30 days should
66+
// constrain to the regular month. See chinese-30-day-leap-months.js for
67+
// constrain behaviour in leap months with 30 days
68+
if (!has30) {
69+
const regularMonth = monthCode.slice(0, 3);
70+
71+
// Test constrain overflow - day 30 should be constrained to day 30 of the
72+
// regular month
73+
const constrain30 = Temporal.PlainMonthDay.from({ calendar, monthCode, day: 30 });
74+
assert.sameValue(constrain30.monthCode, regularMonth, `${monthCode}-30 should be constrained to ${regularMonth}-30`);
75+
assert.sameValue(constrain30.day, 30, `day 30 should be preserved for ${monthCode}`);
76+
// Reference year is tested in chinese-month-codes.js
77+
78+
// Test constrain overflow - day 31 should be constrained to day 30 of the
79+
// regular month
80+
const constrain31 = Temporal.PlainMonthDay.from({ calendar, monthCode, day: 31 });
81+
assert.sameValue(constrain31.monthCode, regularMonth, `${monthCode}-31 should be constrained to ${regularMonth}-30`);
82+
assert.sameValue(constrain31.day, 30, `day 31 should be constrained to 30 for ${monthCode}`);
83+
// Reference year is tested in chinese-month-codes.js
84+
85+
// Test reject overflow - day 30 should throw
86+
assert.throws(RangeError, () => {
87+
Temporal.PlainMonthDay.from({ calendar, monthCode, day: 30 }, { overflow: "reject" });
88+
}, `${monthCode}-30 should throw with reject`);
89+
}
4890

4991
// Test reject overflow - day 31 should throw
5092
assert.throws(RangeError, () => {

test/intl402/Temporal/PlainMonthDay/from/chinese-month-codes.js

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
/*---
55
esid: sec-temporal.plainmonthday.from
66
description: PlainMonthDay can be created for all regular month codes (M01-M12) in Chinese calendar
7+
includes: [temporalHelpers.js]
78
features: [Temporal, Intl.Era-monthcode]
89
---*/
910

@@ -12,29 +13,38 @@ features: [Temporal, Intl.Era-monthcode]
1213
// Leap months (M01L-M12L) are tested elsewhere
1314

1415
const calendar = "chinese";
15-
const monthCodes = [
16-
"M01", "M02", "M03", "M04", "M05", "M06",
17-
"M07", "M08", "M09", "M10", "M11", "M12"
16+
const monthCodesWithYears = [
17+
{ monthCode: "M01", referenceYear30: 1970 },
18+
{ monthCode: "M02", referenceYear30: 1972 },
19+
{ monthCode: "M03", referenceYear30: 1966 },
20+
{ monthCode: "M04", referenceYear30: 1970 },
21+
{ monthCode: "M05", referenceYear30: 1972 },
22+
{ monthCode: "M06", referenceYear30: 1971 },
23+
{ monthCode: "M07", referenceYear30: 1972 },
24+
{ monthCode: "M08", referenceYear30: 1971 },
25+
{ monthCode: "M09", referenceYear30: 1972 },
26+
{ monthCode: "M10", referenceYear30: 1972 },
27+
{ monthCode: "M11", referenceYear30: 1970 },
28+
{ monthCode: "M12", referenceYear30: 1972 }
1829
];
1930

20-
for (const monthCode of monthCodes) {
31+
for (const { monthCode, referenceYear30 } of monthCodesWithYears) {
2132
// Test creation with monthCode
33+
// The reference year is 1972 for days that occur every year
2234
const pmd = Temporal.PlainMonthDay.from({ calendar, monthCode, day: 1 });
23-
assert.sameValue(pmd.monthCode, monthCode, `monthCode ${monthCode} should be preserved`);
24-
assert.sameValue(pmd.day, 1, "day should be 1");
35+
TemporalHelpers.assertPlainMonthDay(pmd, monthCode, 1, `${monthCode}-01`, 1972);
36+
37+
// Test with day 29 (every month has at least 29 days every year)
38+
const pmd29 = Temporal.PlainMonthDay.from({ calendar, monthCode, day: 29 });
39+
TemporalHelpers.assertPlainMonthDay(pmd29, monthCode, 29, `${monthCode}-29`, 1972);
2540

2641
// Test with day 30 (months can have 29 or 30 days)
2742
const pmd30 = Temporal.PlainMonthDay.from({ calendar, monthCode, day: 30 });
28-
assert.sameValue(pmd30.monthCode, monthCode, `${monthCode} with day 30 should be valid`);
29-
assert.sameValue(pmd30.day, 30, `day should be 30 for ${monthCode}`);
43+
TemporalHelpers.assertPlainMonthDay(pmd30, monthCode, 30, `${monthCode}-30`, referenceYear30);
3044

3145
// Test constrain overflow - Chinese months vary from 29-30 days
32-
const constrained = Temporal.PlainMonthDay.from(
33-
{ calendar, monthCode, day: 31 },
34-
{ overflow: "constrain" }
35-
);
36-
assert.sameValue(constrained.monthCode, monthCode, `${monthCode} should be preserved with constrain`);
37-
assert.sameValue(constrained.day, 30, `day 31 should be constrained to 30 for ${monthCode}`);
46+
const constrained = Temporal.PlainMonthDay.from({ calendar, monthCode, day: 31 });
47+
TemporalHelpers.assertPlainMonthDay(constrained, monthCode, 30, `${monthCode}-31 constrained`, referenceYear30);
3848

3949
// Test reject overflow for day 31
4050
assert.throws(RangeError, () => {

test/intl402/Temporal/PlainMonthDay/from/coptic-month-codes.js

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
esid: sec-temporal.plainmonthday.from
66
description: PlainMonthDay can be created for all month codes (M01-M13) in Coptic calendar
77
features: [Temporal, Intl.Era-monthcode]
8+
includes: [temporalHelpers.js]
89
---*/
910

1011
// Test that all month codes M01-M13 are valid for the Coptic calendar
@@ -22,21 +23,18 @@ const regularMonthCodes = [
2223
for (const monthCode of regularMonthCodes) {
2324
// Test creation with monthCode
2425
const pmd = Temporal.PlainMonthDay.from({ calendar, monthCode, day: 1 });
25-
assert.sameValue(pmd.monthCode, monthCode, `monthCode ${monthCode} should be preserved`);
26-
assert.sameValue(pmd.day, 1, "day should be 1");
26+
TemporalHelpers.assertPlainMonthDay(pmd, monthCode, 1, `${monthCode}-01`, 1972);
2727

2828
// Test with day 30 (all regular months have 30 days)
2929
const pmd30 = Temporal.PlainMonthDay.from({ calendar, monthCode, day: 30 });
30-
assert.sameValue(pmd30.monthCode, monthCode, `${monthCode} with day 30 should be valid`);
31-
assert.sameValue(pmd30.day, 30, `day should be 30 for ${monthCode}`);
30+
TemporalHelpers.assertPlainMonthDay(pmd30, monthCode, 30, `${monthCode}-30`, 1972);
3231

3332
// Test overflow: constrain to 30
3433
const constrained = Temporal.PlainMonthDay.from(
3534
{ calendar, monthCode, day: 31 },
3635
{ overflow: "constrain" }
3736
);
38-
assert.sameValue(constrained.monthCode, monthCode, `${monthCode} should be preserved with constrain`);
39-
assert.sameValue(constrained.day, 30, `day 31 should be constrained to 30 for ${monthCode}`);
37+
TemporalHelpers.assertPlainMonthDay(constrained, monthCode, 30, `day 31 should be constrained to 30 for ${monthCode}`, 1972);
4038

4139
// Test overflow: reject should throw for day 31
4240
assert.throws(RangeError, () => {
@@ -47,17 +45,16 @@ for (const monthCode of regularMonthCodes) {
4745
// M13: Short month (Epagomenal days) with 5 or 6 days
4846

4947
// Test M13 with day 6 (maximum, valid in leap years)
48+
// Reference year 1971
5049
const pmdM13Day6 = Temporal.PlainMonthDay.from({ calendar, monthCode: "M13", day: 6 });
51-
assert.sameValue(pmdM13Day6.monthCode, "M13", "M13 should be valid with day 6");
52-
assert.sameValue(pmdM13Day6.day, 6, "day should be 6 for M13");
50+
TemporalHelpers.assertPlainMonthDay(pmdM13Day6, "M13", 6, "M13-06", 1971);
5351

5452
// Test M13 overflow: constrain to maximum
5553
const constrained = Temporal.PlainMonthDay.from(
5654
{ calendar, monthCode: "M13", day: 7 },
5755
{ overflow: "constrain" }
5856
);
59-
assert.sameValue(constrained.monthCode, "M13", "M13 should be preserved with constrain");
60-
assert.sameValue(constrained.day, 6, "day 7 should be constrained to 6 for M13");
57+
TemporalHelpers.assertPlainMonthDay(constrained, "M13", 6, "day 7 should be constrained to 6 for M13", 1971);
6158

6259
// Test M13 overflow: reject should throw for day 7
6360
assert.throws(RangeError, () => {
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright (C) 2024 Mozilla Corporation. All rights reserved.
2+
// This code is governed by the BSD license found in the LICENSE file.
3+
4+
/*---
5+
esid: sec-temporal.plainmonthday.from
6+
features: [Temporal]
7+
description: Check correct results for 30-day leap months
8+
includes: [temporalHelpers.js]
9+
---*/
10+
11+
// These reference years happen to be identical to the ones in
12+
// chinese-30-day-leap-months.js.
13+
14+
const monthCodesWithYears = [
15+
{ monthCode: "M03L", referenceYear: 1955 },
16+
{ monthCode: "M04L", referenceYear: 1944 },
17+
{ monthCode: "M05L", referenceYear: 1952 },
18+
{ monthCode: "M06L", referenceYear: 1941 },
19+
{ monthCode: "M07L", referenceYear: 1938 }
20+
];
21+
22+
const calendar = "dangi";
23+
24+
// Months can have up to 30 days.
25+
const day = 30;
26+
27+
for (let {monthCode, referenceYear} of monthCodesWithYears) {
28+
let pmd = Temporal.PlainMonthDay.from({calendar, monthCode, day});
29+
TemporalHelpers.assertPlainMonthDay(pmd, monthCode, day, monthCode, referenceYear);
30+
31+
let constrain = Temporal.PlainMonthDay.from({calendar, monthCode, day: day + 1}, {overflow: "constrain"});
32+
TemporalHelpers.assertPlainMonthDay(constrain, monthCode, day, `${monthCode} (constrained)`, referenceYear);
33+
assert.sameValue(constrain.equals(pmd), true);
34+
35+
assert.throws(RangeError, () => {
36+
Temporal.PlainMonthDay.from({calendar, monthCode, day: day + 1}, {overflow: "reject"});
37+
});
38+
}
39+

0 commit comments

Comments
 (0)