LibJS: Implement Temporal.PlainDate.prototype.add/subtract/equals

This commit is contained in:
Timothy Flynn 2024-11-22 10:54:15 -05:00 committed by Andreas Kling
parent 9fbb5a57fa
commit c2ead84bd9
Notes: github-actions[bot] 2024-11-23 13:48:09 +00:00
7 changed files with 133 additions and 0 deletions

View file

@ -11,6 +11,7 @@
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Temporal/Calendar.h>
#include <LibJS/Runtime/Temporal/DateEquations.h>
#include <LibJS/Runtime/Temporal/Duration.h>
#include <LibJS/Runtime/Temporal/PlainDate.h>
#include <LibJS/Runtime/Temporal/PlainDateConstructor.h>
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
@ -315,4 +316,33 @@ i8 compare_iso_date(ISODate iso_date1, ISODate iso_date2)
return 0;
}
// 3.5.14 AddDurationToDate ( operation, temporalDate, temporalDurationLike, options ), https://tc39.es/proposal-temporal/#sec-temporal-adddurationtodate
ThrowCompletionOr<GC::Ref<PlainDate>> add_duration_to_date(VM& vm, ArithmeticOperation operation, PlainDate const& temporal_date, Value temporal_duration_like, Value options)
{
// 1. Let calendar be temporalDate.[[Calendar]].
auto const& calendar = temporal_date.calendar();
// 2. Let duration be ? ToTemporalDuration(temporalDurationLike).
auto duration = TRY(to_temporal_duration(vm, temporal_duration_like));
// 3. If operation is SUBTRACT, set duration to CreateNegatedTemporalDuration(duration).
if (operation == ArithmeticOperation::Subtract)
duration = create_negated_temporal_duration(vm, duration);
// 4. Let dateDuration be ? ToDateDurationRecordWithoutTime(duration).
auto date_duration = TRY(to_date_duration_record_without_time(vm, duration));
// 5. Let resolvedOptions be ? GetOptionsObject(options).
auto resolved_options = TRY(get_options_object(vm, options));
// 6. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
auto overflow = TRY(get_temporal_overflow_option(vm, resolved_options));
// 7. Let result be ? CalendarDateAdd(calendar, temporalDate.[[ISODate]], dateDuration, overflow).
auto result = TRY(calendar_date_add(vm, calendar, temporal_date.iso_date(), date_duration, overflow));
// 8. Return ! CreateTemporalDate(result, calendar).
return MUST(create_temporal_date(vm, result, calendar));
}
}

View file

@ -51,5 +51,6 @@ String pad_iso_year(i32 year);
String temporal_date_to_string(PlainDate const&, ShowCalendar);
bool iso_date_within_limits(ISODate);
i8 compare_iso_date(ISODate, ISODate);
ThrowCompletionOr<GC::Ref<PlainDate>> add_duration_to_date(VM&, ArithmeticOperation, PlainDate const&, Value temporal_duration_like, Value options);
}

View file

@ -47,6 +47,9 @@ void PlainDatePrototype::initialize(Realm& realm)
define_native_accessor(realm, vm.names.inLeapYear, in_leap_year_getter, {}, Attribute::Configurable);
u8 attr = Attribute::Writable | Attribute::Configurable;
define_native_function(realm, vm.names.add, add, 1, attr);
define_native_function(realm, vm.names.subtract, subtract, 1, attr);
define_native_function(realm, vm.names.equals, equals, 1, attr);
define_native_function(realm, vm.names.toString, to_string, 0, attr);
define_native_function(realm, vm.names.toLocaleString, to_locale_string, 0, attr);
define_native_function(realm, vm.names.toJSON, to_json, 0, attr);
@ -180,6 +183,52 @@ JS_DEFINE_NATIVE_FUNCTION(PlainDatePrototype::year_of_week_getter)
return *result;
}
// 3.3.21 Temporal.PlainDate.prototype.add ( temporalDurationLike [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.add
JS_DEFINE_NATIVE_FUNCTION(PlainDatePrototype::add)
{
auto temporal_duration_like = vm.argument(0);
auto options = vm.argument(1);
// 1. Let temporalDate be the this value.
// 2. Perform ? RequireInternalSlot(temporalDate, [[InitializedTemporalDate]]).
auto temporal_date = TRY(typed_this_object(vm));
// 3. Return ? AddDurationToDate(ADD, temporalDate, temporalDurationLike, options).
return TRY(add_duration_to_date(vm, ArithmeticOperation::Add, temporal_date, temporal_duration_like, options));
}
// 3.3.22 Temporal.PlainDate.prototype.subtract ( temporalDurationLike [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.subtract
JS_DEFINE_NATIVE_FUNCTION(PlainDatePrototype::subtract)
{
auto temporal_duration_like = vm.argument(0);
auto options = vm.argument(1);
// 1. Let temporalDate be the this value.
auto temporal_date = TRY(typed_this_object(vm));
// 2. Perform ? RequireInternalSlot(temporalDate, [[InitializedTemporalDate]]).
// 3. Return ? AddDurationToDate(SUBTRACT, temporalDate, temporalDurationLike, options).
return TRY(add_duration_to_date(vm, ArithmeticOperation::Subtract, temporal_date, temporal_duration_like, options));
}
// 3.3.27 Temporal.PlainDate.prototype.equals ( other ), https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.equals
JS_DEFINE_NATIVE_FUNCTION(PlainDatePrototype::equals)
{
// 1. Let temporalDate be the this value.
// 2. Perform ? RequireInternalSlot(temporalDate, [[InitializedTemporalDate]]).
auto temporal_date = TRY(typed_this_object(vm));
// 3. Set other to ? ToTemporalDate(other).
auto other = TRY(to_temporal_date(vm, vm.argument(0)));
// 4. If CompareISODate(temporalDate.[[ISODate]], other.[[ISODate]]) ≠ 0, return false.
if (compare_iso_date(temporal_date->iso_date(), other->iso_date()) != 0)
return false;
// 5. Return CalendarEquals(temporalDate.[[Calendar]], other.[[Calendar]]).
return calendar_equals(temporal_date->calendar(), other->calendar());
}
// 3.3.30 Temporal.PlainDate.prototype.toString ( [ options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.tostring
JS_DEFINE_NATIVE_FUNCTION(PlainDatePrototype::to_string)
{

View file

@ -39,6 +39,9 @@ private:
JS_DECLARE_NATIVE_FUNCTION(days_in_year_getter);
JS_DECLARE_NATIVE_FUNCTION(months_in_year_getter);
JS_DECLARE_NATIVE_FUNCTION(in_leap_year_getter);
JS_DECLARE_NATIVE_FUNCTION(add);
JS_DECLARE_NATIVE_FUNCTION(subtract);
JS_DECLARE_NATIVE_FUNCTION(equals);
JS_DECLARE_NATIVE_FUNCTION(to_string);
JS_DECLARE_NATIVE_FUNCTION(to_locale_string);
JS_DECLARE_NATIVE_FUNCTION(to_json);

View file

@ -0,0 +1,19 @@
describe("correct behavior", () => {
test("length is 1", () => {
expect(Temporal.PlainDate.prototype.add).toHaveLength(1);
});
test("basic functionality", () => {
const plainDate = new Temporal.PlainDate(1970, 1, 1);
const result = plainDate.add(new Temporal.Duration(51, 6, 0, 5));
expect(result.equals(new Temporal.PlainDate(2021, 7, 6))).toBeTrue();
});
});
describe("errors", () => {
test("this value must be a Temporal.PlainDate object", () => {
expect(() => {
Temporal.PlainDate.prototype.add.call("foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainDate");
});
});

View file

@ -0,0 +1,12 @@
describe("correct behavior", () => {
test("length is 1", () => {
expect(Temporal.PlainDate.prototype.equals).toHaveLength(1);
});
test("basic functionality", () => {
const firstPlainDate = new Temporal.PlainDate(1, 1, 1, "gregory");
const secondPlainDate = new Temporal.PlainDate(0, 1, 1, "gregory");
expect(firstPlainDate.equals(firstPlainDate)).toBeTrue();
expect(firstPlainDate.equals(secondPlainDate)).toBeFalse();
});
});

View file

@ -0,0 +1,19 @@
describe("correct behavior", () => {
test("length is 1", () => {
expect(Temporal.PlainDate.prototype.subtract).toHaveLength(1);
});
test("basic functionality", () => {
const plainDate = new Temporal.PlainDate(2021, 7, 6);
const result = plainDate.subtract(new Temporal.Duration(51, 6, 0, 5));
expect(result.equals(new Temporal.PlainDate(1970, 1, 1))).toBeTrue();
});
});
describe("errors", () => {
test("this value must be a Temporal.PlainDate object", () => {
expect(() => {
Temporal.PlainDate.prototype.subtract.call("foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainDate");
});
});