Ver código fonte

LibJS: Implement the Temporal.PlainYearMonth constructor

And the simple Temporal.PlainYearMonth.prototype getters, so that the
constructed Temporal.PlainYearMonth may actually be validated.
Timothy Flynn 7 meses atrás
pai
commit
b68d67693e
27 arquivos alterados com 926 adições e 3 exclusões
  1. 3 0
      Libraries/LibJS/CMakeLists.txt
  2. 4 3
      Libraries/LibJS/Forward.h
  3. 12 0
      Libraries/LibJS/Print.cpp
  4. 2 0
      Libraries/LibJS/Runtime/Intrinsics.cpp
  5. 3 0
      Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp
  6. 48 0
      Libraries/LibJS/Runtime/Temporal/Calendar.cpp
  7. 2 0
      Libraries/LibJS/Runtime/Temporal/Calendar.h
  8. 152 0
      Libraries/LibJS/Runtime/Temporal/PlainYearMonth.cpp
  9. 39 0
      Libraries/LibJS/Runtime/Temporal/PlainYearMonth.h
  10. 116 0
      Libraries/LibJS/Runtime/Temporal/PlainYearMonthConstructor.cpp
  11. 34 0
      Libraries/LibJS/Runtime/Temporal/PlainYearMonthConstructor.h
  12. 126 0
      Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.cpp
  13. 38 0
      Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.h
  14. 2 0
      Libraries/LibJS/Runtime/Temporal/Temporal.cpp
  15. 14 0
      Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.compare.js
  16. 130 0
      Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.from.js
  17. 60 0
      Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.js
  18. 15 0
      Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.calendarId.js
  19. 14 0
      Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.daysInMonth.js
  20. 14 0
      Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.daysInYear.js
  21. 14 0
      Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.era.js
  22. 14 0
      Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.eraYear.js
  23. 14 0
      Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.inLeapYear.js
  24. 14 0
      Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.month.js
  25. 14 0
      Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.monthCode.js
  26. 14 0
      Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.monthsInYear.js
  27. 14 0
      Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.year.js

+ 3 - 0
Libraries/LibJS/CMakeLists.txt

@@ -217,6 +217,9 @@ set(SOURCES
     Runtime/Temporal/PlainMonthDay.cpp
     Runtime/Temporal/PlainMonthDay.cpp
     Runtime/Temporal/PlainMonthDayConstructor.cpp
     Runtime/Temporal/PlainMonthDayConstructor.cpp
     Runtime/Temporal/PlainMonthDayPrototype.cpp
     Runtime/Temporal/PlainMonthDayPrototype.cpp
+    Runtime/Temporal/PlainYearMonth.cpp
+    Runtime/Temporal/PlainYearMonthConstructor.cpp
+    Runtime/Temporal/PlainYearMonthPrototype.cpp
     Runtime/Temporal/PlainTime.cpp
     Runtime/Temporal/PlainTime.cpp
     Runtime/Temporal/Temporal.cpp
     Runtime/Temporal/Temporal.cpp
     Runtime/Temporal/TimeZone.cpp
     Runtime/Temporal/TimeZone.cpp

+ 4 - 3
Libraries/LibJS/Forward.h

@@ -87,9 +87,10 @@
     __JS_ENUMERATE(RelativeTimeFormat, relative_time_format, RelativeTimeFormatPrototype, RelativeTimeFormatConstructor) \
     __JS_ENUMERATE(RelativeTimeFormat, relative_time_format, RelativeTimeFormatPrototype, RelativeTimeFormatConstructor) \
     __JS_ENUMERATE(Segmenter, segmenter, SegmenterPrototype, SegmenterConstructor)
     __JS_ENUMERATE(Segmenter, segmenter, SegmenterPrototype, SegmenterConstructor)
 
 
-#define JS_ENUMERATE_TEMPORAL_OBJECTS                                          \
-    __JS_ENUMERATE(Duration, duration, DurationPrototype, DurationConstructor) \
-    __JS_ENUMERATE(PlainMonthDay, plain_month_day, PlainMonthDayPrototype, PlainMonthDayConstructor)
+#define JS_ENUMERATE_TEMPORAL_OBJECTS                                                                \
+    __JS_ENUMERATE(Duration, duration, DurationPrototype, DurationConstructor)                       \
+    __JS_ENUMERATE(PlainMonthDay, plain_month_day, PlainMonthDayPrototype, PlainMonthDayConstructor) \
+    __JS_ENUMERATE(PlainYearMonth, plain_year_month, PlainYearMonthPrototype, PlainYearMonthConstructor)
 
 
 #define JS_ENUMERATE_BUILTIN_NAMESPACE_OBJECTS \
 #define JS_ENUMERATE_BUILTIN_NAMESPACE_OBJECTS \
     __JS_ENUMERATE(AtomicsObject, atomics)     \
     __JS_ENUMERATE(AtomicsObject, atomics)     \

+ 12 - 0
Libraries/LibJS/Print.cpp

@@ -49,6 +49,7 @@
 #include <LibJS/Runtime/StringPrototype.h>
 #include <LibJS/Runtime/StringPrototype.h>
 #include <LibJS/Runtime/Temporal/Duration.h>
 #include <LibJS/Runtime/Temporal/Duration.h>
 #include <LibJS/Runtime/Temporal/PlainMonthDay.h>
 #include <LibJS/Runtime/Temporal/PlainMonthDay.h>
+#include <LibJS/Runtime/Temporal/PlainYearMonth.h>
 #include <LibJS/Runtime/TypedArray.h>
 #include <LibJS/Runtime/TypedArray.h>
 #include <LibJS/Runtime/Value.h>
 #include <LibJS/Runtime/Value.h>
 #include <LibJS/Runtime/WeakMap.h>
 #include <LibJS/Runtime/WeakMap.h>
@@ -845,6 +846,15 @@ ErrorOr<void> print_temporal_plain_month_day(JS::PrintContext& print_context, JS
     return {};
     return {};
 }
 }
 
 
+ErrorOr<void> print_temporal_plain_year_month(JS::PrintContext& print_context, JS::Temporal::PlainYearMonth const& plain_year_month, HashTable<JS::Object*>& seen_objects)
+{
+    TRY(print_type(print_context, "Temporal.PlainYearMonth"sv));
+    TRY(js_out(print_context, " \033[34;1m{:04}-{:02}\033[0m", plain_year_month.iso_date().year, plain_year_month.iso_date().month));
+    TRY(js_out(print_context, "\n  calendar: "));
+    TRY(print_value(print_context, JS::PrimitiveString::create(plain_year_month.vm(), plain_year_month.calendar()), seen_objects));
+    return {};
+}
+
 ErrorOr<void> print_boolean_object(JS::PrintContext& print_context, JS::BooleanObject const& boolean_object, HashTable<JS::Object*>& seen_objects)
 ErrorOr<void> print_boolean_object(JS::PrintContext& print_context, JS::BooleanObject const& boolean_object, HashTable<JS::Object*>& seen_objects)
 {
 {
     TRY(print_type(print_context, "Boolean"sv));
     TRY(print_type(print_context, "Boolean"sv));
@@ -964,6 +974,8 @@ ErrorOr<void> print_value(JS::PrintContext& print_context, JS::Value value, Hash
             return print_temporal_duration(print_context, static_cast<JS::Temporal::Duration&>(object), seen_objects);
             return print_temporal_duration(print_context, static_cast<JS::Temporal::Duration&>(object), seen_objects);
         if (is<JS::Temporal::PlainMonthDay>(object))
         if (is<JS::Temporal::PlainMonthDay>(object))
             return print_temporal_plain_month_day(print_context, static_cast<JS::Temporal::PlainMonthDay&>(object), seen_objects);
             return print_temporal_plain_month_day(print_context, static_cast<JS::Temporal::PlainMonthDay&>(object), seen_objects);
+        if (is<JS::Temporal::PlainYearMonth>(object))
+            return print_temporal_plain_year_month(print_context, static_cast<JS::Temporal::PlainYearMonth&>(object), seen_objects);
         return print_object(print_context, object, seen_objects);
         return print_object(print_context, object, seen_objects);
     }
     }
 
 

+ 2 - 0
Libraries/LibJS/Runtime/Intrinsics.cpp

@@ -103,6 +103,8 @@
 #include <LibJS/Runtime/Temporal/DurationPrototype.h>
 #include <LibJS/Runtime/Temporal/DurationPrototype.h>
 #include <LibJS/Runtime/Temporal/PlainMonthDayConstructor.h>
 #include <LibJS/Runtime/Temporal/PlainMonthDayConstructor.h>
 #include <LibJS/Runtime/Temporal/PlainMonthDayPrototype.h>
 #include <LibJS/Runtime/Temporal/PlainMonthDayPrototype.h>
+#include <LibJS/Runtime/Temporal/PlainYearMonthConstructor.h>
+#include <LibJS/Runtime/Temporal/PlainYearMonthPrototype.h>
 #include <LibJS/Runtime/Temporal/Temporal.h>
 #include <LibJS/Runtime/Temporal/Temporal.h>
 #include <LibJS/Runtime/TypedArray.h>
 #include <LibJS/Runtime/TypedArray.h>
 #include <LibJS/Runtime/TypedArrayConstructor.h>
 #include <LibJS/Runtime/TypedArrayConstructor.h>

+ 3 - 0
Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp

@@ -18,6 +18,7 @@
 #include <LibJS/Runtime/Temporal/PlainDate.h>
 #include <LibJS/Runtime/Temporal/PlainDate.h>
 #include <LibJS/Runtime/Temporal/PlainDateTime.h>
 #include <LibJS/Runtime/Temporal/PlainDateTime.h>
 #include <LibJS/Runtime/Temporal/PlainMonthDay.h>
 #include <LibJS/Runtime/Temporal/PlainMonthDay.h>
+#include <LibJS/Runtime/Temporal/PlainYearMonth.h>
 #include <LibJS/Runtime/Temporal/TimeZone.h>
 #include <LibJS/Runtime/Temporal/TimeZone.h>
 
 
 namespace JS::Temporal {
 namespace JS::Temporal {
@@ -467,6 +468,8 @@ ThrowCompletionOr<bool> is_partial_temporal_object(VM& vm, Value value)
     // FIXME: Add the other types as we define them.
     // FIXME: Add the other types as we define them.
     if (is<PlainMonthDay>(object))
     if (is<PlainMonthDay>(object))
         return false;
         return false;
+    if (is<PlainYearMonth>(object))
+        return false;
 
 
     // 3. Let calendarProperty be ? Get(value, "calendar").
     // 3. Let calendarProperty be ? Get(value, "calendar").
     auto calendar_property = TRY(object.get(vm.names.calendar));
     auto calendar_property = TRY(object.get(vm.names.calendar));

+ 48 - 0
Libraries/LibJS/Runtime/Temporal/Calendar.cpp

@@ -14,6 +14,7 @@
 #include <LibJS/Runtime/Temporal/ISO8601.h>
 #include <LibJS/Runtime/Temporal/ISO8601.h>
 #include <LibJS/Runtime/Temporal/PlainDate.h>
 #include <LibJS/Runtime/Temporal/PlainDate.h>
 #include <LibJS/Runtime/Temporal/PlainMonthDay.h>
 #include <LibJS/Runtime/Temporal/PlainMonthDay.h>
+#include <LibJS/Runtime/Temporal/PlainYearMonth.h>
 #include <LibJS/Runtime/Temporal/TimeZone.h>
 #include <LibJS/Runtime/Temporal/TimeZone.h>
 #include <LibJS/Runtime/VM.h>
 #include <LibJS/Runtime/VM.h>
 #include <LibUnicode/Locale.h>
 #include <LibUnicode/Locale.h>
@@ -324,6 +325,8 @@ ThrowCompletionOr<String> to_temporal_calendar_identifier(VM& vm, Value temporal
         // FIXME: Add the other calendar-holding types as we define them.
         // FIXME: Add the other calendar-holding types as we define them.
         if (is<PlainMonthDay>(temporal_calendar_object))
         if (is<PlainMonthDay>(temporal_calendar_object))
             return static_cast<PlainMonthDay const&>(temporal_calendar_object).calendar();
             return static_cast<PlainMonthDay const&>(temporal_calendar_object).calendar();
+        if (is<PlainYearMonth>(temporal_calendar_object))
+            return static_cast<PlainYearMonth const&>(temporal_calendar_object).calendar();
     }
     }
 
 
     // 2. If temporalCalendarLike is not a String, throw a TypeError exception.
     // 2. If temporalCalendarLike is not a String, throw a TypeError exception.
@@ -346,6 +349,8 @@ ThrowCompletionOr<String> get_temporal_calendar_identifier_with_iso_default(VM&
     // FIXME: Add the other calendar-holding types as we define them.
     // FIXME: Add the other calendar-holding types as we define them.
     if (is<PlainMonthDay>(item))
     if (is<PlainMonthDay>(item))
         return static_cast<PlainMonthDay const&>(item).calendar();
         return static_cast<PlainMonthDay const&>(item).calendar();
+    if (is<PlainYearMonth>(item))
+        return static_cast<PlainYearMonth const&>(item).calendar();
 
 
     // 2. Let calendarLike be ? Get(item, "calendar").
     // 2. Let calendarLike be ? Get(item, "calendar").
     auto calendar_like = TRY(item.get(vm.names.calendar));
     auto calendar_like = TRY(item.get(vm.names.calendar));
@@ -360,6 +365,30 @@ ThrowCompletionOr<String> get_temporal_calendar_identifier_with_iso_default(VM&
     return TRY(to_temporal_calendar_identifier(vm, calendar_like));
     return TRY(to_temporal_calendar_identifier(vm, calendar_like));
 }
 }
 
 
+// 12.2.11 CalendarYearMonthFromFields ( calendar, fields, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-calendaryearmonthfromfields
+ThrowCompletionOr<ISODate> calendar_year_month_from_fields(VM& vm, StringView calendar, CalendarFields fields, Overflow overflow)
+{
+    // 1. Perform ? CalendarResolveFields(calendar, fields, YEAR-MONTH).
+    TRY(calendar_resolve_fields(vm, calendar, fields, DateType::YearMonth));
+
+    // FIXME: 2. Let firstDayIndex be the 1-based index of the first day of the month described by fields (i.e., 1 unless the
+    //           month's first day is skipped by this calendar.)
+    static auto constexpr first_day_index = 1;
+
+    // 3. Set fields.[[Day]] to firstDayIndex.
+    fields.day = first_day_index;
+
+    // 4. Let result be ? CalendarDateToISO(calendar, fields, overflow).
+    auto result = TRY(calendar_date_to_iso(vm, calendar, fields, overflow));
+
+    // 5. If ISOYearMonthWithinLimits(result) is false, throw a RangeError exception.
+    if (!iso_year_month_within_limits(result))
+        return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidISODate);
+
+    // 6. Return result.
+    return result;
+}
+
 // 12.2.12 CalendarMonthDayFromFields ( calendar, fields, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-calendarmonthdayfromfields
 // 12.2.12 CalendarMonthDayFromFields ( calendar, fields, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-calendarmonthdayfromfields
 ThrowCompletionOr<ISODate> calendar_month_day_from_fields(VM& vm, StringView calendar, CalendarFields fields, Overflow overflow)
 ThrowCompletionOr<ISODate> calendar_month_day_from_fields(VM& vm, StringView calendar, CalendarFields fields, Overflow overflow)
 {
 {
@@ -530,6 +559,25 @@ u8 iso_day_of_week(ISODate const& iso_date)
     return day_of_week;
     return day_of_week;
 }
 }
 
 
+// 12.2.19 CalendarDateToISO ( calendar, fields, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-calendardatetoiso
+ThrowCompletionOr<ISODate> calendar_date_to_iso(VM& vm, StringView calendar, CalendarFields const& fields, Overflow overflow)
+{
+    // 1. If calendar is "iso8601", then
+    if (calendar == "iso8601"sv) {
+        // a. Assert: fields.[[Year]], fields.[[Month]], and fields.[[Day]] are not UNSET.
+        VERIFY(fields.year.has_value());
+        VERIFY(fields.month.has_value());
+        VERIFY(fields.day.has_value());
+
+        // b. Return ? RegulateISODate(fields.[[Year]], fields.[[Month]], fields.[[Day]], overflow).
+        return TRY(regulate_iso_date(vm, *fields.year, *fields.month, *fields.day, overflow));
+    }
+
+    // 2. Return an implementation-defined ISO Date Record, or throw a RangeError exception, as described below.
+    // FIXME: Create an ISODateRecord based on an ISO8601 calendar for now. See also: CalendarResolveFields.
+    return calendar_month_day_to_iso_reference_date(vm, "iso8601"sv, fields, overflow);
+}
+
 // 12.2.20 CalendarMonthDayToISOReferenceDate ( calendar, fields, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-calendarmonthdaytoisoreferencedate
 // 12.2.20 CalendarMonthDayToISOReferenceDate ( calendar, fields, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-calendarmonthdaytoisoreferencedate
 ThrowCompletionOr<ISODate> calendar_month_day_to_iso_reference_date(VM& vm, StringView calendar, CalendarFields const& fields, Overflow overflow)
 ThrowCompletionOr<ISODate> calendar_month_day_to_iso_reference_date(VM& vm, StringView calendar, CalendarFields const& fields, Overflow overflow)
 {
 {

+ 2 - 0
Libraries/LibJS/Runtime/Temporal/Calendar.h

@@ -99,6 +99,7 @@ using CalendarFieldListOrPartial = Variant<Partial, CalendarFieldList>;
 ThrowCompletionOr<String> canonicalize_calendar(VM&, StringView id);
 ThrowCompletionOr<String> canonicalize_calendar(VM&, StringView id);
 Vector<String> const& available_calendars();
 Vector<String> const& available_calendars();
 ThrowCompletionOr<CalendarFields> prepare_calendar_fields(VM&, StringView calendar, Object const& fields, CalendarFieldList calendar_field_names, CalendarFieldList non_calendar_field_names, CalendarFieldListOrPartial required_field_names);
 ThrowCompletionOr<CalendarFields> prepare_calendar_fields(VM&, StringView calendar, Object const& fields, CalendarFieldList calendar_field_names, CalendarFieldList non_calendar_field_names, CalendarFieldListOrPartial required_field_names);
+ThrowCompletionOr<ISODate> calendar_year_month_from_fields(VM&, StringView calendar, CalendarFields, Overflow);
 ThrowCompletionOr<ISODate> calendar_month_day_from_fields(VM&, StringView calendar, CalendarFields, Overflow);
 ThrowCompletionOr<ISODate> calendar_month_day_from_fields(VM&, StringView calendar, CalendarFields, Overflow);
 String format_calendar_annotation(StringView id, ShowCalendar);
 String format_calendar_annotation(StringView id, ShowCalendar);
 bool calendar_equals(StringView one, StringView two);
 bool calendar_equals(StringView one, StringView two);
@@ -110,6 +111,7 @@ Vector<CalendarField> calendar_field_keys_present(CalendarFields const&);
 CalendarFields calendar_merge_fields(StringView calendar, CalendarFields const& fields, CalendarFields const& additional_fields);
 CalendarFields calendar_merge_fields(StringView calendar, CalendarFields const& fields, CalendarFields const& additional_fields);
 ThrowCompletionOr<String> to_temporal_calendar_identifier(VM&, Value temporal_calendar_like);
 ThrowCompletionOr<String> to_temporal_calendar_identifier(VM&, Value temporal_calendar_like);
 ThrowCompletionOr<String> get_temporal_calendar_identifier_with_iso_default(VM&, Object const& item);
 ThrowCompletionOr<String> get_temporal_calendar_identifier_with_iso_default(VM&, Object const& item);
+ThrowCompletionOr<ISODate> calendar_date_to_iso(VM&, StringView calendar, CalendarFields const&, Overflow);
 ThrowCompletionOr<ISODate> calendar_month_day_to_iso_reference_date(VM&, StringView calendar, CalendarFields const&, Overflow);
 ThrowCompletionOr<ISODate> calendar_month_day_to_iso_reference_date(VM&, StringView calendar, CalendarFields const&, Overflow);
 CalendarDate calendar_iso_to_date(StringView calendar, ISODate const&);
 CalendarDate calendar_iso_to_date(StringView calendar, ISODate const&);
 Vector<CalendarField> calendar_extra_fields(StringView calendar, CalendarFieldList);
 Vector<CalendarField> calendar_extra_fields(StringView calendar, CalendarFieldList);

+ 152 - 0
Libraries/LibJS/Runtime/Temporal/PlainYearMonth.cpp

@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
+ * Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Runtime/AbstractOperations.h>
+#include <LibJS/Runtime/Intrinsics.h>
+#include <LibJS/Runtime/Realm.h>
+#include <LibJS/Runtime/Temporal/Calendar.h>
+#include <LibJS/Runtime/Temporal/PlainYearMonth.h>
+#include <LibJS/Runtime/Temporal/PlainYearMonthConstructor.h>
+#include <LibJS/Runtime/VM.h>
+
+namespace JS::Temporal {
+
+GC_DEFINE_ALLOCATOR(PlainYearMonth);
+
+// 9 Temporal.PlainYearMonth Objects, https://tc39.es/proposal-temporal/#sec-temporal-plainyearmonth-objects
+PlainYearMonth::PlainYearMonth(ISODate iso_date, String calendar, Object& prototype)
+    : Object(ConstructWithPrototypeTag::Tag, prototype)
+    , m_iso_date(iso_date)
+    , m_calendar(move(calendar))
+{
+}
+
+// 9.5.2 ToTemporalYearMonth ( item [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalyearmonth
+ThrowCompletionOr<GC::Ref<PlainYearMonth>> to_temporal_year_month(VM& vm, Value item, Value options)
+{
+    // 1. If options is not present, set options to undefined.
+
+    // 2. If item is an Object, then
+    if (item.is_object()) {
+        auto const& object = item.as_object();
+
+        // a. If item has an [[InitializedTemporalYearMonth]] internal slot, then
+        if (is<PlainYearMonth>(object)) {
+            auto const& plain_year_month = static_cast<PlainYearMonth const&>(object);
+
+            // i. Let resolvedOptions be ? GetOptionsObject(options).
+            auto resolved_options = TRY(get_options_object(vm, options));
+
+            // ii. Perform ? GetTemporalOverflowOption(resolvedOptions).
+            TRY(get_temporal_overflow_option(vm, resolved_options));
+
+            // iii. Return ! CreateTemporalYearMonth(item.[[ISODate]], item.[[Calendar]]).
+            return MUST(create_temporal_year_month(vm, plain_year_month.iso_date(), plain_year_month.calendar()));
+        }
+
+        // b. Let calendar be ? GetTemporalCalendarIdentifierWithISODefault(item).
+        auto calendar = TRY(get_temporal_calendar_identifier_with_iso_default(vm, object));
+
+        // c. Let fields be ? PrepareCalendarFields(calendar, item, « YEAR, MONTH, MONTH-CODE », «», «»).
+        auto fields = TRY(prepare_calendar_fields(vm, calendar, object, { { CalendarField::Year, CalendarField::Month, CalendarField::MonthCode } }, {}, CalendarFieldList {}));
+
+        // d. Let resolvedOptions be ? GetOptionsObject(options).
+        auto resolved_options = TRY(get_options_object(vm, options));
+
+        // e. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
+        auto overflow = TRY(get_temporal_overflow_option(vm, resolved_options));
+
+        // f. Let isoDate be ? CalendarYearMonthFromFields(calendar, fields, overflow).
+        auto iso_date = TRY(calendar_year_month_from_fields(vm, calendar, move(fields), overflow));
+
+        // g. Return ! CreateTemporalYearMonth(isoDate, calendar).
+        return MUST(create_temporal_year_month(vm, iso_date, move(calendar)));
+    }
+
+    // 3. If item is not a String, throw a TypeError exception.
+    if (!item.is_string())
+        return vm.throw_completion<TypeError>(ErrorType::TemporalInvalidPlainYearMonth);
+
+    // 4. Let result be ? ParseISODateTime(item, « TemporalYearMonthString »).
+    auto parse_result = TRY(parse_iso_date_time(vm, item.as_string().utf8_string_view(), { { Production::TemporalYearMonthString } }));
+
+    // 5. Let calendar be result.[[Calendar]].
+    // 6. If calendar is empty, set calendar to "iso8601".
+    auto calendar = parse_result.calendar.value_or("iso8601"_string);
+
+    // 7. Set calendar to ? CanonicalizeCalendar(calendar).
+    calendar = TRY(canonicalize_calendar(vm, calendar));
+
+    // 8. Let isoDate be CreateISODateRecord(result.[[Year]], result.[[Month]], result.[[Day]]).
+    auto iso_date = create_iso_date_record(*parse_result.year, parse_result.month, parse_result.day);
+
+    // 9. Set result to ISODateToFields(calendar, isoDate, YEAR-MONTH).
+    auto result = iso_date_to_fields(calendar, iso_date, DateType::YearMonth);
+
+    // 10. Let resolvedOptions be ? GetOptionsObject(options).
+    auto resolved_options = TRY(get_options_object(vm, options));
+
+    // 11. Perform ? GetTemporalOverflowOption(resolvedOptions).
+    TRY(get_temporal_overflow_option(vm, resolved_options));
+
+    // 12. NOTE: The following operation is called with CONSTRAIN regardless of the value of overflow, in order for the
+    //     calendar to store a canonical value in the [[Day]] field of the [[ISODate]] internal slot of the result.
+    // 13. Set isoDate to ? CalendarYearMonthFromFields(calendar, result, CONSTRAIN).
+    iso_date = TRY(calendar_year_month_from_fields(vm, calendar, result, Overflow::Constrain));
+
+    // 14. Return ! CreateTemporalYearMonth(isoDate, calendar).
+    return MUST(create_temporal_year_month(vm, iso_date, move(calendar)));
+}
+
+// 9.5.3 ISOYearMonthWithinLimits ( isoDate ), https://tc39.es/proposal-temporal/#sec-temporal-isoyearmonthwithinlimits
+bool iso_year_month_within_limits(ISODate iso_date)
+{
+    // 1. If isoDate.[[Year]] < -271821 or isoDate.[[Year]] > 275760, then
+    if (iso_date.year < -271821 || iso_date.year > 275760) {
+        // a. Return false.
+        return false;
+    }
+
+    // 2. If isoDate.[[Year]] = -271821 and isoDate.[[Month]] < 4, then
+    if (iso_date.year == -271821 && iso_date.month < 4) {
+        // a. Return false.
+        return false;
+    }
+
+    // 3. If isoDate.[[Year]] = 275760 and isoDate.[[Month]] > 9, then
+    if (iso_date.year == 275760 && iso_date.month > 9) {
+        // a. Return false.
+        return false;
+    }
+
+    // 4. Return true.
+    return true;
+}
+
+// 9.5.5 CreateTemporalYearMonth ( isoDate, calendar [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporalyearmonth
+ThrowCompletionOr<GC::Ref<PlainYearMonth>> create_temporal_year_month(VM& vm, ISODate iso_date, String calendar, GC::Ptr<FunctionObject> new_target)
+{
+    auto& realm = *vm.current_realm();
+
+    // 1. If ISOYearMonthWithinLimits(isoDate) is false, throw a RangeError exception.
+    if (!iso_year_month_within_limits(iso_date))
+        return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidPlainYearMonth);
+
+    // 2. If newTarget is not present, set newTarget to %Temporal.PlainYearMonth%.
+    if (!new_target)
+        new_target = realm.intrinsics().temporal_plain_year_month_constructor();
+
+    // 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.PlainYearMonth.prototype%", « [[InitializedTemporalYearMonth]], [[ISODate]], [[Calendar]] »).
+    // 4. Set object.[[ISODate]] to isoDate.
+    // 5. Set object.[[Calendar]] to calendar.
+    auto object = TRY(ordinary_create_from_constructor<PlainYearMonth>(vm, *new_target, &Intrinsics::temporal_plain_year_month_prototype, iso_date, move(calendar)));
+
+    // 6. Return object.
+    return object;
+}
+
+}

+ 39 - 0
Libraries/LibJS/Runtime/Temporal/PlainYearMonth.h

@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
+ * Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <LibJS/Runtime/Completion.h>
+#include <LibJS/Runtime/Object.h>
+#include <LibJS/Runtime/Temporal/AbstractOperations.h>
+#include <LibJS/Runtime/Temporal/PlainDate.h>
+
+namespace JS::Temporal {
+
+class PlainYearMonth final : public Object {
+    JS_OBJECT(PlainYearMonth, Object);
+    GC_DECLARE_ALLOCATOR(PlainYearMonth);
+
+public:
+    virtual ~PlainYearMonth() override = default;
+
+    [[nodiscard]] ISODate iso_date() const { return m_iso_date; }
+    [[nodiscard]] String const& calendar() const { return m_calendar; }
+
+private:
+    PlainYearMonth(ISODate, String calendar, Object& prototype);
+
+    ISODate m_iso_date; // [[ISODate]]
+    String m_calendar;  // [[Calendar]]
+};
+
+ThrowCompletionOr<GC::Ref<PlainYearMonth>> to_temporal_year_month(VM&, Value item, Value options = js_undefined());
+bool iso_year_month_within_limits(ISODate);
+ThrowCompletionOr<GC::Ref<PlainYearMonth>> create_temporal_year_month(VM&, ISODate, String calendar, GC::Ptr<FunctionObject> new_target = {});
+
+}

+ 116 - 0
Libraries/LibJS/Runtime/Temporal/PlainYearMonthConstructor.cpp

@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
+ * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
+ * Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Runtime/Temporal/Calendar.h>
+#include <LibJS/Runtime/Temporal/PlainYearMonth.h>
+#include <LibJS/Runtime/Temporal/PlainYearMonthConstructor.h>
+
+namespace JS::Temporal {
+
+GC_DEFINE_ALLOCATOR(PlainYearMonthConstructor);
+
+// 9.1 The Temporal.PlainYearMonth Constructor, https://tc39.es/proposal-temporal/#sec-temporal-plainyearmonth-constructor
+PlainYearMonthConstructor::PlainYearMonthConstructor(Realm& realm)
+    : NativeFunction(realm.vm().names.PlainYearMonth.as_string(), realm.intrinsics().function_prototype())
+{
+}
+
+void PlainYearMonthConstructor::initialize(Realm& realm)
+{
+    Base::initialize(realm);
+
+    auto& vm = this->vm();
+
+    // 9.2.1 Temporal.PlainYearMonth.prototype, https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype
+    define_direct_property(vm.names.prototype, realm.intrinsics().temporal_plain_year_month_prototype(), 0);
+
+    define_direct_property(vm.names.length, Value(2), Attribute::Configurable);
+
+    u8 attr = Attribute::Writable | Attribute::Configurable;
+    define_native_function(realm, vm.names.from, from, 1, attr);
+    define_native_function(realm, vm.names.compare, compare, 2, attr);
+}
+
+// 9.1.1 Temporal.PlainYearMonth ( isoYear, isoMonth [ , calendar [ , referenceISODay ] ] ), https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth
+ThrowCompletionOr<Value> PlainYearMonthConstructor::call()
+{
+    auto& vm = this->vm();
+
+    // 1. If NewTarget is undefined, then
+    //     a. Throw a TypeError exception.
+    return vm.throw_completion<TypeError>(ErrorType::ConstructorWithoutNew, "Temporal.PlainYearMonth");
+}
+
+// 9.1.1 Temporal.PlainYearMonth ( isoYear, isoMonth [ , calendar [ , referenceISODay ] ] ), https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth
+ThrowCompletionOr<GC::Ref<Object>> PlainYearMonthConstructor::construct(FunctionObject& new_target)
+{
+    auto& vm = this->vm();
+
+    auto iso_year = vm.argument(0);
+    auto iso_month = vm.argument(1);
+    auto calendar_value = vm.argument(2);
+    auto reference_iso_day = vm.argument(3);
+
+    // 2. If referenceISODay is undefined, then
+    if (reference_iso_day.is_undefined()) {
+        // a. Set referenceISODay to 1𝔽.
+        reference_iso_day = Value { 1 };
+    }
+
+    // 3. Let y be ? ToIntegerWithTruncation(isoYear).
+    auto year = TRY(to_integer_with_truncation(vm, iso_year, ErrorType::TemporalInvalidPlainYearMonth));
+
+    // 4. Let m be ? ToIntegerWithTruncation(isoMonth).
+    auto month = TRY(to_integer_with_truncation(vm, iso_month, ErrorType::TemporalInvalidPlainYearMonth));
+
+    // 5. If calendar is undefined, set calendar to "iso8601".
+    if (calendar_value.is_undefined())
+        calendar_value = PrimitiveString::create(vm, "iso8601"_string);
+
+    // 6. If calendar is not a String, throw a TypeError exception.
+    if (!calendar_value.is_string())
+        return vm.throw_completion<TypeError>(ErrorType::NotAString, "calendar"sv);
+
+    // 7. Set calendar to ? CanonicalizeCalendar(calendar).
+    auto calendar = TRY(canonicalize_calendar(vm, calendar_value.as_string().utf8_string_view()));
+
+    // 8. Let ref be ? ToIntegerWithTruncation(referenceISODay).
+    auto reference = TRY(to_integer_with_truncation(vm, reference_iso_day, ErrorType::TemporalInvalidPlainYearMonth));
+
+    // 9. If IsValidISODate(y, m, ref) is false, throw a RangeError exception.
+    if (!is_valid_iso_date(year, month, reference))
+        return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidPlainYearMonth);
+
+    // 10. Let isoDate be CreateISODateRecord(y, m, ref).
+    auto iso_date = create_iso_date_record(year, month, reference);
+
+    // 11. Return ? CreateTemporalYearMonth(isoDate, calendar, NewTarget).
+    return TRY(create_temporal_year_month(vm, iso_date, move(calendar), &new_target));
+}
+
+// 9.2.2 Temporal.PlainYearMonth.from ( item [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.from
+JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthConstructor::from)
+{
+    // 1. Return ? ToTemporalYearMonth(item, options).
+    return TRY(to_temporal_year_month(vm, vm.argument(0), vm.argument(1)));
+}
+
+// 9.2.3 Temporal.PlainYearMonth.compare ( one, two ), https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.compare
+JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthConstructor::compare)
+{
+    // 1. Set one to ? ToTemporalYearMonth(one).
+    auto one = TRY(to_temporal_year_month(vm, vm.argument(0)));
+
+    // 2. Set two to ? ToTemporalYearMonth(two).
+    auto two = TRY(to_temporal_year_month(vm, vm.argument(1)));
+
+    // 3. Return 𝔽(CompareISODate(one.[[ISODate]], two.[[ISODate]])).
+    return compare_iso_date(one->iso_date(), two->iso_date());
+}
+
+}

+ 34 - 0
Libraries/LibJS/Runtime/Temporal/PlainYearMonthConstructor.h

@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
+ * Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/NativeFunction.h>
+
+namespace JS::Temporal {
+
+class PlainYearMonthConstructor final : public NativeFunction {
+    JS_OBJECT(PlainYearMonthConstructor, NativeFunction);
+    GC_DECLARE_ALLOCATOR(PlainYearMonthConstructor);
+
+public:
+    virtual void initialize(Realm&) override;
+    virtual ~PlainYearMonthConstructor() override = default;
+
+    virtual ThrowCompletionOr<Value> call() override;
+    virtual ThrowCompletionOr<GC::Ref<Object>> construct(FunctionObject& new_target) override;
+
+private:
+    explicit PlainYearMonthConstructor(Realm&);
+
+    virtual bool has_constructor() const override { return true; }
+
+    JS_DECLARE_NATIVE_FUNCTION(from);
+    JS_DECLARE_NATIVE_FUNCTION(compare);
+};
+
+}

+ 126 - 0
Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.cpp

@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
+ * Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Runtime/Temporal/Calendar.h>
+#include <LibJS/Runtime/Temporal/PlainYearMonthPrototype.h>
+
+namespace JS::Temporal {
+
+GC_DEFINE_ALLOCATOR(PlainYearMonthPrototype);
+
+// 9.3 Properties of the Temporal.PlainYearMonth Prototype Object, https://tc39.es/proposal-temporal/#sec-properties-of-the-temporal-plainyearmonth-prototype-object
+PlainYearMonthPrototype::PlainYearMonthPrototype(Realm& realm)
+    : PrototypeObject(realm.intrinsics().object_prototype())
+{
+}
+
+void PlainYearMonthPrototype::initialize(Realm& realm)
+{
+    Base::initialize(realm);
+
+    auto& vm = this->vm();
+
+    // 9.3.2 Temporal.PlainYearMonth.prototype[ %Symbol.toStringTag% ], https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype-%symbol.tostringtag%
+    define_direct_property(vm.well_known_symbol_to_string_tag(), PrimitiveString::create(vm, "Temporal.PlainYearMonth"_string), Attribute::Configurable);
+
+    define_native_accessor(realm, vm.names.calendarId, calendar_id_getter, {}, Attribute::Configurable);
+    define_native_accessor(realm, vm.names.era, era_getter, {}, Attribute::Configurable);
+    define_native_accessor(realm, vm.names.eraYear, era_year_getter, {}, Attribute::Configurable);
+    define_native_accessor(realm, vm.names.year, year_getter, {}, Attribute::Configurable);
+    define_native_accessor(realm, vm.names.month, month_getter, {}, Attribute::Configurable);
+    define_native_accessor(realm, vm.names.monthCode, month_code_getter, {}, Attribute::Configurable);
+    define_native_accessor(realm, vm.names.daysInYear, days_in_year_getter, {}, Attribute::Configurable);
+    define_native_accessor(realm, vm.names.daysInMonth, days_in_month_getter, {}, Attribute::Configurable);
+    define_native_accessor(realm, vm.names.monthsInYear, months_in_year_getter, {}, Attribute::Configurable);
+    define_native_accessor(realm, vm.names.inLeapYear, in_leap_year_getter, {}, Attribute::Configurable);
+}
+
+// 9.3.3 get Temporal.PlainYearMonth.prototype.calendarId, https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.calendarid
+JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthPrototype::calendar_id_getter)
+{
+    // 1. Let yearMonth be the this value.
+    // 2. Perform ? RequireInternalSlot(yearMonth, [[InitializedTemporalYearMonth]]).
+    auto year_month = TRY(typed_this_object(vm));
+
+    // 3. Return yearMonth.[[Calendar]].
+    return PrimitiveString::create(vm, year_month->calendar());
+}
+
+// 9.3.4 get Temporal.PlainYearMonth.prototype.era, https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.era
+JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthPrototype::era_getter)
+{
+    // 1. Let plainYearMonth be the this value.
+    // 2. Perform ? RequireInternalSlot(plainYearMonth, [[InitializedTemporalYearMonth]]).
+    auto year_month = TRY(typed_this_object(vm));
+
+    // 3. Return CalendarISOToDate(plainYearMonth.[[Calendar]], plainYearMonth.[[ISODate]]).[[Era]].
+    auto result = calendar_iso_to_date(year_month->calendar(), year_month->iso_date()).era;
+
+    if (!result.has_value())
+        return js_undefined();
+
+    return PrimitiveString::create(vm, result.release_value());
+}
+
+// 9.3.5 get Temporal.PlainYearMonth.prototype.eraYear, https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.erayear
+JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthPrototype::era_year_getter)
+{
+    // 1. Let plainYearMonth be the this value.
+    // 2. Perform ? RequireInternalSlot(plainYearMonth, [[InitializedTemporalYearMonth]]).
+    auto year_month = TRY(typed_this_object(vm));
+
+    // 3. Let result be CalendarISOToDate(plainYearMonth.[[Calendar]], plainYearMonth.[[ISODate]]).[[EraYear]].
+    auto result = calendar_iso_to_date(year_month->calendar(), year_month->iso_date()).era_year;
+
+    // 4. If result is undefined, return undefined.
+    if (!result.has_value())
+        return js_undefined();
+
+    // 5. Return 𝔽(result).
+    return *result;
+}
+
+#define JS_ENUMERATE_PLAIN_MONTH_YEAR_SIMPLE_FIELDS \
+    __JS_ENUMERATE(year)                            \
+    __JS_ENUMERATE(month)                           \
+    __JS_ENUMERATE(days_in_year)                    \
+    __JS_ENUMERATE(days_in_month)                   \
+    __JS_ENUMERATE(months_in_year)                  \
+    __JS_ENUMERATE(in_leap_year)
+
+// 9.3.6 get Temporal.PlainYearMonth.prototype.year, https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.year
+// 9.3.7 get Temporal.PlainYearMonth.prototype.month, https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.month
+// 9.3.9 get Temporal.PlainYearMonth.prototype.daysInYear, https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.daysinyear
+// 9.3.10 get Temporal.PlainYearMonth.prototype.daysInMonth, https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.daysinmonth
+// 9.3.11 get Temporal.PlainYearMonth.prototype.monthsInYear, https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.monthsinyear
+// 9.3.12 get Temporal.PlainYearMonth.prototype.inLeapYear, https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.inleapyear
+#define __JS_ENUMERATE(field)                                                                         \
+    JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthPrototype::field##_getter)                                \
+    {                                                                                                 \
+        /* 1. Let yearMonth be the this value. */                                                     \
+        /* 2. Perform ? RequireInternalSlot(yearMonth, [[InitializedTemporalYearMonth]]). */          \
+        auto year_month = TRY(typed_this_object(vm));                                                 \
+                                                                                                      \
+        /* 3. Return CalendarISOToDate(yearMonth.[[Calendar]], yearMonth.[[ISODate]]).[[<field>]]. */ \
+        return calendar_iso_to_date(year_month->calendar(), year_month->iso_date()).field;            \
+    }
+JS_ENUMERATE_PLAIN_MONTH_YEAR_SIMPLE_FIELDS
+#undef __JS_ENUMERATE
+
+// 9.3.8 get Temporal.PlainYearMonth.prototype.monthCode, https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.monthcode
+JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthPrototype::month_code_getter)
+{
+    // 1. Let yearMonth be the this value.
+    // 2. Perform ? RequireInternalSlot(yearMonth, [[InitializedTemporalYearMonth]]).
+    auto year_month = TRY(typed_this_object(vm));
+
+    // 3. Return CalendarISOToDate(yearMonth.[[Calendar]], yearMonth.[[ISODate]]).[[MonthCode]].
+    auto month_code = calendar_iso_to_date(year_month->calendar(), year_month->iso_date()).month_code;
+    return PrimitiveString::create(vm, move(month_code));
+}
+
+}

+ 38 - 0
Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.h

@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
+ * Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/PrototypeObject.h>
+#include <LibJS/Runtime/Temporal/PlainYearMonth.h>
+
+namespace JS::Temporal {
+
+class PlainYearMonthPrototype final : public PrototypeObject<PlainYearMonthPrototype, PlainYearMonth> {
+    JS_PROTOTYPE_OBJECT(PlainYearMonthPrototype, PlainYearMonth, Temporal.PlainYearMonth);
+    GC_DECLARE_ALLOCATOR(PlainYearMonthPrototype);
+
+public:
+    virtual void initialize(Realm&) override;
+    virtual ~PlainYearMonthPrototype() override = default;
+
+private:
+    explicit PlainYearMonthPrototype(Realm&);
+
+    JS_DECLARE_NATIVE_FUNCTION(calendar_id_getter);
+    JS_DECLARE_NATIVE_FUNCTION(era_getter);
+    JS_DECLARE_NATIVE_FUNCTION(era_year_getter);
+    JS_DECLARE_NATIVE_FUNCTION(year_getter);
+    JS_DECLARE_NATIVE_FUNCTION(month_getter);
+    JS_DECLARE_NATIVE_FUNCTION(month_code_getter);
+    JS_DECLARE_NATIVE_FUNCTION(days_in_year_getter);
+    JS_DECLARE_NATIVE_FUNCTION(days_in_month_getter);
+    JS_DECLARE_NATIVE_FUNCTION(months_in_year_getter);
+    JS_DECLARE_NATIVE_FUNCTION(in_leap_year_getter);
+};
+
+}

+ 2 - 0
Libraries/LibJS/Runtime/Temporal/Temporal.cpp

@@ -8,6 +8,7 @@
 #include <LibJS/Runtime/GlobalObject.h>
 #include <LibJS/Runtime/GlobalObject.h>
 #include <LibJS/Runtime/Temporal/DurationConstructor.h>
 #include <LibJS/Runtime/Temporal/DurationConstructor.h>
 #include <LibJS/Runtime/Temporal/PlainMonthDayConstructor.h>
 #include <LibJS/Runtime/Temporal/PlainMonthDayConstructor.h>
+#include <LibJS/Runtime/Temporal/PlainYearMonthConstructor.h>
 #include <LibJS/Runtime/Temporal/Temporal.h>
 #include <LibJS/Runtime/Temporal/Temporal.h>
 
 
 namespace JS::Temporal {
 namespace JS::Temporal {
@@ -32,6 +33,7 @@ void Temporal::initialize(Realm& realm)
     u8 attr = Attribute::Writable | Attribute::Configurable;
     u8 attr = Attribute::Writable | Attribute::Configurable;
     define_intrinsic_accessor(vm.names.Duration, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_duration_constructor(); });
     define_intrinsic_accessor(vm.names.Duration, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_duration_constructor(); });
     define_intrinsic_accessor(vm.names.PlainMonthDay, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_plain_month_day_constructor(); });
     define_intrinsic_accessor(vm.names.PlainMonthDay, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_plain_month_day_constructor(); });
+    define_intrinsic_accessor(vm.names.PlainYearMonth, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_plain_year_month_constructor(); });
 }
 }
 
 
 }
 }

+ 14 - 0
Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.compare.js

@@ -0,0 +1,14 @@
+describe("correct behavior", () => {
+    test("length is 2", () => {
+        expect(Temporal.PlainYearMonth.compare).toHaveLength(2);
+    });
+
+    test("basic functionality", () => {
+        const plainYearMonth1 = new Temporal.PlainYearMonth(2021, 8);
+        expect(Temporal.PlainYearMonth.compare(plainYearMonth1, plainYearMonth1)).toBe(0);
+        const plainYearMonth2 = new Temporal.PlainYearMonth(2021, 9);
+        expect(Temporal.PlainYearMonth.compare(plainYearMonth2, plainYearMonth2)).toBe(0);
+        expect(Temporal.PlainYearMonth.compare(plainYearMonth1, plainYearMonth2)).toBe(-1);
+        expect(Temporal.PlainYearMonth.compare(plainYearMonth2, plainYearMonth1)).toBe(1);
+    });
+});

+ 130 - 0
Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.from.js

@@ -0,0 +1,130 @@
+describe("correct behavior", () => {
+    test("length is 1", () => {
+        expect(Temporal.PlainYearMonth.from).toHaveLength(1);
+    });
+
+    test("PlainYearMonth instance argument", () => {
+        const plainYearMonth_ = new Temporal.PlainYearMonth(2021, 7);
+        const plainYearMonth = Temporal.PlainYearMonth.from(plainYearMonth_);
+        expect(plainYearMonth.year).toBe(2021);
+        expect(plainYearMonth.month).toBe(7);
+        expect(plainYearMonth.monthCode).toBe("M07");
+        expect(plainYearMonth.daysInYear).toBe(365);
+        expect(plainYearMonth.daysInMonth).toBe(31);
+        expect(plainYearMonth.monthsInYear).toBe(12);
+        expect(plainYearMonth.inLeapYear).toBeFalse();
+    });
+
+    test("fields object argument", () => {
+        const object = {
+            year: 2021,
+            month: 7,
+        };
+        const plainYearMonth = Temporal.PlainYearMonth.from(object);
+        expect(plainYearMonth.year).toBe(2021);
+        expect(plainYearMonth.month).toBe(7);
+        expect(plainYearMonth.monthCode).toBe("M07");
+        expect(plainYearMonth.daysInYear).toBe(365);
+        expect(plainYearMonth.daysInMonth).toBe(31);
+        expect(plainYearMonth.monthsInYear).toBe(12);
+        expect(plainYearMonth.inLeapYear).toBeFalse();
+    });
+
+    test("from year month string", () => {
+        const plainYearMonth = Temporal.PlainYearMonth.from("2021-07");
+        expect(plainYearMonth.year).toBe(2021);
+        expect(plainYearMonth.month).toBe(7);
+        expect(plainYearMonth.monthCode).toBe("M07");
+    });
+
+    test("from date time string", () => {
+        const plainYearMonth = Temporal.PlainYearMonth.from("2021-07-06T23:42:01");
+        expect(plainYearMonth.year).toBe(2021);
+        expect(plainYearMonth.month).toBe(7);
+        expect(plainYearMonth.monthCode).toBe("M07");
+        expect(plainYearMonth.daysInYear).toBe(365);
+        expect(plainYearMonth.daysInMonth).toBe(31);
+        expect(plainYearMonth.monthsInYear).toBe(12);
+        expect(plainYearMonth.inLeapYear).toBeFalse();
+    });
+
+    test("compares calendar name in year month string in lowercase", () => {
+        const values = [
+            "2023-02[u-ca=iso8601]",
+            "2023-02[u-ca=isO8601]",
+            "2023-02[u-ca=iSo8601]",
+            "2023-02[u-ca=iSO8601]",
+            "2023-02[u-ca=Iso8601]",
+            "2023-02[u-ca=IsO8601]",
+            "2023-02[u-ca=ISo8601]",
+            "2023-02[u-ca=ISO8601]",
+        ];
+
+        for (const value of values) {
+            expect(() => {
+                Temporal.PlainYearMonth.from(value);
+            }).not.toThrowWithMessage(
+                RangeError,
+                "YYYY-MM string format can only be used with the iso8601 calendar"
+            );
+        }
+    });
+});
+
+describe("errors", () => {
+    test("missing fields", () => {
+        expect(() => {
+            Temporal.PlainYearMonth.from({});
+        }).toThrowWithMessage(TypeError, "Required property year is missing or undefined");
+        expect(() => {
+            Temporal.PlainYearMonth.from({ year: 0 });
+        }).toThrowWithMessage(TypeError, "Required property month is missing or undefined");
+        expect(() => {
+            Temporal.PlainYearMonth.from({ month: 1 });
+        }).toThrowWithMessage(TypeError, "Required property year is missing or undefined");
+    });
+
+    test("invalid year month string", () => {
+        expect(() => {
+            Temporal.PlainYearMonth.from("foo");
+        }).toThrowWithMessage(RangeError, "Invalid ISO date time");
+    });
+
+    test("string must not contain a UTC designator", () => {
+        expect(() => {
+            Temporal.PlainYearMonth.from("2021-07-06T23:42:01Z");
+        }).toThrowWithMessage(RangeError, "Invalid ISO date time");
+    });
+
+    test("extended year must not be negative zero", () => {
+        expect(() => {
+            Temporal.PlainYearMonth.from("-000000-01");
+        }).toThrowWithMessage(RangeError, "Invalid ISO date time");
+        expect(() => {
+            Temporal.PlainYearMonth.from("−000000-01"); // U+2212
+        }).toThrowWithMessage(RangeError, "Invalid ISO date time");
+    });
+
+    test("can only use iso8601 calendar with year month strings", () => {
+        expect(() => {
+            Temporal.PlainYearMonth.from("2023-02[u-ca=iso8602]");
+        }).toThrowWithMessage(RangeError, "Invalid calendar identifier 'iso8602'");
+
+        expect(() => {
+            Temporal.PlainYearMonth.from("2023-02[u-ca=ladybird]");
+        }).toThrowWithMessage(RangeError, "Invalid calendar identifier 'ladybird'");
+    });
+
+    test("doesn't throw non-iso8601 calendar error when using a superset format string such as DateTime", () => {
+        // NOTE: This will still throw, but only because "ladybird" is not a recognised calendar, not because of the string format restriction.
+        try {
+            Temporal.PlainYearMonth.from("2023-02-10T22:57[u-ca=ladybird]");
+        } catch (e) {
+            expect(e).toBeInstanceOf(RangeError);
+            expect(e.message).not.toBe(
+                "MM-DD string format can only be used with the iso8601 calendar"
+            );
+            expect(e.message).toBe("Invalid calendar identifier 'ladybird'");
+        }
+    });
+});

+ 60 - 0
Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.js

@@ -0,0 +1,60 @@
+describe("errors", () => {
+    test("called without new", () => {
+        expect(() => {
+            Temporal.PlainYearMonth();
+        }).toThrowWithMessage(
+            TypeError,
+            "Temporal.PlainYearMonth constructor must be called with 'new'"
+        );
+    });
+
+    test("cannot pass Infinity", () => {
+        expect(() => {
+            new Temporal.PlainYearMonth(Infinity);
+        }).toThrowWithMessage(RangeError, "Invalid plain year month");
+        expect(() => {
+            new Temporal.PlainYearMonth(0, Infinity);
+        }).toThrowWithMessage(RangeError, "Invalid plain year month");
+        expect(() => {
+            new Temporal.PlainYearMonth(0, 1, undefined, Infinity);
+        }).toThrowWithMessage(RangeError, "Invalid plain year month");
+        expect(() => {
+            new Temporal.PlainYearMonth(-Infinity);
+        }).toThrowWithMessage(RangeError, "Invalid plain year month");
+        expect(() => {
+            new Temporal.PlainYearMonth(0, -Infinity);
+        }).toThrowWithMessage(RangeError, "Invalid plain year month");
+        expect(() => {
+            new Temporal.PlainYearMonth(0, 1, undefined, -Infinity);
+        }).toThrowWithMessage(RangeError, "Invalid plain year month");
+    });
+
+    test("cannot pass invalid ISO month/day", () => {
+        expect(() => {
+            new Temporal.PlainYearMonth(0, 0);
+        }).toThrowWithMessage(RangeError, "Invalid plain year month");
+        expect(() => {
+            new Temporal.PlainYearMonth(0, 1, undefined, 0);
+        }).toThrowWithMessage(RangeError, "Invalid plain year month");
+    });
+});
+
+describe("normal behavior", () => {
+    test("length is 2", () => {
+        expect(Temporal.PlainYearMonth).toHaveLength(2);
+    });
+
+    test("basic functionality", () => {
+        const plainYearMonth = new Temporal.PlainYearMonth(2021, 7);
+        expect(typeof plainYearMonth).toBe("object");
+        expect(plainYearMonth).toBeInstanceOf(Temporal.PlainYearMonth);
+        expect(Object.getPrototypeOf(plainYearMonth)).toBe(Temporal.PlainYearMonth.prototype);
+    });
+
+    // FIXME: Re-implement this test with Temporal.PlainYearMonth.prototype.toString({ calendarName: "always" }).
+    // test("default reference day is 1", () => {
+    //     const plainYearMonth = new Temporal.PlainYearMonth(2021, 7);
+    //     const fields = plainYearMonth.getISOFields();
+    //     expect(fields.isoDay).toBe(1);
+    // });
+});

+ 15 - 0
Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.calendarId.js

@@ -0,0 +1,15 @@
+describe("correct behavior", () => {
+    test("calendarId basic functionality", () => {
+        const calendar = "iso8601";
+        const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+        expect(plainYearMonth.calendarId).toBe("iso8601");
+    });
+});
+
+describe("errors", () => {
+    test("this value must be a Temporal.PlainYearMonth object", () => {
+        expect(() => {
+            Reflect.get(Temporal.PlainYearMonth.prototype, "calendarId", "foo");
+        }).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainYearMonth");
+    });
+});

+ 14 - 0
Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.daysInMonth.js

@@ -0,0 +1,14 @@
+describe("correct behavior", () => {
+    test("basic functionality", () => {
+        const plainYearMonth = new Temporal.PlainYearMonth(2021, 7);
+        expect(plainYearMonth.daysInMonth).toBe(31);
+    });
+});
+
+describe("errors", () => {
+    test("this value must be a Temporal.PlainYearMonth object", () => {
+        expect(() => {
+            Reflect.get(Temporal.PlainYearMonth.prototype, "daysInMonth", "foo");
+        }).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainYearMonth");
+    });
+});

+ 14 - 0
Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.daysInYear.js

@@ -0,0 +1,14 @@
+describe("correct behavior", () => {
+    test("basic functionality", () => {
+        const plainYearMonth = new Temporal.PlainYearMonth(2021, 7);
+        expect(plainYearMonth.daysInYear).toBe(365);
+    });
+});
+
+describe("errors", () => {
+    test("this value must be a Temporal.PlainYearMonth object", () => {
+        expect(() => {
+            Reflect.get(Temporal.PlainYearMonth.prototype, "daysInYear", "foo");
+        }).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainYearMonth");
+    });
+});

+ 14 - 0
Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.era.js

@@ -0,0 +1,14 @@
+describe("correct behavior", () => {
+    test("basic functionality", () => {
+        const plainYearMonth = new Temporal.PlainYearMonth(2021, 7);
+        expect(plainYearMonth.era).toBeUndefined();
+    });
+});
+
+describe("errors", () => {
+    test("this value must be a Temporal.PlainYearMonth object", () => {
+        expect(() => {
+            Reflect.get(Temporal.PlainYearMonth.prototype, "era", "foo");
+        }).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainYearMonth");
+    });
+});

+ 14 - 0
Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.eraYear.js

@@ -0,0 +1,14 @@
+describe("correct behavior", () => {
+    test("basic functionality", () => {
+        const plainYearMonth = new Temporal.PlainYearMonth(2021, 7);
+        expect(plainYearMonth.eraYear).toBeUndefined();
+    });
+});
+
+describe("errors", () => {
+    test("this value must be a Temporal.PlainYearMonth object", () => {
+        expect(() => {
+            Reflect.get(Temporal.PlainYearMonth.prototype, "eraYear", "foo");
+        }).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainYearMonth");
+    });
+});

+ 14 - 0
Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.inLeapYear.js

@@ -0,0 +1,14 @@
+describe("correct behavior", () => {
+    test("basic functionality", () => {
+        const plainYearMonth = new Temporal.PlainYearMonth(2021, 7);
+        expect(plainYearMonth.inLeapYear).toBe(false);
+    });
+});
+
+describe("errors", () => {
+    test("this value must be a Temporal.PlainYearMonth object", () => {
+        expect(() => {
+            Reflect.get(Temporal.PlainYearMonth.prototype, "inLeapYear", "foo");
+        }).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainYearMonth");
+    });
+});

+ 14 - 0
Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.month.js

@@ -0,0 +1,14 @@
+describe("correct behavior", () => {
+    test("basic functionality", () => {
+        const plainYearMonth = new Temporal.PlainYearMonth(2021, 7);
+        expect(plainYearMonth.month).toBe(7);
+    });
+});
+
+describe("errors", () => {
+    test("this value must be a Temporal.PlainYearMonth object", () => {
+        expect(() => {
+            Reflect.get(Temporal.PlainYearMonth.prototype, "month", "foo");
+        }).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainYearMonth");
+    });
+});

+ 14 - 0
Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.monthCode.js

@@ -0,0 +1,14 @@
+describe("correct behavior", () => {
+    test("basic functionality", () => {
+        const plainYearMonth = new Temporal.PlainYearMonth(2021, 7);
+        expect(plainYearMonth.monthCode).toBe("M07");
+    });
+});
+
+describe("errors", () => {
+    test("this value must be a Temporal.PlainYearMonth object", () => {
+        expect(() => {
+            Reflect.get(Temporal.PlainYearMonth.prototype, "monthCode", "foo");
+        }).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainYearMonth");
+    });
+});

+ 14 - 0
Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.monthsInYear.js

@@ -0,0 +1,14 @@
+describe("correct behavior", () => {
+    test("basic functionality", () => {
+        const plainYearMonth = new Temporal.PlainYearMonth(2021, 7);
+        expect(plainYearMonth.monthsInYear).toBe(12);
+    });
+});
+
+describe("errors", () => {
+    test("this value must be a Temporal.PlainYearMonth object", () => {
+        expect(() => {
+            Reflect.get(Temporal.PlainYearMonth.prototype, "monthsInYear", "foo");
+        }).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainYearMonth");
+    });
+});

+ 14 - 0
Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.year.js

@@ -0,0 +1,14 @@
+describe("correct behavior", () => {
+    test("basic functionality", () => {
+        const plainYearMonth = new Temporal.PlainYearMonth(2021, 7);
+        expect(plainYearMonth.year).toBe(2021);
+    });
+});
+
+describe("errors", () => {
+    test("this value must be a Temporal.PlainYearMonth object", () => {
+        expect(() => {
+            Reflect.get(Temporal.PlainYearMonth.prototype, "year", "foo");
+        }).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainYearMonth");
+    });
+});