Просмотр исходного кода

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

Linus Groh 4 лет назад
Родитель
Сommit
402f04c2fc

+ 1 - 0
Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h

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

+ 14 - 0
Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp

@@ -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)
 {

+ 1 - 0
Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h

@@ -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);

+ 18 - 0
Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp

@@ -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)
 {

+ 1 - 0
Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h

@@ -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);

+ 47 - 0
Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.cpp

@@ -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)
 {

+ 2 - 0
Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.h

@@ -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);
 
 }

+ 28 - 0
Userland/Libraries/LibJS/Runtime/Temporal/PlainDatePrototype.cpp

@@ -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)
 {

+ 1 - 0
Userland/Libraries/LibJS/Runtime/Temporal/PlainDatePrototype.h

@@ -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);
 };
 

+ 51 - 0
Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainDate/PlainDate.prototype.toString.js

@@ -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");
+    });
+});