LibJS: Implement Temporal.PlainDate.prototype.toString()

This commit is contained in:
Linus Groh 2021-08-18 21:15:04 +01:00 committed by Andreas Kling
parent 310192f918
commit 402f04c2fc
10 changed files with 164 additions and 0 deletions

View file

@ -80,6 +80,7 @@ namespace JS {
P(byteLength) \
P(byteOffset) \
P(calendar) \
P(calendarName) \
P(call) \
P(callee) \
P(caller) \

View file

@ -195,6 +195,20 @@ Optional<String> to_temporal_rounding_mode(GlobalObject& global_object, Object&
return option.as_string().string();
}
// 13.11 ToShowCalendarOption ( normalizedOptions ), https://tc39.es/proposal-temporal/#sec-temporal-toshowcalendaroption
Optional<String> to_show_calendar_option(GlobalObject& global_object, Object& normalized_options)
{
auto& vm = global_object.vm();
// 1. Return ? GetOption(normalizedOptions, "calendarName", « String », « "auto", "always", "never" », "auto").
auto option = get_option(global_object, normalized_options, vm.names.calendarName, { OptionType::String }, { "auto"sv, "always"sv, "never"sv }, js_string(vm, "auto"sv));
if (vm.exception())
return {};
VERIFY(option.is_string());
return option.as_string().string();
}
// 13.14 ToTemporalRoundingIncrement ( normalizedOptions, dividend, inclusive ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalroundingincrement
u64 to_temporal_rounding_increment(GlobalObject& global_object, Object& normalized_options, Optional<double> dividend, bool inclusive)
{

View file

@ -64,6 +64,7 @@ Object* get_options_object(GlobalObject&, Value options);
Value get_option(GlobalObject&, Object& options, PropertyName const& property, Vector<OptionType> const& types, Vector<StringView> const& values, Value fallback);
Optional<String> to_temporal_overflow(GlobalObject&, Object& normalized_options);
Optional<String> to_temporal_rounding_mode(GlobalObject&, Object& normalized_options, String const& fallback);
Optional<String> to_show_calendar_option(GlobalObject&, Object& normalized_options);
u64 to_temporal_rounding_increment(GlobalObject&, Object& normalized_options, Optional<double> dividend, bool inclusive);
Optional<String> to_smallest_temporal_unit(GlobalObject&, Object& normalized_options, Vector<StringView> const& disallowed_units, Optional<String> fallback);
double constrain_to_range(double x, double minimum, double maximum);

View file

@ -477,6 +477,24 @@ PlainMonthDay* month_day_from_fields(GlobalObject& global_object, Object& calend
return static_cast<PlainMonthDay*>(month_day_object);
}
// 12.1.27 FormatCalendarAnnotation ( id, showCalendar ), https://tc39.es/proposal-temporal/#sec-temporal-formatcalendarannotation
String format_calendar_annotation(StringView id, StringView show_calendar)
{
// 1. Assert: showCalendar is "auto", "always", or "never".
VERIFY(show_calendar == "auto"sv || show_calendar == "always"sv || show_calendar == "never"sv);
// 2. If showCalendar is "never", return the empty String.
if (show_calendar == "never"sv)
return String::empty();
// 3. If showCalendar is "auto" and id is "iso8601", return the empty String.
if (show_calendar == "auto"sv && id == "iso8601"sv)
return String::empty();
// 4. Return the string-concatenation of "[u-ca=", id, and "]".
return String::formatted("[u-ca={}]", id);
}
// 12.1.28 CalendarEquals ( one, two ), https://tc39.es/proposal-temporal/#sec-temporal-calendarequals
bool calendar_equals(GlobalObject& global_object, Object& one, Object& two)
{

View file

@ -52,6 +52,7 @@ Object* get_temporal_calendar_with_iso_default(GlobalObject&, Object&);
PlainDate* date_from_fields(GlobalObject&, Object& calendar, Object& fields, Object& options);
PlainYearMonth* year_month_from_fields(GlobalObject&, Object& calendar, Object& fields, Object* options = nullptr);
PlainMonthDay* month_day_from_fields(GlobalObject& global_object, Object& calendar, Object& fields, Object* options = nullptr);
String format_calendar_annotation(StringView id, StringView show_calendar);
bool calendar_equals(GlobalObject&, Object& one, Object& two);
Object* consolidate_calendars(GlobalObject&, Object& one, Object& two);
bool is_iso_leap_year(i32 year);

View file

@ -332,6 +332,53 @@ ISODate balance_iso_date(double year_, double month_, double day)
return ISODate { .year = year, .month = static_cast<u8>(month), .day = static_cast<u8>(day) };
}
// 3.5.7 PadISOYear ( y ), https://tc39.es/proposal-temporal/#sec-temporal-padisoyear
String pad_iso_year(i32 y)
{
// 1. Assert: y is an integer.
// 2. If y > 999 and y ≤ 9999, then
if (y > 999 && y <= 9999) {
// a. Return y formatted as a four-digit decimal number.
return String::number(y);
}
// 3. If y ≥ 0, let yearSign be "+"; otherwise, let yearSign be "-".
auto year_sign = y >= 0 ? '+' : '-';
// 4. Let year be abs(y), formatted as a six-digit decimal number, padded to the left with zeroes as necessary.
// 5. Return the string-concatenation of yearSign and year.
return String::formatted("{}{:06}", year_sign, abs(y));
}
// 3.5.8 TemporalDateToString ( temporalDate, showCalendar ), https://tc39.es/proposal-temporal/#sec-temporal-temporaldatetostring
Optional<String> temporal_date_to_string(GlobalObject& global_object, PlainDate& temporal_date, StringView show_calendar)
{
auto& vm = global_object.vm();
// 1. Assert: Type(temporalDate) is Object.
// 2. Assert: temporalDate has an [[InitializedTemporalDate]] internal slot.
// 3. Let year be ! PadISOYear(temporalDate.[[ISOYear]]).
auto year = pad_iso_year(temporal_date.iso_year());
// 4. Let month be temporalDate.[[ISOMonth]] formatted as a two-digit decimal number, padded to the left with a zero if necessary.
auto month = String::formatted("{:02}", temporal_date.iso_month());
// 5. Let day be temporalDate.[[ISODay]] formatted as a two-digit decimal number, padded to the left with a zero if necessary.
auto day = String::formatted("{:02}", temporal_date.iso_day());
// 6. Let calendarID be ? ToString(temporalDate.[[Calendar]]).
auto calendar_id = Value(&temporal_date.calendar()).to_string(global_object);
if (vm.exception())
return {};
// 7. Let calendar be ! FormatCalendarAnnotation(calendarID, showCalendar).
auto calendar = format_calendar_annotation(calendar_id, show_calendar);
// 8. Return the string-concatenation of year, the code unit 0x002D (HYPHEN-MINUS), month, the code unit 0x002D (HYPHEN-MINUS), day, and calendar.
return String::formatted("{}-{}-{}{}", year, month, day, calendar);
}
// 3.5.10 CompareISODate ( y1, m1, d1, y2, m2, d2 ), https://tc39.es/proposal-temporal/#sec-temporal-compareisodate
i8 compare_iso_date(i32 year1, u8 month1, u8 day1, i32 year2, u8 month2, u8 day2)
{

View file

@ -45,6 +45,8 @@ 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(double year, double month, double day);
String pad_iso_year(i32 y);
Optional<String> temporal_date_to_string(GlobalObject&, PlainDate&, StringView show_calendar);
i8 compare_iso_date(i32 year1, u8 month1, u8 day1, i32 year2, u8 month2, u8 day2);
}

View file

@ -51,6 +51,7 @@ void PlainDatePrototype::initialize(GlobalObject& global_object)
define_native_function(vm.names.getISOFields, get_iso_fields, 0, attr);
define_native_function(vm.names.withCalendar, with_calendar, 1, attr);
define_native_function(vm.names.equals, equals, 1, attr);
define_native_function(vm.names.toString, to_string, 0, attr);
define_native_function(vm.names.valueOf, value_of, 0, attr);
}
@ -397,6 +398,33 @@ JS_DEFINE_NATIVE_FUNCTION(PlainDatePrototype::equals)
return Value(calendar_equals(global_object, temporal_date->calendar(), other->calendar()));
}
// 3.3.28 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 = typed_this(global_object);
if (vm.exception())
return {};
// 3. Set options to ? GetOptionsObject(options).
auto* options = get_options_object(global_object, vm.argument(0));
if (vm.exception())
return {};
// 4. Let showCalendar be ? ToShowCalendarOption(options).
auto show_calendar = to_show_calendar_option(global_object, *options);
if (vm.exception())
return {};
// 5. Return ? TemporalDateToString(temporalDate, showCalendar).
auto string = temporal_date_to_string(global_object, *temporal_date, *show_calendar);
if (vm.exception())
return {};
return js_string(vm, *string);
}
// 3.3.31 Temporal.PlainDate.prototype.valueOf ( ), https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.valueof
JS_DEFINE_NATIVE_FUNCTION(PlainDatePrototype::value_of)
{

View file

@ -37,6 +37,7 @@ private:
JS_DECLARE_NATIVE_FUNCTION(get_iso_fields);
JS_DECLARE_NATIVE_FUNCTION(with_calendar);
JS_DECLARE_NATIVE_FUNCTION(equals);
JS_DECLARE_NATIVE_FUNCTION(to_string);
JS_DECLARE_NATIVE_FUNCTION(value_of);
};

View file

@ -0,0 +1,51 @@
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");
plainDate = new Temporal.PlainDate(2021, 7, 6, { toString: () => "foo" });
expect(plainDate.toString()).toBe("2021-07-06[u-ca=foo]");
expect(plainDate.toString({ calendarName: "auto" })).toBe("2021-07-06[u-ca=foo]");
expect(plainDate.toString({ calendarName: "always" })).toBe("2021-07-06[u-ca=foo]");
expect(plainDate.toString({ calendarName: "never" })).toBe("2021-07-06");
plainDate = new Temporal.PlainDate(0, 1, 1);
expect(plainDate.toString()).toBe("+000000-01-01");
plainDate = new Temporal.PlainDate(999, 1, 1);
expect(plainDate.toString()).toBe("+000999-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 a Temporal.PlainDate");
});
test("calendarName option must be one of 'auto', 'always', 'never'", () => {
const plainDate = new Temporal.PlainDate(2021, 7, 6);
expect(() => {
plainDate.toString({ calendarName: "foo" });
}).toThrowWithMessage(RangeError, "foo is not a valid value for option calendarName");
});
});