[Temporal] Add Calendar.prototype.dateFromFields

Also add AO: RegulateISODate, ResolveISOMonth, ISODateFromFields

Spec Text:
https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.datefromfields
https://tc39.es/proposal-temporal/#sec-temporal-regulateisodate
https://tc39.es/proposal-temporal/#sec-temporal-resolveisomonth
https://tc39.es/proposal-temporal/#sec-temporal-isodatefromfields

Note:
This is only the non-intl version. The intl version in
https://tc39.es/proposal-temporal/#sup-temporal.calendar.prototype.datefromfields
will be implemented in later cl.

Bug: v8:11544
Change-Id: I493dc60694421e9908eb5d785fdb8b07fc968699
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3408462
Reviewed-by: Adam Klein <adamk@chromium.org>
Commit-Queue: Frank Tang <ftang@chromium.org>
Cr-Commit-Position: refs/heads/main@{#79793}
This commit is contained in:
Frank Tang 2022-04-05 09:48:40 -07:00 committed by V8 LUCI CQ
parent 908e7ac767
commit dda56765dc
4 changed files with 242 additions and 22 deletions

View File

@ -302,8 +302,6 @@ TO_BE_IMPLEMENTED(TemporalTimeZonePrototypeGetPreviousTransition)
TO_BE_IMPLEMENTED(TemporalTimeZonePrototypeToJSON)
/* Temporal.Calendar */
/* Temporal #sec-temporal.calendar.prototype.datefromfields */
TO_BE_IMPLEMENTED(TemporalCalendarPrototypeDateFromFields)
/* Temporal #sec-temporal.calendar.prototype.yearmonthfromfields */
TO_BE_IMPLEMENTED(TemporalCalendarPrototypeYearMonthFromFields)
/* Temporal #sec-temporal.calendar.prototype.monthdayfromfields */
@ -404,6 +402,17 @@ TO_BE_IMPLEMENTED(TemporalZonedDateTimePrototypeToLocaleString)
JSTemporal##T ::METHOD(isolate, obj, args.atOrUndefined(isolate, 1))); \
}
#define TEMPORAL_PROTOTYPE_METHOD2(T, METHOD, name) \
BUILTIN(Temporal##T##Prototype##METHOD) { \
HandleScope scope(isolate); \
const char* method = "Temporal." #T ".prototype." #name; \
CHECK_RECEIVER(JSTemporal##T, obj, method); \
RETURN_RESULT_OR_FAILURE( \
isolate, \
JSTemporal##T ::METHOD(isolate, obj, args.atOrUndefined(isolate, 1), \
args.atOrUndefined(isolate, 2))); \
}
#define TEMPORAL_PROTOTYPE_METHOD3(T, METHOD, name) \
BUILTIN(Temporal##T##Prototype##METHOD) { \
HandleScope scope(isolate); \
@ -785,6 +794,7 @@ TEMPORAL_GET_BIGINT_AFTER_DIVID(Instant, EpochMicroseconds, nanoseconds, 1000,
// Calendar
TEMPORAL_CONSTRUCTOR1(Calendar)
TEMPORAL_PROTOTYPE_METHOD2(Calendar, DateFromFields, dateFromFields)
TEMPORAL_ID_BY_TO_STRING(Calendar)
TEMPORAL_PROTOTYPE_METHOD1(Calendar, DaysInMonth, daysInMonth)
TEMPORAL_PROTOTYPE_METHOD1(Calendar, DaysInWeek, daysInWeek)

View File

@ -1847,10 +1847,10 @@ MaybeHandle<JSTemporalPlainDate> ToTemporalDate(Isolate* isolate,
// e. Let fieldNames be ? CalendarFields(calendar, « "day", "month",
// "monthCode", "year" »).
Handle<FixedArray> field_names = factory->NewFixedArray(4);
field_names->set(0, *(factory->day_string()));
field_names->set(1, *(factory->month_string()));
field_names->set(2, *(factory->monthCode_string()));
field_names->set(3, *(factory->year_string()));
field_names->set(0, ReadOnlyRoots(isolate).day_string());
field_names->set(1, ReadOnlyRoots(isolate).month_string());
field_names->set(2, ReadOnlyRoots(isolate).monthCode_string());
field_names->set(3, ReadOnlyRoots(isolate).year_string());
ASSIGN_RETURN_ON_EXCEPTION(isolate, field_names,
CalendarFields(isolate, calendar, field_names),
JSTemporalPlainDate);
@ -4856,6 +4856,186 @@ int32_t ToISODayOfWeek(Isolate* isolate, int32_t year, int32_t month,
return weekday == 0 ? 7 : weekday;
}
// #sec-temporal-regulateisodate
Maybe<bool> RegulateISODate(Isolate* isolate, ShowOverflow overflow,
int32_t year, int32_t* month, int32_t* day) {
TEMPORAL_ENTER_FUNC();
// 1. Assert: year, month, and day are integers.
// 2. Assert: overflow is either "constrain" or "reject".
switch (overflow) {
// 3. If overflow is "reject", then
case ShowOverflow::kReject:
// a. If ! IsValidISODate(year, month, day) is false, throw a RangeError
// exception.
if (!IsValidISODate(isolate, year, *month, *day)) {
THROW_NEW_ERROR_RETURN_VALUE(
isolate, NEW_TEMPORAL_INVALD_ARG_RANGE_ERROR(), Nothing<bool>());
}
// b. Return the Record { [[Year]]: year, [[Month]]: month, [[Day]]: day
// }.
return Just(true);
// 4. If overflow is "constrain", then
case ShowOverflow::kConstrain:
// a. Set month to ! ConstrainToRange(month, 1, 12).
*month = std::max(std::min(*month, 12), 1);
// b. Set day to ! ConstrainToRange(day, 1, ! ISODaysInMonth(year,
// month)).
*day = std::max(std::min(*day, ISODaysInMonth(isolate, year, *month)), 1);
// c. Return the Record { [[Year]]: year, [[Month]]: month, [[Day]]: day
// }.
return Just(true);
}
}
// #sec-temporal-resolveisomonth
Maybe<int32_t> ResolveISOMonth(Isolate* isolate, Handle<JSReceiver> fields) {
Factory* factory = isolate->factory();
// 1. Let month be ? Get(fields, "month").
Handle<Object> month_obj;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, month_obj,
Object::GetPropertyOrElement(isolate, fields, factory->month_string()),
Nothing<int32_t>());
// 2. Let monthCode be ? Get(fields, "monthCode").
Handle<Object> month_code_obj;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, month_code_obj,
Object::GetPropertyOrElement(isolate, fields,
factory->monthCode_string()),
Nothing<int32_t>());
// 3. If monthCode is undefined, then
if (month_code_obj->IsUndefined(isolate)) {
// a. If month is undefined, throw a TypeError exception.
if (month_obj->IsUndefined(isolate)) {
THROW_NEW_ERROR_RETURN_VALUE(
isolate, NEW_TEMPORAL_INVALD_ARG_TYPE_ERROR(), Nothing<int32_t>());
}
// b. Return month.
// Note: In Temporal spec, "month" in fields is always converted by
// ToPositiveInteger inside PrepareTemporalFields before calling
// ResolveISOMonth. Therefore the month_obj is always a positive integer.
DCHECK(month_obj->IsSmi() || month_obj->IsHeapNumber());
return Just(FastD2I(month_obj->Number()));
}
// 4. Assert: Type(monthCode) is String.
DCHECK(month_code_obj->IsString());
Handle<String> month_code;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, month_code,
Object::ToString(isolate, month_code_obj),
Nothing<int32_t>());
// 5. Let monthLength be the length of monthCode.
// 6. If monthLength is not 3, throw a RangeError exception.
if (month_code->length() != 3) {
THROW_NEW_ERROR_RETURN_VALUE(
isolate,
NewRangeError(MessageTemplate::kPropertyValueOutOfRange,
factory->monthCode_string()),
Nothing<int32_t>());
}
// 7. Let numberPart be the substring of monthCode from 1.
// 8. Set numberPart to ! ToIntegerOrInfinity(numberPart).
// 9. If numberPart < 1 or numberPart > 12, throw a RangeError exception.
uint16_t m0 = month_code->Get(0);
uint16_t m1 = month_code->Get(1);
uint16_t m2 = month_code->Get(2);
if (!((m0 == 'M') && ((m1 == '0' && '1' <= m2 && m2 <= '9') ||
(m1 == '1' && '0' <= m2 && m2 <= '2')))) {
THROW_NEW_ERROR_RETURN_VALUE(
isolate,
NewRangeError(MessageTemplate::kPropertyValueOutOfRange,
factory->monthCode_string()),
Nothing<int32_t>());
}
int32_t number_part =
10 * static_cast<int32_t>(m1 - '0') + static_cast<int32_t>(m2 - '0');
// 10. If month is not undefined, and month ≠ numberPart, then
// 11. If ! SameValueNonNumeric(monthCode, ! BuildISOMonthCode(numberPart)) is
// false, then a. Throw a RangeError exception.
// Note: In Temporal spec, "month" in fields is always converted by
// ToPositiveInteger inside PrepareTemporalFields before calling
// ResolveISOMonth. Therefore the month_obj is always a positive integer.
if (!month_obj->IsUndefined() &&
FastD2I(month_obj->Number()) != number_part) {
// a. Throw a RangeError exception.
THROW_NEW_ERROR_RETURN_VALUE(
isolate,
NewRangeError(MessageTemplate::kPropertyValueOutOfRange,
factory->month_string()),
Nothing<int32_t>());
}
// 12. Return numberPart.
return Just(number_part);
}
// #sec-temporal-isodatefromfields
Maybe<bool> ISODateFromFields(Isolate* isolate, Handle<JSReceiver> fields,
Handle<JSReceiver> options,
const char* method_name, int32_t* year,
int32_t* month, int32_t* day) {
Factory* factory = isolate->factory();
// 1. Assert: Type(fields) is Object.
// 2. Let overflow be ? ToTemporalOverflow(options).
Maybe<ShowOverflow> maybe_overflow =
ToTemporalOverflow(isolate, options, method_name);
MAYBE_RETURN(maybe_overflow, Nothing<bool>());
// 3. Set fields to ? PrepareTemporalFields(fields, « "day", "month",
// "monthCode", "year" », «»).
Handle<FixedArray> field_names = factory->NewFixedArray(4);
field_names->set(0, ReadOnlyRoots(isolate).day_string());
field_names->set(1, ReadOnlyRoots(isolate).month_string());
field_names->set(2, ReadOnlyRoots(isolate).monthCode_string());
field_names->set(3, ReadOnlyRoots(isolate).year_string());
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, fields,
PrepareTemporalFields(isolate, fields, field_names,
RequiredFields::kNone),
Nothing<bool>());
// 4. Let year be ? Get(fields, "year").
Handle<Object> year_obj;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, year_obj,
Object::GetPropertyOrElement(isolate, fields, factory->year_string()),
Nothing<bool>());
// 5. If year is undefined, throw a TypeError exception.
if (year_obj->IsUndefined(isolate)) {
THROW_NEW_ERROR_RETURN_VALUE(isolate, NEW_TEMPORAL_INVALD_ARG_TYPE_ERROR(),
Nothing<bool>());
}
// Note: "year" in fields is always converted by
// ToIntegerThrowOnInfinity inside the PrepareTemporalFields above.
// Therefore the year_obj is always an integer.
DCHECK(year_obj->IsSmi() || year_obj->IsHeapNumber());
*year = FastD2I(year_obj->Number());
// 6. Let month be ? ResolveISOMonth(fields).
Maybe<int32_t> maybe_month = ResolveISOMonth(isolate, fields);
MAYBE_RETURN(maybe_month, Nothing<bool>());
*month = maybe_month.FromJust();
// 7. Let day be ? Get(fields, "day").
Handle<Object> day_obj;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, day_obj,
Object::GetPropertyOrElement(isolate, fields, factory->day_string()),
Nothing<bool>());
// 8. If day is undefined, throw a TypeError exception.
if (day_obj->IsUndefined(isolate)) {
THROW_NEW_ERROR_RETURN_VALUE(isolate, NEW_TEMPORAL_INVALD_ARG_TYPE_ERROR(),
Nothing<bool>());
}
// Note: "day" in fields is always converted by
// ToIntegerThrowOnInfinity inside the PrepareTemporalFields above.
// Therefore the day_obj is always an integer.
DCHECK(day_obj->IsSmi() || day_obj->IsHeapNumber());
*day = FastD2I(day_obj->Number());
// 9. Return ? RegulateISODate(year, month, day, overflow).
return RegulateISODate(isolate, maybe_overflow.FromJust(), *year, month, day);
}
} // namespace
// #sec-temporal.calendar.prototype.daysinyear
@ -5109,6 +5289,47 @@ MaybeHandle<Smi> JSTemporalCalendar::DaysInWeek(
return handle(Smi::FromInt(7), isolate);
}
// #sec-temporal.calendar.prototype.datefromfields
MaybeHandle<JSTemporalPlainDate> JSTemporalCalendar::DateFromFields(
Isolate* isolate, Handle<JSTemporalCalendar> calendar,
Handle<Object> fields_obj, Handle<Object> options_obj) {
// 1. Let calendar be the this value.
// 2. Perform ? RequireInternalSlot(calendar,
// [[InitializedTemporalCalendar]]).
// 3. Assert: calendar.[[Identifier]] is "iso8601".
// 4. If Type(fields) is not Object, throw a TypeError exception.
const char* method_name = "Temporal.Calendar.prototype.dateFromFields";
if (!fields_obj->IsJSReceiver()) {
THROW_NEW_ERROR(isolate,
NewTypeError(MessageTemplate::kCalledOnNonObject,
isolate->factory()->NewStringFromAsciiChecked(
method_name)),
JSTemporalPlainDate);
}
Handle<JSReceiver> fields = Handle<JSReceiver>::cast(fields_obj);
// 5. Set options to ? GetOptionsObject(options).
Handle<JSReceiver> options;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, options, GetOptionsObject(isolate, options_obj, method_name),
JSTemporalPlainDate);
if (calendar->calendar_index() == 0) {
int32_t year;
int32_t month;
int32_t day;
// 6. Let result be ? ISODateFromFields(fields, options).
Maybe<bool> maybe_result = ISODateFromFields(
isolate, fields, options, method_name, &year, &month, &day);
MAYBE_RETURN(maybe_result, Handle<JSTemporalPlainDate>());
DCHECK(maybe_result.FromJust());
// 7. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]],
// result.[[Day]], calendar).
return CreateTemporalDate(isolate, year, month, day, calendar);
}
// TODO(ftang) add intl implementation inside #ifdef V8_INTL_SUPPORT
UNREACHABLE();
}
// #sec-temporal.calendar.prototype.tostring
MaybeHandle<String> JSTemporalCalendar::ToString(
Isolate* isolate, Handle<JSTemporalCalendar> calendar,

View File

@ -92,6 +92,11 @@ class JSTemporalCalendar
Isolate* isolate, Handle<JSTemporalCalendar> calendar,
Handle<Object> temporal_date_like);
// #sec-temporal.calendar.prototype.datefromfields
V8_WARN_UNUSED_RESULT static MaybeHandle<JSTemporalPlainDate> DateFromFields(
Isolate* isolate, Handle<JSTemporalCalendar> calendar,
Handle<Object> fields, Handle<Object> options);
// #sec-temporal.calendar.prototype.tostring
static MaybeHandle<String> ToString(Isolate* isolate,
Handle<JSTemporalCalendar> calendar,

View File

@ -471,15 +471,8 @@
'built-ins/Temporal/Calendar/prototype/dateAdd/throw-range-error-from-ToTemporalDate': [FAIL],
'built-ins/Temporal/Calendar/prototype/dateAdd/throw-range-error-from-ToTemporalDuration': [FAIL],
'built-ins/Temporal/Calendar/prototype/dateAdd/throw-type-error-from-GetOptionsObject': [FAIL],
'built-ins/Temporal/Calendar/prototype/dateFromFields/branding': [FAIL],
'built-ins/Temporal/Calendar/prototype/dateFromFields/fields-not-object': [FAIL],
'built-ins/Temporal/Calendar/prototype/dateFromFields/infinity-throws-rangeerror': [FAIL],
'built-ins/Temporal/Calendar/prototype/dateFromFields/overflow-invalid-string': [FAIL],
'built-ins/Temporal/Calendar/prototype/dateFromFields/overflow-undefined': [FAIL],
'built-ins/Temporal/Calendar/prototype/dateFromFields/overflow-wrong-type': [FAIL],
'built-ins/Temporal/Calendar/prototype/dateFromFields/throws-range-error': [FAIL],
'built-ins/Temporal/Calendar/prototype/dateFromFields/throws-type-error': [FAIL],
'built-ins/Temporal/Calendar/prototype/dateFromFields/throw-type-error-from-GetOptionsObject': [FAIL],
'built-ins/Temporal/Calendar/prototype/dateFromFields/with-year-monthCode-day': [FAIL],
'built-ins/Temporal/Calendar/prototype/dateFromFields/with-year-monthCode-day-need-constrain': [FAIL],
'built-ins/Temporal/Calendar/prototype/dateFromFields/with-year-month-day': [FAIL],
@ -518,23 +511,18 @@
'built-ins/Temporal/Calendar/prototype/day/infinity-throws-rangeerror': [FAIL],
'built-ins/Temporal/Calendar/prototype/day/month-day': [FAIL],
'built-ins/Temporal/Calendar/prototype/dayOfWeek/basic': [FAIL],
'built-ins/Temporal/Calendar/prototype/dayOfWeek/calendar-fields-iterable': [FAIL],
'built-ins/Temporal/Calendar/prototype/dayOfWeek/calendar-temporal-object': [FAIL],
'built-ins/Temporal/Calendar/prototype/dayOfWeek/infinity-throws-rangeerror': [FAIL],
'built-ins/Temporal/Calendar/prototype/dayOfYear/basic': [FAIL],
'built-ins/Temporal/Calendar/prototype/dayOfYear/calendar-fields-iterable': [FAIL],
'built-ins/Temporal/Calendar/prototype/dayOfYear/calendar-temporal-object': [FAIL],
'built-ins/Temporal/Calendar/prototype/dayOfYear/infinity-throws-rangeerror': [FAIL],
'built-ins/Temporal/Calendar/prototype/daysInMonth/basic': [FAIL],
'built-ins/Temporal/Calendar/prototype/daysInMonth/calendar-fields-iterable': [FAIL],
'built-ins/Temporal/Calendar/prototype/daysInMonth/calendar-temporal-object': [FAIL],
'built-ins/Temporal/Calendar/prototype/daysInMonth/infinity-throws-rangeerror': [FAIL],
'built-ins/Temporal/Calendar/prototype/daysInWeek/basic': [FAIL],
'built-ins/Temporal/Calendar/prototype/daysInWeek/calendar-fields-iterable': [FAIL],
'built-ins/Temporal/Calendar/prototype/daysInWeek/calendar-temporal-object': [FAIL],
'built-ins/Temporal/Calendar/prototype/daysInWeek/infinity-throws-rangeerror': [FAIL],
'built-ins/Temporal/Calendar/prototype/daysInYear/basic': [FAIL],
'built-ins/Temporal/Calendar/prototype/daysInYear/calendar-fields-iterable': [FAIL],
'built-ins/Temporal/Calendar/prototype/daysInYear/calendar-temporal-object': [FAIL],
'built-ins/Temporal/Calendar/prototype/daysInYear/infinity-throws-rangeerror': [FAIL],
'built-ins/Temporal/Calendar/prototype/day/string': [FAIL],
@ -548,7 +536,6 @@
'built-ins/Temporal/Calendar/prototype/fields/repeated-throw': [FAIL],
'built-ins/Temporal/Calendar/prototype/fields/reverse': [FAIL],
'built-ins/Temporal/Calendar/prototype/inLeapYear/basic': [FAIL],
'built-ins/Temporal/Calendar/prototype/inLeapYear/calendar-fields-iterable': [FAIL],
'built-ins/Temporal/Calendar/prototype/inLeapYear/calendar-temporal-object': [FAIL],
'built-ins/Temporal/Calendar/prototype/inLeapYear/infinity-throws-rangeerror': [FAIL],
'built-ins/Temporal/Calendar/prototype/mergeFields/arguments-empty-object': [FAIL],
@ -589,7 +576,6 @@
'built-ins/Temporal/Calendar/prototype/month/infinity-throws-rangeerror': [FAIL],
'built-ins/Temporal/Calendar/prototype/month/month-day-throw-type-error': [FAIL],
'built-ins/Temporal/Calendar/prototype/monthsInYear/basic': [FAIL],
'built-ins/Temporal/Calendar/prototype/monthsInYear/calendar-fields-iterable': [FAIL],
'built-ins/Temporal/Calendar/prototype/monthsInYear/calendar-temporal-object': [FAIL],
'built-ins/Temporal/Calendar/prototype/monthsInYear/infinity-throws-rangeerror': [FAIL],
'built-ins/Temporal/Calendar/prototype/month/string': [FAIL],
@ -607,7 +593,6 @@
'built-ins/Temporal/Calendar/prototype/weekOfYear/cross-year': [FAIL],
'built-ins/Temporal/Calendar/prototype/weekOfYear/infinity-throws-rangeerror': [FAIL],
'built-ins/Temporal/Calendar/prototype/year/basic': [FAIL],
'built-ins/Temporal/Calendar/prototype/year/calendar-fields-iterable': [FAIL],
'built-ins/Temporal/Calendar/prototype/year/calendar-temporal-object': [FAIL],
'built-ins/Temporal/Calendar/prototype/year/infinity-throws-rangeerror': [FAIL],
'built-ins/Temporal/Calendar/prototype/yearMonthFromFields/branding': [FAIL],
@ -926,7 +911,6 @@
'built-ins/Temporal/PlainDate/from/argument-plaindate': [FAIL],
'built-ins/Temporal/PlainDate/from/argument-plaindatetime': [FAIL],
'built-ins/Temporal/PlainDate/from/argument-string': [FAIL],
'built-ins/Temporal/PlainDate/from/calendar-fields-iterable': [FAIL],
'built-ins/Temporal/PlainDate/from/calendar-temporal-object': [FAIL],
'built-ins/Temporal/PlainDate/from/infinity-throws-rangeerror': [FAIL],
'built-ins/Temporal/PlainDate/from/limits': [FAIL],