mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 03:27:34 +00:00
LibJS: Implement Temporal.Calendar.prototype.yearMonthFromFields()
This commit is contained in:
parent
c1c12497b5
commit
ed9d37bd40
10 changed files with 223 additions and 14 deletions
|
@ -420,6 +420,7 @@ namespace JS {
|
|||
P(withPlainDate) \
|
||||
P(writable) \
|
||||
P(year) \
|
||||
P(yearMonthFromFields) \
|
||||
P(years) \
|
||||
P(zonedDateTime) \
|
||||
P(zonedDateTimeISO)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
|
||||
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -714,6 +715,48 @@ Optional<ISODate> iso_date_from_fields(GlobalObject& global_object, Object& fiel
|
|||
return regulate_iso_date(global_object, year.as_double(), month, day.as_double(), *overflow);
|
||||
}
|
||||
|
||||
// 12.1.39 ISOYearMonthFromFields ( fields, options ), https://tc39.es/proposal-temporal/#sec-temporal-isoyearmonthfromfields
|
||||
Optional<ISOYearMonth> iso_year_month_from_fields(GlobalObject& global_object, Object& fields, Object& options)
|
||||
{
|
||||
auto& vm = global_object.vm();
|
||||
|
||||
// 1. Assert: Type(fields) is Object.
|
||||
|
||||
// 2. Let overflow be ? ToTemporalOverflow(options).
|
||||
auto overflow = to_temporal_overflow(global_object, options);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
|
||||
// 3. Set fields to ? PrepareTemporalFields(fields, « "month", "monthCode", "year" », «»).
|
||||
auto* prepared_fields = prepare_temporal_fields(global_object, fields, { "month"sv, "monthCode"sv, "year"sv }, {});
|
||||
if (vm.exception())
|
||||
return {};
|
||||
|
||||
// 4. Let year be ? Get(fields, "year").
|
||||
auto year = prepared_fields->get(vm.names.year);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
|
||||
// 5. If year is undefined, throw a TypeError exception.
|
||||
if (year.is_undefined()) {
|
||||
vm.throw_exception<TypeError>(global_object, ErrorType::TemporalMissingRequiredProperty, vm.names.year.as_string());
|
||||
return {};
|
||||
}
|
||||
|
||||
// 6. Let month be ? ResolveISOMonth(fields).
|
||||
auto month = resolve_iso_month(global_object, *prepared_fields);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
|
||||
// 7. Let result be ? RegulateISOYearMonth(year, month, overflow).
|
||||
auto result = regulate_iso_year_month(global_object, year.as_double(), month, *overflow);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
|
||||
// 8. Return the new Record { [[Year]]: result.[[Year]], [[Month]]: result.[[Month]], [[ReferenceISODay]]: 1 }.
|
||||
return ISOYearMonth { .year = result->year, .month = result->month, .reference_iso_day = 1 };
|
||||
}
|
||||
|
||||
// 12.1.41 ISOYear ( temporalObject ), https://tc39.es/proposal-temporal/#sec-temporal-isoyear
|
||||
i32 iso_year(Object& temporal_object)
|
||||
{
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include <LibJS/Runtime/Object.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDate.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainYearMonth.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
@ -59,6 +60,7 @@ u8 to_iso_week_of_year(i32 year, u8 month, u8 day);
|
|||
String build_iso_month_code(u8 month);
|
||||
double resolve_iso_month(GlobalObject&, Object& fields);
|
||||
Optional<ISODate> iso_date_from_fields(GlobalObject&, Object& fields, Object& options);
|
||||
Optional<ISOYearMonth> iso_year_month_from_fields(GlobalObject&, Object& fields, Object& options);
|
||||
i32 iso_year(Object& temporal_object);
|
||||
u8 iso_month(Object& temporal_object);
|
||||
String iso_month_code(Object& temporal_object);
|
||||
|
|
|
@ -34,6 +34,7 @@ void CalendarPrototype::initialize(GlobalObject& global_object)
|
|||
|
||||
u8 attr = Attribute::Writable | Attribute::Configurable;
|
||||
define_native_function(vm.names.dateFromFields, date_from_fields, 2, attr);
|
||||
define_native_function(vm.names.yearMonthFromFields, year_month_from_fields, 2, attr);
|
||||
define_native_function(vm.names.year, year, 1, attr);
|
||||
define_native_function(vm.names.month, month, 1, attr);
|
||||
define_native_function(vm.names.monthCode, month_code, 1, attr);
|
||||
|
@ -107,6 +108,40 @@ JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::date_from_fields)
|
|||
return create_temporal_date(global_object, result->year, result->month, result->day, *calendar);
|
||||
}
|
||||
|
||||
// 12.4.5 Temporal.Calendar.prototype.yearMonthFromFields ( fields, options ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.yearmonthfromfields
|
||||
// NOTE: This is the minimum yearMonthFromFields implementation for engines without ECMA-402.
|
||||
JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::year_month_from_fields)
|
||||
{
|
||||
// 1. Let calendar be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]).
|
||||
auto* calendar = typed_this(global_object);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
|
||||
// 3. Assert: calendar.[[Identifier]] is "iso8601".
|
||||
VERIFY(calendar->identifier() == "iso8601"sv);
|
||||
|
||||
// 4. If Type(fields) is not Object, throw a TypeError exception.
|
||||
auto fields = vm.argument(0);
|
||||
if (!fields.is_object()) {
|
||||
vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, fields.to_string_without_side_effects());
|
||||
return {};
|
||||
}
|
||||
|
||||
// 5. Set options to ? GetOptionsObject(options).
|
||||
auto* options = get_options_object(global_object, vm.argument(1));
|
||||
if (vm.exception())
|
||||
return {};
|
||||
|
||||
// 6. Let result be ? ISOYearMonthFromFields(fields, options).
|
||||
auto result = iso_year_month_from_fields(global_object, fields.as_object(), *options);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
|
||||
// 7. Return ? CreateTemporalYearMonth(result.[[Year]], result.[[Month]], calendar, result.[[ReferenceISODay]]).
|
||||
return create_temporal_year_month(global_object, result->year, result->month, *calendar, result->reference_iso_day);
|
||||
}
|
||||
|
||||
// 12.4.9 Temporal.Calendar.prototype.year ( temporalDateLike ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.year
|
||||
// NOTE: This is the minimum year implementation for engines without ECMA-402.
|
||||
JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::year)
|
||||
|
|
|
@ -21,6 +21,7 @@ public:
|
|||
private:
|
||||
JS_DECLARE_NATIVE_FUNCTION(id_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(date_from_fields);
|
||||
JS_DECLARE_NATIVE_FUNCTION(year_month_from_fields);
|
||||
JS_DECLARE_NATIVE_FUNCTION(year);
|
||||
JS_DECLARE_NATIVE_FUNCTION(month);
|
||||
JS_DECLARE_NATIVE_FUNCTION(month_code);
|
||||
|
|
|
@ -237,18 +237,18 @@ bool is_valid_iso_date(i32 year, u8 month, u8 day)
|
|||
}
|
||||
|
||||
// 3.5.6 BalanceISODate ( year, month, day ), https://tc39.es/proposal-temporal/#sec-temporal-balanceisodate
|
||||
ISODate balance_iso_date(i32 year, i32 month, i32 day)
|
||||
ISODate balance_iso_date(double year_, double month_, double day)
|
||||
{
|
||||
// 1. Assert: year, month, and day are integers.
|
||||
|
||||
// 2. Let balancedYearMonth be ! BalanceISOYearMonth(year, month).
|
||||
auto balanced_year_month = balance_iso_year_month(year, month);
|
||||
auto balanced_year_month = balance_iso_year_month(year_, month_);
|
||||
|
||||
// 3. Set month to balancedYearMonth.[[Month]].
|
||||
month = balanced_year_month.month;
|
||||
auto month = balanced_year_month.month;
|
||||
|
||||
// 4. Set year to balancedYearMonth.[[Year]].
|
||||
year = balanced_year_month.year;
|
||||
auto year = balanced_year_month.year;
|
||||
|
||||
// 5. NOTE: To deal with negative numbers of days whose absolute value is greater than the number of days in a year, the following section subtracts years and adds days until the number of days is greater than −366 or −365.
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ PlainDate* create_temporal_date(GlobalObject&, i32 iso_year, u8 iso_month, u8 is
|
|||
PlainDate* to_temporal_date(GlobalObject&, Value item, Object* options = nullptr);
|
||||
Optional<ISODate> regulate_iso_date(GlobalObject&, double year, double month, double day, String const& overflow);
|
||||
bool is_valid_iso_date(i32 year, u8 month, u8 day);
|
||||
ISODate balance_iso_date(i32 year, i32 month, i32 day);
|
||||
ISODate balance_iso_date(double year, double month, double day);
|
||||
i8 compare_iso_date(i32 year1, u8 month1, u8 day1, i32 year2, u8 month2, u8 day2);
|
||||
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDate.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainYearMonth.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainYearMonthConstructor.h>
|
||||
|
@ -28,19 +29,67 @@ void PlainYearMonth::visit_edges(Visitor& visitor)
|
|||
visitor.visit(&m_calendar);
|
||||
}
|
||||
|
||||
// 9.5.5 BalanceISOYearMonth ( year, month ), https://tc39.es/proposal-temporal/#sec-temporal-balanceisoyearmonth
|
||||
ISOYearMonth balance_iso_year_month(i32 year, i32 month)
|
||||
// 9.5.2 RegulateISOYearMonth ( year, month, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-regulateisoyearmonth
|
||||
Optional<ISOYearMonth> regulate_iso_year_month(GlobalObject& global_object, double year, double month, String const& overflow)
|
||||
{
|
||||
auto& vm = global_object.vm();
|
||||
|
||||
// 1. Assert: year and month are integers.
|
||||
VERIFY(year == trunc(year) && month == trunc(month));
|
||||
|
||||
// 2. Set year to year + floor((month - 1) / 12).
|
||||
year += (month - 1) / 12;
|
||||
// 2. Assert: overflow is either "constrain" or "reject".
|
||||
// NOTE: Asserted by the VERIFY_NOT_REACHED at the end
|
||||
|
||||
// 3. Set month to (month − 1) modulo 12 + 1.
|
||||
month = (month - 1) % 12 + 1;
|
||||
// 3. If overflow is "constrain", then
|
||||
if (overflow == "constrain"sv) {
|
||||
// IMPLEMENTATION DEFINED: This is an optimization that allows us to treat `year` (a double) as normal integer from this point onwards.
|
||||
// This does not change the exposed behaviour as the subsequent call to CreateTemporalYearMonth will check that its value is a valid ISO
|
||||
// values (for years: -273975 - 273975) which is a subset of this check.
|
||||
// If RegulateISOYearMonth is ever used outside ISOYearMonthFromFields, this may need to be changed.
|
||||
if (!AK::is_within_range<i32>(year)) {
|
||||
vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidPlainYearMonth);
|
||||
return {};
|
||||
}
|
||||
|
||||
// 4. Return the new Record { [[Year]]: year, [[Month]]: month }.
|
||||
return ISOYearMonth { .year = year, .month = static_cast<u8>(month) };
|
||||
// a. Return ! ConstrainISOYearMonth(year, month).
|
||||
return constrain_iso_year_month(year, month);
|
||||
}
|
||||
|
||||
// 4. If overflow is "reject", then
|
||||
if (overflow == "reject"sv) {
|
||||
// IMPLEMENTATION DEFINED: This is an optimization that allows us to treat these doubles as normal integers from this point onwards.
|
||||
// This does not change the exposed behaviour as the call to IsValidISOMonth and subsequent call to CreateTemporalDateTime will check
|
||||
// that these values are valid ISO values (for years: -273975 - 273975, for months: 1 - 12) all of which are subsets of this check.
|
||||
if (!AK::is_within_range<i32>(year) || !AK::is_within_range<u8>(month)) {
|
||||
vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidPlainYearMonth);
|
||||
return {};
|
||||
}
|
||||
|
||||
// a. If ! IsValidISOMonth(month) is false, throw a RangeError exception.
|
||||
if (!is_valid_iso_month(month)) {
|
||||
vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidPlainYearMonth);
|
||||
return {};
|
||||
}
|
||||
|
||||
// b. Return the new Record { [[Year]]: year, [[Month]]: month }.
|
||||
return ISOYearMonth { .year = static_cast<i32>(year), .month = static_cast<u8>(month), .reference_iso_day = 0 };
|
||||
}
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
// 9.5.3 IsValidISOMonth ( month ), https://tc39.es/proposal-temporal/#sec-temporal-isvalidisomonth
|
||||
bool is_valid_iso_month(u8 month)
|
||||
{
|
||||
// 1. Assert: month is an integer.
|
||||
// 2. If month < 1 or month > 12, then
|
||||
if (month < 1 || month > 12) {
|
||||
// a.Return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. Return true.
|
||||
return true;
|
||||
}
|
||||
|
||||
// 9.5.4 ISOYearMonthWithinLimits ( year, month ), https://tc39.es/proposal-temporal/#sec-temporal-isoyearmonthwithinlimits
|
||||
|
@ -70,6 +119,36 @@ bool iso_year_month_within_limits(i32 year, u8 month)
|
|||
return true;
|
||||
}
|
||||
|
||||
// 9.5.5 BalanceISOYearMonth ( year, month ), https://tc39.es/proposal-temporal/#sec-temporal-balanceisoyearmonth
|
||||
ISOYearMonth balance_iso_year_month(double year, double month)
|
||||
{
|
||||
// 1. Assert: year and month are integers.
|
||||
VERIFY(year == trunc(year) && month == trunc(month));
|
||||
|
||||
// 2. Set year to year + floor((month - 1) / 12).
|
||||
year += floor((month - 1) / 12);
|
||||
|
||||
// 3. Set month to (month − 1) modulo 12 + 1.
|
||||
month = fmod(month - 1, 12) + 1;
|
||||
|
||||
// 4. Return the new Record { [[Year]]: year, [[Month]]: month }.
|
||||
return ISOYearMonth { .year = static_cast<i32>(year), .month = static_cast<u8>(month), .reference_iso_day = 0 };
|
||||
}
|
||||
|
||||
// 9.5.6 ConstrainISOYearMonth ( year, month ), https://tc39.es/proposal-temporal/#sec-temporal-constrainisoyearmonth
|
||||
ISOYearMonth constrain_iso_year_month(double year, double month)
|
||||
{
|
||||
// 1. Assert: year and month are integers.
|
||||
VERIFY(year == trunc(year) && month == trunc(month));
|
||||
|
||||
// 2. Set month to ! ConstrainToRange(month, 1, 12).
|
||||
month = constrain_to_range(month, 1, 12);
|
||||
|
||||
// 3. Return the Record { [[Year]]: year, [[Month]]: month }.
|
||||
// NOTE: `year` is known to be in the i32 range.
|
||||
return ISOYearMonth { .year = static_cast<i32>(year), .month = static_cast<u8>(month), .reference_iso_day = 0 };
|
||||
}
|
||||
|
||||
// 9.5.7 CreateTemporalYearMonth ( isoYear, isoMonth, calendar, referenceISODay [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporalyearmonth
|
||||
PlainYearMonth* create_temporal_year_month(GlobalObject& global_object, i32 iso_year, u8 iso_month, Object& calendar, u8 reference_iso_day, FunctionObject* new_target)
|
||||
{
|
||||
|
|
|
@ -36,10 +36,14 @@ private:
|
|||
struct ISOYearMonth {
|
||||
i32 year;
|
||||
u8 month;
|
||||
u8 reference_iso_day;
|
||||
};
|
||||
|
||||
ISOYearMonth balance_iso_year_month(i32 year, i32 month);
|
||||
Optional<ISOYearMonth> regulate_iso_year_month(GlobalObject&, double year, double month, String const& overflow);
|
||||
bool is_valid_iso_month(u8 month);
|
||||
bool iso_year_month_within_limits(i32 year, u8 month);
|
||||
ISOYearMonth balance_iso_year_month(double year, double month);
|
||||
ISOYearMonth constrain_iso_year_month(double year, double month);
|
||||
PlainYearMonth* create_temporal_year_month(GlobalObject&, i32 iso_year, u8 iso_month, Object& calendar, u8 reference_iso_day, FunctionObject* new_target = nullptr);
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
describe("correct behavior", () => {
|
||||
test("length is 2", () => {
|
||||
expect(Temporal.Calendar.prototype.yearMonthFromFields).toHaveLength(2);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
const calendar = new Temporal.Calendar("iso8601");
|
||||
const plainYearMonth = calendar.yearMonthFromFields({ year: 2021, month: 7 });
|
||||
expect(plainYearMonth.calendar).toBe(calendar);
|
||||
expect(plainYearMonth.year).toBe(2021);
|
||||
expect(plainYearMonth.month).toBe(7);
|
||||
});
|
||||
|
||||
test("with monthCode", () => {
|
||||
const calendar = new Temporal.Calendar("iso8601");
|
||||
const plainYearMonth = calendar.yearMonthFromFields({ year: 2021, monthCode: "M07" });
|
||||
expect(plainYearMonth.calendar).toBe(calendar);
|
||||
expect(plainYearMonth.year).toBe(2021);
|
||||
expect(plainYearMonth.month).toBe(7);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("first argument must be an object", () => {
|
||||
const calendar = new Temporal.Calendar("iso8601");
|
||||
expect(() => {
|
||||
calendar.yearMonthFromFields(42);
|
||||
}).toThrowWithMessage(TypeError, "42 is not an object");
|
||||
});
|
||||
|
||||
test("year field is required", () => {
|
||||
const calendar = new Temporal.Calendar("iso8601");
|
||||
expect(() => {
|
||||
calendar.yearMonthFromFields({ month: 7 });
|
||||
}).toThrowWithMessage(TypeError, "Required property year is missing or undefined");
|
||||
});
|
||||
|
||||
test("month or monthCode field is required", () => {
|
||||
const calendar = new Temporal.Calendar("iso8601");
|
||||
expect(() => {
|
||||
calendar.yearMonthFromFields({ year: 2021 });
|
||||
}).toThrowWithMessage(TypeError, "Required property month is missing or undefined");
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue