1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 06:17:35 +00:00

LibJS: Allow string as parameter in Temporal's round() / total()

This is a normative change in the Temporal spec.

See: 1f0c586
This commit is contained in:
Linus Groh 2021-11-19 00:50:31 +00:00
parent 93ee922027
commit d0c29c9735
9 changed files with 199 additions and 89 deletions

View file

@ -288,92 +288,107 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::abs)
return TRY(create_temporal_duration(global_object, fabs(duration->years()), fabs(duration->months()), fabs(duration->weeks()), fabs(duration->days()), fabs(duration->hours()), fabs(duration->minutes()), fabs(duration->seconds()), fabs(duration->milliseconds()), fabs(duration->microseconds()), fabs(duration->nanoseconds()))); return TRY(create_temporal_duration(global_object, fabs(duration->years()), fabs(duration->months()), fabs(duration->weeks()), fabs(duration->days()), fabs(duration->hours()), fabs(duration->minutes()), fabs(duration->seconds()), fabs(duration->milliseconds()), fabs(duration->microseconds()), fabs(duration->nanoseconds())));
} }
// 7.3.21 Temporal.Duration.prototype.total ( options ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.total // 7.3.21 Temporal.Duration.prototype.total ( totalOf ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.total
JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::total) JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::total)
{ {
// 1. Let duration be the this value. // 1. Let duration be the this value.
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
auto* duration = TRY(typed_this_object(global_object)); auto* duration = TRY(typed_this_object(global_object));
// 3. If options is undefined, throw a TypeError exception. // 3. If totalOf is undefined, throw a TypeError exception.
if (vm.argument(0).is_undefined()) if (vm.argument(0).is_undefined())
return vm.throw_completion<TypeError>(global_object, ErrorType::TemporalMissingOptionsObject); return vm.throw_completion<TypeError>(global_object, ErrorType::TemporalMissingOptionsObject);
// 4. Set options to ? GetOptionsObject(options). Object* total_of;
auto* options = TRY(get_options_object(global_object, vm.argument(0)));
// 5. Let relativeTo be ? ToRelativeTemporalObject(options). // 4. If Type(totalOf) is String, then
auto relative_to = TRY(to_relative_temporal_object(global_object, *options)); if (vm.argument(0).is_string()) {
// a. Let paramString be totalOf.
// 6. Let unit be ? ToTemporalDurationTotalUnit(options). // b. Set totalOf to ! OrdinaryObjectCreate(null).
auto unit = TRY(to_temporal_duration_total_unit(global_object, *options)); total_of = Object::create(global_object, nullptr);
// 7. Let unbalanceResult be ? UnbalanceDurationRelative(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], unit, relativeTo). // c. Perform ! CreateDataPropertyOrThrow(totalOf, "unit", paramString).
MUST(total_of->create_data_property_or_throw(vm.names.unit, vm.argument(0)));
}
// 5. Else,
else {
// a. Set totalOf to ? GetOptionsObject(totalOf).
total_of = TRY(get_options_object(global_object, vm.argument(0)));
}
// 6. Let relativeTo be ? ToRelativeTemporalObject(totalOf).
auto relative_to = TRY(to_relative_temporal_object(global_object, *total_of));
// 7. Let unit be ? ToTemporalDurationTotalUnit(totalOf).
auto unit = TRY(to_temporal_duration_total_unit(global_object, *total_of));
// 8. Let unbalanceResult be ? UnbalanceDurationRelative(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], unit, relativeTo).
auto unbalance_result = TRY(unbalance_duration_relative(global_object, duration->years(), duration->months(), duration->weeks(), duration->days(), unit, relative_to)); auto unbalance_result = TRY(unbalance_duration_relative(global_object, duration->years(), duration->months(), duration->weeks(), duration->days(), unit, relative_to));
// 8. Let intermediate be undefined. // 9. Let intermediate be undefined.
ZonedDateTime* intermediate = nullptr; ZonedDateTime* intermediate = nullptr;
// 9. If relativeTo has an [[InitializedTemporalZonedDateTime]] internal slot, then // 10. If relativeTo has an [[InitializedTemporalZonedDateTime]] internal slot, then
if (relative_to.is_object() && is<ZonedDateTime>(relative_to.as_object())) { if (relative_to.is_object() && is<ZonedDateTime>(relative_to.as_object())) {
// a. Set intermediate to ? MoveRelativeZonedDateTime(relativeTo, unbalanceResult.[[Years]], unbalanceResult.[[Months]], unbalanceResult.[[Weeks]], 0). // a. Set intermediate to ? MoveRelativeZonedDateTime(relativeTo, unbalanceResult.[[Years]], unbalanceResult.[[Months]], unbalanceResult.[[Weeks]], 0).
intermediate = TRY(move_relative_zoned_date_time(global_object, static_cast<ZonedDateTime&>(relative_to.as_object()), unbalance_result.years, unbalance_result.months, unbalance_result.weeks, 0)); intermediate = TRY(move_relative_zoned_date_time(global_object, static_cast<ZonedDateTime&>(relative_to.as_object()), unbalance_result.years, unbalance_result.months, unbalance_result.weeks, 0));
} }
// 10. Let balanceResult be ? BalanceDuration(unbalanceResult.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], unit, intermediate). // 11. Let balanceResult be ? BalanceDuration(unbalanceResult.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], unit, intermediate).
auto balance_result = TRY(balance_duration(global_object, unbalance_result.days, duration->hours(), duration->minutes(), duration->seconds(), duration->milliseconds(), duration->microseconds(), *js_bigint(vm, Crypto::SignedBigInteger::create_from(duration->nanoseconds())), unit, intermediate)); auto balance_result = TRY(balance_duration(global_object, unbalance_result.days, duration->hours(), duration->minutes(), duration->seconds(), duration->milliseconds(), duration->microseconds(), *js_bigint(vm, Crypto::SignedBigInteger::create_from(duration->nanoseconds())), unit, intermediate));
// 11. Let roundResult be ? RoundDuration(unbalanceResult.[[Years]], unbalanceResult.[[Months]], unbalanceResult.[[Weeks]], balanceResult.[[Days]], balanceResult.[[Hours]], balanceResult.[[Minutes]], balanceResult.[[Seconds]], balanceResult.[[Milliseconds]], balanceResult.[[Microseconds]], balanceResult.[[Nanoseconds]], 1, unit, "trunc", relativeTo). // 12. Let roundResult be ? RoundDuration(unbalanceResult.[[Years]], unbalanceResult.[[Months]], unbalanceResult.[[Weeks]], balanceResult.[[Days]], balanceResult.[[Hours]], balanceResult.[[Minutes]], balanceResult.[[Seconds]], balanceResult.[[Milliseconds]], balanceResult.[[Microseconds]], balanceResult.[[Nanoseconds]], 1, unit, "trunc", relativeTo).
auto round_result = TRY(round_duration(global_object, unbalance_result.years, unbalance_result.months, unbalance_result.weeks, balance_result.days, balance_result.hours, balance_result.minutes, balance_result.seconds, balance_result.milliseconds, balance_result.microseconds, balance_result.nanoseconds, 1, unit, "trunc"sv, relative_to.is_object() ? &relative_to.as_object() : nullptr)); auto round_result = TRY(round_duration(global_object, unbalance_result.years, unbalance_result.months, unbalance_result.weeks, balance_result.days, balance_result.hours, balance_result.minutes, balance_result.seconds, balance_result.milliseconds, balance_result.microseconds, balance_result.nanoseconds, 1, unit, "trunc"sv, relative_to.is_object() ? &relative_to.as_object() : nullptr));
double whole; double whole;
// 12. If unit is "year", then // 13. If unit is "year", then
if (unit == "year"sv) { if (unit == "year"sv) {
// a. Let whole be roundResult.[[Years]]. // a. Let whole be roundResult.[[Years]].
whole = round_result.years; whole = round_result.years;
} }
// 13. Else if unit is "month", then // 14. Else if unit is "month", then
else if (unit == "month"sv) { else if (unit == "month"sv) {
// a. Let whole be roundResult.[[Months]]. // a. Let whole be roundResult.[[Months]].
whole = round_result.months; whole = round_result.months;
} }
// 14. Else if unit is "week", then // 15. Else if unit is "week", then
else if (unit == "week"sv) { else if (unit == "week"sv) {
// a. Let whole be roundResult.[[Weeks]]. // a. Let whole be roundResult.[[Weeks]].
whole = round_result.weeks; whole = round_result.weeks;
} }
// 15. Else if unit is "day", then // 16. Else if unit is "day", then
else if (unit == "day"sv) { else if (unit == "day"sv) {
// a. Let whole be roundResult.[[Days]]. // a. Let whole be roundResult.[[Days]].
whole = round_result.days; whole = round_result.days;
} }
// 16. Else if unit is "hour", then // 17. Else if unit is "hour", then
else if (unit == "hour"sv) { else if (unit == "hour"sv) {
// a. Let whole be roundResult.[[Hours]]. // a. Let whole be roundResult.[[Hours]].
whole = round_result.hours; whole = round_result.hours;
} }
// 17. Else if unit is "minute", then // 18. Else if unit is "minute", then
else if (unit == "minute"sv) { else if (unit == "minute"sv) {
// a. Let whole be roundResult.[[Minutes]]. // a. Let whole be roundResult.[[Minutes]].
whole = round_result.minutes; whole = round_result.minutes;
} }
// 18. Else if unit is "second", then // 19. Else if unit is "second", then
else if (unit == "second"sv) { else if (unit == "second"sv) {
// a. Let whole be roundResult.[[Seconds]]. // a. Let whole be roundResult.[[Seconds]].
whole = round_result.seconds; whole = round_result.seconds;
} }
// 19. Else if unit is "millisecond", then // 20. Else if unit is "millisecond", then
else if (unit == "millisecond"sv) { else if (unit == "millisecond"sv) {
// a. Let whole be roundResult.[[Milliseconds]]. // a. Let whole be roundResult.[[Milliseconds]].
whole = round_result.milliseconds; whole = round_result.milliseconds;
} }
// 20. Else if unit is "microsecond", then // 21. Else if unit is "microsecond", then
else if (unit == "microsecond"sv) { else if (unit == "microsecond"sv) {
// a. Let whole be roundResult.[[Microseconds]]. // a. Let whole be roundResult.[[Microseconds]].
whole = round_result.microseconds; whole = round_result.microseconds;
} }
// 21. Else, // 22. Else,
else { else {
// a. Assert: unit is "nanosecond". // a. Assert: unit is "nanosecond".
VERIFY(unit == "nanosecond"sv); VERIFY(unit == "nanosecond"sv);
@ -382,7 +397,7 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::total)
whole = round_result.nanoseconds; whole = round_result.nanoseconds;
} }
// 22. Return whole + roundResult.[[Remainder]]. // 23. Return whole + roundResult.[[Remainder]].
return whole + round_result.remainder; return whole + round_result.remainder;
} }

View file

@ -243,24 +243,40 @@ JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::since)
return TRY(create_temporal_duration(global_object, 0, 0, 0, 0, result.hours, result.minutes, result.seconds, result.milliseconds, result.microseconds, result.nanoseconds)); return TRY(create_temporal_duration(global_object, 0, 0, 0, 0, result.hours, result.minutes, result.seconds, result.milliseconds, result.microseconds, result.nanoseconds));
} }
// 8.3.11 Temporal.Instant.prototype.round ( options ), https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.round // 8.3.11 Temporal.Instant.prototype.round ( roundTo ), https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.round
JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::round) JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::round)
{ {
// 1. Let instant be the this value. // 1. Let instant be the this value.
// 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
auto* instant = TRY(typed_this_object(global_object)); auto* instant = TRY(typed_this_object(global_object));
// 3. If options is undefined, then // 3. If roundTo is undefined, then
if (vm.argument(0).is_undefined()) { if (vm.argument(0).is_undefined()) {
// a. Throw a TypeError exception. // a. Throw a TypeError exception.
return vm.throw_completion<TypeError>(global_object, ErrorType::TemporalMissingOptionsObject); return vm.throw_completion<TypeError>(global_object, ErrorType::TemporalMissingOptionsObject);
} }
// 4. Set options to ? GetOptionsObject(options). Object* round_to;
auto* options = TRY(get_options_object(global_object, vm.argument(0)));
// 5. Let smallestUnit be ? ToSmallestTemporalUnit(options, « "year", "month", "week", "day" », undefined). // 4. If Type(roundTo) is String, then
auto smallest_unit_value = TRY(to_smallest_temporal_unit(global_object, *options, { "year"sv, "month"sv, "week"sv, "day"sv }, {})); if (vm.argument(0).is_string()) {
// a. Let paramString be roundTo.
// b. Set roundTo to ! OrdinaryObjectCreate(null).
round_to = Object::create(global_object, nullptr);
// FIXME: "_smallestUnit_" is a spec bug, see https://github.com/tc39/proposal-temporal/pull/1931
// c. Perform ! CreateDataPropertyOrThrow(roundTo, "_smallestUnit_", paramString).
MUST(round_to->create_data_property_or_throw(vm.names.smallestUnit, vm.argument(0)));
}
// 5. Else,
else {
// a. Set roundTo to ? GetOptionsObject(roundTo).
round_to = TRY(get_options_object(global_object, vm.argument(0)));
}
// 6. Let smallestUnit be ? ToSmallestTemporalUnit(roundTo, « "year", "month", "week", "day" », undefined).
auto smallest_unit_value = TRY(to_smallest_temporal_unit(global_object, *round_to, { "year"sv, "month"sv, "week"sv, "day"sv }, {}));
// 6. If smallestUnit is undefined, throw a RangeError exception. // 6. If smallestUnit is undefined, throw a RangeError exception.
if (!smallest_unit_value.has_value()) if (!smallest_unit_value.has_value())
@ -269,8 +285,8 @@ JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::round)
// At this point smallest_unit_value can only be a string // At this point smallest_unit_value can only be a string
auto& smallest_unit = *smallest_unit_value; auto& smallest_unit = *smallest_unit_value;
// 7. Let roundingMode be ? ToTemporalRoundingMode(options, "halfExpand"). // 7. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand").
auto rounding_mode = TRY(to_temporal_rounding_mode(global_object, *options, "halfExpand")); auto rounding_mode = TRY(to_temporal_rounding_mode(global_object, *round_to, "halfExpand"));
double maximum; double maximum;
// 8. If smallestUnit is "hour", then // 8. If smallestUnit is "hour", then
@ -306,8 +322,8 @@ JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::round)
maximum = 86400000000000; maximum = 86400000000000;
} }
// 14. Let roundingIncrement be ? ToTemporalRoundingIncrement(options, maximum, true). // 14. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo, maximum, true).
auto rounding_increment = TRY(to_temporal_rounding_increment(global_object, *options, maximum, true)); auto rounding_increment = TRY(to_temporal_rounding_increment(global_object, *round_to, maximum, true));
// 15. Let roundedNs be ! RoundTemporalInstant(instant.[[Nanoseconds]], roundingIncrement, smallestUnit, roundingMode). // 15. Let roundedNs be ! RoundTemporalInstant(instant.[[Nanoseconds]], roundingIncrement, smallestUnit, roundingMode).
auto* rounded_ns = round_temporal_instant(global_object, instant->nanoseconds(), rounding_increment, smallest_unit, rounding_mode); auto* rounded_ns = round_temporal_instant(global_object, instant->nanoseconds(), rounding_increment, smallest_unit, rounding_mode);

View file

@ -506,42 +506,58 @@ JS_DEFINE_NATIVE_FUNCTION(PlainDateTimePrototype::subtract)
return TRY(create_temporal_date_time(global_object, result.year, result.month, result.day, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond, date_time->calendar())); return TRY(create_temporal_date_time(global_object, result.year, result.month, result.day, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond, date_time->calendar()));
} }
// 5.3.30 Temporal.PlainDateTime.prototype.round ( options ), https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.round // 5.3.30 Temporal.PlainDateTime.prototype.round ( roundTo ), https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.round
JS_DEFINE_NATIVE_FUNCTION(PlainDateTimePrototype::round) JS_DEFINE_NATIVE_FUNCTION(PlainDateTimePrototype::round)
{ {
// 1. Let dateTime be the this value. // 1. Let dateTime be the this value.
// 2. Perform ? RequireInternalSlot(dateTime, [[InitializedTemporalDateTime]]). // 2. Perform ? RequireInternalSlot(dateTime, [[InitializedTemporalDateTime]]).
auto* date_time = TRY(typed_this_object(global_object)); auto* date_time = TRY(typed_this_object(global_object));
// 3. If options is undefined, then // 3. If roundTo is undefined, then
if (vm.argument(0).is_undefined()) { if (vm.argument(0).is_undefined()) {
// a. Throw a TypeError exception. // a. Throw a TypeError exception.
return vm.throw_completion<TypeError>(global_object, ErrorType::TemporalMissingOptionsObject); return vm.throw_completion<TypeError>(global_object, ErrorType::TemporalMissingOptionsObject);
} }
// 4. Set options to ? GetOptionsObject(options). Object* round_to;
auto* options = TRY(get_options_object(global_object, vm.argument(0)));
// 5. Let smallestUnit be ? ToSmallestTemporalUnit(options, « "year", "month", "week" », undefined). // 4. If Type(roundTo) is String, then
auto smallest_unit_value = TRY(to_smallest_temporal_unit(global_object, *options, { "year"sv, "month"sv, "week"sv }, {})); if (vm.argument(0).is_string()) {
// a. Let paramString be roundTo.
// 6. If smallestUnit is undefined, throw a RangeError exception. // b. Set roundTo to ! OrdinaryObjectCreate(null).
round_to = Object::create(global_object, nullptr);
// FIXME: "_smallestUnit_" is a spec bug, see https://github.com/tc39/proposal-temporal/pull/1931
// c. Perform ! CreateDataPropertyOrThrow(roundTo, "_smallestUnit_", paramString).
MUST(round_to->create_data_property_or_throw(vm.names.smallestUnit, vm.argument(0)));
}
// 5. Else,
else {
// a. Set roundTo to ? GetOptionsObject(roundTo).
round_to = TRY(get_options_object(global_object, vm.argument(0)));
}
// 6. Let smallestUnit be ? ToSmallestTemporalUnit(roundTo, « "year", "month", "week" », undefined).
auto smallest_unit_value = TRY(to_smallest_temporal_unit(global_object, *round_to, { "year"sv, "month"sv, "week"sv }, {}));
// 7. If smallestUnit is undefined, throw a RangeError exception.
if (!smallest_unit_value.has_value()) if (!smallest_unit_value.has_value())
return vm.throw_completion<RangeError>(global_object, ErrorType::OptionIsNotValidValue, vm.names.undefined.as_string(), "smallestUnit"); return vm.throw_completion<RangeError>(global_object, ErrorType::OptionIsNotValidValue, vm.names.undefined.as_string(), "smallestUnit");
// NOTE: At this point smallest_unit_value can only be a string // NOTE: At this point smallest_unit_value can only be a string
auto& smallest_unit = *smallest_unit_value; auto& smallest_unit = *smallest_unit_value;
// 7. Let roundingMode be ? ToTemporalRoundingMode(options, "halfExpand"). // 8. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand").
auto rounding_mode = TRY(to_temporal_rounding_mode(global_object, *options, "halfExpand")); auto rounding_mode = TRY(to_temporal_rounding_mode(global_object, *round_to, "halfExpand"));
// 8. Let roundingIncrement be ? ToTemporalDateTimeRoundingIncrement(options, smallestUnit). // 9. Let roundingIncrement be ? ToTemporalDateTimeRoundingIncrement(roundTo, smallestUnit).
auto rounding_increment = TRY(to_temporal_date_time_rounding_increment(global_object, *options, smallest_unit)); auto rounding_increment = TRY(to_temporal_date_time_rounding_increment(global_object, *round_to, smallest_unit));
// 9. Let result be ! RoundISODateTime(dateTime.[[ISOYear]], dateTime.[[ISOMonth]], dateTime.[[ISODay]], dateTime.[[ISOHour]], dateTime.[[ISOMinute]], dateTime.[[ISOSecond]], dateTime.[[ISOMillisecond]], dateTime.[[ISOMicrosecond]], dateTime.[[ISONanosecond]], roundingIncrement, smallestUnit, roundingMode). // 10. Let result be ! RoundISODateTime(dateTime.[[ISOYear]], dateTime.[[ISOMonth]], dateTime.[[ISODay]], dateTime.[[ISOHour]], dateTime.[[ISOMinute]], dateTime.[[ISOSecond]], dateTime.[[ISOMillisecond]], dateTime.[[ISOMicrosecond]], dateTime.[[ISONanosecond]], roundingIncrement, smallestUnit, roundingMode).
auto result = round_iso_date_time(date_time->iso_year(), date_time->iso_month(), date_time->iso_day(), date_time->iso_hour(), date_time->iso_minute(), date_time->iso_second(), date_time->iso_millisecond(), date_time->iso_microsecond(), date_time->iso_nanosecond(), rounding_increment, smallest_unit, rounding_mode); auto result = round_iso_date_time(date_time->iso_year(), date_time->iso_month(), date_time->iso_day(), date_time->iso_hour(), date_time->iso_minute(), date_time->iso_second(), date_time->iso_millisecond(), date_time->iso_microsecond(), date_time->iso_nanosecond(), rounding_increment, smallest_unit, rounding_mode);
// 10. Return ? CreateTemporalDateTime(result.[[Year]], result.[[Month]], result.[[Day]], result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]], dateTime.[[Calendar]]). // 11. Return ? CreateTemporalDateTime(result.[[Year]], result.[[Month]], result.[[Day]], result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]], dateTime.[[Calendar]]).
return TRY(create_temporal_date_time(global_object, result.year, result.month, result.day, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond, date_time->calendar())); return TRY(create_temporal_date_time(global_object, result.year, result.month, result.day, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond, date_time->calendar()));
} }

View file

@ -250,60 +250,76 @@ JS_DEFINE_NATIVE_FUNCTION(PlainTimePrototype::with)
return TRY(create_temporal_time(global_object, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond)); return TRY(create_temporal_time(global_object, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond));
} }
// 4.3.15 Temporal.PlainTime.prototype.round ( options ), https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.round // 4.3.15 Temporal.PlainTime.prototype.round ( roundTo ), https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.round
JS_DEFINE_NATIVE_FUNCTION(PlainTimePrototype::round) JS_DEFINE_NATIVE_FUNCTION(PlainTimePrototype::round)
{ {
// 1. Let temporalTime be the this value. // 1. Let temporalTime be the this value.
// 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]). // 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
auto* temporal_time = TRY(typed_this_object(global_object)); auto* temporal_time = TRY(typed_this_object(global_object));
// 3. If options is undefined, then // 3. If roundTo is undefined, then
if (vm.argument(0).is_undefined()) { if (vm.argument(0).is_undefined()) {
// a. Throw a TypeError exception. // a. Throw a TypeError exception.
return vm.throw_completion<TypeError>(global_object, ErrorType::TemporalMissingOptionsObject); return vm.throw_completion<TypeError>(global_object, ErrorType::TemporalMissingOptionsObject);
} }
// 4. Set options to ? GetOptionsObject(options). Object* round_to;
auto* options = TRY(get_options_object(global_object, vm.argument(0)));
// 5. Let smallestUnit be ? ToSmallestTemporalUnit(options, « "year", "month", "week", "day" », undefined). // 4. If Type(roundTo) is String, then
auto smallest_unit_value = TRY(to_smallest_temporal_unit(global_object, *options, { "year"sv, "month"sv, "week"sv, "day"sv }, {})); if (vm.argument(0).is_string()) {
// a. Let paramString be roundTo.
// 6. If smallestUnit is undefined, throw a RangeError exception. // b. Set roundTo to ! OrdinaryObjectCreate(null).
round_to = Object::create(global_object, nullptr);
// FIXME: "_smallestUnit_" is a spec bug, see https://github.com/tc39/proposal-temporal/pull/1931
// c. Perform ! CreateDataPropertyOrThrow(roundTo, "_smallestUnit_", paramString).
MUST(round_to->create_data_property_or_throw(vm.names.smallestUnit, vm.argument(0)));
}
// 5. Else,
else {
// a. Set roundTo to ? GetOptionsObject(roundTo).
round_to = TRY(get_options_object(global_object, vm.argument(0)));
}
// 6. Let smallestUnit be ? ToSmallestTemporalUnit(roundTo, « "year", "month", "week", "day" », undefined).
auto smallest_unit_value = TRY(to_smallest_temporal_unit(global_object, *round_to, { "year"sv, "month"sv, "week"sv, "day"sv }, {}));
// 7. If smallestUnit is undefined, throw a RangeError exception.
if (!smallest_unit_value.has_value()) if (!smallest_unit_value.has_value())
return vm.throw_completion<RangeError>(global_object, ErrorType::OptionIsNotValidValue, vm.names.undefined.as_string(), "smallestUnit"); return vm.throw_completion<RangeError>(global_object, ErrorType::OptionIsNotValidValue, vm.names.undefined.as_string(), "smallestUnit");
// NOTE: At this point smallest_unit_value can only be a string // NOTE: At this point smallest_unit_value can only be a string
auto& smallest_unit = *smallest_unit_value; auto& smallest_unit = *smallest_unit_value;
// 7. Let roundingMode be ? ToTemporalRoundingMode(options, "halfExpand"). // 8. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand").
auto rounding_mode = TRY(to_temporal_rounding_mode(global_object, *options, "halfExpand")); auto rounding_mode = TRY(to_temporal_rounding_mode(global_object, *round_to, "halfExpand"));
double maximum; double maximum;
// 8. If smallestUnit is "hour", then // 9. If smallestUnit is "hour", then
if (smallest_unit == "hour"sv) { if (smallest_unit == "hour"sv) {
// a. Let maximum be 24. // a. Let maximum be 24.
maximum = 24; maximum = 24;
} }
// 9. Else if smallestUnit is "minute" or "second", then // 10. Else if smallestUnit is "minute" or "second", then
else if (smallest_unit == "minute"sv || smallest_unit == "second"sv) { else if (smallest_unit == "minute"sv || smallest_unit == "second"sv) {
// a. Let maximum be 60. // a. Let maximum be 60.
maximum = 60; maximum = 60;
} }
// 10. Else, // 11. Else,
else { else {
// a. Let maximum be 1000. // a. Let maximum be 1000.
maximum = 1000; maximum = 1000;
} }
// 11. Let roundingIncrement be ? ToTemporalRoundingIncrement(options, maximum, false). // 12. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo, maximum, false).
auto rounding_increment = TRY(to_temporal_rounding_increment(global_object, *options, maximum, false)); auto rounding_increment = TRY(to_temporal_rounding_increment(global_object, *round_to, maximum, false));
// 12. Let result be ! RoundTime(temporalTime.[[ISOHour]], temporalTime.[[ISOMinute]], temporalTime.[[ISOSecond]], temporalTime.[[ISOMillisecond]], temporalTime.[[ISOMicrosecond]], temporalTime.[[ISONanosecond]], roundingIncrement, smallestUnit, roundingMode). // 13. Let result be ! RoundTime(temporalTime.[[ISOHour]], temporalTime.[[ISOMinute]], temporalTime.[[ISOSecond]], temporalTime.[[ISOMillisecond]], temporalTime.[[ISOMicrosecond]], temporalTime.[[ISONanosecond]], roundingIncrement, smallestUnit, roundingMode).
auto result = round_time(temporal_time->iso_hour(), temporal_time->iso_minute(), temporal_time->iso_second(), temporal_time->iso_millisecond(), temporal_time->iso_microsecond(), temporal_time->iso_nanosecond(), rounding_increment, smallest_unit, rounding_mode); auto result = round_time(temporal_time->iso_hour(), temporal_time->iso_minute(), temporal_time->iso_second(), temporal_time->iso_millisecond(), temporal_time->iso_microsecond(), temporal_time->iso_nanosecond(), rounding_increment, smallest_unit, rounding_mode);
// 13. Return ? CreateTemporalTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]). // 14. Return ? CreateTemporalTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]).
return TRY(create_temporal_time(global_object, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond)); return TRY(create_temporal_time(global_object, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond));
} }

View file

@ -942,85 +942,101 @@ JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::subtract)
return MUST(create_temporal_zoned_date_time(global_object, *epoch_nanoseconds, time_zone, calendar)); return MUST(create_temporal_zoned_date_time(global_object, *epoch_nanoseconds, time_zone, calendar));
} }
// 6.3.39 Temporal.ZonedDateTime.prototype.round ( options ), https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.round // 6.3.39 Temporal.ZonedDateTime.prototype.round ( roundTo ), https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.round
JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::round) JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::round)
{ {
// 1. Let zonedDateTime be the this value. // 1. Let zonedDateTime be the this value.
// 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]). // 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]).
auto* zoned_date_time = TRY(typed_this_object(global_object)); auto* zoned_date_time = TRY(typed_this_object(global_object));
// 3. If options is undefined, then // 3. If roundTo is undefined, then
if (vm.argument(0).is_undefined()) { if (vm.argument(0).is_undefined()) {
// a. Throw a TypeError exception. // a. Throw a TypeError exception.
return vm.throw_completion<TypeError>(global_object, ErrorType::TemporalMissingOptionsObject); return vm.throw_completion<TypeError>(global_object, ErrorType::TemporalMissingOptionsObject);
} }
// 4. Set options to ? GetOptionsObject(options). Object* round_to;
auto* options = TRY(get_options_object(global_object, vm.argument(0)));
// 5. Let smallestUnit be ? ToSmallestTemporalUnit(options, « "year", "month", "week" », undefined). // 4. If Type(roundTo) is String, then
auto smallest_unit_value = TRY(to_smallest_temporal_unit(global_object, *options, { "year"sv, "month"sv, "week"sv }, {})); if (vm.argument(0).is_string()) {
// a. Let paramString be roundTo.
// 6. If smallestUnit is undefined, throw a RangeError exception. // b. Set roundTo to ! OrdinaryObjectCreate(null).
round_to = Object::create(global_object, nullptr);
// FIXME: "_smallestUnit_" is a spec bug, see https://github.com/tc39/proposal-temporal/pull/1931
// c. Perform ! CreateDataPropertyOrThrow(roundTo, "_smallestUnit_", paramString).
MUST(round_to->create_data_property_or_throw(vm.names.smallestUnit, vm.argument(0)));
}
// 5. Else,
else {
// a. Set roundTo to ? GetOptionsObject(roundTo).
round_to = TRY(get_options_object(global_object, vm.argument(0)));
}
// 6. Let smallestUnit be ? ToSmallestTemporalUnit(roundTo, « "year", "month", "week" », undefined).
auto smallest_unit_value = TRY(to_smallest_temporal_unit(global_object, *round_to, { "year"sv, "month"sv, "week"sv }, {}));
// 7. If smallestUnit is undefined, throw a RangeError exception.
if (!smallest_unit_value.has_value()) if (!smallest_unit_value.has_value())
return vm.throw_completion<RangeError>(global_object, ErrorType::OptionIsNotValidValue, vm.names.undefined.as_string(), "smallestUnit"); return vm.throw_completion<RangeError>(global_object, ErrorType::OptionIsNotValidValue, vm.names.undefined.as_string(), "smallestUnit");
// NOTE: At this point smallest_unit_value can only be a string // NOTE: At this point smallest_unit_value can only be a string
auto& smallest_unit = *smallest_unit_value; auto& smallest_unit = *smallest_unit_value;
// 7. Let roundingMode be ? ToTemporalRoundingMode(options, "halfExpand"). // 8. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand").
auto rounding_mode = TRY(to_temporal_rounding_mode(global_object, *options, "halfExpand"sv)); auto rounding_mode = TRY(to_temporal_rounding_mode(global_object, *round_to, "halfExpand"sv));
// 8. Let roundingIncrement be ? ToTemporalDateTimeRoundingIncrement(options, smallestUnit). // 9. Let roundingIncrement be ? ToTemporalDateTimeRoundingIncrement(options, smallestUnit).
auto rounding_increment = TRY(to_temporal_date_time_rounding_increment(global_object, *options, smallest_unit)); auto rounding_increment = TRY(to_temporal_date_time_rounding_increment(global_object, *round_to, smallest_unit));
// 9. Let timeZone be zonedDateTime.[[TimeZone]]. // 10. Let timeZone be zonedDateTime.[[TimeZone]].
auto& time_zone = zoned_date_time->time_zone(); auto& time_zone = zoned_date_time->time_zone();
// 10. Let instant be ! CreateTemporalInstant(zonedDateTime.[[Nanoseconds]]). // 11. Let instant be ! CreateTemporalInstant(zonedDateTime.[[Nanoseconds]]).
auto* instant = MUST(create_temporal_instant(global_object, zoned_date_time->nanoseconds())); auto* instant = MUST(create_temporal_instant(global_object, zoned_date_time->nanoseconds()));
// 11. Let calendar be zonedDateTime.[[Calendar]]. // 12. Let calendar be zonedDateTime.[[Calendar]].
auto& calendar = zoned_date_time->calendar(); auto& calendar = zoned_date_time->calendar();
// 12. Let temporalDateTime be ? BuiltinTimeZoneGetPlainDateTimeFor(timeZone, instant, calendar). // 13. Let temporalDateTime be ? BuiltinTimeZoneGetPlainDateTimeFor(timeZone, instant, calendar).
auto* temporal_date_time = TRY(builtin_time_zone_get_plain_date_time_for(global_object, &time_zone, *instant, calendar)); auto* temporal_date_time = TRY(builtin_time_zone_get_plain_date_time_for(global_object, &time_zone, *instant, calendar));
// 13. Let isoCalendar be ! GetISO8601Calendar(). // 14. Let isoCalendar be ! GetISO8601Calendar().
auto* iso_calendar = get_iso8601_calendar(global_object); auto* iso_calendar = get_iso8601_calendar(global_object);
// 14. Let dtStart be ? CreateTemporalDateTime(temporalDateTime.[[ISOYear]], temporalDateTime.[[ISOMonth]], temporalDateTime.[[ISODay]], 0, 0, 0, 0, 0, 0, isoCalendar). // 15. Let dtStart be ? CreateTemporalDateTime(temporalDateTime.[[ISOYear]], temporalDateTime.[[ISOMonth]], temporalDateTime.[[ISODay]], 0, 0, 0, 0, 0, 0, isoCalendar).
auto* dt_start = TRY(create_temporal_date_time(global_object, temporal_date_time->iso_year(), temporal_date_time->iso_month(), temporal_date_time->iso_day(), 0, 0, 0, 0, 0, 0, *iso_calendar)); auto* dt_start = TRY(create_temporal_date_time(global_object, temporal_date_time->iso_year(), temporal_date_time->iso_month(), temporal_date_time->iso_day(), 0, 0, 0, 0, 0, 0, *iso_calendar));
// 15. Let instantStart be ? BuiltinTimeZoneGetInstantFor(timeZone, dtStart, "compatible"). // 16. Let instantStart be ? BuiltinTimeZoneGetInstantFor(timeZone, dtStart, "compatible").
auto* instant_start = TRY(builtin_time_zone_get_instant_for(global_object, &time_zone, *dt_start, "compatible"sv)); auto* instant_start = TRY(builtin_time_zone_get_instant_for(global_object, &time_zone, *dt_start, "compatible"sv));
// 16. Let startNs be instantStart.[[Nanoseconds]]. // 17. Let startNs be instantStart.[[Nanoseconds]].
auto& start_ns = instant_start->nanoseconds(); auto& start_ns = instant_start->nanoseconds();
// 17. Let endNs be ? AddZonedDateTime(startNs, timeZone, zonedDateTime.[[Calendar]], 0, 0, 0, 1, 0, 0, 0, 0, 0, 0). // 18. Let endNs be ? AddZonedDateTime(startNs, timeZone, zonedDateTime.[[Calendar]], 0, 0, 0, 1, 0, 0, 0, 0, 0, 0).
// TODO: Shouldn't `zonedDateTime.[[Calendar]]` be `calendar` for consistency? // TODO: Shouldn't `zonedDateTime.[[Calendar]]` be `calendar` for consistency?
auto* end_ns = TRY(add_zoned_date_time(global_object, start_ns, &time_zone, zoned_date_time->calendar(), 0, 0, 0, 1, 0, 0, 0, 0, 0, 0)); auto* end_ns = TRY(add_zoned_date_time(global_object, start_ns, &time_zone, zoned_date_time->calendar(), 0, 0, 0, 1, 0, 0, 0, 0, 0, 0));
// 18. Let dayLengthNs be (endNs startNs). // 19. Let dayLengthNs be (endNs startNs).
auto day_length_ns = end_ns->big_integer().minus(start_ns.big_integer()).to_double(); auto day_length_ns = end_ns->big_integer().minus(start_ns.big_integer()).to_double();
// 19. If dayLengthNs is 0, then // 20. If dayLengthNs is 0, then
if (day_length_ns == 0) { if (day_length_ns == 0) {
// a. Throw a RangeError exception. // a. Throw a RangeError exception.
return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalZonedDateTimeRoundZeroLengthDay); return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalZonedDateTimeRoundZeroLengthDay);
} }
// 20. Let roundResult be ! RoundISODateTime(temporalDateTime.[[ISOYear]], temporalDateTime.[[ISOMonth]], temporalDateTime.[[ISODay]], temporalDateTime.[[ISOHour]], temporalDateTime.[[ISOMinute]], temporalDateTime.[[ISOSecond]], temporalDateTime.[[ISOMillisecond]], temporalDateTime.[[ISOMicrosecond]], temporalDateTime.[[ISONanosecond]], roundingIncrement, smallestUnit, roundingMode, dayLengthNs). // 21. Let roundResult be ! RoundISODateTime(temporalDateTime.[[ISOYear]], temporalDateTime.[[ISOMonth]], temporalDateTime.[[ISODay]], temporalDateTime.[[ISOHour]], temporalDateTime.[[ISOMinute]], temporalDateTime.[[ISOSecond]], temporalDateTime.[[ISOMillisecond]], temporalDateTime.[[ISOMicrosecond]], temporalDateTime.[[ISONanosecond]], roundingIncrement, smallestUnit, roundingMode, dayLengthNs).
auto round_result = round_iso_date_time(temporal_date_time->iso_year(), temporal_date_time->iso_month(), temporal_date_time->iso_day(), temporal_date_time->iso_hour(), temporal_date_time->iso_minute(), temporal_date_time->iso_second(), temporal_date_time->iso_millisecond(), temporal_date_time->iso_microsecond(), temporal_date_time->iso_nanosecond(), rounding_increment, smallest_unit, rounding_mode, day_length_ns); auto round_result = round_iso_date_time(temporal_date_time->iso_year(), temporal_date_time->iso_month(), temporal_date_time->iso_day(), temporal_date_time->iso_hour(), temporal_date_time->iso_minute(), temporal_date_time->iso_second(), temporal_date_time->iso_millisecond(), temporal_date_time->iso_microsecond(), temporal_date_time->iso_nanosecond(), rounding_increment, smallest_unit, rounding_mode, day_length_ns);
// 21. Let offsetNanoseconds be ? GetOffsetNanosecondsFor(timeZone, instant). // 22. Let offsetNanoseconds be ? GetOffsetNanosecondsFor(timeZone, instant).
auto offset_nanoseconds = TRY(get_offset_nanoseconds_for(global_object, &time_zone, *instant)); auto offset_nanoseconds = TRY(get_offset_nanoseconds_for(global_object, &time_zone, *instant));
// 22. Let epochNanoseconds be ? InterpretISODateTimeOffset(roundResult.[[Year]], roundResult.[[Month]], roundResult.[[Day]], roundResult.[[Hour]], roundResult.[[Minute]], roundResult.[[Second]], roundResult.[[Millisecond]], roundResult.[[Microsecond]], roundResult.[[Nanosecond]], option, offsetNanoseconds, timeZone, "compatible", "prefer", match exactly). // 23. Let epochNanoseconds be ? InterpretISODateTimeOffset(roundResult.[[Year]], roundResult.[[Month]], roundResult.[[Day]], roundResult.[[Hour]], roundResult.[[Minute]], roundResult.[[Second]], roundResult.[[Millisecond]], roundResult.[[Microsecond]], roundResult.[[Nanosecond]], option, offsetNanoseconds, timeZone, "compatible", "prefer", match exactly).
auto* epoch_nanoseconds = TRY(interpret_iso_date_time_offset(global_object, round_result.year, round_result.month, round_result.day, round_result.hour, round_result.minute, round_result.second, round_result.millisecond, round_result.microsecond, round_result.nanosecond, OffsetBehavior::Option, offset_nanoseconds, &time_zone, "compatible"sv, "prefer"sv, MatchBehavior::MatchExactly)); auto* epoch_nanoseconds = TRY(interpret_iso_date_time_offset(global_object, round_result.year, round_result.month, round_result.day, round_result.hour, round_result.minute, round_result.second, round_result.millisecond, round_result.microsecond, round_result.nanosecond, OffsetBehavior::Option, offset_nanoseconds, &time_zone, "compatible"sv, "prefer"sv, MatchBehavior::MatchExactly));
// 23. Return ! CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar). // 24. Return ! CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar).
return MUST(create_temporal_zoned_date_time(global_object, *epoch_nanoseconds, time_zone, calendar)); return MUST(create_temporal_zoned_date_time(global_object, *epoch_nanoseconds, time_zone, calendar));
} }

View file

@ -21,6 +21,13 @@ describe("correct behavior", () => {
}).epochNanoseconds }).epochNanoseconds
).toBe(1800000000000n); ).toBe(1800000000000n);
}); });
test("string argument is implicitly converted to options object", () => {
const instant = new Temporal.Instant(1111111111111n);
expect(
instant.round("second").equals(instant.round({ smallestUnit: "second" }))
).toBeTrue();
});
}); });
describe("errors", () => { describe("errors", () => {

View file

@ -81,6 +81,13 @@ describe("correct behavior", () => {
expect(fifthRoundedPlainDateTime.microsecond).toBe(0); expect(fifthRoundedPlainDateTime.microsecond).toBe(0);
expect(fifthRoundedPlainDateTime.nanosecond).toBe(0); expect(fifthRoundedPlainDateTime.nanosecond).toBe(0);
}); });
test("string argument is implicitly converted to options object", () => {
const plainDateTime = new Temporal.PlainDateTime(2021, 11, 3, 18, 8, 10, 100, 200, 300);
expect(
plainDateTime.round("minute").equals(plainDateTime.round({ smallestUnit: "minute" }))
).toBeTrue();
});
}); });
describe("errors", () => { describe("errors", () => {

View file

@ -61,6 +61,13 @@ describe("correct behavior", () => {
expect(fifthRoundedPlainTime.microsecond).toBe(0); expect(fifthRoundedPlainTime.microsecond).toBe(0);
expect(fifthRoundedPlainTime.nanosecond).toBe(0); expect(fifthRoundedPlainTime.nanosecond).toBe(0);
}); });
test("string argument is implicitly converted to options object", () => {
const plainTime = new Temporal.PlainTime(18, 15, 9, 100, 200, 300);
expect(
plainTime.round("minute").equals(plainTime.round({ smallestUnit: "minute" }))
).toBeTrue();
});
}); });
describe("errors", () => { describe("errors", () => {

View file

@ -29,6 +29,16 @@ describe("correct behavior", () => {
}).epochNanoseconds }).epochNanoseconds
).toBe(1800000000000n); ).toBe(1800000000000n);
}); });
test("string argument is implicitly converted to options object", () => {
const zonedDateTime = new Temporal.ZonedDateTime(
1111111111111n,
new Temporal.TimeZone("UTC")
);
expect(
zonedDateTime.round("second").equals(zonedDateTime.round({ smallestUnit: "second" }))
).toBeTrue();
});
}); });
describe("errors", () => { describe("errors", () => {