diff --git a/Libraries/LibJS/CMakeLists.txt b/Libraries/LibJS/CMakeLists.txt index c010b88da9b..b5e21ade214 100644 --- a/Libraries/LibJS/CMakeLists.txt +++ b/Libraries/LibJS/CMakeLists.txt @@ -217,6 +217,9 @@ set(SOURCES Runtime/Temporal/PlainMonthDay.cpp Runtime/Temporal/PlainMonthDayConstructor.cpp Runtime/Temporal/PlainMonthDayPrototype.cpp + Runtime/Temporal/PlainYearMonth.cpp + Runtime/Temporal/PlainYearMonthConstructor.cpp + Runtime/Temporal/PlainYearMonthPrototype.cpp Runtime/Temporal/PlainTime.cpp Runtime/Temporal/Temporal.cpp Runtime/Temporal/TimeZone.cpp diff --git a/Libraries/LibJS/Forward.h b/Libraries/LibJS/Forward.h index e73e9051da4..766ae9fd521 100644 --- a/Libraries/LibJS/Forward.h +++ b/Libraries/LibJS/Forward.h @@ -87,9 +87,10 @@ __JS_ENUMERATE(RelativeTimeFormat, relative_time_format, RelativeTimeFormatPrototype, RelativeTimeFormatConstructor) \ __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 \ __JS_ENUMERATE(AtomicsObject, atomics) \ diff --git a/Libraries/LibJS/Print.cpp b/Libraries/LibJS/Print.cpp index 6a894526049..6ac16a8804b 100644 --- a/Libraries/LibJS/Print.cpp +++ b/Libraries/LibJS/Print.cpp @@ -49,6 +49,7 @@ #include #include #include +#include #include #include #include @@ -845,6 +846,15 @@ ErrorOr print_temporal_plain_month_day(JS::PrintContext& print_context, JS return {}; } +ErrorOr print_temporal_plain_year_month(JS::PrintContext& print_context, JS::Temporal::PlainYearMonth const& plain_year_month, HashTable& 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 print_boolean_object(JS::PrintContext& print_context, JS::BooleanObject const& boolean_object, HashTable& seen_objects) { TRY(print_type(print_context, "Boolean"sv)); @@ -964,6 +974,8 @@ ErrorOr print_value(JS::PrintContext& print_context, JS::Value value, Hash return print_temporal_duration(print_context, static_cast(object), seen_objects); if (is(object)) return print_temporal_plain_month_day(print_context, static_cast(object), seen_objects); + if (is(object)) + return print_temporal_plain_year_month(print_context, static_cast(object), seen_objects); return print_object(print_context, object, seen_objects); } diff --git a/Libraries/LibJS/Runtime/Intrinsics.cpp b/Libraries/LibJS/Runtime/Intrinsics.cpp index f511939245c..45f352355e3 100644 --- a/Libraries/LibJS/Runtime/Intrinsics.cpp +++ b/Libraries/LibJS/Runtime/Intrinsics.cpp @@ -103,6 +103,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp b/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp index f1ac2c6e7a6..63434bf6454 100644 --- a/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp +++ b/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include namespace JS::Temporal { @@ -467,6 +468,8 @@ ThrowCompletionOr is_partial_temporal_object(VM& vm, Value value) // FIXME: Add the other types as we define them. if (is(object)) return false; + if (is(object)) + return false; // 3. Let calendarProperty be ? Get(value, "calendar"). auto calendar_property = TRY(object.get(vm.names.calendar)); diff --git a/Libraries/LibJS/Runtime/Temporal/Calendar.cpp b/Libraries/LibJS/Runtime/Temporal/Calendar.cpp index 529e588bc93..7ff0dc06897 100644 --- a/Libraries/LibJS/Runtime/Temporal/Calendar.cpp +++ b/Libraries/LibJS/Runtime/Temporal/Calendar.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -324,6 +325,8 @@ ThrowCompletionOr to_temporal_calendar_identifier(VM& vm, Value temporal // FIXME: Add the other calendar-holding types as we define them. if (is(temporal_calendar_object)) return static_cast(temporal_calendar_object).calendar(); + if (is(temporal_calendar_object)) + return static_cast(temporal_calendar_object).calendar(); } // 2. If temporalCalendarLike is not a String, throw a TypeError exception. @@ -346,6 +349,8 @@ ThrowCompletionOr get_temporal_calendar_identifier_with_iso_default(VM& // FIXME: Add the other calendar-holding types as we define them. if (is(item)) return static_cast(item).calendar(); + if (is(item)) + return static_cast(item).calendar(); // 2. Let calendarLike be ? Get(item, "calendar"). auto calendar_like = TRY(item.get(vm.names.calendar)); @@ -360,6 +365,30 @@ ThrowCompletionOr get_temporal_calendar_identifier_with_iso_default(VM& 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 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(ErrorType::TemporalInvalidISODate); + + // 6. Return result. + return result; +} + // 12.2.12 CalendarMonthDayFromFields ( calendar, fields, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-calendarmonthdayfromfields ThrowCompletionOr 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; } +// 12.2.19 CalendarDateToISO ( calendar, fields, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-calendardatetoiso +ThrowCompletionOr 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 ThrowCompletionOr calendar_month_day_to_iso_reference_date(VM& vm, StringView calendar, CalendarFields const& fields, Overflow overflow) { diff --git a/Libraries/LibJS/Runtime/Temporal/Calendar.h b/Libraries/LibJS/Runtime/Temporal/Calendar.h index b7a620ce354..df2dce55fee 100644 --- a/Libraries/LibJS/Runtime/Temporal/Calendar.h +++ b/Libraries/LibJS/Runtime/Temporal/Calendar.h @@ -99,6 +99,7 @@ using CalendarFieldListOrPartial = Variant; ThrowCompletionOr canonicalize_calendar(VM&, StringView id); Vector const& available_calendars(); ThrowCompletionOr prepare_calendar_fields(VM&, StringView calendar, Object const& fields, CalendarFieldList calendar_field_names, CalendarFieldList non_calendar_field_names, CalendarFieldListOrPartial required_field_names); +ThrowCompletionOr calendar_year_month_from_fields(VM&, StringView calendar, CalendarFields, Overflow); ThrowCompletionOr calendar_month_day_from_fields(VM&, StringView calendar, CalendarFields, Overflow); String format_calendar_annotation(StringView id, ShowCalendar); bool calendar_equals(StringView one, StringView two); @@ -110,6 +111,7 @@ Vector calendar_field_keys_present(CalendarFields const&); CalendarFields calendar_merge_fields(StringView calendar, CalendarFields const& fields, CalendarFields const& additional_fields); ThrowCompletionOr to_temporal_calendar_identifier(VM&, Value temporal_calendar_like); ThrowCompletionOr get_temporal_calendar_identifier_with_iso_default(VM&, Object const& item); +ThrowCompletionOr calendar_date_to_iso(VM&, StringView calendar, CalendarFields const&, Overflow); ThrowCompletionOr calendar_month_day_to_iso_reference_date(VM&, StringView calendar, CalendarFields const&, Overflow); CalendarDate calendar_iso_to_date(StringView calendar, ISODate const&); Vector calendar_extra_fields(StringView calendar, CalendarFieldList); diff --git a/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.cpp b/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.cpp new file mode 100644 index 00000000000..2280d3a7212 --- /dev/null +++ b/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2021-2023, Linus Groh + * Copyright (c) 2024, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +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> 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(object)) { + auto const& plain_year_month = static_cast(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(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> create_temporal_year_month(VM& vm, ISODate iso_date, String calendar, GC::Ptr 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(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(vm, *new_target, &Intrinsics::temporal_plain_year_month_prototype, iso_date, move(calendar))); + + // 6. Return object. + return object; +} + +} diff --git a/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.h b/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.h new file mode 100644 index 00000000000..36884ed74c9 --- /dev/null +++ b/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021-2023, Linus Groh + * Copyright (c) 2024, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +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> to_temporal_year_month(VM&, Value item, Value options = js_undefined()); +bool iso_year_month_within_limits(ISODate); +ThrowCompletionOr> create_temporal_year_month(VM&, ISODate, String calendar, GC::Ptr new_target = {}); + +} diff --git a/Libraries/LibJS/Runtime/Temporal/PlainYearMonthConstructor.cpp b/Libraries/LibJS/Runtime/Temporal/PlainYearMonthConstructor.cpp new file mode 100644 index 00000000000..11f76dc42e6 --- /dev/null +++ b/Libraries/LibJS/Runtime/Temporal/PlainYearMonthConstructor.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2021-2023, Linus Groh + * Copyright (c) 2021, Luke Wilde + * Copyright (c) 2024, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +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 PlainYearMonthConstructor::call() +{ + auto& vm = this->vm(); + + // 1. If NewTarget is undefined, then + // a. Throw a TypeError exception. + return vm.throw_completion(ErrorType::ConstructorWithoutNew, "Temporal.PlainYearMonth"); +} + +// 9.1.1 Temporal.PlainYearMonth ( isoYear, isoMonth [ , calendar [ , referenceISODay ] ] ), https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth +ThrowCompletionOr> 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(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(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()); +} + +} diff --git a/Libraries/LibJS/Runtime/Temporal/PlainYearMonthConstructor.h b/Libraries/LibJS/Runtime/Temporal/PlainYearMonthConstructor.h new file mode 100644 index 00000000000..41b966b3557 --- /dev/null +++ b/Libraries/LibJS/Runtime/Temporal/PlainYearMonthConstructor.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2021-2022, Linus Groh + * Copyright (c) 2024, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +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 call() override; + virtual ThrowCompletionOr> 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); +}; + +} diff --git a/Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.cpp b/Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.cpp new file mode 100644 index 00000000000..f9aba3f9c81 --- /dev/null +++ b/Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2021-2023, Linus Groh + * Copyright (c) 2024, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +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]]).[[]]. */ \ + 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)); +} + +} diff --git a/Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.h b/Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.h new file mode 100644 index 00000000000..53a73ff791e --- /dev/null +++ b/Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021, Linus Groh + * Copyright (c) 2024, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace JS::Temporal { + +class PlainYearMonthPrototype final : public PrototypeObject { + 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); +}; + +} diff --git a/Libraries/LibJS/Runtime/Temporal/Temporal.cpp b/Libraries/LibJS/Runtime/Temporal/Temporal.cpp index e8d067e3de5..190620a905c 100644 --- a/Libraries/LibJS/Runtime/Temporal/Temporal.cpp +++ b/Libraries/LibJS/Runtime/Temporal/Temporal.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include namespace JS::Temporal { @@ -32,6 +33,7 @@ void Temporal::initialize(Realm& realm) 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.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(); }); } } diff --git a/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.compare.js b/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.compare.js new file mode 100644 index 00000000000..789845e8d2e --- /dev/null +++ b/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); + }); +}); diff --git a/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.from.js b/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.from.js new file mode 100644 index 00000000000..55fa5e358e1 --- /dev/null +++ b/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'"); + } + }); +}); diff --git a/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.js b/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.js new file mode 100644 index 00000000000..e6887e86c07 --- /dev/null +++ b/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); + // }); +}); diff --git a/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.calendarId.js b/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.calendarId.js new file mode 100644 index 00000000000..800f2413e51 --- /dev/null +++ b/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"); + }); +}); diff --git a/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.daysInMonth.js b/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.daysInMonth.js new file mode 100644 index 00000000000..59c07055d41 --- /dev/null +++ b/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"); + }); +}); diff --git a/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.daysInYear.js b/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.daysInYear.js new file mode 100644 index 00000000000..a0a114902f7 --- /dev/null +++ b/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"); + }); +}); diff --git a/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.era.js b/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.era.js new file mode 100644 index 00000000000..74770431070 --- /dev/null +++ b/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"); + }); +}); diff --git a/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.eraYear.js b/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.eraYear.js new file mode 100644 index 00000000000..7ebaef8b627 --- /dev/null +++ b/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"); + }); +}); diff --git a/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.inLeapYear.js b/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.inLeapYear.js new file mode 100644 index 00000000000..f79e77e7bda --- /dev/null +++ b/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"); + }); +}); diff --git a/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.month.js b/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.month.js new file mode 100644 index 00000000000..8c09f11cbe8 --- /dev/null +++ b/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"); + }); +}); diff --git a/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.monthCode.js b/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.monthCode.js new file mode 100644 index 00000000000..8fdd1852dae --- /dev/null +++ b/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"); + }); +}); diff --git a/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.monthsInYear.js b/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.monthsInYear.js new file mode 100644 index 00000000000..6ca5f84084a --- /dev/null +++ b/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"); + }); +}); diff --git a/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.year.js b/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.year.js new file mode 100644 index 00000000000..13e0021ab34 --- /dev/null +++ b/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"); + }); +});