LibJS: Implement stringification Temporal.PlainMonthDay prototypes

This commit is contained in:
Timothy Flynn 2024-11-20 14:57:18 -05:00
parent bd6545f93e
commit 2487c26e1b
13 changed files with 232 additions and 1 deletions

View file

@ -84,6 +84,28 @@ ThrowCompletionOr<Overflow> get_temporal_overflow_option(VM& vm, Object const& o
return Overflow::Reject;
}
// 13.10 GetTemporalShowCalendarNameOption ( options ), https://tc39.es/proposal-temporal/#sec-temporal-gettemporalshowcalendarnameoption
ThrowCompletionOr<ShowCalendar> get_temporal_show_calendar_name_option(VM& vm, Object const& options)
{
// 1. Let stringValue be ? GetOption(options, "calendarName", STRING, « "auto", "always", "never", "critical" », "auto").
auto string_value = TRY(get_option(vm, options, vm.names.calendarName, OptionType::String, { "auto"sv, "always"sv, "never"sv, "critical"sv }, "auto"sv));
// 2. If stringValue is "always", return ALWAYS.
if (string_value.as_string().utf8_string_view() == "always"sv)
return ShowCalendar::Always;
// 3. If stringValue is "never", return NEVER.
if (string_value.as_string().utf8_string_view() == "never"sv)
return ShowCalendar::Never;
// 4. If stringValue is "critical", return CRITICAL.
if (string_value.as_string().utf8_string_view() == "critical"sv)
return ShowCalendar::Critical;
// 5. Return AUTO.
return ShowCalendar::Auto;
}
// 13.14 ValidateTemporalRoundingIncrement ( increment, dividend, inclusive ), https://tc39.es/proposal-temporal/#sec-validatetemporalroundingincrement
ThrowCompletionOr<void> validate_temporal_rounding_increment(VM& vm, u64 increment, u64 dividend, bool inclusive)
{

View file

@ -38,6 +38,13 @@ enum class Overflow {
Reject,
};
enum class ShowCalendar {
Auto,
Always,
Never,
Critical,
};
enum class TimeStyle {
Separated,
Unseparated,
@ -144,6 +151,7 @@ struct ParsedISODateTime {
double iso_date_to_epoch_days(double year, double month, double date);
double epoch_days_to_epoch_ms(double day, double time);
ThrowCompletionOr<Overflow> get_temporal_overflow_option(VM&, Object const& options);
ThrowCompletionOr<ShowCalendar> get_temporal_show_calendar_name_option(VM&, Object const& options);
ThrowCompletionOr<void> validate_temporal_rounding_increment(VM&, u64 increment, u64 dividend, bool inclusive);
ThrowCompletionOr<Precision> get_temporal_fractional_second_digits_option(VM&, Object const& options);
SecondsStringPrecision to_seconds_string_precision_record(UnitValue, Precision);

View file

@ -310,6 +310,24 @@ ThrowCompletionOr<ISODate> calendar_month_day_from_fields(VM& vm, StringView cal
return result;
}
// 12.2.13 FormatCalendarAnnotation ( id, showCalendar ), https://tc39.es/proposal-temporal/#sec-temporal-formatcalendarannotation
String format_calendar_annotation(StringView id, ShowCalendar show_calendar)
{
// 1. If showCalendar is NEVER, return the empty String.
if (show_calendar == ShowCalendar::Never)
return String {};
// 2. If showCalendar is AUTO and id is "iso8601", return the empty String.
if (show_calendar == ShowCalendar::Auto && id == "iso8601"sv)
return String {};
// 3. If showCalendar is CRITICAL, let flag be "!"; else, let flag be the empty String.
auto flag = show_calendar == ShowCalendar::Critical ? "!"sv : ""sv;
// 4. Return the string-concatenation of "[", flag, "u-ca=", id, and "]".
return MUST(String::formatted("[{}u-ca={}]", flag, id));
}
// 12.2.15 ISODaysInMonth ( year, month ), https://tc39.es/proposal-temporal/#sec-temporal-isodaysinmonth
u8 iso_days_in_month(double year, double month)
{

View file

@ -100,6 +100,7 @@ ThrowCompletionOr<String> canonicalize_calendar(VM&, StringView id);
Vector<String> const& available_calendars();
ThrowCompletionOr<CalendarFields> prepare_calendar_fields(VM&, StringView calendar, Object const& fields, CalendarFieldList calendar_field_names, CalendarFieldList non_calendar_field_names, CalendarFieldListOrPartial required_field_names);
ThrowCompletionOr<ISODate> calendar_month_day_from_fields(VM&, StringView calendar, CalendarFields, Overflow);
String format_calendar_annotation(StringView id, ShowCalendar);
u8 iso_days_in_month(double year, double month);
YearWeek iso_week_of_year(ISODate const&);
u16 iso_day_of_year(ISODate const&);

View file

@ -84,6 +84,23 @@ bool is_valid_iso_date(double year, double month, double day)
return true;
}
// 3.5.9 PadISOYear ( y ), https://tc39.es/proposal-temporal/#sec-temporal-padisoyear
String pad_iso_year(i32 year)
{
// 1. If y ≥ 0 and y ≤ 9999, then
if (year >= 0 && year <= 9999) {
// a. Return ToZeroPaddedDecimalString(y, 4).
return MUST(String::formatted("{:04}", year));
}
// 2. If y > 0, let yearSign be "+"; otherwise, let yearSign be "-".
auto year_sign = year > 0 ? '+' : '-';
// 3. Let year be ToZeroPaddedDecimalString(abs(y), 6).
// 4. Return the string-concatenation of yearSign and year.
return MUST(String::formatted("{}{:06}", year_sign, abs(year)));
}
// 3.5.11 ISODateWithinLimits ( isoDate ), https://tc39.es/proposal-temporal/#sec-temporal-isodatewithinlimits
bool iso_date_within_limits(ISODate iso_date)
{

View file

@ -25,6 +25,7 @@ struct ISODate {
ISODate create_iso_date_record(double year, double month, double day);
ThrowCompletionOr<ISODate> regulate_iso_date(VM& vm, double year, double month, double day, Overflow overflow);
bool is_valid_iso_date(double year, double month, double day);
String pad_iso_year(i32 year);
bool iso_date_within_limits(ISODate);
}

View file

@ -9,7 +9,6 @@
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Intrinsics.h>
#include <LibJS/Runtime/Realm.h>
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
#include <LibJS/Runtime/Temporal/Calendar.h>
#include <LibJS/Runtime/Temporal/PlainMonthDay.h>
#include <LibJS/Runtime/Temporal/PlainMonthDayConstructor.h>
@ -141,4 +140,31 @@ ThrowCompletionOr<GC::Ref<PlainMonthDay>> create_temporal_month_day(VM& vm, ISOD
return object;
}
// 10.5.3 TemporalMonthDayToString ( monthDay, showCalendar ), https://tc39.es/proposal-temporal/#sec-temporal-temporalmonthdaytostring
String temporal_month_day_to_string(PlainMonthDay const& month_day, ShowCalendar show_calendar)
{
// 1. Let month be ToZeroPaddedDecimalString(monthDay.[[ISODate]].[[Month]], 2).
// 2. Let day be ToZeroPaddedDecimalString(monthDay.[[ISODate]].[[Day]], 2).
// 3. Let result be the string-concatenation of month, the code unit 0x002D (HYPHEN-MINUS), and day.
auto result = MUST(String::formatted("{:02}-{:02}", month_day.iso_date().month, month_day.iso_date().day));
// 4. If showCalendar is one of ALWAYS or CRITICAL, or if monthDay.[[Calendar]] is not "iso8601", then
if (show_calendar == ShowCalendar::Always || show_calendar == ShowCalendar::Critical || month_day.calendar() != "iso8601"sv) {
// a. Let year be PadISOYear(monthDay.[[ISODate]].[[Year]]).
auto year = pad_iso_year(month_day.iso_date().year);
// b. Set result to the string-concatenation of year, the code unit 0x002D (HYPHEN-MINUS), and result.
result = MUST(String::formatted("{}-{}", year, result));
}
// 5. Let calendarString be FormatCalendarAnnotation(monthDay.[[Calendar]], showCalendar).
auto calendar_string = format_calendar_annotation(month_day.calendar(), show_calendar);
// 6. Set result to the string-concatenation of result and calendarString.
result = MUST(String::formatted("{}{}", result, calendar_string));
// 7. Return result.
return result;
}
}

View file

@ -10,6 +10,7 @@
#include <AK/String.h>
#include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
#include <LibJS/Runtime/Temporal/PlainDate.h>
namespace JS::Temporal {
@ -33,5 +34,6 @@ private:
ThrowCompletionOr<GC::Ref<PlainMonthDay>> to_temporal_month_day(VM&, Value item, Value options = js_undefined());
ThrowCompletionOr<GC::Ref<PlainMonthDay>> create_temporal_month_day(VM&, ISODate, String calendar, GC::Ptr<FunctionObject> new_target = {});
String temporal_month_day_to_string(PlainMonthDay const&, ShowCalendar);
}

View file

@ -32,6 +32,11 @@ void PlainMonthDayPrototype::initialize(Realm& realm)
define_native_accessor(realm, vm.names.calendarId, calendar_id_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.monthCode, month_code_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.day, day_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);
}
// 10.3.3 get Temporal.PlainMonthDay.prototype.calendarId, https://tc39.es/proposal-temporal/#sec-get-temporal.plainmonthday.prototype.calendarid
@ -67,4 +72,44 @@ JS_DEFINE_NATIVE_FUNCTION(PlainMonthDayPrototype::day_getter)
return calendar_iso_to_date(month_day->calendar(), month_day->iso_date()).day;
}
// 10.3.8 Temporal.PlainMonthDay.prototype.toString ( [ options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.tostring
JS_DEFINE_NATIVE_FUNCTION(PlainMonthDayPrototype::to_string)
{
// 1. Let monthDay be the this value.
// 2. Perform ? RequireInternalSlot(monthDay, [[InitializedTemporalMonthDay]]).
auto month_day = 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 TemporalMonthDayToString(monthDay, showCalendar).
return PrimitiveString::create(vm, temporal_month_day_to_string(month_day, show_calendar));
}
// 10.3.9 Temporal.PlainMonthDay.prototype.toLocaleString ( [ locales [ , options ] ] ), https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.tolocalestring
// NOTE: This is the minimum toLocaleString implementation for engines without ECMA-402.
JS_DEFINE_NATIVE_FUNCTION(PlainMonthDayPrototype::to_locale_string)
{
// 1. Let monthDay be the this value.
// 2. Perform ? RequireInternalSlot(monthDay, [[InitializedTemporalMonthDay]]).
auto month_day = TRY(typed_this_object(vm));
// 3. Return TemporalMonthDayToString(monthDay, auto).
return PrimitiveString::create(vm, temporal_month_day_to_string(month_day, ShowCalendar::Auto));
}
// 10.3.10 Temporal.PlainMonthDay.prototype.toJSON ( ), https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.tolocalestring
JS_DEFINE_NATIVE_FUNCTION(PlainMonthDayPrototype::to_json)
{
// 1. Let monthDay be the this value.
// 2. Perform ? RequireInternalSlot(monthDay, [[InitializedTemporalMonthDay]]).
auto month_day = TRY(typed_this_object(vm));
// 3. Return TemporalMonthDayToString(monthDay, auto).
return PrimitiveString::create(vm, temporal_month_day_to_string(month_day, ShowCalendar::Auto));
}
}

View file

@ -26,6 +26,9 @@ private:
JS_DECLARE_NATIVE_FUNCTION(calendar_id_getter);
JS_DECLARE_NATIVE_FUNCTION(month_code_getter);
JS_DECLARE_NATIVE_FUNCTION(day_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.PlainMonthDay.prototype.toJSON).toHaveLength(0);
});
test("basic functionality", () => {
let plainMonthDay;
plainMonthDay = new Temporal.PlainMonthDay(7, 6);
expect(plainMonthDay.toJSON()).toBe("07-06");
plainMonthDay = new Temporal.PlainMonthDay(7, 6, "gregory", 2021);
expect(plainMonthDay.toJSON()).toBe("2021-07-06[u-ca=gregory]");
});
});
describe("errors", () => {
test("this value must be a Temporal.PlainMonthDay object", () => {
expect(() => {
Temporal.PlainMonthDay.prototype.toJSON.call("foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainMonthDay");
});
});

View file

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

View file

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