diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index ac33bcab6c..7bf829fd98 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -136,6 +136,7 @@ namespace JS { P(exec) \ P(exp) \ P(expm1) \ + P(fields) \ P(fill) \ P(filter) \ P(finally) \ diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp index 4080da7961..cb959dcd06 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -15,6 +16,64 @@ namespace JS::Temporal { +static Optional to_option_type(Value value) +{ + if (value.is_boolean()) + return OptionType::Boolean; + if (value.is_string()) + return OptionType::String; + if (value.is_number()) + return OptionType::Number; + return {}; +} + +// 13.1 IterableToListOfType ( items, elementTypes ), https://tc39.es/proposal-temporal/#sec-iterabletolistoftype +MarkedValueList iterable_to_list_of_type(GlobalObject& global_object, Value items, Vector const& element_types) +{ + auto& vm = global_object.vm(); + auto& heap = global_object.heap(); + + // 1. Let iteratorRecord be ? GetIterator(items, sync). + auto iterator_record = get_iterator(global_object, items, IteratorHint::Sync); + if (vm.exception()) + return MarkedValueList { heap }; + + // 2. Let values be a new empty List. + MarkedValueList values(heap); + + // 3. Let next be true. + auto next = true; + // 4. Repeat, while next is not false, + while (next) { + // a. Set next to ? IteratorStep(iteratorRecord). + auto* iterator_result = iterator_step(global_object, *iterator_record); + if (vm.exception()) + return MarkedValueList { heap }; + next = iterator_result; + + // b. If next is not false, then + if (next) { + // i. Let nextValue be ? IteratorValue(next). + auto next_value = iterator_value(global_object, *iterator_result); + if (vm.exception()) + return MarkedValueList { heap }; + // ii. If Type(nextValue) is not an element of elementTypes, then + if (auto type = to_option_type(next_value); !type.has_value() || !element_types.contains_slow(*type)) { + // 1. Let completion be ThrowCompletion(a newly created TypeError object). + vm.throw_exception(global_object, ErrorType::FixmeAddAnErrorString); + // 2. Return ? IteratorClose(iteratorRecord, completion). + iterator_close(*iterator_record); + return MarkedValueList { heap }; + } + // iii. Append nextValue to the end of the List values. + values.append(next_value); + } + } + + // 5. Return values. + return values; +} + // 13.2 GetOptionsObject ( options ), https://tc39.es/proposal-temporal/#sec-getoptionsobject Object* get_options_object(GlobalObject& global_object, Value options) { @@ -37,17 +96,6 @@ Object* get_options_object(GlobalObject& global_object, Value options) return {}; } -static Optional to_option_type(Value value) -{ - if (value.is_boolean()) - return OptionType::Boolean; - if (value.is_string()) - return OptionType::String; - if (value.is_number()) - return OptionType::Number; - return {}; -} - // 13.3 GetOption ( options, property, types, values, fallback ), https://tc39.es/proposal-temporal/#sec-getoption Value get_option(GlobalObject& global_object, Object& options, String const& property, Vector const& types, Vector const& values, Value fallback) { @@ -464,6 +512,26 @@ Optional parse_temporal_calendar_string([[maybe_unused]] GlobalObject& g return id_part.value(); } +// 13.38 ParseTemporalDateString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldatestring +Optional parse_temporal_date_string(GlobalObject& global_object, String const& iso_string) +{ + auto& vm = global_object.vm(); + + // 1. Assert: Type(isoString) is String. + + // 2. If isoString does not satisfy the syntax of a TemporalDateString (see 13.33), then + // a. Throw a RangeError exception. + // TODO + + // 3. Let result be ? ParseISODateTime(isoString). + auto result = parse_iso_date_time(global_object, iso_string); + if (vm.exception()) + return {}; + + // 4. Return the new Record { [[Year]]: result.[[Year]], [[Month]]: result.[[Month]], [[Day]]: result.[[Day]], [[Calendar]]: result.[[Calendar]] }. + return TemporalDate { .year = result->year, .month = result->month, .day = result->day, .calendar = move(result->calendar) }; +} + // 13.40 ParseTemporalDurationString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldurationstring Optional parse_temporal_duration_string(GlobalObject& global_object, String const& iso_string) { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h index 5b32fd5f83..7734db1bef 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h @@ -59,6 +59,7 @@ struct TemporalTimeZone { Optional name; }; +MarkedValueList iterable_to_list_of_type(GlobalObject&, Value items, Vector const& element_types); Object* get_options_object(GlobalObject&, Value options); Value get_option(GlobalObject&, Object& options, String const& property, Vector const& types, Vector const& values, Value fallback); Optional to_temporal_overflow(GlobalObject&, Object& normalized_options); @@ -70,6 +71,7 @@ BigInt* round_number_to_increment(GlobalObject&, BigInt const&, u64 increment, S Optional parse_iso_date_time(GlobalObject&, String const& iso_string); Optional parse_temporal_instant_string(GlobalObject&, String const& iso_string); Optional parse_temporal_calendar_string(GlobalObject&, String const& iso_string); +Optional parse_temporal_date_string(GlobalObject&, String const& iso_string); Optional parse_temporal_duration_string(GlobalObject&, String const& iso_string); Optional parse_temporal_time_zone_string(GlobalObject&, String const& iso_string); double to_positive_integer_or_infinity(GlobalObject&, Value argument); diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp index 322552102d..ee75aed581 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -82,6 +83,41 @@ Calendar* get_iso8601_calendar(GlobalObject& global_object) return get_builtin_calendar(global_object, "iso8601"); } +// 12.1.5 CalendarFields ( calendar, fieldNames ), https://tc39.es/proposal-temporal/#sec-temporal-calendarfields +Vector calendar_fields(GlobalObject& global_object, Object& calendar, Vector const& field_names) +{ + auto& vm = global_object.vm(); + + // 1. Let fields be ? GetMethod(calendar, "fields"). + auto fields = Value(&calendar).get_method(global_object, vm.names.fields); + if (vm.exception()) + return {}; + + // 2. Let fieldsArray be ! CreateArrayFromList(fieldNames). + Vector field_names_values; + for (auto& field_name : field_names) + field_names_values.append(js_string(vm, field_name)); + Value fields_array = Array::create_from(global_object, field_names_values); + + // 3. If fields is not undefined, then + if (fields) { + // a. Set fieldsArray to ? Call(fields, calendar, « fieldsArray »). + fields_array = vm.call(*fields, &calendar, fields_array); + if (vm.exception()) + return {}; + } + + // 4. Return ? IterableToListOfType(fieldsArray, « String »). + auto list = iterable_to_list_of_type(global_object, fields_array, { OptionType::String }); + if (vm.exception()) + return {}; + + Vector result; + for (auto& value : list) + result.append(value.as_string().string()); + return result; +} + // 12.1.21 ToTemporalCalendar ( temporalCalendarLike ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalcalendar Object* to_temporal_calendar(GlobalObject& global_object, Value temporal_calendar_like) { @@ -148,6 +184,53 @@ Object* to_temporal_calendar_with_iso_default(GlobalObject& global_object, Value return to_temporal_calendar(global_object, temporal_calendar_like); } +// 12.1.23 GetTemporalCalendarWithISODefault ( item ), https://tc39.es/proposal-temporal/#sec-temporal-gettemporalcalendarwithisodefault +Object* get_temporal_calendar_with_iso_default(GlobalObject& global_object, Object& item) +{ + auto& vm = global_object.vm(); + + // 1. If item has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], [[InitializedTemporalTime]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, then + // TODO: The rest of the Temporal built-ins + if (is(item)) { + // a. Return item.[[Calendar]]. + return &static_cast(item).calendar(); + } + + // 2. Let calendar be ? Get(item, "calendar"). + auto calendar = item.get(vm.names.calendar); + if (vm.exception()) + return {}; + + // 3. Return ? ToTemporalCalendarWithISODefault(calendar). + return to_temporal_calendar_with_iso_default(global_object, calendar); +} + +// 12.1.24 DateFromFields ( calendar, fields, options ), https://tc39.es/proposal-temporal/#sec-temporal-datefromfields +PlainDate* date_from_fields(GlobalObject& global_object, Object& calendar, Object& fields, Object& options) +{ + auto& vm = global_object.vm(); + + // 1. Assert: Type(calendar) is Object. + // 2. Assert: Type(fields) is Object. + + // 3. Let date be ? Invoke(calendar, "dateFromFields", « fields, options »). + auto date = calendar.invoke(vm.names.dateFromFields, &fields, &options); + if (vm.exception()) + return {}; + + // 4. Perform ? RequireInternalSlot(date, [[InitializedTemporalDate]]). + auto* date_object = date.to_object(global_object); + if (!date_object) + return {}; + if (!is(date_object)) { + vm.throw_exception(global_object, ErrorType::NotA, "Temporal.PlainDate"); + return {}; + } + + // 5. Return date. + return static_cast(date_object); +} + // 12.1.30 IsISOLeapYear ( year ), https://tc39.es/proposal-temporal/#sec-temporal-isisoleapyear bool is_iso_leap_year(i32 year) { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h index 1bf37652f2..fd40bcc531 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h @@ -33,8 +33,11 @@ Calendar* create_temporal_calendar(GlobalObject&, String const& identifier, Func bool is_builtin_calendar(String const& identifier); Calendar* get_builtin_calendar(GlobalObject&, String const& identifier); Calendar* get_iso8601_calendar(GlobalObject&); +Vector calendar_fields(GlobalObject&, Object& calendar, Vector const& field_names); Object* to_temporal_calendar(GlobalObject&, Value); Object* to_temporal_calendar_with_iso_default(GlobalObject&, Value); +Object* get_temporal_calendar_with_iso_default(GlobalObject&, Object&); +PlainDate* date_from_fields(GlobalObject&, Object& calendar, Object& fields, Object& options); bool is_iso_leap_year(i32 year); i32 iso_days_in_month(i32 year, i32 month); String build_iso_month_code(i32 month); diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.cpp index 1b59f6283b..2343d95149 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.cpp @@ -66,6 +66,82 @@ PlainDate* create_temporal_date(GlobalObject& global_object, i32 iso_year, i32 i return object; } +// 3.5.2 ToTemporalDate ( item [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaldate +PlainDate* to_temporal_date(GlobalObject& global_object, Value item, Object* options) +{ + auto& vm = global_object.vm(); + + // 1. If options is not present, set options to ! OrdinaryObjectCreate(null). + if (!options) + options = Object::create(global_object, nullptr); + + // 2. Assert: Type(options) is Object. + + // 3. If Type(item) is Object, then + if (item.is_object()) { + auto& item_object = item.as_object(); + // a. If item has an [[InitializedTemporalDate]] internal slot, then + if (is(item_object)) { + // i. Return item. + return static_cast(&item_object); + } + + // b. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then + // i. Let instant be ! CreateTemporalInstant(item.[[Nanoseconds]]). + // ii. Let plainDateTime be ? BuiltinTimeZoneGetPlainDateTimeFor(item.[[TimeZone]], instant, item.[[Calendar]]). + // iii. Return ! CreateTemporalDate(plainDateTime.[[ISOYear]], plainDateTime.[[ISOMonth]], plainDateTime.[[ISODay]], plainDateTime.[[Calendar]]). + // TODO + + // c. If item has an [[InitializedTemporalDateTime]] internal slot, then + // i. Return ! CreateTemporalDate(item.[[ISOYear]], item.[[ISOMonth]], item.[[ISODay]], item.[[Calendar]]). + // TODO + + // d. Let calendar be ? GetTemporalCalendarWithISODefault(item). + auto* calendar = get_temporal_calendar_with_iso_default(global_object, item_object); + if (vm.exception()) + return {}; + + // e. Let fieldNames be ? CalendarFields(calendar, « "day", "month", "monthCode", "year" »). + auto field_names = calendar_fields(global_object, *calendar, { "day"sv, "month"sv, "monthCode"sv, "year"sv }); + if (vm.exception()) + return {}; + + // f. Let fields be ? PrepareTemporalFields(item, fieldNames, «»). + auto* fields = prepare_temporal_fields(global_object, item_object, field_names, {}); + if (vm.exception()) + return {}; + + // g. Return ? DateFromFields(calendar, fields, options). + return date_from_fields(global_object, *calendar, *fields, *options); + } + + // 4. Perform ? ToTemporalOverflow(options). + (void)to_temporal_overflow(global_object, *options); + if (vm.exception()) + return {}; + + // 5. Let string be ? ToString(item). + auto string = item.to_string(global_object); + if (vm.exception()) + return {}; + + // 6. Let result be ? ParseTemporalDateString(string). + auto result = parse_temporal_date_string(global_object, string); + if (vm.exception()) + return {}; + + // 7. Assert: ! IsValidISODate(result.[[Year]], result.[[Month]], result.[[Day]]) is true. + VERIFY(is_valid_iso_date(result->year, result->month, result->day)); + + // 8. Let calendar be ? ToTemporalCalendarWithISODefault(result.[[Calendar]]). + auto calendar = to_temporal_calendar_with_iso_default(global_object, result->calendar.has_value() ? js_string(vm, *result->calendar) : js_undefined()); + if (vm.exception()) + return {}; + + // 9. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], calendar). + return create_temporal_date(global_object, result->year, result->month, result->day, *calendar); +} + // 3.5.4 RegulateISODate ( year, month, day, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-regulateisodate Optional regulate_iso_date(GlobalObject& global_object, double year, double month, double day, String const& overflow) { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.h index 1bbae1d0ec..e42046c69f 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.h @@ -35,6 +35,7 @@ private: }; PlainDate* create_temporal_date(GlobalObject&, i32 iso_year, i32 iso_month, i32 iso_day, Object& calendar, FunctionObject* new_target = nullptr); +PlainDate* to_temporal_date(GlobalObject&, Value item, Object* options = nullptr); Optional regulate_iso_date(GlobalObject&, double year, double month, double day, String const& overflow); bool is_valid_iso_date(i32 year, i32 month, i32 day);