summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Groh <mail@linusgroh.de>2021-08-16 00:35:05 +0100
committerLinus Groh <mail@linusgroh.de>2021-08-16 14:10:41 +0100
commited9d37bd4049c4c178912d321183a8b146d5410f (patch)
tree76d8fecfd9119f3a0c460458b482733f5f1de798
parentc1c12497b56edbdbd02cb3f687a676f1c6a39cac (diff)
downloadserenity-ed9d37bd4049c4c178912d321183a8b146d5410f.zip
LibJS: Implement Temporal.Calendar.prototype.yearMonthFromFields()
-rw-r--r--Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h1
-rw-r--r--Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp43
-rw-r--r--Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h2
-rw-r--r--Userland/Libraries/LibJS/Runtime/Temporal/CalendarPrototype.cpp35
-rw-r--r--Userland/Libraries/LibJS/Runtime/Temporal/CalendarPrototype.h1
-rw-r--r--Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.cpp8
-rw-r--r--Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.h2
-rw-r--r--Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.cpp95
-rw-r--r--Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.h6
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Temporal/Calendar/Calendar.prototype.yearMonthFromFields.js44
10 files changed, 223 insertions, 14 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h
index 7d74ae6959..1e1d5c3b13 100644
--- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h
+++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h
@@ -420,6 +420,7 @@ namespace JS {
P(withPlainDate) \
P(writable) \
P(year) \
+ P(yearMonthFromFields) \
P(years) \
P(zonedDateTime) \
P(zonedDateTimeISO)
diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp
index e3f4597060..13f291aee5 100644
--- a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp
@@ -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)
{
diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h
index 653d37b175..162144f85c 100644
--- a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h
+++ b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h
@@ -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);
diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/CalendarPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/CalendarPrototype.cpp
index ac01702ae7..ecdd4ad969 100644
--- a/Userland/Libraries/LibJS/Runtime/Temporal/CalendarPrototype.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Temporal/CalendarPrototype.cpp
@@ -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)
diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/CalendarPrototype.h b/Userland/Libraries/LibJS/Runtime/Temporal/CalendarPrototype.h
index 7cee764bba..c4d5a0d494 100644
--- a/Userland/Libraries/LibJS/Runtime/Temporal/CalendarPrototype.h
+++ b/Userland/Libraries/LibJS/Runtime/Temporal/CalendarPrototype.h
@@ -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);
diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.cpp
index 83d1a660fb..0b71e50244 100644
--- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.cpp
@@ -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.
diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.h
index b0985fe52d..37489ee3b7 100644
--- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.h
+++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.h
@@ -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);
}
diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.cpp
index 248aa43192..aa61eb9a4b 100644
--- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.cpp
@@ -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. Assert: overflow is either "constrain" or "reject".
+ // NOTE: Asserted by the VERIFY_NOT_REACHED at the end
+
+ // 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 {};
+ }
+
+ // a. Return ! ConstrainISOYearMonth(year, month).
+ return constrain_iso_year_month(year, month);
+ }
- // 2. Set year to year + floor((month - 1) / 12).
- year += (month - 1) / 12;
+ // 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 };
+ }
- // 3. Set month to (month โˆ’ 1) modulo 12 + 1.
- month = (month - 1) % 12 + 1;
+ VERIFY_NOT_REACHED();
+}
- // 4. Return the new Record { [[Year]]: year, [[Month]]: month }.
- return ISOYearMonth { .year = year, .month = static_cast<u8>(month) };
+// 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)
{
diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.h
index 12160fd011..063cb6ec25 100644
--- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.h
+++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.h
@@ -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);
}
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Temporal/Calendar/Calendar.prototype.yearMonthFromFields.js b/Userland/Libraries/LibJS/Tests/builtins/Temporal/Calendar/Calendar.prototype.yearMonthFromFields.js
new file mode 100644
index 0000000000..058a137cd7
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Temporal/Calendar/Calendar.prototype.yearMonthFromFields.js
@@ -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");
+ });
+});