LibJS: Implement Temporal.PlainDateTime.prototype.with* methods
Includes with, withCalendar, and withPlainTime.
This commit is contained in:
parent
e3082b5bed
commit
990daaf63a
Notes:
github-actions[bot]
2024-11-24 10:45:09 +00:00
Author: https://github.com/trflynn89 Commit: https://github.com/LadybirdBrowser/ladybird/commit/990daaf63a3 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2544
7 changed files with 232 additions and 0 deletions
|
@ -10,6 +10,7 @@
|
|||
#include <LibJS/Runtime/Temporal/Calendar.h>
|
||||
#include <LibJS/Runtime/Temporal/Duration.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDateTimePrototype.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainTime.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
|
@ -54,6 +55,9 @@ void PlainDateTimePrototype::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.with, with, 1, attr);
|
||||
define_native_function(realm, vm.names.withPlainTime, with_plain_time, 0, attr);
|
||||
define_native_function(realm, vm.names.withCalendar, with_calendar, 1, attr);
|
||||
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.until, until, 1, attr);
|
||||
|
@ -220,6 +224,100 @@ JS_DEFINE_NATIVE_FUNCTION(PlainDateTimePrototype::year_of_week_getter)
|
|||
return *result;
|
||||
}
|
||||
|
||||
// 5.3.25 Temporal.PlainDateTime.prototype.with ( temporalDateTimeLike [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.with
|
||||
JS_DEFINE_NATIVE_FUNCTION(PlainDateTimePrototype::with)
|
||||
{
|
||||
auto temporal_date_time_like = vm.argument(0);
|
||||
auto options = vm.argument(1);
|
||||
|
||||
// 1. Let dateTime be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(dateTime, [[InitializedTemporalDateTime]]).
|
||||
auto date_time = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. If ? IsPartialTemporalObject(temporalDateTimeLike) is false, throw a TypeError exception.
|
||||
if (!TRY(is_partial_temporal_object(vm, temporal_date_time_like)))
|
||||
return vm.throw_completion<TypeError>(ErrorType::TemporalObjectMustBePartialTemporalObject);
|
||||
|
||||
// 4. Let calendar be dateTime.[[Calendar]].
|
||||
auto const& calendar = date_time->calendar();
|
||||
|
||||
// 5. Let fields be ISODateToFields(calendar, dateTime.[[ISODateTime]].[[ISODate]], DATE).
|
||||
auto fields = iso_date_to_fields(calendar, date_time->iso_date_time().iso_date, DateType::Date);
|
||||
|
||||
// 6. Set fields.[[Hour]] to dateTime.[[ISODateTime]].[[Time]].[[Hour]].
|
||||
fields.hour = date_time->iso_date_time().time.hour;
|
||||
|
||||
// 7. Set fields.[[Minute]] to dateTime.[[ISODateTime]].[[Time]].[[Minute]].
|
||||
fields.minute = date_time->iso_date_time().time.minute;
|
||||
|
||||
// 8. Set fields.[[Second]] to dateTime.[[ISODateTime]].[[Time]].[[Second]].
|
||||
fields.second = date_time->iso_date_time().time.second;
|
||||
|
||||
// 9. Set fields.[[Millisecond]] to dateTime.[[ISODateTime]].[[Time]].[[Millisecond]].
|
||||
fields.millisecond = date_time->iso_date_time().time.millisecond;
|
||||
|
||||
// 10. Set fields.[[Microsecond]] to dateTime.[[ISODateTime]].[[Time]].[[Microsecond]].
|
||||
fields.microsecond = date_time->iso_date_time().time.microsecond;
|
||||
|
||||
// 11. Set fields.[[Nanosecond]] to dateTime.[[ISODateTime]].[[Time]].[[Nanosecond]].
|
||||
fields.nanosecond = date_time->iso_date_time().time.nanosecond;
|
||||
|
||||
// 12. Let partialDateTime be ? PrepareCalendarFields(calendar, temporalDateTimeLike, « YEAR, MONTH, MONTH-CODE, DAY », « HOUR, MINUTE, SECOND, MILLISECOND, MICROSECOND, NANOSECOND », PARTIAL).
|
||||
static constexpr auto calendar_field_names = to_array({ CalendarField::Year, CalendarField::Month, CalendarField::MonthCode, CalendarField::Day });
|
||||
static constexpr auto non_calendar_field_names = to_array({ CalendarField::Hour, CalendarField::Minute, CalendarField::Second, CalendarField::Millisecond, CalendarField::Microsecond, CalendarField::Nanosecond });
|
||||
auto partial_date_time = TRY(prepare_calendar_fields(vm, calendar, temporal_date_time_like.as_object(), calendar_field_names, non_calendar_field_names, Partial {}));
|
||||
|
||||
// 13. Set fields to CalendarMergeFields(calendar, fields, partialDateTime).
|
||||
fields = calendar_merge_fields(calendar, fields, partial_date_time);
|
||||
|
||||
// 14. Let resolvedOptions be ? GetOptionsObject(options).
|
||||
auto resolved_options = TRY(get_options_object(vm, options));
|
||||
|
||||
// 15. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
|
||||
auto overflow = TRY(get_temporal_overflow_option(vm, resolved_options));
|
||||
|
||||
// 16. Let result be ? InterpretTemporalDateTimeFields(calendar, fields, overflow).
|
||||
auto result = TRY(interpret_temporal_date_time_fields(vm, calendar, fields, overflow));
|
||||
|
||||
// 17. Return ? CreateTemporalDateTime(result, calendar).
|
||||
return MUST(create_temporal_date_time(vm, result, calendar));
|
||||
}
|
||||
|
||||
// 5.3.26 Temporal.PlainDateTime.prototype.withPlainTime ( [ plainTimeLike ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.withplaintime
|
||||
JS_DEFINE_NATIVE_FUNCTION(PlainDateTimePrototype::with_plain_time)
|
||||
{
|
||||
auto plain_time_like = vm.argument(0);
|
||||
|
||||
// 1. Let dateTime be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(dateTime, [[InitializedTemporalDateTime]]).
|
||||
auto date_time = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. Let time be ? ToTimeRecordOrMidnight(plainTimeLike).
|
||||
auto time = TRY(to_time_record_or_midnight(vm, plain_time_like));
|
||||
|
||||
// 4. Let isoDateTime be CombineISODateAndTimeRecord(dateTime.[[ISODateTime]].[[ISODate]], time).
|
||||
auto iso_date_time = combine_iso_date_and_time_record(date_time->iso_date_time().iso_date, time);
|
||||
|
||||
// 5. Return ? CreateTemporalDateTime(isoDateTime, dateTime.[[Calendar]]).
|
||||
return TRY(create_temporal_date_time(vm, iso_date_time, date_time->calendar()));
|
||||
}
|
||||
|
||||
// 5.3.27 Temporal.PlainDateTime.prototype.withCalendar ( calendarLike ), https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.withcalendar
|
||||
JS_DEFINE_NATIVE_FUNCTION(PlainDateTimePrototype::with_calendar)
|
||||
{
|
||||
auto calendar_like = vm.argument(0);
|
||||
|
||||
// 1. Let dateTime be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(dateTime, [[InitializedTemporalDateTime]]).
|
||||
auto date_time = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. Let calendar be ? ToTemporalCalendarIdentifier(calendarLike).
|
||||
auto calendar = TRY(to_temporal_calendar_identifier(vm, calendar_like));
|
||||
|
||||
// 4. Return ! CreateTemporalDateTime(dateTime.[[ISODateTime]], calendar).
|
||||
return TRY(create_temporal_date_time(vm, date_time->iso_date_time(), calendar));
|
||||
}
|
||||
|
||||
// 5.3.28 Temporal.PlainDateTime.prototype.add ( temporalDurationLike [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.add
|
||||
JS_DEFINE_NATIVE_FUNCTION(PlainDateTimePrototype::add)
|
||||
{
|
||||
|
|
|
@ -45,6 +45,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(with);
|
||||
JS_DECLARE_NATIVE_FUNCTION(with_plain_time);
|
||||
JS_DECLARE_NATIVE_FUNCTION(with_calendar);
|
||||
JS_DECLARE_NATIVE_FUNCTION(add);
|
||||
JS_DECLARE_NATIVE_FUNCTION(subtract);
|
||||
JS_DECLARE_NATIVE_FUNCTION(until);
|
||||
|
|
|
@ -190,6 +190,20 @@ ThrowCompletionOr<GC::Ref<PlainTime>> to_temporal_time(VM& vm, Value item, Value
|
|||
return MUST(create_temporal_time(vm, time));
|
||||
}
|
||||
|
||||
// 4.5.7 ToTimeRecordOrMidnight ( item ), https://tc39.es/proposal-temporal/#sec-temporal-totimerecordormidnight
|
||||
ThrowCompletionOr<Time> to_time_record_or_midnight(VM& vm, Value item)
|
||||
{
|
||||
// 1. If item is undefined, return MidnightTimeRecord().
|
||||
if (item.is_undefined())
|
||||
return midnight_time_record();
|
||||
|
||||
// 2. Let plainTime be ? ToTemporalTime(item).
|
||||
auto plain_time = TRY(to_temporal_time(vm, item));
|
||||
|
||||
// 3. Return plainTime.[[Time]].
|
||||
return plain_time->time();
|
||||
}
|
||||
|
||||
// 4.5.8 RegulateTime ( hour, minute, second, millisecond, microsecond, nanosecond, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-regulatetime
|
||||
ThrowCompletionOr<Time> regulate_time(VM& vm, double hour, double minute, double second, double millisecond, double microsecond, double nanosecond, Overflow overflow)
|
||||
{
|
||||
|
|
|
@ -53,6 +53,7 @@ Time midnight_time_record();
|
|||
Time noon_time_record();
|
||||
TimeDuration difference_time(Time const&, Time const&);
|
||||
ThrowCompletionOr<GC::Ref<PlainTime>> to_temporal_time(VM&, Value item, Value options = js_undefined());
|
||||
ThrowCompletionOr<Time> to_time_record_or_midnight(VM&, Value item);
|
||||
ThrowCompletionOr<Time> regulate_time(VM&, double hour, double minute, double second, double millisecond, double microsecond, double nanosecond, Overflow);
|
||||
bool is_valid_time(double hour, double minute, double second, double millisecond, double microsecond, double nanosecond);
|
||||
Time balance_time(double hour, double minute, double second, double millisecond, double microsecond, double nanosecond);
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
describe("correct behavior", () => {
|
||||
test("length is 1", () => {
|
||||
expect(Temporal.PlainDateTime.prototype.with).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
const plainDateTime = new Temporal.PlainDateTime(1970, 1, 1);
|
||||
const values = [
|
||||
[{ year: 2021 }, new Temporal.PlainDateTime(2021, 1, 1)],
|
||||
[{ year: 2021, month: 7 }, new Temporal.PlainDateTime(2021, 7, 1)],
|
||||
[{ year: 2021, month: 7, day: 6 }, new Temporal.PlainDateTime(2021, 7, 6)],
|
||||
[{ year: 2021, monthCode: "M07", day: 6 }, new Temporal.PlainDateTime(2021, 7, 6)],
|
||||
[
|
||||
{ hour: 18, minute: 14, second: 47 },
|
||||
new Temporal.PlainDateTime(1970, 1, 1, 18, 14, 47),
|
||||
],
|
||||
[
|
||||
{
|
||||
year: 2021,
|
||||
month: 7,
|
||||
day: 6,
|
||||
hour: 18,
|
||||
minute: 14,
|
||||
second: 47,
|
||||
millisecond: 123,
|
||||
microsecond: 456,
|
||||
nanosecond: 789,
|
||||
},
|
||||
new Temporal.PlainDateTime(2021, 7, 6, 18, 14, 47, 123, 456, 789),
|
||||
],
|
||||
];
|
||||
for (const [arg, expected] of values) {
|
||||
expect(plainDateTime.with(arg).equals(expected)).toBeTrue();
|
||||
}
|
||||
|
||||
// Supplying the same values doesn't change the date/time, but still creates a new object
|
||||
const plainDateTimeLike = {
|
||||
year: plainDateTime.year,
|
||||
month: plainDateTime.month,
|
||||
day: plainDateTime.day,
|
||||
hour: plainDateTime.hour,
|
||||
minute: plainDateTime.minute,
|
||||
second: plainDateTime.second,
|
||||
millisecond: plainDateTime.millisecond,
|
||||
microsecond: plainDateTime.microsecond,
|
||||
nanosecond: plainDateTime.nanosecond,
|
||||
};
|
||||
expect(plainDateTime.with(plainDateTimeLike)).not.toBe(plainDateTime);
|
||||
expect(plainDateTime.with(plainDateTimeLike).equals(plainDateTime)).toBeTrue();
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.PlainDateTime object", () => {
|
||||
expect(() => {
|
||||
Temporal.PlainDateTime.prototype.with.call("foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainDateTime");
|
||||
});
|
||||
|
||||
test("argument must be an object", () => {
|
||||
expect(() => {
|
||||
new Temporal.PlainDateTime(1970, 1, 1).with("foo");
|
||||
}).toThrowWithMessage(TypeError, "Object must be a partial Temporal object");
|
||||
expect(() => {
|
||||
new Temporal.PlainDateTime(1970, 1, 1).with(42);
|
||||
}).toThrowWithMessage(TypeError, "Object must be a partial Temporal object");
|
||||
});
|
||||
|
||||
test("argument must have one of 'day', 'hour', 'microsecond', 'millisecond', 'minute', 'month', 'monthCode', 'nanosecond', 'second', 'year'", () => {
|
||||
expect(() => {
|
||||
new Temporal.PlainDateTime(1970, 1, 1).with({});
|
||||
}).toThrowWithMessage(TypeError, "Object must be a partial Temporal object");
|
||||
});
|
||||
|
||||
test("argument must not have 'calendar' or 'timeZone'", () => {
|
||||
expect(() => {
|
||||
new Temporal.PlainDateTime(1970, 1, 1).with({ calendar: {} });
|
||||
}).toThrowWithMessage(TypeError, "Object must be a partial Temporal object");
|
||||
expect(() => {
|
||||
new Temporal.PlainDateTime(1970, 1, 1).with({ timeZone: {} });
|
||||
}).toThrowWithMessage(TypeError, "Object must be a partial Temporal object");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,13 @@
|
|||
describe("correct behavior", () => {
|
||||
test("length is 1", () => {
|
||||
expect(Temporal.PlainDateTime.prototype.withCalendar).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
const calendar = "gregory";
|
||||
const firstPlainDateTime = new Temporal.PlainDateTime(1, 2, 3);
|
||||
expect(firstPlainDateTime.calendarId).not.toBe(calendar);
|
||||
const secondPlainDateTime = firstPlainDateTime.withCalendar(calendar);
|
||||
expect(secondPlainDateTime.calendarId).toBe(calendar);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
describe("correct behavior", () => {
|
||||
test("length is 0", () => {
|
||||
expect(Temporal.PlainDateTime.prototype.withPlainTime).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
const firstPlainDateTime = new Temporal.PlainDateTime(1, 2, 3, 4, 5, 6, 7, 8, 9);
|
||||
const plainTime = new Temporal.PlainTime(10, 11, 12, 13, 14, 15);
|
||||
const secondPlainDateTime = firstPlainDateTime.withPlainTime(plainTime);
|
||||
expect(secondPlainDateTime.year).toBe(1);
|
||||
expect(secondPlainDateTime.month).toBe(2);
|
||||
expect(secondPlainDateTime.day).toBe(3);
|
||||
expect(secondPlainDateTime.hour).toBe(10);
|
||||
expect(secondPlainDateTime.minute).toBe(11);
|
||||
expect(secondPlainDateTime.second).toBe(12);
|
||||
expect(secondPlainDateTime.millisecond).toBe(13);
|
||||
expect(secondPlainDateTime.microsecond).toBe(14);
|
||||
expect(secondPlainDateTime.nanosecond).toBe(15);
|
||||
});
|
||||
});
|
Loading…
Add table
Reference in a new issue