LibJS: Implement stringification Temporal.PlainDate prototypes

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

View file

@ -255,6 +255,25 @@ String pad_iso_year(i32 year)
return MUST(String::formatted("{}{:06}", year_sign, abs(year)));
}
// 3.5.10 TemporalDateToString ( temporalDate, showCalendar ), https://tc39.es/proposal-temporal/#sec-temporal-temporaldatetostring
String temporal_date_to_string(PlainDate const& temporal_date, ShowCalendar show_calendar)
{
// 1. Let year be PadISOYear(temporalDate.[[ISODate]].[[Year]]).
auto year = pad_iso_year(temporal_date.iso_date().year);
// 2. Let month be ToZeroPaddedDecimalString(temporalDate.[[ISODate]].[[Month]], 2).
auto month = temporal_date.iso_date().month;
// 3. Let day be ToZeroPaddedDecimalString(temporalDate.[[ISODate]].[[Day]], 2).
auto day = temporal_date.iso_date().day;
// 4. Let calendar be FormatCalendarAnnotation(temporalDate.[[Calendar]], showCalendar).
auto calendar = format_calendar_annotation(temporal_date.calendar(), show_calendar);
// 5. Return the string-concatenation of year, the code unit 0x002D (HYPHEN-MINUS), month, the code unit 0x002D (HYPHEN-MINUS), day, and calendar.
return MUST(String::formatted("{}-{:02}-{:02}{}", year, month, day, calendar));
}
// 3.5.11 ISODateWithinLimits ( isoDate ), https://tc39.es/proposal-temporal/#sec-temporal-isodatewithinlimits
bool iso_date_within_limits(ISODate iso_date)
{

View file

@ -48,6 +48,7 @@ ThrowCompletionOr<ISODate> regulate_iso_date(VM& vm, double year, double month,
bool is_valid_iso_date(double year, double month, double day);
ISODate balance_iso_date(double year, double month, double day);
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);

View file

@ -6,6 +6,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
#include <LibJS/Runtime/Temporal/Calendar.h>
#include <LibJS/Runtime/Temporal/PlainDatePrototype.h>
@ -44,6 +45,11 @@ void PlainDatePrototype::initialize(Realm& realm)
define_native_accessor(realm, vm.names.daysInYear, days_in_year_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);
u8 attr = Attribute::Writable | Attribute::Configurable;
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);
}
// 3.3.3 get Temporal.PlainDate.prototype.calendarId, https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.calendarid
@ -174,4 +180,44 @@ JS_DEFINE_NATIVE_FUNCTION(PlainDatePrototype::year_of_week_getter)
return *result;
}
// 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)
{
// 1. Let temporalDate be the this value.
// 2. Perform ? RequireInternalSlot(temporalDate, [[InitializedTemporalDate]]).
auto temporal_date = TRY(typed_this_object(vm));
// 3. Let resolvedOptions be ? GetOptionsObject(options).
auto resolved_options = TRY(get_options_object(vm, vm.argument(0)));
// 4. Let showCalendar be ? GetTemporalShowCalendarNameOption(resolvedOptions).
auto show_calendar = TRY(get_temporal_show_calendar_name_option(vm, resolved_options));
// 5. Return TemporalDateToString(temporalDate, showCalendar).
return PrimitiveString::create(vm, temporal_date_to_string(temporal_date, show_calendar));
}
// 3.3.31 Temporal.PlainDate.prototype.toLocaleString ( [ locales [ , options ] ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.tolocalestring
// NOTE: This is the minimum toLocaleString implementation for engines without ECMA-402.
JS_DEFINE_NATIVE_FUNCTION(PlainDatePrototype::to_locale_string)
{
// 1. Let temporalDate be the this value.
// 2. Perform ? RequireInternalSlot(temporalDate, [[InitializedTemporalDate]]).
auto temporal_date = TRY(typed_this_object(vm));
// 3. Return TemporalDateToString(temporalDate, AUTO).
return PrimitiveString::create(vm, temporal_date_to_string(temporal_date, ShowCalendar::Auto));
}
// 3.3.32 Temporal.PlainDate.prototype.toJSON ( ), https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.tojson
JS_DEFINE_NATIVE_FUNCTION(PlainDatePrototype::to_json)
{
// 1. Let temporalDate be the this value.
// 2. Perform ? RequireInternalSlot(temporalDate, [[InitializedTemporalDate]]).
auto temporal_date = TRY(typed_this_object(vm));
// 3. Return TemporalDateToString(temporalDate, AUTO).
return PrimitiveString::create(vm, temporal_date_to_string(temporal_date, ShowCalendar::Auto));
}
}

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(to_string);
JS_DECLARE_NATIVE_FUNCTION(to_locale_string);
JS_DECLARE_NATIVE_FUNCTION(to_json);
};
}

View file

@ -0,0 +1,23 @@
describe("correct behavior", () => {
test("length is 0", () => {
expect(Temporal.PlainDate.prototype.toJSON).toHaveLength(0);
});
test("basic functionality", () => {
let plainDate;
plainDate = new Temporal.PlainDate(2021, 7, 6);
expect(plainDate.toJSON()).toBe("2021-07-06");
plainDate = new Temporal.PlainDate(2021, 7, 6, "gregory");
expect(plainDate.toJSON()).toBe("2021-07-06[u-ca=gregory]");
});
});
describe("errors", () => {
test("this value must be a Temporal.PlainDate object", () => {
expect(() => {
Temporal.PlainDate.prototype.toJSON.call("foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainDate");
});
});

View file

@ -0,0 +1,23 @@
describe("correct behavior", () => {
test("length is 0", () => {
expect(Temporal.PlainDate.prototype.toLocaleString).toHaveLength(0);
});
test("basic functionality", () => {
let plainDate;
plainDate = new Temporal.PlainDate(2021, 7, 6);
expect(plainDate.toLocaleString()).toBe("2021-07-06");
plainDate = new Temporal.PlainDate(2021, 7, 6, "gregory");
expect(plainDate.toLocaleString()).toBe("2021-07-06[u-ca=gregory]");
});
});
describe("errors", () => {
test("this value must be a Temporal.PlainDate object", () => {
expect(() => {
Temporal.PlainDate.prototype.toLocaleString.call("foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainDate");
});
});

View file

@ -0,0 +1,56 @@
describe("correct behavior", () => {
test("length is 0", () => {
expect(Temporal.PlainDate.prototype.toString).toHaveLength(0);
});
test("basic functionality", () => {
let plainDate;
plainDate = new Temporal.PlainDate(2021, 7, 6);
expect(plainDate.toString()).toBe("2021-07-06");
expect(plainDate.toString({ calendarName: "auto" })).toBe("2021-07-06");
expect(plainDate.toString({ calendarName: "always" })).toBe("2021-07-06[u-ca=iso8601]");
expect(plainDate.toString({ calendarName: "never" })).toBe("2021-07-06");
expect(plainDate.toString({ calendarName: "critical" })).toBe("2021-07-06[!u-ca=iso8601]");
plainDate = new Temporal.PlainDate(2021, 7, 6, "gregory");
expect(plainDate.toString()).toBe("2021-07-06[u-ca=gregory]");
expect(plainDate.toString({ calendarName: "auto" })).toBe("2021-07-06[u-ca=gregory]");
expect(plainDate.toString({ calendarName: "always" })).toBe("2021-07-06[u-ca=gregory]");
expect(plainDate.toString({ calendarName: "never" })).toBe("2021-07-06");
expect(plainDate.toString({ calendarName: "critical" })).toBe("2021-07-06[!u-ca=gregory]");
plainDate = new Temporal.PlainDate(0, 1, 1);
expect(plainDate.toString()).toBe("0000-01-01");
plainDate = new Temporal.PlainDate(999, 1, 1);
expect(plainDate.toString()).toBe("0999-01-01");
plainDate = new Temporal.PlainDate(9999, 1, 1);
expect(plainDate.toString()).toBe("9999-01-01");
plainDate = new Temporal.PlainDate(12345, 1, 1);
expect(plainDate.toString()).toBe("+012345-01-01");
plainDate = new Temporal.PlainDate(123456, 1, 1);
expect(plainDate.toString()).toBe("+123456-01-01");
plainDate = new Temporal.PlainDate(-12345, 1, 1);
expect(plainDate.toString()).toBe("-012345-01-01");
});
});
describe("errors", () => {
test("this value must be a Temporal.PlainDate object", () => {
expect(() => {
Temporal.PlainDate.prototype.toString.call("foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainDate");
});
test("calendarName option must be one of 'auto', 'always', 'never', 'critical'", () => {
const plainDate = new Temporal.PlainDate(2021, 7, 6);
expect(() => {
plainDate.toString({ calendarName: "foo" });
}).toThrowWithMessage(RangeError, "foo is not a valid value for option calendarName");
});
});