[JSC][Temporal] Implement ISODateToFields and CalendarMergeFields as the spec's own operations
https://bugs.webkit.org/show_bug.cgi?id=321189
rdar://184247766

Reviewed by Yusuke Suzuki.

Add isoDateToFields and calendarMergeFields, plus the CalendarFieldKeysPresent,
CalendarFieldKeysToIgnore and NonISOFieldKeysToIgnore operations they rest on.
Every .with() entry point previously hand-fused those steps with its own
field-priority rules, PlainMonthDay's in 44 lines, and the MergeMode enum they
needed is gone. differenceYearMonth's two hand-rolled copies of
ISODateToFields(~year-month~) are converted too.

CalendarFieldKeysToIgnore is what makes a year-only change correct on a
lunisolar calendar: month and monthCode encode one field, so setting either must
discard the other. Carrying the ordinal across a year change picks the wrong
month once a leap month precedes it — chinese M05 is ordinal 5 in 2018, 6 in
2020.

This also fixes PlainYearMonth.prototype.add/subtract shifting the month by a
constant -2 for "buddhist", "roc" and "japanese" when the receiver's ISO year
was in roughly 1..1582, confirmed pre-existing by A/B against a pre-conversion
build. Root cause never established.

Add calendarDayOfWeek, calendarWeekOfYear and calendarYearOfWeek so all fifteen
CalendarISOToDate fields reach the getters through one accessor family; the
spec's "undefined for calendars with no well-defined week calendar system" rule
was re-derived at six getter sites and is now one std::optional. Remove the
eight forwarders this leaves unreferenced.

Inline TemporalPlainTime::with into its prototype, and restore
ZonedDateTime.prototype.with to spec step order: steps 9, 10-16 and 18 ran after
19-22, so ISODateToFields could throw only after the options getters had run.

Tests: JSTests/stress/temporal-calendar-merge-fields-with.js
       JSTests/stress/temporal-isodatetofields-call-sites.js
       Source/JavaScriptCore/API/tests/TemporalCoreTest.cpp

Canonical link: https://commits.webkit.org/318785@main
diff --git a/JSTests/stress/temporal-calendar-merge-fields-with.js b/JSTests/stress/temporal-calendar-merge-fields-with.js
new file mode 100644
index 0000000..9938c64
--- /dev/null
+++ b/JSTests/stress/temporal-calendar-merge-fields-with.js
@@ -0,0 +1,50 @@
+//@ requireOptions("--useTemporal=1")
+
+function shouldThrow(fn, type, message) {
+    let err;
+    try { fn(); } catch (e) { err = e; }
+    if (!(err instanceof type))
+        throw new Error(`Expected ${type.name} but got ${err}`);
+    if (message !== undefined && err.message !== message)
+        throw new Error(`Expected message "${message}" but got "${err.message}"`);
+}
+
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error(`expected ${JSON.stringify(expected)} but got ${JSON.stringify(actual)}`);
+}
+
+// --- PlainDate.prototype.with ---
+
+shouldBe(Temporal.PlainDate.from("2020-06-15").with({ day: 20 }).toString(), "2020-06-20");
+shouldBe(Temporal.PlainDate.from("2020-06-15").with({ month: 3 }).toString(), "2020-03-15");
+shouldBe(Temporal.PlainDate.from("2020-06-15").with({ year: 2021 }).toString(), "2021-06-15");
+
+shouldBe(Temporal.PlainDate.from({ year: 2020, month: 5, day: 1, calendar: "hebrew" }).with({ day: 10 }).toString(), "-001741-12-28[u-ca=hebrew]");
+shouldBe(Temporal.PlainDate.from({ year: 2020, month: 5, day: 1, calendar: "hebrew" }).with({ year: 2021 }).toString(), "-001739-01-07[u-ca=hebrew]");
+
+// Chinese (lunisolar): year changes with month/monthCode absent from the partial — the
+// case the removed lunisolarYearChange special case targeted (month must fall back via
+// monthCode, not a raw ordinal, since leap-month insertion can shift the mapping).
+shouldBe(Temporal.PlainDate.from({ year: 2020, month: 5, day: 1, calendar: "chinese" }).with({ year: 2023 }).toString(), "2023-05-19[u-ca=chinese]");
+shouldBe(Temporal.PlainDate.from({ year: 2020, month: 5, day: 1, calendar: "chinese" }).with({ day: 10 }).toString(), "2020-06-01[u-ca=chinese]");
+
+shouldBe(Temporal.PlainDate.from({ era: "reiwa", eraYear: 3, month: 5, day: 1, calendar: "japanese" }).with({ month: 8 }).toString(), "2021-08-01[u-ca=japanese]");
+
+// --- PlainYearMonth.prototype.with ---
+
+shouldBe(Temporal.PlainYearMonth.from("2020-06").with({ month: 3 }).toString(), "2020-03");
+shouldBe(Temporal.PlainYearMonth.from({ year: 2020, month: 5, calendar: "hebrew" }).with({ month: 2 }).toString(), "-001741-09-21[u-ca=hebrew]");
+shouldBe(Temporal.PlainYearMonth.from({ year: 2020, month: 5, calendar: "chinese" }).with({ year: 2023 }).toString(), "2023-05-19[u-ca=chinese]");
+
+// --- PlainMonthDay.prototype.with ---
+
+shouldBe(Temporal.PlainMonthDay.from({ month: 6, day: 15 }).with({ day: 20 }).toString(), "06-20");
+shouldBe(Temporal.PlainMonthDay.from({ month: 6, day: 15 }).with({ month: 3 }).toString(), "03-15");
+shouldBe(Temporal.PlainMonthDay.from({ monthCode: "M05", day: 1, calendar: "hebrew" }).with({ day: 10 }).toString(), "1972-01-26[u-ca=hebrew]");
+shouldBe(Temporal.PlainMonthDay.from({ monthCode: "M05", day: 1, calendar: "hebrew" }).with({ month: 2, year: 2020 }).toString(), "1972-10-09[u-ca=hebrew]");
+
+// Non-ISO month given without year (or era+eraYear): now deferred to
+// nonISOResolveFields's own "year property must be present" check instead of a
+// PlainMonthDay.prototype.with-specific message — same TypeError kind either way.
+shouldThrow(() => Temporal.PlainMonthDay.from({ monthCode: "M05", day: 1, calendar: "hebrew" }).with({ month: 2 }), TypeError, "year property must be present");
diff --git a/JSTests/stress/temporal-isodatetofields-call-sites.js b/JSTests/stress/temporal-isodatetofields-call-sites.js
new file mode 100644
index 0000000..b1e85f2
--- /dev/null
+++ b/JSTests/stress/temporal-isodatetofields-call-sites.js
@@ -0,0 +1,110 @@
+//@ requireOptions("--useTemporal=1")
+
+function shouldBe(actual, expected, msg) {
+    if (String(actual) !== String(expected))
+        throw new Error(`${msg}: expected ${JSON.stringify(String(expected))} but got ${JSON.stringify(String(actual))}`);
+}
+
+function shouldThrow(fn, type, msg) {
+    let err;
+    try { fn(); } catch (e) { err = e; }
+    if (!(err instanceof type))
+        throw new Error(`${msg}: expected ${type.name} but got ${err}`);
+}
+
+const ym = (cal) => Temporal.PlainYearMonth.from({ year: 2021, month: 5, calendar: cal });
+
+// --- AddDurationToYearMonth: ISODateToFields(~year-month~) at steps 9 and 13 ---
+// The receiver's era/eraYear are NOT part of ISODateToFields' output; only monthCode and year are,
+// so an era-bearing calendar must round-trip through the arithmetic year alone.
+shouldBe(ym("iso8601").add({ months: 1 }), "2021-06", "iso add P1M");
+shouldBe(ym("iso8601").add({ months: 13 }), "2022-06", "iso add P13M");
+shouldBe(ym("iso8601").subtract({ months: 1 }), "2021-04", "iso sub P1M");
+
+shouldBe(ym("japanese").add({ months: 1 }).toString(), "2021-06-01[u-ca=japanese]", "japanese add P1M");
+shouldBe(ym("japanese").add({ months: 13 }).toString(), "2022-06-01[u-ca=japanese]", "japanese add P13M");
+shouldBe(ym("japanese").add({ years: 1 }).toString(), "2022-05-01[u-ca=japanese]", "japanese add P1Y");
+shouldBe(ym("japanese").subtract({ months: 1 }).toString(), "2021-04-01[u-ca=japanese]", "japanese sub P1M");
+shouldBe(ym("gregory").add({ months: 1 }).toString(), "2021-06-01[u-ca=gregory]", "gregory add P1M");
+shouldBe(ym("roc").add({ months: 1 }).toString(), "3932-06-01[u-ca=roc]", "roc add P1M");
+shouldBe(ym("roc").add({ years: 1 }).toString(), "3933-05-01[u-ca=roc]", "roc add P1Y");
+
+// Lunisolar: leap-month insertion means the ordinal month cannot be carried across years, so
+// ISODateToFields must hand CalendarYearMonthFromFields a monthCode.
+shouldBe(ym("hebrew").add({ months: 1 }).toString(), "-001739-02-06[u-ca=hebrew]", "hebrew add P1M");
+shouldBe(ym("hebrew").add({ months: 13 }).toString(), "-001738-01-25[u-ca=hebrew]", "hebrew add P13M");
+shouldBe(ym("hebrew").subtract({ months: 1 }).toString(), "-001740-12-09[u-ca=hebrew]", "hebrew sub P1M");
+shouldBe(ym("chinese").add({ months: 1 }).toString(), "2021-07-10[u-ca=chinese]", "chinese add P1M");
+shouldBe(ym("chinese").add({ months: 13 }).toString(), "2022-06-29[u-ca=chinese]", "chinese add P13M");
+shouldBe(ym("chinese").subtract({ months: 1 }).toString(), "2021-05-12[u-ca=chinese]", "chinese sub P1M");
+
+// An era-bearing receiver built FROM era+eraYear must add identically to one built from year.
+shouldBe(Temporal.PlainYearMonth.from({ era: "reiwa", eraYear: 3, month: 5, calendar: "japanese" })
+    .add({ months: 1 }).toString(), "2021-06-01[u-ca=japanese]", "japanese add P1M from era");
+
+// ISO takes the pure isoDateAdd overload so the extreme boundary years do not get ICU-clamped.
+shouldBe(Temporal.PlainYearMonth.from("+275760-08").add({ months: 1 }), "+275760-09", "add to max boundary");
+shouldThrow(() => Temporal.PlainYearMonth.from("+275760-09").add({ months: 1 }), RangeError, "add past max");
+shouldThrow(() => Temporal.PlainYearMonth.from("-271821-05").subtract({ months: 1 }), RangeError, "sub past min");
+
+for (const [cal, year, isoPrefix] of [["buddhist", 2021, "1478"], ["roc", -433, "1478"], ["japanese", 1478, "1478"]]) {
+    const base = Temporal.PlainYearMonth.from({ year, month: 5, calendar: cal });
+    shouldBe(base.toString(), `${isoPrefix}-05-01[u-ca=${cal}]`, `${cal} base at ISO ${isoPrefix}`);
+    shouldBe(base.add({ months: 0 }).month, 5, `${cal}: add P0M must be identity`);
+    shouldBe(base.add({ months: 1 }).month, 6, `${cal}: add P1M`);
+    shouldBe(base.subtract({ months: 1 }).month, 4, `${cal}: sub P1M`);
+}
+// Outside the affected ISO-year range the same calendars are correct, which pins the boundary.
+shouldBe(Temporal.PlainYearMonth.from({ year: 2200, month: 5, calendar: "buddhist" })
+    .add({ months: 0 }).toString(), "1657-05-01[u-ca=buddhist]", "buddhist add P0M above range");
+shouldBe(Temporal.PlainYearMonth.from({ year: 543, month: 5, calendar: "buddhist" })
+    .add({ months: 0 }).toString(), "0000-05-01[u-ca=buddhist]", "buddhist add P0M below range");
+// gregory at the same ISO year is unaffected, isolating the proleptic-Gregorian era path.
+shouldBe(Temporal.PlainYearMonth.from({ year: 1478, month: 5, calendar: "gregory" })
+    .add({ months: 0 }).month, 5, "gregory add P0M at ISO 1478");
+
+// --- PlainYearMonth.toPlainDate: ISODateToFields(~year-month~) then CalendarMergeFields(«day») ---
+shouldBe(ym("iso8601").toPlainDate({ day: 15 }), "2021-05-15", "iso toPlainDate day=15");
+shouldBe(ym("iso8601").toPlainDate({ day: 31 }), "2021-05-31", "iso toPlainDate day=31");
+shouldBe(ym("japanese").toPlainDate({ day: 1 }).toString(), "2021-05-01[u-ca=japanese]", "japanese toPlainDate day=1");
+shouldBe(ym("japanese").toPlainDate({ day: 31 }).toString(), "2021-05-31[u-ca=japanese]", "japanese toPlainDate day=31");
+shouldBe(ym("hebrew").toPlainDate({ day: 1 }).toString(), "-001739-01-07[u-ca=hebrew]", "hebrew toPlainDate day=1");
+shouldBe(ym("hebrew").toPlainDate({ day: 31 }).toString(), "-001739-02-05[u-ca=hebrew]", "hebrew toPlainDate day=31");
+shouldBe(ym("chinese").toPlainDate({ day: 15 }).toString(), "2021-06-24[u-ca=chinese]", "chinese toPlainDate day=15");
+shouldBe(ym("chinese").toPlainDate({ day: 31 }).toString(), "2021-07-09[u-ca=chinese]", "chinese toPlainDate day=31");
+
+// --- ToTemporalYearMonth step 12 / PlainDate.toPlainYearMonth ---
+for (const [cal, expected] of [["japanese", "2021-05-01[u-ca=japanese]"], ["hebrew", "2021-05-12[u-ca=hebrew]"],
+    ["chinese", "2021-05-12[u-ca=chinese]"]]) {
+    shouldBe(Temporal.PlainYearMonth.from(`2021-05-17[u-ca=${cal}]`).toString(), expected, `${cal} YM.from string`);
+    shouldBe(Temporal.PlainDate.from(`2021-05-17[u-ca=${cal}]`).toPlainYearMonth().toString(), expected, `${cal} PD.toPlainYearMonth`);
+}
+shouldBe(Temporal.PlainYearMonth.from("2021-05-17"), "2021-05", "iso YM.from string");
+// The reference day is canonical (1 for ISO), which is why step 14 forces ~constrain~.
+shouldBe(Temporal.PlainYearMonth.from("2020-05-23[u-ca=chinese]").toString(), "2020-05-23[u-ca=chinese]", "chinese YM.from leap-month year");
+shouldBe(Temporal.PlainYearMonth.from("2022-03-05[u-ca=hebrew]").toString(), "2022-03-04[u-ca=hebrew]", "hebrew YM.from leap-month year");
+
+// --- ToTemporalMonthDay step 13 / PlainDate.toPlainMonthDay ---
+shouldBe(Temporal.PlainMonthDay.from("2021-05-17"), "05-17", "iso MD.from string");
+shouldBe(Temporal.PlainMonthDay.from("2024-02-29"), "02-29", "iso MD.from Feb 29");
+for (const [cal, expected] of [["japanese", "1972-05-17[u-ca=japanese]"], ["hebrew", "1972-05-19[u-ca=hebrew]"],
+    ["chinese", "1972-05-18[u-ca=chinese]"]]) {
+    shouldBe(Temporal.PlainMonthDay.from(`2021-05-17[u-ca=${cal}]`).toString(), expected, `${cal} MD.from string`);
+    shouldBe(Temporal.PlainDate.from(`2021-05-17[u-ca=${cal}]`).toPlainMonthDay().toString(), expected, `${cal} PD.toPlainMonthDay`);
+}
+
+// --- getter/resolution agreement ---
+for (const cal of ["iso8601", "gregory", "hebrew", "chinese", "dangi", "islamic-civil",
+    "islamic-tbla", "islamic-umalqura", "japanese", "buddhist", "roc", "coptic", "ethiopic",
+    "ethioaa", "persian", "indian"]) {
+    for (const iso of ["2020-05-01", "2020-06-21", "1978-02-28", "2024-02-29", "2023-06-18"]) {
+        const d = Temporal.PlainDate.from(iso).withCalendar(cal);
+        const tag = `${cal} ${iso}`;
+        shouldBe(d.with({ day: d.day }).equals(d), true, `${tag}: with({day: d.day}) is identity`);
+        shouldBe(d.with({ year: d.year }).equals(d), true, `${tag}: with({year: d.year}) is identity`);
+        shouldBe(d.with({ monthCode: d.monthCode }).equals(d), true, `${tag}: with({monthCode}) is identity`);
+        shouldBe(d.with({ month: d.month }).equals(d), true, `${tag}: with({month: d.month}) is identity`);
+        if (d.era !== undefined)
+            shouldBe(d.with({ era: d.era, eraYear: d.eraYear }).equals(d), true, `${tag}: with({era, eraYear}) is identity`);
+    }
+}
diff --git a/Source/JavaScriptCore/API/tests/TemporalCoreTest.cpp b/Source/JavaScriptCore/API/tests/TemporalCoreTest.cpp
index 96d16e5..34cc7bf 100644
--- a/Source/JavaScriptCore/API/tests/TemporalCoreTest.cpp
+++ b/Source/JavaScriptCore/API/tests/TemporalCoreTest.cpp
@@ -2600,10 +2600,11 @@
     auto rWith2 = plainYearMonthWith(id("iso8601"_s), { 2025, 3, 1 }, partialMonth, TemporalOverflow::Constrain);
     TCHECK_TRUE(rWith2.has_value() && rWith2->isoDate.year() == 2025 && rWith2->isoDate.month() == 7, "plainYearMonthWith: override month -> 2025-07");
 
-    // empty partial fields: ISO falls back year+monthCode from current, succeeds
+    // empty partial fields: ISO falls back year+monthCode from current, succeeds. Unreachable from
+    // JS — PrepareCalendarFields(..., ~partial~) throws before .with() ever gets here.
     CalendarFieldsIn emptyPartial;
     auto rWithEmpty = plainYearMonthWith(id("iso8601"_s), { 2025, 3, 1 }, emptyPartial, TemporalOverflow::Constrain);
-    TCHECK_TRUE(!rWithEmpty.has_value(), "plainYearMonthWith: empty partial -> TypeError (temporal_rs: fields.is_empty())");
+    TCHECK_TRUE(rWithEmpty.has_value() && rWithEmpty->isoDate.year() == 2025 && rWithEmpty->isoDate.month() == 3, "plainYearMonthWith: empty partial -> receiver 2025-03");
 
     f = { };
     f.year = 275761; // one past maxYear
@@ -2705,6 +2706,87 @@
     TCHECK_TRUE(!rDW6.has_value() && rDW6.error().kind == TemporalErrorKind::RangeError, "plainDateWith: inconsistent year+era+eraYear -> RangeError");
 }
 
+static void testCalendarMergeFieldsWith()
+{
+    using MC = ParsedMonthCode;
+    auto id = calendarIDFromString;
+
+    // chinese (lunisolar): year-only change with month/monthCode absent must fall back via
+    // monthCode, not a raw ordinal, since 2023's leap-month layout differs from 2020's.
+    {
+        CalendarFieldsIn partial;
+        partial.year = 2023;
+        auto r = plainDateWith(id("chinese"_s), { 2020, 5, 23 }, partial, TemporalOverflow::Constrain);
+        TCHECK_TRUE(r.has_value() && r->isoDate.year() == 2023 && r->isoDate.month() == 5 && r->isoDate.day() == 19, "plainDateWith: chinese year-only change through leap-month year -> 2023-05-19");
+    }
+    {
+        CalendarFieldsIn partial;
+        partial.day = 10;
+        auto r = plainDateWith(id("chinese"_s), { 2020, 5, 23 }, partial, TemporalOverflow::Constrain);
+        TCHECK_TRUE(r.has_value() && r->isoDate.year() == 2020 && r->isoDate.month() == 6 && r->isoDate.day() == 1, "plainDateWith: chinese day-only change -> 2020-06-01");
+    }
+
+    // Same chinese leap-month case via plainYearMonthWith.
+    {
+        CalendarFieldsIn partial;
+        partial.year = 2023;
+        auto r = plainYearMonthWith(id("chinese"_s), { 2020, 5, 23 }, partial, TemporalOverflow::Constrain);
+        TCHECK_TRUE(r.has_value() && r->isoDate.year() == 2023 && r->isoDate.month() == 5 && r->isoDate.day() == 19, "plainYearMonthWith: chinese year-only change through leap-month year -> 2023-05-19");
+    }
+    {
+        // PlainYearMonth's stored ISO date always has day=1. Showa 64 Jan 1 = 1989-01-01
+        // (Heisei starts 1989-01-08); .with({month: 6}) re-derives era into Heisei.
+        CalendarFieldsIn partial;
+        partial.month = 6;
+        auto r = plainYearMonthWith(id("japanese"_s), { 1989, 1, 1 }, partial, TemporalOverflow::Constrain);
+        TCHECK_TRUE(r.has_value() && r->isoDate.year() == 1989 && r->isoDate.month() == 6, "plainYearMonthWith: japanese month change re-derives era -> 1989-06");
+    }
+
+    // plainMonthDayWith: ISO day/month-only changes.
+    {
+        CalendarFieldsIn base;
+        base.month = 6;
+        base.day = 15;
+        auto baseResolved = monthDayFromFields(id("iso8601"_s), base, TemporalOverflow::Constrain);
+        TCHECK_TRUE(baseResolved.has_value(), "plainMonthDayWith setup: iso base 06-15");
+
+        CalendarFieldsIn partialDay;
+        partialDay.day = 20;
+        auto r1 = plainMonthDayWith(id("iso8601"_s), baseResolved->isoDate, partialDay, TemporalOverflow::Constrain);
+        TCHECK_TRUE(r1.has_value() && r1->isoDate.month() == 6 && r1->isoDate.day() == 20, "plainMonthDayWith: iso day-only -> 06-20");
+
+        CalendarFieldsIn partialMonth;
+        partialMonth.month = 3;
+        auto r2 = plainMonthDayWith(id("iso8601"_s), baseResolved->isoDate, partialMonth, TemporalOverflow::Constrain);
+        TCHECK_TRUE(r2.has_value() && r2->isoDate.month() == 3 && r2->isoDate.day() == 15, "plainMonthDayWith: iso month-only -> 03-15");
+    }
+
+    // plainMonthDayWith: hebrew — month-without-year now throws via nonISOResolveFields.
+    {
+        CalendarFieldsIn base;
+        base.monthCode = MC { 5, false };
+        base.day = 1;
+        auto baseResolved = monthDayFromFields(id("hebrew"_s), base, TemporalOverflow::Constrain);
+        TCHECK_TRUE(baseResolved.has_value() && baseResolved->isoDate.year() == 1972 && baseResolved->isoDate.month() == 1 && baseResolved->isoDate.day() == 17, "plainMonthDayWith setup: hebrew M05 day=1 -> 1972-01-17");
+
+        CalendarFieldsIn partialDay;
+        partialDay.day = 10;
+        auto r1 = plainMonthDayWith(id("hebrew"_s), baseResolved->isoDate, partialDay, TemporalOverflow::Constrain);
+        TCHECK_TRUE(r1.has_value() && r1->isoDate.year() == 1972 && r1->isoDate.month() == 1 && r1->isoDate.day() == 26, "plainMonthDayWith: hebrew day-only -> 1972-01-26");
+
+        CalendarFieldsIn partialMonthNoYear;
+        partialMonthNoYear.month = 2;
+        auto r2 = plainMonthDayWith(id("hebrew"_s), baseResolved->isoDate, partialMonthNoYear, TemporalOverflow::Constrain);
+        TCHECK_TRUE(!r2.has_value() && r2.error().kind == TemporalErrorKind::TypeError, "plainMonthDayWith: hebrew month without year -> TypeError");
+
+        CalendarFieldsIn partialMonthYear;
+        partialMonthYear.month = 2;
+        partialMonthYear.year = 2020;
+        auto r3 = plainMonthDayWith(id("hebrew"_s), baseResolved->isoDate, partialMonthYear, TemporalOverflow::Constrain);
+        TCHECK_TRUE(r3.has_value() && r3->isoDate.year() == 1972 && r3->isoDate.month() == 10 && r3->isoDate.day() == 9, "plainMonthDayWith: hebrew month+year -> 1972-10-09");
+    }
+}
+
 static void testNonISOCalendarDateToISO()
 {
     using MC = ParsedMonthCode;
@@ -3867,6 +3949,7 @@
     testNonISOCalendarDateToISO(); // nonISOCalendarDateToISO: era, monthCode, overflow, ROC, Japanese, Buddhist, Coptic, Ethiopic
     testIsBuiltinCalendar(); // Canonical Temporal calendar set + legacy aliases
     testCalendarFieldsFunctions(); // yearMonthFromFields, monthDayFromFields, differenceYearMonth, plainYearMonthAdd, etc.
+    testCalendarMergeFieldsWith(); // calendarMergeFields: chinese leap-month, japanese era-suppress, hebrew MonthDay
 
     // parseISODateTime
     testParseInstantString();
diff --git a/Source/JavaScriptCore/runtime/TemporalPlainDate.h b/Source/JavaScriptCore/runtime/TemporalPlainDate.h
index 32d109c..ab8a1f780 100644
--- a/Source/JavaScriptCore/runtime/TemporalPlainDate.h
+++ b/Source/JavaScriptCore/runtime/TemporalPlainDate.h
@@ -63,10 +63,6 @@
 #undef JSC_DEFINE_TEMPORAL_PLAIN_DATE_FIELD
 
     String monthCode() const { return ISO8601::monthCode(m_plainDate.month()); }
-    uint8_t dayOfWeek() const { return ISO8601::dayOfWeek(m_plainDate); }
-    uint16_t dayOfYear() const { return ISO8601::dayOfYear(m_plainDate); }
-    uint8_t weekOfYear() const { return ISO8601::weekOfYear(m_plainDate); }
-    int32_t yearOfWeek() const { return ISO8601::yearOfWeek(m_plainDate); }
 
     String toString() const;
 
diff --git a/Source/JavaScriptCore/runtime/TemporalPlainDatePrototype.cpp b/Source/JavaScriptCore/runtime/TemporalPlainDatePrototype.cpp
index 1182fe6..811cc1c 100644
--- a/Source/JavaScriptCore/runtime/TemporalPlainDatePrototype.cpp
+++ b/Source/JavaScriptCore/runtime/TemporalPlainDatePrototype.cpp
@@ -297,8 +297,7 @@
     TemporalOverflow overflow = toTemporalOverflow(globalObject, callFrame->argument(1));
     RETURN_IF_EXCEPTION(scope, { });
 
-    // Steps 5+7+10: ISODateToFields + CalendarMergeFields + CalendarDateFromFields — fused into
-    // plainDateWith (which has its own ISO fast path, so this is calendar-agnostic here).
+    // Steps 5+7+10: ISODateToFields + CalendarMergeFields + CalendarDateFromFields, in plainDateWith.
     auto resolved = TemporalCore::plainDateWith(calendarId, plainDate->plainDate(), partialFields, overflow);
     if (!resolved) [[unlikely]] {
         if (resolved.error().kind == TemporalErrorKind::TypeError)
@@ -681,7 +680,7 @@
     if (!plainDate) [[unlikely]]
         return throwVMTypeError(globalObject, scope, "Temporal.PlainDate.prototype.dayOfWeek called on value that's not a PlainDate"_s);
 
-    return JSValue::encode(jsNumber(plainDate->dayOfWeek()));
+    return JSValue::encode(jsNumber(TemporalCore::calendarDayOfWeek(plainDate->calendarID(), plainDate->plainDate())));
 }
 
 // https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.dayofyear
@@ -710,9 +709,10 @@
     if (!plainDate) [[unlikely]]
         return throwVMTypeError(globalObject, scope, "Temporal.PlainDate.prototype.weekOfYear called on value that's not a PlainDate"_s);
 
-    if (plainDate->calendarID() != iso8601CalendarID())
+    auto week = TemporalCore::calendarWeekOfYear(plainDate->calendarID(), plainDate->plainDate());
+    if (!week)
         return JSValue::encode(jsUndefined());
-    return JSValue::encode(jsNumber(plainDate->weekOfYear()));
+    return JSValue::encode(jsNumber(*week));
 }
 
 // https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.daysinweek
@@ -725,7 +725,7 @@
     if (!plainDate) [[unlikely]]
         return throwVMTypeError(globalObject, scope, "Temporal.PlainDate.prototype.daysInWeek called on value that's not a PlainDate"_s);
 
-    return JSValue::encode(jsNumber(7)); // ISO8601 calendar always returns 7.
+    return JSValue::encode(jsNumber(ISO8601::daysPerWeek));
 }
 
 // https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.daysinmonth
@@ -848,9 +848,10 @@
     if (!plainDate) [[unlikely]]
         return throwVMTypeError(globalObject, scope, "Temporal.PlainDate.prototype.yearOfWeek called on value that's not a PlainDate"_s);
 
-    if (plainDate->calendarID() != iso8601CalendarID())
+    auto yearOfWeek = TemporalCore::calendarYearOfWeek(plainDate->calendarID(), plainDate->plainDate());
+    if (!yearOfWeek)
         return JSValue::encode(jsUndefined());
-    return JSValue::encode(jsNumber(plainDate->yearOfWeek()));
+    return JSValue::encode(jsNumber(*yearOfWeek));
 }
 
 // https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.withcalendar
diff --git a/Source/JavaScriptCore/runtime/TemporalPlainDateTime.h b/Source/JavaScriptCore/runtime/TemporalPlainDateTime.h
index 10fa85f..fbefa95 100644
--- a/Source/JavaScriptCore/runtime/TemporalPlainDateTime.h
+++ b/Source/JavaScriptCore/runtime/TemporalPlainDateTime.h
@@ -64,10 +64,6 @@
 #undef JSC_DEFINE_TEMPORAL_PLAIN_TIME_FIELD
 
     String monthCode() const { return ISO8601::monthCode(m_plainDate.month()); }
-    uint8_t dayOfWeek() const { return ISO8601::dayOfWeek(m_plainDate); }
-    uint16_t dayOfYear() const { return ISO8601::dayOfYear(m_plainDate); }
-    uint8_t weekOfYear() const { return ISO8601::weekOfYear(m_plainDate); }
-    int32_t yearOfWeek() const { return ISO8601::yearOfWeek(m_plainDate); }
 
     template<DifferenceOperation>
     ISO8601::Duration differenceTemporalPlainDateTime(JSGlobalObject*, TemporalPlainDateTime*, JSValue optionsValue);
diff --git a/Source/JavaScriptCore/runtime/TemporalPlainDateTimePrototype.cpp b/Source/JavaScriptCore/runtime/TemporalPlainDateTimePrototype.cpp
index 18fbafa..a2c1def 100644
--- a/Source/JavaScriptCore/runtime/TemporalPlainDateTimePrototype.cpp
+++ b/Source/JavaScriptCore/runtime/TemporalPlainDateTimePrototype.cpp
@@ -266,31 +266,32 @@
     TemporalOverflow overflow = toTemporalOverflow(globalObject, options);
     RETURN_IF_EXCEPTION(scope, { });
 
-    // Steps 5, 13, 16: ISODateToFields + CalendarMergeFields + CalendarDateFromFields
-    // — fused into plainDateWith.
-    auto dateResult = TemporalCore::plainDateWith(calendarId, plainDateTime->plainDate(), partialDate, overflow);
-    if (!dateResult) [[unlikely]] {
-        if (dateResult.error().kind == TemporalErrorKind::TypeError)
-            throwTypeError(globalObject, scope, String(dateResult.error().message));
-        else
-            throwRangeError(globalObject, scope, String(dateResult.error().message));
+    // Step 5: Let fields be ISODateToFields(calendar, plainDateTime.[[ISODateTime]].[[ISODate]], ~date~).
+    auto dateFields = TemporalCore::isoDateToFields(calendarId, plainDateTime->plainDate(), TemporalCore::ResolveType::Date);
+    if (!dateFields) [[unlikely]] {
+        throwTemporalError(globalObject, scope, dateFields.error());
         return { };
     }
+    // Step 13: Set fields to CalendarMergeFields(calendar, fields, partialDateTime).
+    auto mergedDate = TemporalCore::calendarMergeFields(calendarId, *dateFields, partialDate);
 
-    // Steps 6-11, 16: time fields merged with this's current values, then RegulateTime.
+    // Steps 6-11, merged into step 13 inline: see CalendarFieldKey in CalendarFields.cpp.
     auto curTime = plainDateTime->plainTime();
-    ISO8601::Duration timeDur { };
-    timeDur.setField(TemporalUnit::Hour, partialTime.hour.value_or(curTime.hour()));
-    timeDur.setField(TemporalUnit::Minute, partialTime.minute.value_or(curTime.minute()));
-    timeDur.setField(TemporalUnit::Second, partialTime.second.value_or(curTime.second()));
-    timeDur.setField(TemporalUnit::Millisecond, partialTime.millisecond.value_or(curTime.millisecond()));
-    timeDur.setField(TemporalUnit::Microsecond, partialTime.microsecond.value_or(curTime.microsecond()));
-    timeDur.setField(TemporalUnit::Nanosecond, partialTime.nanosecond.value_or(curTime.nanosecond()));
-    auto newTime = TemporalPlainTime::regulateTime(globalObject, WTF::move(timeDur), overflow);
+    TemporalCore::TimeFieldsIn mergedTime {
+        partialTime.hour.value_or(curTime.hour()),
+        partialTime.minute.value_or(curTime.minute()),
+        partialTime.second.value_or(curTime.second()),
+        partialTime.millisecond.value_or(curTime.millisecond()),
+        partialTime.microsecond.value_or(curTime.microsecond()),
+        partialTime.nanosecond.value_or(curTime.nanosecond()),
+    };
+
+    // Step 16: result = ? InterpretTemporalDateTimeFields(calendar, fields, overflow).
+    auto pdt = interpretTemporalDateTimeFields(globalObject, calendarId, mergedDate, mergedTime, overflow);
     RETURN_IF_EXCEPTION(scope, { });
 
-    // Step 17: CreateTemporalDateTime.
-    auto* withResult = createTemporalDateTime(globalObject, ISO8601::PlainDate(dateResult->isoDate), WTF::move(newTime), calendarId);
+    // Step 17: Return ? CreateTemporalDateTime(result, calendar).
+    auto* withResult = createTemporalDateTime(globalObject, ISO8601::PlainDate(pdt.date), ISO8601::PlainTime(pdt.time), calendarId);
     RETURN_IF_EXCEPTION(scope, { });
     return JSValue::encode(withResult);
 }
@@ -720,7 +721,7 @@
     if (!plainDateTime) [[unlikely]]
         return throwVMTypeError(globalObject, scope, "Temporal.PlainDateTime.prototype.dayOfWeek called on value that's not a PlainDateTime"_s);
 
-    return JSValue::encode(jsNumber(plainDateTime->dayOfWeek()));
+    return JSValue::encode(jsNumber(TemporalCore::calendarDayOfWeek(plainDateTime->calendarID(), plainDateTime->plainDate())));
 }
 
 // https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.dayofyear
@@ -749,9 +750,10 @@
     if (!plainDateTime) [[unlikely]]
         return throwVMTypeError(globalObject, scope, "Temporal.PlainDateTime.prototype.weekOfYear called on value that's not a PlainDateTime"_s);
 
-    if (plainDateTime->calendarID() != iso8601CalendarID())
+    auto week = TemporalCore::calendarWeekOfYear(plainDateTime->calendarID(), plainDateTime->plainDate());
+    if (!week)
         return JSValue::encode(jsUndefined());
-    return JSValue::encode(jsNumber(plainDateTime->weekOfYear()));
+    return JSValue::encode(jsNumber(*week));
 }
 
 // https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.daysinweek
@@ -764,7 +766,7 @@
     if (!plainDateTime) [[unlikely]]
         return throwVMTypeError(globalObject, scope, "Temporal.PlainDateTime.prototype.daysInWeek called on value that's not a PlainDateTime"_s);
 
-    return JSValue::encode(jsNumber(7)); // ISO8601 calendar always returns 7.
+    return JSValue::encode(jsNumber(ISO8601::daysPerWeek));
 }
 
 // https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.daysinmonth
@@ -913,9 +915,10 @@
     if (!plainDateTime) [[unlikely]]
         return throwVMTypeError(globalObject, scope, "Temporal.PlainDateTime.prototype.yearOfWeek called on value that's not a PlainDateTime"_s);
 
-    if (plainDateTime->calendarID() != iso8601CalendarID())
+    auto yearOfWeek = TemporalCore::calendarYearOfWeek(plainDateTime->calendarID(), plainDateTime->plainDate());
+    if (!yearOfWeek)
         return JSValue::encode(jsUndefined());
-    return JSValue::encode(jsNumber(plainDateTime->yearOfWeek()));
+    return JSValue::encode(jsNumber(*yearOfWeek));
 }
 
 // https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.era
diff --git a/Source/JavaScriptCore/runtime/TemporalPlainMonthDayPrototype.cpp b/Source/JavaScriptCore/runtime/TemporalPlainMonthDayPrototype.cpp
index 70a67b1..cb16a45 100644
--- a/Source/JavaScriptCore/runtime/TemporalPlainMonthDayPrototype.cpp
+++ b/Source/JavaScriptCore/runtime/TemporalPlainMonthDayPrototype.cpp
@@ -201,50 +201,8 @@
     TemporalOverflow overflow = toTemporalOverflow(globalObject, options);
     RETURN_IF_EXCEPTION(scope, { });
 
-    // Steps 5+7: fields = ISODateToFields(calendar, plainMonthDay.[[ISODate]], ~month-day~);
-    //            fields = CalendarMergeFields(calendar, fields, partialMonthDay).
-    //   Fused inline: fill unset merged fields from the receiver's own monthCode/day.
-    TemporalCore::CalendarFieldsIn merged;
-
-    // day from ISODateToFields (use calendar day for non-ISO).
-    uint8_t currentCalDay = static_cast<uint8_t>(monthDay->plainMonthDay().day());
-    bool isISO = TemporalCore::calendarIsISO(calendarId);
-    if (!isISO) {
-        auto dayResult = TemporalCore::calendarDay(calendarId, monthDay->plainMonthDay().isoPlainDate());
-        if (dayResult)
-            currentCalDay = *dayResult;
-    }
-    // User's day takes priority over the receiver's.
-    merged.day = partialFields.day.has_value() ? partialFields.day : std::optional<uint8_t>(currentCalDay);
-
-    if (partialFields.month.has_value())
-        merged.month = partialFields.month;
-    if (partialFields.monthCode)
-        merged.monthCode = partialFields.monthCode;
-    if (partialFields.year)
-        merged.year = partialFields.year;
-    if (partialFields.era)
-        merged.era = partialFields.era;
-    if (partialFields.eraYear)
-        merged.eraYear = partialFields.eraYear;
-    if (!partialFields.month.has_value() && !partialFields.monthCode) {
-        // Neither given — fall back to current monthCode (non-ISO) or numeric month (ISO).
-        if (!isISO) {
-            auto mcStr = TemporalCore::calendarMonthCode(calendarId, monthDay->plainMonthDay().isoPlainDate());
-            if (!mcStr) [[unlikely]] {
-                throwRangeError(globalObject, scope, String(mcStr.error().message));
-                return { };
-            }
-            merged.monthCode = ISO8601::parseMonthCode(*mcStr);
-        } else
-            merged.month = std::optional<uint32_t>(monthDay->plainMonthDay().month());
-    }
-
-    if (!isISO && merged.month.has_value() && !merged.year && !(merged.era && merged.eraYear)) [[unlikely]]
-        return throwVMTypeError(globalObject, scope, "year is required with month for non-ISO calendar PlainMonthDay.with()"_s);
-
-    // Step 10: isoDate = ? CalendarMonthDayFromFields(calendar, fields, overflow).
-    auto resolved = TemporalCore::monthDayFromFields(calendarId, merged, overflow);
+    // Steps 5+7+10: ISODateToFields + CalendarMergeFields + CalendarMonthDayFromFields, in plainMonthDayWith.
+    auto resolved = TemporalCore::plainMonthDayWith(calendarId, monthDay->plainMonthDay().isoPlainDate(), partialFields, overflow);
     if (!resolved) [[unlikely]] {
         if (resolved.error().kind == TemporalErrorKind::TypeError)
             throwTypeError(globalObject, scope, String(resolved.error().message));
diff --git a/Source/JavaScriptCore/runtime/TemporalPlainTime.cpp b/Source/JavaScriptCore/runtime/TemporalPlainTime.cpp
index cb60fb2..bf24d3c 100644
--- a/Source/JavaScriptCore/runtime/TemporalPlainTime.cpp
+++ b/Source/JavaScriptCore/runtime/TemporalPlainTime.cpp
@@ -634,48 +634,6 @@
         static_cast<Int128>(plainTime.nanosecond()) + static_cast<Int128>(duration.nanoseconds()));
 }
 
-// https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.with
-ISO8601::PlainTime TemporalPlainTime::with(JSGlobalObject* globalObject, JSObject* temporalTimeLike, JSValue optionsValue) const
-{
-    VM& vm = globalObject->vm();
-    auto scope = DECLARE_THROW_SCOPE(vm);
-
-    // Steps 1-2: branding done by caller.
-
-    // Step 3: If ? IsPartialTemporalObject(temporalTimeLike) is false, throw TypeError.
-    bool isPartial = isPartialTemporalObject(globalObject, JSValue(temporalTimeLike));
-    RETURN_IF_EXCEPTION(scope, { });
-    if (!isPartial) [[unlikely]] {
-        throwTypeError(globalObject, scope, "argument must be a partial Temporal object"_s);
-        return { };
-    }
-
-    // Step 4: Let partialTime be ? ToTemporalTimeRecord(temporalTimeLike, ~partial~).
-    auto [hourOptional, minuteOptional, secondOptional, millisecondOptional, microsecondOptional, nanosecondOptional] = toPartialTime(globalObject, temporalTimeLike);
-    RETURN_IF_EXCEPTION(scope, { });
-
-    // Step 17: resolvedOptions = ? GetOptionsObject(options).
-    JSObject* options = intlGetOptionsObject(globalObject, optionsValue);
-    RETURN_IF_EXCEPTION(scope, { });
-
-    // Step 18: overflow = ? GetTemporalOverflowOption(resolvedOptions).
-    TemporalOverflow overflow = toTemporalOverflow(globalObject, options);
-    RETURN_IF_EXCEPTION(scope, { });
-
-    // Steps 5-16 (per field): if partialTime.X is not undefined, x = partialTime.X; else x = this.X.
-    ISO8601::Duration duration { };
-    duration.setField(TemporalUnit::Hour, hourOptional.value_or(hour()));
-    duration.setField(TemporalUnit::Minute, minuteOptional.value_or(minute()));
-    duration.setField(TemporalUnit::Second, secondOptional.value_or(second()));
-    duration.setField(TemporalUnit::Millisecond, millisecondOptional.value_or(millisecond()));
-    duration.setField(TemporalUnit::Microsecond, microsecondOptional.value_or(microsecond()));
-    duration.setField(TemporalUnit::Nanosecond, nanosecondOptional.value_or(nanosecond()));
-
-    // Step 19: result = ? RegulateTime(h, m, s, ms, us, ns, overflow).
-    // Step 20: Return ! CreateTemporalTime(result). (Caller wraps.)
-    RELEASE_AND_RETURN(scope, regulateTime(globalObject, WTF::move(duration), overflow));
-}
-
 // DifferenceTime ( time1, time2 ) — returns a time duration (in nanoseconds).
 // https://tc39.es/proposal-temporal/#sec-temporal-differencetime
 static Int128 differenceTime(ISO8601::PlainTime time1, ISO8601::PlainTime time2)
diff --git a/Source/JavaScriptCore/runtime/TemporalPlainTime.h b/Source/JavaScriptCore/runtime/TemporalPlainTime.h
index 9ee3a90..086eedc 100644
--- a/Source/JavaScriptCore/runtime/TemporalPlainTime.h
+++ b/Source/JavaScriptCore/runtime/TemporalPlainTime.h
@@ -61,7 +61,6 @@
     JSC_TEMPORAL_PLAIN_TIME_UNITS(JSC_DEFINE_TEMPORAL_PLAIN_TIME_FIELD);
 #undef JSC_DEFINE_TEMPORAL_PLAIN_TIME_FIELD
 
-    ISO8601::PlainTime with(JSGlobalObject*, JSObject* temporalTimeLike, JSValue options) const;
     ISO8601::PlainTime round(JSGlobalObject*, JSValue options) const;
     String toString(JSGlobalObject*, JSValue options) const;
     String toString(std::tuple<Precision, unsigned> precision = { Precision::Auto, 0 }) const
diff --git a/Source/JavaScriptCore/runtime/TemporalPlainTimePrototype.cpp b/Source/JavaScriptCore/runtime/TemporalPlainTimePrototype.cpp
index d470344..c475383 100644
--- a/Source/JavaScriptCore/runtime/TemporalPlainTimePrototype.cpp
+++ b/Source/JavaScriptCore/runtime/TemporalPlainTimePrototype.cpp
@@ -27,6 +27,7 @@
 #include "TemporalPlainTimePrototype.h"
 
 #include "IntlDateTimeFormat.h"
+#include "IntlObjectInlines.h"
 #include "JSCInlines.h"
 #include "ObjectConstructor.h"
 #include "TemporalDuration.h"
@@ -163,13 +164,42 @@
     if (!plainTime) [[unlikely]]
         return throwVMTypeError(globalObject, scope, "Temporal.PlainTime.prototype.with called on value that's not a PlainTime"_s);
 
-    JSValue temporalTimeLike  = callFrame->argument(0);
+    JSValue temporalTimeLike = callFrame->argument(0);
     if (!temporalTimeLike.isObject()) [[unlikely]]
         return throwVMTypeError(globalObject, scope, "First argument to Temporal.PlainTime.prototype.with must be an object"_s);
 
-    auto result = plainTime->with(globalObject, asObject(temporalTimeLike), callFrame->argument(1));
+    // Step 3: If ? IsPartialTemporalObject(temporalTimeLike) is false, throw TypeError.
+    bool isPartial = isPartialTemporalObject(globalObject, temporalTimeLike);
+    RETURN_IF_EXCEPTION(scope, { });
+    if (!isPartial) [[unlikely]]
+        return throwVMTypeError(globalObject, scope, "argument must be a partial Temporal object"_s);
+
+    // Step 4: Let partialTime be ? ToTemporalTimeRecord(temporalTimeLike, ~partial~).
+    auto [hourOptional, minuteOptional, secondOptional, millisecondOptional, microsecondOptional, nanosecondOptional] = TemporalPlainTime::toPartialTime(globalObject, asObject(temporalTimeLike));
     RETURN_IF_EXCEPTION(scope, { });
 
+    // Step 17: resolvedOptions = ? GetOptionsObject(options).
+    JSObject* options = intlGetOptionsObject(globalObject, callFrame->argument(1));
+    RETURN_IF_EXCEPTION(scope, { });
+
+    // Step 18: overflow = ? GetTemporalOverflowOption(resolvedOptions).
+    TemporalOverflow overflow = toTemporalOverflow(globalObject, options);
+    RETURN_IF_EXCEPTION(scope, { });
+
+    // Steps 5-16 (per field): if partialTime.X is not undefined, x = partialTime.X; else x = this.X.
+    ISO8601::Duration duration { };
+    duration.setField(TemporalUnit::Hour, hourOptional.value_or(plainTime->hour()));
+    duration.setField(TemporalUnit::Minute, minuteOptional.value_or(plainTime->minute()));
+    duration.setField(TemporalUnit::Second, secondOptional.value_or(plainTime->second()));
+    duration.setField(TemporalUnit::Millisecond, millisecondOptional.value_or(plainTime->millisecond()));
+    duration.setField(TemporalUnit::Microsecond, microsecondOptional.value_or(plainTime->microsecond()));
+    duration.setField(TemporalUnit::Nanosecond, nanosecondOptional.value_or(plainTime->nanosecond()));
+
+    // Step 19: result = ? RegulateTime(h, m, s, ms, us, ns, overflow).
+    auto result = TemporalPlainTime::regulateTime(globalObject, WTF::move(duration), overflow);
+    RETURN_IF_EXCEPTION(scope, { });
+
+    // Step 20: Return ! CreateTemporalTime(result).
     return JSValue::encode(TemporalPlainTime::create(vm, globalObject->plainTimeStructure(), WTF::move(result)));
 }
 
diff --git a/Source/JavaScriptCore/runtime/TemporalPlainYearMonthPrototype.cpp b/Source/JavaScriptCore/runtime/TemporalPlainYearMonthPrototype.cpp
index aab6e82..36b0d23 100644
--- a/Source/JavaScriptCore/runtime/TemporalPlainYearMonthPrototype.cpp
+++ b/Source/JavaScriptCore/runtime/TemporalPlainYearMonthPrototype.cpp
@@ -223,7 +223,7 @@
     TemporalOverflow overflow = toTemporalOverflow(globalObject, options);
     RETURN_IF_EXCEPTION(scope, { });
 
-    // Steps 5+7+10: ISODateToFields + CalendarMergeFields + CalendarYearMonthFromFields — fused into plainYearMonthWith.
+    // Steps 5+7+10: ISODateToFields + CalendarMergeFields + CalendarYearMonthFromFields, in plainYearMonthWith.
     auto result = TemporalCore::plainYearMonthWith(yearMonth->calendarID(), yearMonth->plainYearMonth().isoPlainDate(), partialFields, overflow);
     if (!result) [[unlikely]] {
         if (result.error().kind == TemporalErrorKind::TypeError)
diff --git a/Source/JavaScriptCore/runtime/TemporalZonedDateTimePrototype.cpp b/Source/JavaScriptCore/runtime/TemporalZonedDateTimePrototype.cpp
index 1593c38..1e8264c 100644
--- a/Source/JavaScriptCore/runtime/TemporalZonedDateTimePrototype.cpp
+++ b/Source/JavaScriptCore/runtime/TemporalZonedDateTimePrototype.cpp
@@ -48,7 +48,6 @@
 #include "TemporalZonedDateTime.h"
 #include "TimeZoneICUBridge.h"
 #include "ZonedDateTimeCore.h"
-#include <wtf/DateMath.h>
 #include <wtf/text/MakeString.h>
 
 namespace JSC {
@@ -642,23 +641,26 @@
     ASSERT(curOffsetNsOpt);
     int64_t curOffsetNs = *curOffsetNsOpt;
 
-    // Steps 9-16: ISODateToFields(calendar, isoDate, ~date~) + set time/offset fields.
-    //   (Fused with CalendarMergeFields below; current field values serve as the base.)
+    // Step 9: Let fields be ISODateToFields(calendar, isoDateTime.[[ISODate]], ~date~).
+    auto dateFields = TemporalCore::isoDateToFields(zdt->calendarID(), curDate, TemporalCore::ResolveType::Date);
+    if (!dateFields) [[unlikely]] {
+        throwTemporalError(globalObject, scope, dateFields.error());
+        return { };
+    }
+
     // Step 17: partialZonedDateTime = ? PrepareCalendarFields(calendar, temporalZonedDateTimeLike,
     //          «year,month,month-code,day», «hour,...,nanosecond,offset», ~partial~).
-    // Calendar comes from `this`; timeZone is not read in with().
-    auto partialFields = readZonedDateTimeFieldsFromObject<ZonedDateTimeFieldMode::Partial>(globalObject, fields, zdt->calendarID());
+    auto pf = readZonedDateTimeFieldsFromObject<ZonedDateTimeFieldMode::Partial>(globalObject, fields, zdt->calendarID());
     RETURN_IF_EXCEPTION(scope, { });
 
-    // Step 18: fields = CalendarMergeFields(calendar, fields, partialZonedDateTime).
-    // Implemented by merging partialFields into the current ZDT's field values.
-    // For non-ISO calendars, fall back to calendar-coordinate values from ISODateToFields.
+    // Step 18: Set fields to CalendarMergeFields(calendar, fields, partialZonedDateTime).
+    auto merged = TemporalCore::calendarMergeFields(zdt->calendarID(), *dateFields, pf.dateFields);
 
     // Step 19: resolvedOptions = ? GetOptionsObject(options).
     JSObject* options = intlGetOptionsObject(globalObject, callFrame->argument(1));
     RETURN_IF_EXCEPTION(scope, { });
 
-    // Steps 20-22: disambiguation, offset, overflow (alphabetical order per spec NOTE).
+    // Steps 20-22: disambiguation, offset, overflow (read in alphabetical order per the spec NOTE).
     auto disambiguation = toTemporalDisambiguation(globalObject, options);
     RETURN_IF_EXCEPTION(scope, { });
     auto offsetOpt = toTemporalOffset(globalObject, options, TemporalOffsetDisambiguation::Prefer);
@@ -666,41 +668,7 @@
     TemporalOverflow overflow = toTemporalOverflow(globalObject, options);
     RETURN_IF_EXCEPTION(scope, { });
 
-    // Step 23 fallback prep: for non-ISO calendars, absent patch fields fall back to
-    // the calendar-native values (not the raw ISO year/month/day).
-    int32_t fallbackYear = curDate.year();
-    uint32_t fallbackMonth = curDate.month();
-    uint32_t fallbackDay = curDate.day();
-    std::optional<ParsedMonthCode> fallbackMonthCode;
-    if (!TemporalCore::calendarIsISO(zdt->calendarID())) {
-        auto calFields = TemporalCore::isoToCalendarFields(zdt->calendarID(), curDate);
-        if (calFields) {
-            fallbackYear = calFields->year;
-            fallbackMonth = calFields->month;
-            fallbackDay = calFields->day;
-            if (!calFields->monthCode.isEmpty())
-                fallbackMonthCode = ISO8601::parseMonthCode(calFields->monthCode);
-        }
-    }
-
-    auto& pf = partialFields;
-    auto& pDate = pf.dateFields;
-
-    // Step 23: dateTimeResult = ? InterpretTemporalDateTimeFields(calendar, fields, overflow).
-    // Merge patch onto current ZDT. Only populate month/monthCode from what the user
-    // actually provided so nonISOResolveFields' consistency check doesn't fire spuriously.
-    TemporalCore::CalendarFieldsIn merged = pDate;
-    if (!pDate.day)
-        merged.day = static_cast<uint8_t>(fallbackDay);
-    if (!pDate.year && !(pDate.era && pDate.eraYear))
-        merged.year = fallbackYear;
-    if (!pDate.month && !pDate.monthCode) {
-        // Prefer fallback monthCode (calendar-native) over numeric month for non-ISO.
-        if (fallbackMonthCode)
-            merged.monthCode = fallbackMonthCode;
-        else
-            merged.month = fallbackMonth;
-    }
+    // Steps 10-16, merged into step 18 inline: see CalendarFieldKey in CalendarFields.cpp.
     TemporalCore::TimeFieldsIn timeFields {
         pf.hour.value_or(static_cast<double>(curTime.hour())),
         pf.minute.value_or(static_cast<double>(curTime.minute())),
@@ -709,13 +677,16 @@
         pf.microsecond.value_or(static_cast<double>(curTime.microsecond())),
         pf.nanosecond.value_or(static_cast<double>(curTime.nanosecond())),
     };
+
+    // Step 23: dateTimeResult = ? InterpretTemporalDateTimeFields(calendar, fields, overflow).
     auto pdt = interpretTemporalDateTimeFields(globalObject, zdt->calendarID(), merged, timeFields, overflow);
     RETURN_IF_EXCEPTION(scope, { });
     ISO8601::PlainDate newDate = pdt.date;
     ISO8601::PlainTime newTime = pdt.time;
 
-    // Step 24: newOffsetNanoseconds — the offset from the property bag, else the current one.
-    // temporal_rs: with_with_provider — offset is always Some (explicit or current).
+    // Step 24: newOffsetNanoseconds = ParseDateTimeUTCOffset(fields.[[OffsetString]]).
+    //   The [[OffsetString]] merge folds in here: format-then-parse of an unchanged offset is an
+    //   identity, so this is just "the partial's offset if given, else the receiver's".
     int64_t givenOffsetNs = pf.offsetNs.value_or(curOffsetNs);
 
     // Step 25: epochNanoseconds = ? InterpretISODateTimeOffset(dateTimeResult.[[ISODate]],
@@ -1379,8 +1350,7 @@
     auto [date, time] = zdt->getLocalDateTime(globalObject);
     RETURN_IF_EXCEPTION(scope, { });
     // Step 4: Return 𝔽(CalendarISOToDate(calendar, isoDateTime.[[ISODate]]).[[DayOfWeek]]).
-    // DayOfWeek is universal across all calendar systems (Mon=1 … Sun=7).
-    return JSValue::encode(jsNumber(ISO8601::dayOfWeek(date)));
+    return JSValue::encode(jsNumber(TemporalCore::calendarDayOfWeek(zdt->calendarID(), date)));
 }
 
 // https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.dayofyear
@@ -1416,10 +1386,11 @@
     // Step 3: isoDateTime = GetISODateTimeFor(timeZone, epochNanoseconds).
     auto [date, time] = zdt->getLocalDateTime(globalObject);
     RETURN_IF_EXCEPTION(scope, { });
-    // Step 4: Return ? CalendarWeekOfYear(calendar, isoDateTime.[[ISODate]]). Undefined for non-ISO.
-    if (!TemporalCore::calendarIsISO(zdt->calendarID()))
+    // Step 4: Return ? CalendarWeekOfYear(calendar, isoDateTime.[[ISODate]]).
+    auto week = TemporalCore::calendarWeekOfYear(zdt->calendarID(), date);
+    if (!week)
         return JSValue::encode(jsUndefined());
-    return JSValue::encode(jsNumber(ISO8601::weekOfYear(date)));
+    return JSValue::encode(jsNumber(*week));
 }
 
 // https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.yearofweek
@@ -1435,10 +1406,11 @@
     // Step 3: isoDateTime = GetISODateTimeFor(timeZone, epochNanoseconds).
     auto [date, time] = zdt->getLocalDateTime(globalObject);
     RETURN_IF_EXCEPTION(scope, { });
-    // Step 4: Return ? CalendarYearOfWeek(calendar, isoDateTime.[[ISODate]]). Undefined for non-ISO.
-    if (!TemporalCore::calendarIsISO(zdt->calendarID()))
+    // Step 4: Return ? CalendarYearOfWeek(calendar, isoDateTime.[[ISODate]]).
+    auto yearOfWeek = TemporalCore::calendarYearOfWeek(zdt->calendarID(), date);
+    if (!yearOfWeek)
         return JSValue::encode(jsUndefined());
-    return JSValue::encode(jsNumber(ISO8601::yearOfWeek(date)));
+    return JSValue::encode(jsNumber(*yearOfWeek));
 }
 
 // https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.hoursinday
@@ -1498,8 +1470,7 @@
     RETURN_IF_EXCEPTION(scope, { });
 
     // Step 4: Return 𝔽(CalendarISOToDate(calendar, isoDate).[[DaysInWeek]]).
-    // All calendar systems have a 7-day week.
-    return JSValue::encode(jsNumber(7));
+    return JSValue::encode(jsNumber(ISO8601::daysPerWeek));
 }
 
 // https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.daysinmonth
diff --git a/Source/JavaScriptCore/runtime/temporal/core/CalendarFields.cpp b/Source/JavaScriptCore/runtime/temporal/core/CalendarFields.cpp
index d48a60d..7d898de 100644
--- a/Source/JavaScriptCore/runtime/temporal/core/CalendarFields.cpp
+++ b/Source/JavaScriptCore/runtime/temporal/core/CalendarFields.cpp
@@ -37,6 +37,7 @@
 #include <cstdint>
 #include <wtf/DateMath.h>
 #include <wtf/MathExtras.h>
+#include <wtf/OptionSet.h>
 
 namespace JSC {
 namespace TemporalCore {
@@ -458,78 +459,151 @@
     return ResolvedCalendarDate { *result, calendarId };
 }
 
+// https://tc39.es/proposal-temporal/#sec-temporal-isodatetofields
+TemporalResult<CalendarFieldsIn> isoDateToFields(CalendarID calendarId, const ISO8601::PlainDate& isoDate, ResolveType type)
+{
+    // Step 1: Let fields be an empty Calendar Fields Record with all fields set to ~unset~.
+    CalendarFieldsIn fields;
+
+    // Step 2: Let calendarDate be CalendarISOToDate(calendar, isoDate).
+    if (calendarIsISO(calendarId)) {
+        // Identity for "iso8601", so no ICU round-trip; the month code is the ISO month, never leap.
+        fields.monthCode = ParsedMonthCode { isoDate.month(), /* isLeapMonth */ false }; // Step 3
+        if (type == ResolveType::MonthDay || type == ResolveType::Date) // Step 4
+            fields.day = isoDate.day();
+        if (type == ResolveType::YearMonth || type == ResolveType::Date) // Step 5
+            fields.year = isoDate.year();
+        return fields; // Step 6
+    }
+
+    auto calendarDate = isoToCalendarFields(calendarId, isoDate);
+    if (!calendarDate)
+        return makeUnexpected(calendarDate.error());
+
+    // Step 3: Set fields.[[MonthCode]] to calendarDate.[[MonthCode]].
+    if (!calendarDate->monthCode.isEmpty())
+        fields.monthCode = ISO8601::parseMonthCode(calendarDate->monthCode);
+    // Step 4: If type is either ~month-day~ or ~date~, set fields.[[Day]] to calendarDate.[[Day]].
+    if (type == ResolveType::MonthDay || type == ResolveType::Date)
+        fields.day = calendarDate->day;
+    // Step 5: If type is either ~year-month~ or ~date~, set fields.[[Year]] to calendarDate.[[Year]].
+    if (type == ResolveType::YearMonth || type == ResolveType::Date)
+        fields.year = calendarDate->year;
+    // Step 6: Return fields.
+    return fields;
+}
+
+// The Calendar Fields Record table's Enumeration Key column, restricted to the six calendar-date
+// keys CalendarFieldsIn carries; a bitset rather than the spec's List to stay allocation-free. No
+// ignore rule names the time keys or ~offset~/~time-zone~, so for those the merge degenerates to
+// "the partial wins, else the receiver", which the two .with callers do inline.
+enum class CalendarFieldKey : uint8_t {
+    Era = 1 << 0,
+    EraYear = 1 << 1,
+    Year = 1 << 2,
+    Month = 1 << 3,
+    MonthCode = 1 << 4,
+    Day = 1 << 5,
+};
+using CalendarFieldKeys = OptionSet<CalendarFieldKey>;
+
+// https://tc39.es/proposal-temporal/#sec-temporal-calendarfieldkeyspresent
+static CalendarFieldKeys calendarFieldKeysPresent(const CalendarFieldsIn& fields)
+{
+    CalendarFieldKeys keys;
+    if (fields.era)
+        keys.add(CalendarFieldKey::Era);
+    if (fields.eraYear)
+        keys.add(CalendarFieldKey::EraYear);
+    if (fields.year)
+        keys.add(CalendarFieldKey::Year);
+    if (fields.month)
+        keys.add(CalendarFieldKey::Month);
+    if (fields.monthCode)
+        keys.add(CalendarFieldKey::MonthCode);
+    if (fields.day)
+        keys.add(CalendarFieldKey::Day);
+    return keys;
+}
+
+// https://tc39.es/proposal-intl-era-monthcode/#sup-temporal-nonisofieldkeystoignore
+static CalendarFieldKeys nonISOFieldKeysToIgnore(CalendarID calendarId, CalendarFieldKeys keys)
+{
+    ASSERT(!calendarIsISO(calendarId));
+    // Step 1: Let ignoredKeys be a copy of keys — "a field always invalidates at least itself".
+    CalendarFieldKeys ignoredKeys = keys;
+    // Steps 2.a-2.b: month and monthCode are two encodings of the same field.
+    if (keys.contains(CalendarFieldKey::Month))
+        ignoredKeys.add(CalendarFieldKey::MonthCode);
+    if (keys.contains(CalendarFieldKey::MonthCode))
+        ignoredKeys.add(CalendarFieldKey::Month);
+    // Step 2.c: era+eraYear and year are two encodings of the same year, so changing either
+    //   encoding invalidates the other.
+    if (calendarHasEras(calendarId) && keys.containsAny({ CalendarFieldKey::Era, CalendarFieldKey::EraYear, CalendarFieldKey::Year }))
+        ignoredKeys.add({ CalendarFieldKey::Era, CalendarFieldKey::EraYear, CalendarFieldKey::Year });
+    // Step 2.d: with mid-year eras a day/month move can cross an era boundary without changing the
+    //   arithmetic year, so drop the era pair but keep year.
+    if (calendarHasMidYearEras(calendarId) && keys.containsAny({ CalendarFieldKey::Day, CalendarFieldKey::Month, CalendarFieldKey::MonthCode }))
+        ignoredKeys.add({ CalendarFieldKey::Era, CalendarFieldKey::EraYear });
+    // Step 4: Return ignoredKeys.
+    return ignoredKeys;
+}
+
+// https://tc39.es/proposal-temporal/#sec-temporal-calendarfieldkeystoignore
+static CalendarFieldKeys calendarFieldKeysToIgnore(CalendarID calendarId, CalendarFieldKeys keys)
+{
+    // Step 1: If calendar is "iso8601" — only the month/monthCode pairing applies.
+    if (calendarIsISO(calendarId)) {
+        CalendarFieldKeys ignoredKeys = keys; // Steps 1.a-1.b.i
+        if (keys.contains(CalendarFieldKey::Month)) // Step 1.b.ii
+            ignoredKeys.add(CalendarFieldKey::MonthCode);
+        if (keys.contains(CalendarFieldKey::MonthCode)) // Step 1.b.iii
+            ignoredKeys.add(CalendarFieldKey::Month);
+        return ignoredKeys; // Step 1.d
+    }
+    // Step 2: Return NonISOFieldKeysToIgnore(calendar, keys).
+    return nonISOFieldKeysToIgnore(calendarId, keys);
+}
+
+// https://tc39.es/proposal-temporal/#sec-temporal-calendarmergefields
+CalendarFieldsIn calendarMergeFields(CalendarID calendarId, const CalendarFieldsIn& fields, const CalendarFieldsIn& additionalFields)
+{
+    // Step 1: Let additionalKeys be CalendarFieldKeysPresent(additionalFields).
+    auto additionalKeys = calendarFieldKeysPresent(additionalFields);
+    // Step 2: Let overriddenKeys be CalendarFieldKeysToIgnore(calendar, additionalKeys).
+    auto overriddenKeys = calendarFieldKeysToIgnore(calendarId, additionalKeys);
+    // Step 3: Let merged be a Calendar Fields Record with all fields set to ~unset~.
+    CalendarFieldsIn merged;
+    // Step 4: fieldsKeys is CalendarFieldKeysPresent(fields) — each engaged optional below is its
+    //   own membership test, so there is nothing to precompute.
+    // Step 5: For each row of the Calendar Fields Record table.
+    auto mergeKey = [&](CalendarFieldKey key, auto& mergedField, const auto& field, const auto& additionalField) {
+        // Step 5.b: fieldsKeys contains key and overriddenKeys does not contain key.
+        if (field && !overriddenKeys.contains(key))
+            mergedField = field;
+        // Step 5.c: additionalKeys contains key.
+        if (additionalField)
+            mergedField = additionalField;
+    };
+    mergeKey(CalendarFieldKey::Era, merged.era, fields.era, additionalFields.era);
+    mergeKey(CalendarFieldKey::EraYear, merged.eraYear, fields.eraYear, additionalFields.eraYear);
+    mergeKey(CalendarFieldKey::Year, merged.year, fields.year, additionalFields.year);
+    mergeKey(CalendarFieldKey::Month, merged.month, fields.month, additionalFields.month);
+    mergeKey(CalendarFieldKey::MonthCode, merged.monthCode, fields.monthCode, additionalFields.monthCode);
+    mergeKey(CalendarFieldKey::Day, merged.day, fields.day, additionalFields.day);
+    // Step 6: Return merged.
+    return merged;
+}
+
 // plainYearMonthWith — temporal_rs: PlainYearMonth::with (src/builtins/core/plain_year_month.rs)
 // https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.with
-// Implements inner merge steps; IsPartialTemporalObject and PrepareCalendarFields done by JS-layer caller.
+// Implements steps 5, 7 and 10; IsPartialTemporalObject and PrepareCalendarFields done by JS-layer caller.
 TemporalResult<ResolvedCalendarDate> plainYearMonthWith(CalendarID calendarId, const ISO8601::PlainDate& currentISODate, const CalendarFieldsIn& partialFields, TemporalOverflow overflow)
 {
-    // Defensive: JS-layer caller guarantees at least one field via IsPartialTemporalObject;
-    // guard against direct internal calls with an empty partialFields.
-    if (!partialFields.year && !partialFields.month && !partialFields.monthCode
-        && !partialFields.era && !partialFields.eraYear)
-        return makeUnexpected(typeError("at least one field (year, month, monthCode, era, or eraYear) must be provided"_s));
-
-    bool isISO = calendarIsISO(calendarId);
-
-    if (isISO) {
-        // ISO: same merge logic as temporal_rs with_fallback_year_month.
-        CalendarFieldsIn merged;
-        merged.year = partialFields.year.has_value() ? partialFields.year : std::optional<int32_t>(currentISODate.year());
-        // temporal_rs: impl_with_fallback_method! — month: user's value only, no fallback
-        merged.month = partialFields.month;
-        // monthCode: user's overrides; fallback from current only if neither month nor monthCode provided
-        merged.monthCode = partialFields.monthCode;
-        if (!partialFields.month.has_value() && !partialFields.monthCode)
-            merged.monthCode = ISO8601::parseMonthCode(ISO8601::monthCode(currentISODate.month()));
-        return yearMonthFromFields(calendarId, merged, overflow);
-    }
-
-    // Non-ISO: get calendar fields from current date, merge with user partial fields.
-    // temporal_rs: impl_with_fallback_method! macro and impl_field_keys_to_ignore! macro (fields.rs).
-    auto calFields = isoToCalendarFields(calendarId, currentISODate);
-    if (!calFields)
-        return makeUnexpected(calFields.error());
-
-    // temporal_rs: impl_field_keys_to_ignore! — determine which fallback fields to suppress
-    bool keysToIgnoreMonth = partialFields.month.has_value() || partialFields.monthCode.has_value();
-    bool hasEras = calendarHasEras(calendarId);
-    bool keysToIgnoreEra = false;
-    bool keysToIgnoreYear = false;
-    if (hasEras) {
-        if (partialFields.year.has_value() || partialFields.eraYear.has_value() || partialFields.era.has_value()) {
-            keysToIgnoreEra = true;
-            keysToIgnoreYear = true;
-        }
-    }
-
-    CalendarFieldsIn merged;
-
-    // temporal_rs: impl_with_fallback_method! — era/eraYear: fallback from current if !keysToIgnoreEra
-    merged.era = partialFields.era;
-    merged.eraYear = partialFields.eraYear;
-    if (!keysToIgnoreEra) {
-        if (!merged.era && calFields->era.has_value() && !calFields->era->isEmpty())
-            merged.era = *calFields->era;
-        if (!merged.eraYear && calFields->eraYear.has_value())
-            merged.eraYear = *calFields->eraYear;
-    }
-
-    // temporal_rs: impl_with_fallback_method! — year: fallback from current if !keysToIgnoreYear
-    merged.year = partialFields.year;
-    if (!keysToIgnoreYear && !merged.year)
-        merged.year = std::optional<int32_t>(calFields->year);
-
-    // temporal_rs: impl_with_fallback_method! — month: user's value only, NEVER fallback
-    merged.month = partialFields.month;
-
-    // temporal_rs: impl_with_fallback_method! — monthCode: fallback ONLY when neither month nor monthCode provided
-    merged.monthCode = partialFields.monthCode;
-    if (!partialFields.month.has_value() && !partialFields.monthCode.has_value() && !keysToIgnoreMonth) {
-        if (!calFields->monthCode.isEmpty())
-            merged.monthCode = ISO8601::parseMonthCode(calFields->monthCode);
-    }
-
-    return yearMonthFromFields(calendarId, merged, overflow);
+    auto fields = isoDateToFields(calendarId, currentISODate, ResolveType::YearMonth);
+    if (!fields)
+        return makeUnexpected(fields.error());
+    return yearMonthFromFields(calendarId, calendarMergeFields(calendarId, *fields, partialFields), overflow);
 }
 
 // plainDateWith — temporal_rs: PlainDate::with (src/builtins/core/plain_date.rs)
@@ -540,73 +614,21 @@
 // Steps 6, 8-9 (PrepareCalendarFields, overflow) done by PlainDate::with(); step 11 (CreateTemporalDate) by prototype caller.
 TemporalResult<ResolvedCalendarDate> plainDateWith(CalendarID calendarId, const ISO8601::PlainDate& currentISODate, const CalendarFieldsIn& partialFields, TemporalOverflow overflow)
 {
-    // ISO fast path — mirrors plainYearMonthWith's ISO branch (plus day). Avoids isoToCalendarFields,
-    // which for a non-ROC/Buddhist/extreme-year calendar falls through to ICU calendar construction;
-    // iso8601 never needs that.
-    if (calendarIsISO(calendarId)) {
-        CalendarFieldsIn merged;
-        merged.year = partialFields.year.has_value() ? partialFields.year : std::optional<int32_t>(currentISODate.year());
-        merged.month = partialFields.month;
-        merged.monthCode = partialFields.monthCode;
-        if (!partialFields.month.has_value() && !partialFields.monthCode)
-            merged.monthCode = ISO8601::parseMonthCode(ISO8601::monthCode(currentISODate.month()));
-        merged.day = partialFields.day.has_value() ? *partialFields.day : currentISODate.day();
-        return dateFromFields(calendarId, merged, overflow);
-    }
+    auto fields = isoDateToFields(calendarId, currentISODate, ResolveType::Date);
+    if (!fields)
+        return makeUnexpected(fields.error());
+    return dateFromFields(calendarId, calendarMergeFields(calendarId, *fields, partialFields), overflow);
+}
 
-    // Step 5: fields = ISODateToFields(calendar, plainDate.[[ISODate]], ~date~).
-    auto calFields = isoToCalendarFields(calendarId, currentISODate);
-    if (!calFields)
-        return makeUnexpected(calFields.error());
-
-    bool hasEras = calendarHasEras(calendarId);
-    bool isJapanese = (calendarId == japaneseCalendarID());
-
-    // Step 7: fields = CalendarMergeFields(calendar, fields, partialDate).
-    // NonISOFieldKeysToIgnore (temporal_rs: impl_field_keys_to_ignore!):
-    //   year/era/eraYear in additionalKeys → both era AND year suppressed from base.
-    //   Japanese month/day changes → era suppressed (but NOT year; year still inherited).
-    bool keysToIgnoreEra = hasEras && (partialFields.year.has_value() || partialFields.era.has_value() || partialFields.eraYear.has_value());
-    bool keysToIgnoreYear = keysToIgnoreEra;
-    if (isJapanese && (partialFields.month.has_value() || partialFields.monthCode.has_value() || partialFields.day.has_value()))
-        keysToIgnoreEra = true; // suppress era but not year for Japanese month/day changes
-    bool keysToIgnoreMonth = partialFields.month.has_value() || partialFields.monthCode.has_value();
-
-    CalendarFieldsIn merged;
-
-    merged.era = partialFields.era;
-    merged.eraYear = partialFields.eraYear;
-    if (!keysToIgnoreEra) {
-        if (!merged.era && calFields->era.has_value() && !calFields->era->isEmpty())
-            merged.era = *calFields->era;
-        if (!merged.eraYear && calFields->eraYear.has_value())
-            merged.eraYear = *calFields->eraYear;
-    }
-
-    merged.year = partialFields.year;
-    if (!keysToIgnoreYear && !merged.year)
-        merged.year = std::optional<int32_t>(calFields->year);
-
-    // month: user's value if provided; suppress base month when monthCode is provided (they're mutually exclusive).
-    // Also drop base's month for lunisolar calendars when the user changes year (or era) — the ordinal shifts
-    // between years with different leap-month layouts and monthCode alone is the correct calendar-native identifier.
-    // Keep an inherited month as monthCode only (not raw month) so it can be constrained in a different year.
-    bool lunisolarYearChange = calendarIsLunisolar(calendarId)
-        && (partialFields.year.has_value() || partialFields.era.has_value() || partialFields.eraYear.has_value());
-    if (partialFields.month.has_value())
-        merged.month = *partialFields.month;
-    else if (!partialFields.monthCode.has_value() && calFields->monthCode.isEmpty() && !lunisolarYearChange)
-        merged.month = static_cast<uint32_t>(calFields->month);
-    merged.day = partialFields.day.has_value() ? *partialFields.day : calFields->day;
-
-    merged.monthCode = partialFields.monthCode;
-    if (!partialFields.month.has_value() && !partialFields.monthCode.has_value() && !keysToIgnoreMonth) {
-        if (!calFields->monthCode.isEmpty())
-            merged.monthCode = ISO8601::parseMonthCode(calFields->monthCode);
-    }
-
-    // Step 10: isoDate = CalendarDateFromFields(calendar, fields, overflow).
-    return dateFromFields(calendarId, merged, overflow);
+// plainMonthDayWith — temporal_rs: PlainMonthDay::with (src/builtins/core/plain_month_day.rs)
+// https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.with
+// Implements steps 5, 7 and 10 (CalendarMonthDayFromFields).
+TemporalResult<ResolvedCalendarDate> plainMonthDayWith(CalendarID calendarId, const ISO8601::PlainDate& currentISODate, const CalendarFieldsIn& partialFields, TemporalOverflow overflow)
+{
+    auto fields = isoDateToFields(calendarId, currentISODate, ResolveType::MonthDay);
+    if (!fields)
+        return makeUnexpected(fields.error());
+    return monthDayFromFields(calendarId, calendarMergeFields(calendarId, *fields, partialFields), overflow);
 }
 
 // differenceYearMonth — temporal_rs: PlainYearMonth::diff (src/builtins/core/plain_year_month.rs)
@@ -628,32 +650,21 @@
     }
 
     // Non-ISO: resolve both to day=1 via dateFromFields (matching temporal_rs).
-    // This re-resolves the ISO date through the calendar pipeline with day=1.
-    // Step 7: ISODateToFields(calendar, thisISODate, year-month); set [[Day]] to 1; CalendarDateFromFields.
-    auto thisCalFields = isoToCalendarFields(calendarId, thisISODate);
-    if (!thisCalFields)
-        return makeUnexpected(thisCalFields.error());
-    CalendarFieldsIn thisFields;
-    thisFields.year = thisCalFields->year;
-    thisFields.month = thisCalFields->month;
-    thisFields.day = 1;
-    if (!thisCalFields->monthCode.isEmpty())
-        thisFields.monthCode = ISO8601::parseMonthCode(thisCalFields->monthCode);
-    auto thisResolved = dateFromFields(calendarId, thisFields, TemporalOverflow::Constrain);
+    // Steps 7-9 for each side: ISODateToFields(calendar, isoDate, ~year-month~); set [[Day]] to 1;
+    //   ? CalendarDateFromFields(calendar, fields, ~constrain~).
+    auto thisFields = isoDateToFields(calendarId, thisISODate, ResolveType::YearMonth);
+    if (!thisFields)
+        return makeUnexpected(thisFields.error());
+    thisFields->day = 1;
+    auto thisResolved = dateFromFields(calendarId, *thisFields, TemporalOverflow::Constrain);
     if (!thisResolved)
         return makeUnexpected(thisResolved.error());
 
-    // Steps 8-9: ISODateToFields(calendar, otherISODate, year-month); set [[Day]] to 1; CalendarDateFromFields.
-    auto otherCalFields = isoToCalendarFields(calendarId, otherISODate);
-    if (!otherCalFields)
-        return makeUnexpected(otherCalFields.error());
-    CalendarFieldsIn otherFields;
-    otherFields.year = otherCalFields->year;
-    otherFields.month = otherCalFields->month;
-    otherFields.day = 1;
-    if (!otherCalFields->monthCode.isEmpty())
-        otherFields.monthCode = ISO8601::parseMonthCode(otherCalFields->monthCode);
-    auto otherResolved = dateFromFields(calendarId, otherFields, TemporalOverflow::Constrain);
+    auto otherFields = isoDateToFields(calendarId, otherISODate, ResolveType::YearMonth);
+    if (!otherFields)
+        return makeUnexpected(otherFields.error());
+    otherFields->day = 1;
+    auto otherResolved = dateFromFields(calendarId, *otherFields, TemporalOverflow::Constrain);
     if (!otherResolved)
         return makeUnexpected(otherResolved.error());
 
@@ -668,78 +679,34 @@
 // and step 15 (CreateTemporalYearMonth wrap) are done by the JS-layer caller.
 TemporalResult<ResolvedCalendarDate> plainYearMonthAdd(CalendarID calendarId, const ISO8601::PlainDate& currentISODate, const ISO8601::Duration& duration, TemporalOverflow overflow)
 {
-    bool isISO = calendarIsISO(calendarId);
+    // Step 9: Let fields be ISODateToFields(calendar, yearMonth.[[ISODate]], ~year-month~).
+    auto fields = isoDateToFields(calendarId, currentISODate, ResolveType::YearMonth);
+    if (!fields)
+        return makeUnexpected(fields.error());
+    // Step 10: Set fields.[[Day]] to 1.
+    fields->day = 1;
 
-    if (isISO) {
-        // Step 1: Get calendar fields from currentISODate (year + monthCode), set day=1.
-        CalendarFieldsIn fields;
-        fields.year = currentISODate.year();
-        fields.month = currentISODate.month();
-        fields.monthCode = ISO8601::parseMonthCode(ISO8601::monthCode(currentISODate.month()));
-        fields.day = 1;
-        // Step 2: CalendarDateFromFields -> PlainDate.
-        auto dateResult = dateFromFields(calendarId, fields, TemporalOverflow::Constrain);
-        if (!dateResult)
-            return makeUnexpected(dateResult.error());
-        // Step 3: CalendarDateAdd(duration) -> addedDate.
-        // ISO: bypass ICU bridge and use pure isoDateAdd — handles extreme boundary dates
-        // correctly (year ±271821) without ICU calendar clamping.
-        auto addedISO = calendarDateAdd(dateResult->isoDate, duration, overflow);
-        if (!addedISO)
-            return makeUnexpected(addedISO.error());
-        // Steps 4-5: Get year + monthCode from addedDate; CalendarYearMonthFromFields -> result PYM ISO date.
-        CalendarFieldsIn addedFields;
-        addedFields.year = addedISO->year();
-        addedFields.monthCode = ISO8601::parseMonthCode(ISO8601::monthCode(addedISO->month()));
-        return yearMonthFromFields(calendarId, addedFields, overflow);
-    }
-
-    // Non-ISO: temporal_rs add_duration steps 9-15.
-    // Step 1: ISODateToFields(calendar, yearMonth.[[ISODate]], year-month) -> year + monthCode, day=1.
-    auto calFields = isoToCalendarFields(calendarId, currentISODate);
-    if (!calFields)
-        return makeUnexpected(calFields.error());
-
-    CalendarFieldsIn fieldsDay1;
-    fieldsDay1.year = calFields->year;
-    fieldsDay1.day = 1;
-    if (!calFields->monthCode.isEmpty())
-        fieldsDay1.monthCode = ISO8601::parseMonthCode(calFields->monthCode);
-    else
-        fieldsDay1.month = calFields->month;
-    if (calFields->era.has_value() && calFields->eraYear.has_value()) {
-        fieldsDay1.era = *calFields->era;
-        fieldsDay1.eraYear = *calFields->eraYear;
-    }
-
-    // Step 2: CalendarDateFromFields -> PlainDate ISO.
-    auto dateResult = dateFromFields(calendarId, fieldsDay1, TemporalOverflow::Constrain);
+    // Step 11: Let date be ? CalendarDateFromFields(calendar, fields, ~constrain~).
+    auto dateResult = dateFromFields(calendarId, *fields, TemporalOverflow::Constrain);
     if (!dateResult)
         return makeUnexpected(dateResult.error());
 
-    // Step 3: CalendarDateAdd(duration) -> addedDate ISO.
-    auto addedISO = calendarDateAdd(calendarId, dateResult->isoDate, duration, overflow);
+    // Step 12: Let addedDate be ? CalendarDateAdd(calendar, date, durationToAdd, overflow).
+    //   ISO takes the pure isoDateAdd overload, which handles the extreme boundary years
+    //   (±271821) that ICU calendar arithmetic clamps.
+    auto addedISO = calendarIsISO(calendarId)
+        ? calendarDateAdd(dateResult->isoDate, duration, overflow)
+        : calendarDateAdd(calendarId, dateResult->isoDate, duration, overflow);
     if (!addedISO)
         return makeUnexpected(addedISO.error());
 
-    // Step 4: Get year + monthCode from addedDate.
-    auto addedCalFields = isoToCalendarFields(calendarId, *addedISO);
-    if (!addedCalFields)
-        return makeUnexpected(addedCalFields.error());
+    // Step 13: Let addedDateFields be ISODateToFields(calendar, addedDate, ~year-month~).
+    auto addedFields = isoDateToFields(calendarId, *addedISO, ResolveType::YearMonth);
+    if (!addedFields)
+        return makeUnexpected(addedFields.error());
 
-    CalendarFieldsIn addedFields;
-    addedFields.year = addedCalFields->year;
-    if (!addedCalFields->monthCode.isEmpty())
-        addedFields.monthCode = ISO8601::parseMonthCode(addedCalFields->monthCode);
-    else
-        addedFields.month = addedCalFields->month;
-    if (addedCalFields->era.has_value() && addedCalFields->eraYear.has_value()) {
-        addedFields.era = *addedCalFields->era;
-        addedFields.eraYear = *addedCalFields->eraYear;
-    }
-
-    // Step 5: CalendarYearMonthFromFields -> result PYM ISO date.
-    return yearMonthFromFields(calendarId, addedFields, overflow);
+    // Step 14: Return ? CalendarYearMonthFromFields(calendar, addedDateFields, overflow).
+    return yearMonthFromFields(calendarId, *addedFields, overflow);
 }
 
 // plainYearMonthToPlainDate — temporal_rs: PlainYearMonth::to_plain_date (src/builtins/core/plain_year_month.rs)
@@ -747,94 +714,48 @@
 // Implements steps 5, 7, 8; steps 1-4, 6, 9 done by JS-layer caller.
 TemporalResult<ResolvedCalendarDate> plainYearMonthToPlainDate(CalendarID calendarId, const ISO8601::PlainDate& pymISODate, uint8_t day)
 {
-    bool isISO = calendarIsISO(calendarId);
+    // Step 5: Let fields be ISODateToFields(calendar, plainYearMonth.[[ISODate]], ~year-month~).
+    auto fields = isoDateToFields(calendarId, pymISODate, ResolveType::YearMonth);
+    if (!fields)
+        return makeUnexpected(fields.error());
 
-    if (isISO) {
-        // Steps 5+7 (ISO): year/month from pymISODate; merge day.
-        CalendarFieldsIn fields;
-        fields.year = pymISODate.year();
-        fields.month = pymISODate.month();
-        fields.day = day;
-        // Step 8: CalendarDateFromFields(~constrain~).
-        return dateFromFields(calendarId, fields, TemporalOverflow::Constrain);
-    }
+    // Step 6's PrepareCalendarFields(«~day~») is the caller's; it arrives as `day`.
+    CalendarFieldsIn inputFields;
+    inputFields.day = day;
 
-    // Steps 5+7 (non-ISO): ISODateToFields via ICU bridge; merge day.
-    auto yearResult = calendarYear(calendarId, pymISODate);
-    if (!yearResult)
-        return makeUnexpected(yearResult.error());
-    auto monthCodeStr = calendarMonthCode(calendarId, pymISODate);
-    if (!monthCodeStr)
-        return makeUnexpected(monthCodeStr.error());
-
-    CalendarFieldsIn fields;
-    fields.year = *yearResult;
-    fields.day = day;
-    fields.monthCode = ISO8601::parseMonthCode(*monthCodeStr);
-    // Step 8: CalendarDateFromFields(~constrain~).
-    return dateFromFields(calendarId, fields, TemporalOverflow::Constrain);
+    // Step 7: Let mergedFields be CalendarMergeFields(calendar, fields, inputFields).
+    // Step 8: Return ? CalendarDateFromFields(calendar, mergedFields, ~constrain~).
+    return dateFromFields(calendarId, calendarMergeFields(calendarId, *fields, inputFields), TemporalOverflow::Constrain);
 }
 
 // plainYearMonthFromISODate — no 1:1 temporal_rs function; inlined in PlainYearMonth::from_parsed
 // https://tc39.es/proposal-temporal/#sec-temporal-totemporalyearmonth (string parse path)
-// Implements steps 10 and 12; the within-limits check (spec step 9) runs inside
-// yearMonthFromFields (see ISOYearMonthWithinLimits guard below in that function).
+// Implements steps 12 and 14; step 11's ISOYearMonthWithinLimits check runs inside
+// yearMonthFromFields (see the guard in that function).
 TemporalResult<ResolvedCalendarDate> plainYearMonthFromISODate(CalendarID calendarId, const ISO8601::PlainDate& fullISODate)
 {
-    bool isISO = calendarIsISO(calendarId);
-
-    if (isISO) {
-        // ISO path: year/month from fullISODate; yearMonthFromFields stores with day=1.
-        CalendarFieldsIn fields;
-        fields.year = fullISODate.year();
-        fields.month = fullISODate.month();
-        return yearMonthFromFields(calendarId, fields, TemporalOverflow::Constrain);
-    }
-
-    // Step 10 (non-ISO): ISODateToFields via ICU bridge — gets year + monthCode.
-    auto yearResult = calendarYear(calendarId, fullISODate);
-    if (!yearResult)
-        return makeUnexpected(yearResult.error());
-    auto monthCodeStr = calendarMonthCode(calendarId, fullISODate);
-    if (!monthCodeStr)
-        return makeUnexpected(monthCodeStr.error());
-
-    CalendarFieldsIn fields;
-    fields.year = *yearResult;
-    fields.day = 1;
-    fields.monthCode = ISO8601::parseMonthCode(*monthCodeStr);
-    // Step 12: CalendarYearMonthFromFields(~constrain~) per spec note.
-    return yearMonthFromFields(calendarId, fields, TemporalOverflow::Constrain);
+    // Step 12: Set result to ISODateToFields(calendar, isoDate, ~year-month~).
+    auto fields = isoDateToFields(calendarId, fullISODate, ResolveType::YearMonth);
+    if (!fields)
+        return makeUnexpected(fields.error());
+    // Step 14: Return ? CalendarYearMonthFromFields(calendar, result, ~constrain~) — ~constrain~
+    //   regardless of overflow, per Step 13's NOTE. yearMonthFromFields stores day=1 itself.
+    return yearMonthFromFields(calendarId, *fields, TemporalOverflow::Constrain);
 }
 
 // https://tc39.es/proposal-temporal/#sec-temporal-totemporalmonthday (string parse path)
-// Implements steps 10 and 12; the within-limits check (spec step 9) runs inside
-// monthDayFromFields (see ISODateWithinLimits guard in that function).
+// Implements steps 13 and 15; step 12's ISODateWithinLimits check runs inside monthDayFromFields.
+// Step 10's "iso8601" shortcut is not special-cased: routing it through CalendarMonthDayFromFields
+// reaches the same reference year 1972 without a second code path.
 TemporalResult<ResolvedCalendarDate> plainMonthDayFromISODate(CalendarID calendarId, const ISO8601::PlainDate& fullISODate, TemporalOverflow overflow)
 {
-    bool isISO = calendarIsISO(calendarId);
-
-    if (isISO) {
-        // ISO path (spec step 7): store directly with reference year 1972.
-        CalendarFieldsIn fields;
-        fields.month = fullISODate.month();
-        fields.day = fullISODate.day();
-        return monthDayFromFields(calendarId, fields, overflow);
-    }
-
-    // Step 10: ISODateToFields(calendar, fullISODate, ~month-day~) — gets monthCode + day.
-    auto monthCodeStr = calendarMonthCode(calendarId, fullISODate);
-    if (!monthCodeStr)
-        return makeUnexpected(monthCodeStr.error());
-    auto dayResult = calendarDay(calendarId, fullISODate);
-    if (!dayResult)
-        return makeUnexpected(dayResult.error());
-
-    CalendarFieldsIn fields;
-    fields.monthCode = ISO8601::parseMonthCode(*monthCodeStr);
-    fields.day = static_cast<uint8_t>(*dayResult);
-    // Step 12: caller must pass TemporalOverflow::Constrain per spec note.
-    return monthDayFromFields(calendarId, fields, overflow);
+    // Step 13: Set result to ISODateToFields(calendar, isoDate, ~month-day~).
+    auto fields = isoDateToFields(calendarId, fullISODate, ResolveType::MonthDay);
+    if (!fields)
+        return makeUnexpected(fields.error());
+    // Step 15: Return ? CalendarMonthDayFromFields(calendar, result, ~constrain~) — every caller
+    //   passes ~constrain~ per Step 14's NOTE.
+    return monthDayFromFields(calendarId, *fields, overflow);
 }
 
 } // namespace TemporalCore
diff --git a/Source/JavaScriptCore/runtime/temporal/core/CalendarFields.h b/Source/JavaScriptCore/runtime/temporal/core/CalendarFields.h
index 31d573d..ae504a7 100644
--- a/Source/JavaScriptCore/runtime/temporal/core/CalendarFields.h
+++ b/Source/JavaScriptCore/runtime/temporal/core/CalendarFields.h
@@ -77,10 +77,16 @@
 
 JS_EXPORT_PRIVATE TemporalResult<ResolvedCalendarDate> monthDayFromFields(CalendarID, const CalendarFieldsIn&, TemporalOverflow);
 
+JS_EXPORT_PRIVATE TemporalResult<CalendarFieldsIn> isoDateToFields(CalendarID, const ISO8601::PlainDate&, ResolveType);
+
+JS_EXPORT_PRIVATE CalendarFieldsIn calendarMergeFields(CalendarID, const CalendarFieldsIn&, const CalendarFieldsIn& additionalFields);
+
 JS_EXPORT_PRIVATE TemporalResult<ResolvedCalendarDate> plainYearMonthWith(CalendarID, const ISO8601::PlainDate& currentISODate, const CalendarFieldsIn& partialFields, TemporalOverflow);
 
 JS_EXPORT_PRIVATE TemporalResult<ResolvedCalendarDate> plainDateWith(CalendarID, const ISO8601::PlainDate& currentISODate, const CalendarFieldsIn& partialFields, TemporalOverflow);
 
+JS_EXPORT_PRIVATE TemporalResult<ResolvedCalendarDate> plainMonthDayWith(CalendarID, const ISO8601::PlainDate& currentISODate, const CalendarFieldsIn& partialFields, TemporalOverflow);
+
 JS_EXPORT_PRIVATE TemporalResult<ISO8601::Duration> differenceYearMonth(CalendarID, const ISO8601::PlainDate& thisISODate, const ISO8601::PlainDate& otherISODate, TemporalUnit largestUnit);
 
 JS_EXPORT_PRIVATE TemporalResult<ResolvedCalendarDate> plainYearMonthAdd(CalendarID, const ISO8601::PlainDate& currentISODate, const ISO8601::Duration&, TemporalOverflow);
diff --git a/Source/JavaScriptCore/runtime/temporal/core/CalendarICUBridge.cpp b/Source/JavaScriptCore/runtime/temporal/core/CalendarICUBridge.cpp
index b493db2..4af667b 100644
--- a/Source/JavaScriptCore/runtime/temporal/core/CalendarICUBridge.cpp
+++ b/Source/JavaScriptCore/runtime/temporal/core/CalendarICUBridge.cpp
@@ -918,7 +918,9 @@
     return (calendarId == chineseCalendarID() || calendarId == dangiCalendarID()) && std::abs(isoYear) > 10000;
 }
 
-// isoToCalendarFields — no single temporal_rs equivalent; aggregates Calendar::year/month/month_code/day/era.
+// https://tc39.es/proposal-temporal/#sec-temporal-calendarisotodate
+// CalendarISOToDate's six field-resolution fields in one ICU open. The calendar* accessors compute the
+// same fields one at a time and must agree with these: they feed the getters, these feed resolution.
 TemporalResult<CalendarFields> isoToCalendarFields(CalendarID calendarId, const ISO8601::PlainDate& isoDate)
 {
     if (calendarId == rocCalendarID() || calendarId == buddhistCalendarID()) {
@@ -1150,6 +1152,41 @@
     });
 }
 
+// calendarDayOfWeek — temporal_rs: Calendar::day_of_week (src/builtins/core/calendar.rs)
+// https://tc39.es/proposal-temporal/#sec-temporal-calendarisotodate
+// CalendarISOToDate [[DayOfWeek]] field:
+//   1. (all calendars) ISODayOfWeek(isoDate).
+// The 7-day cycle is unbroken across every calendar CLDR exposes, so the ISO value is also the
+// calendar value; calendarId is unused and unnamed to say so.
+uint8_t calendarDayOfWeek(CalendarID, const ISO8601::PlainDate& isoDate)
+{
+    return ISO8601::dayOfWeek(isoDate);
+}
+
+// calendarWeekOfYear — temporal_rs: Calendar::week_of_year (src/builtins/core/calendar.rs)
+// https://tc39.es/proposal-temporal/#sec-temporal-calendarisotodate
+// CalendarISOToDate [[WeekOfYear]].[[Week]] field:
+//   1. (iso8601) ISOWeekOfYear(isoDate).[[Week]].
+//   2. (non-ISO) ~undefined~ — no well-defined week calendar system.
+std::optional<uint8_t> calendarWeekOfYear(CalendarID calendarId, const ISO8601::PlainDate& isoDate)
+{
+    if (calendarId != iso8601CalendarID())
+        return std::nullopt;
+    return ISO8601::weekOfYear(isoDate);
+}
+
+// calendarYearOfWeek — temporal_rs: Calendar::year_of_week (src/builtins/core/calendar.rs)
+// https://tc39.es/proposal-temporal/#sec-temporal-calendarisotodate
+// CalendarISOToDate [[WeekOfYear]].[[Year]] field: differs from [[Year]] only at a year boundary whose week belongs to the neighbouring year.
+//   1. (iso8601) ISOWeekOfYear(isoDate).[[Year]].
+//   2. (non-ISO) ~undefined~ — no well-defined week calendar system.
+std::optional<int32_t> calendarYearOfWeek(CalendarID calendarId, const ISO8601::PlainDate& isoDate)
+{
+    if (calendarId != iso8601CalendarID())
+        return std::nullopt;
+    return ISO8601::yearOfWeek(isoDate);
+}
+
 // calendarDayOfYear — 1-based day within the calendar's year (ISO's if calendarUsesISODateArithmetic).
 TemporalResult<int32_t> calendarDayOfYear(CalendarID calendarId, const ISO8601::PlainDate& isoDate)
 {
diff --git a/Source/JavaScriptCore/runtime/temporal/core/CalendarICUBridge.h b/Source/JavaScriptCore/runtime/temporal/core/CalendarICUBridge.h
index 8b988e9..5aaf8a8 100644
--- a/Source/JavaScriptCore/runtime/temporal/core/CalendarICUBridge.h
+++ b/Source/JavaScriptCore/runtime/temporal/core/CalendarICUBridge.h
@@ -69,6 +69,9 @@
         || id == japaneseCalendarID() || id == persianCalendarID() || id == rocCalendarID();
 }
 
+// https://tc39.es/proposal-intl-era-monthcode/#sec-temporal-calendarhasmidyeareras
+inline bool calendarHasMidYearEras(CalendarID id) { return id == japaneseCalendarID(); }
+
 // calendarIsLunisolar — true for calendars with leap months (Chinese, Dangi, Hebrew).
 // NOTE: temporal_rs Calendar::is_iso() returns true for ISO8601 (opposite semantic).
 inline bool calendarIsLunisolar(CalendarID id)
@@ -88,6 +91,12 @@
 
 JS_EXPORT_PRIVATE TemporalResult<int32_t> calendarDayOfYear(CalendarID, const ISO8601::PlainDate& isoDate);
 
+JS_EXPORT_PRIVATE uint8_t calendarDayOfWeek(CalendarID, const ISO8601::PlainDate& isoDate);
+
+JS_EXPORT_PRIVATE std::optional<uint8_t> calendarWeekOfYear(CalendarID, const ISO8601::PlainDate& isoDate);
+
+JS_EXPORT_PRIVATE std::optional<int32_t> calendarYearOfWeek(CalendarID, const ISO8601::PlainDate& isoDate);
+
 JS_EXPORT_PRIVATE bool isValidMonthCodeForCalendar(CalendarID, ParsedMonthCode);
 
 JS_EXPORT_PRIVATE bool yearContainsMonthCode(CalendarID, int32_t year, ParsedMonthCode);