LibJS: Implement the Temporal.PlainYearMonth constructor

And the simple Temporal.PlainYearMonth.prototype getters, so that the
constructed Temporal.PlainYearMonth may actually be validated.
This commit is contained in:
Timothy Flynn 2024-11-21 11:22:32 -05:00 committed by Andreas Kling
commit b68d67693e
Notes: github-actions[bot] 2024-11-22 18:56:56 +00:00
27 changed files with 926 additions and 3 deletions

View file

@ -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

View file

@ -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 \ #define JS_ENUMERATE_TEMPORAL_OBJECTS \
__JS_ENUMERATE(Duration, duration, DurationPrototype, DurationConstructor) \ __JS_ENUMERATE(Duration, duration, DurationPrototype, DurationConstructor) \
__JS_ENUMERATE(PlainMonthDay, plain_month_day, PlainMonthDayPrototype, PlainMonthDayConstructor) __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) \

View file

@ -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);
} }

View file

@ -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>

View file

@ -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));

View file

@ -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)
{ {

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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 = {});
}

View file

@ -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());
}
}

View file

@ -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);
};
}

View file

@ -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));
}
}

View file

@ -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);
};
}

View file

@ -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(); });
} }
} }

View file

@ -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);
});
});

View file

@ -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'");
}
});
});

View file

@ -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);
// });
});

View file

@ -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");
});
});

View file

@ -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");
});
});

View file

@ -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");
});
});

View file

@ -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");
});
});

View file

@ -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");
});
});

View file

@ -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");
});
});

View file

@ -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");
});
});

View file

@ -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");
});
});

View file

@ -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");
});
});

View file

@ -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");
});
});