1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 20:17:44 +00:00

LibJS: Implement the required AOs for Temporal.Duration.compare

This commit is contained in:
Luke Wilde 2021-11-09 17:19:28 +00:00 committed by Linus Groh
parent 98b9a75591
commit 29072f4b09
9 changed files with 413 additions and 5 deletions

View file

@ -360,6 +360,7 @@ namespace JS {
P(reduceRight) \
P(region) \
P(reject) \
P(relativeTo) \
P(repeat) \
P(resolve) \
P(resolvedOptions) \

View file

@ -225,6 +225,7 @@
M(TemporalInvalidUnitRange, "Invalid unit range, {} is larger than {}") \
M(TemporalInvalidZonedDateTimeOffset, "Invalid offset for the provided date and time in the current time zone") \
M(TemporalMissingOptionsObject, "Required options object is missing or undefined") \
M(TemporalMissingStartingPoint, "A starting point is required for balancing {}") \
M(TemporalObjectMustHaveOneOf, "Object must have at least one of the following properties: {}") \
M(TemporalObjectMustNotHave, "Object must not have a defined {} property") \
M(TemporalPropertyMustBeFinite, "Property must not be Infinity") \

View file

@ -1,6 +1,7 @@
/*
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -514,6 +515,152 @@ ThrowCompletionOr<Optional<String>> to_smallest_temporal_unit(GlobalObject& glob
return smallest_unit;
}
// 13.21 ToRelativeTemporalObject ( options ), https://tc39.es/proposal-temporal/#sec-temporal-torelativetemporalobject
ThrowCompletionOr<Value> to_relative_temporal_object(GlobalObject& global_object, Object const& options)
{
auto& vm = global_object.vm();
// 1. Assert: Type(options) is Object.
// 2. Let value be ? Get(options, "relativeTo").
auto value = TRY(options.get(vm.names.relativeTo));
// 3. If value is undefined, then
if (value.is_undefined()) {
// a. Return value.
return value;
}
// 4. Let offsetBehaviour be option.
auto offset_behavior = OffsetBehavior::Option;
// 5. Let matchBehaviour be match exactly.
auto match_behavior = MatchBehavior::Exactly;
ISODateTime result;
Value offset_string;
Value time_zone;
Object* calendar = nullptr;
// 6. If Type(value) is Object, then
if (value.is_object()) {
auto& value_object = value.as_object();
// a. If value has either an [[InitializedTemporalDateTime]] or [[InitializedTemporalZonedDateTime]] internal slot, then
if (is<PlainDateTime>(value_object) || is<ZonedDateTime>(value_object)) {
// i. Return value.
return value;
}
// b. If value has an [[InitializedTemporalDate]] internal slot, then
if (is<PlainDate>(value_object)) {
auto& plain_date_object = static_cast<PlainDate&>(value_object);
// i. Return ? CreateTemporalDateTime(value.[[ISOYear]], value.[[ISOMonth]], value.[[ISODay]], 0, 0, 0, 0, 0, 0, value.[[Calendar]]).
return TRY(create_temporal_date_time(global_object, plain_date_object.iso_year(), plain_date_object.iso_month(), plain_date_object.iso_day(), 0, 0, 0, 0, 0, 0, plain_date_object.calendar()));
}
// c. Let calendar be ? GetTemporalCalendarWithISODefault(value).
calendar = TRY(get_temporal_calendar_with_iso_default(global_object, value_object));
// d. Let fieldNames be ? CalendarFields(calendar, « "day", "hour", "microsecond", "millisecond", "minute", "month", "monthCode", "nanosecond", "second", "year" »).
auto field_names = TRY(calendar_fields(global_object, *calendar, { "day"sv, "hour"sv, "microsecond"sv, "millisecond"sv, "minute"sv, "month"sv, "monthCode"sv, "nanosecond"sv, "second"sv, "year"sv }));
// e. Let fields be ? PrepareTemporalFields(value, fieldNames, «»).
auto* fields = TRY(prepare_temporal_fields(global_object, value_object, field_names, {}));
// f. Let dateOptions be ! OrdinaryObjectCreate(null).
auto* date_options = Object::create(global_object, nullptr);
// g. Perform ! CreateDataPropertyOrThrow(dateOptions, "overflow", "constrain").
MUST(date_options->create_data_property_or_throw(vm.names.overflow, js_string(vm, "constrain"sv)));
// h. Let result be ? InterpretTemporalDateTimeFields(calendar, fields, dateOptions).
result = TRY(interpret_temporal_date_time_fields(global_object, *calendar, *fields, *date_options));
// i. Let offsetString be ? Get(value, "offset").
offset_string = TRY(value_object.get(vm.names.offset));
// j. Let timeZone be ? Get(value, "timeZone").
time_zone = TRY(value_object.get(vm.names.timeZone));
// k. If offsetString is undefined, then
if (offset_string.is_undefined()) {
// i. Set offsetBehaviour to wall.
offset_behavior = OffsetBehavior::Wall;
}
}
// 7. Else,
else {
// a. Let string be ? ToString(value).
auto string = TRY(value.to_string(global_object));
// b. Let result be ? ParseTemporalRelativeToString(string).
auto parsed_result = TRY(parse_temporal_relative_to_string(global_object, string));
// NOTE: The ISODateTime struct inside `parsed_result` will be moved into `result` at the end of this path to avoid mismatching names.
// Thus, all remaining references to `result` in this path actually refer to `parsed_result`.
// c. Let calendar be ? ToTemporalCalendarWithISODefault(result.[[Calendar]]).
calendar = TRY(to_temporal_calendar_with_iso_default(global_object, parsed_result.date_time.calendar.has_value() ? js_string(vm, *parsed_result.date_time.calendar) : js_undefined()));
// d. Let offsetString be result.[[TimeZoneOffset]].
offset_string = parsed_result.time_zone.offset.has_value() ? js_string(vm, *parsed_result.time_zone.offset) : js_undefined();
// e. Let timeZone be result.[[TimeZoneIANAName]].
time_zone = parsed_result.time_zone.name.has_value() ? js_string(vm, *parsed_result.time_zone.name) : js_undefined();
// f. If result.[[TimeZoneZ]] is true, then
if (parsed_result.time_zone.z) {
// i. Set offsetBehaviour to exact.
offset_behavior = OffsetBehavior::Exact;
}
// g. Else if offsetString is undefined, then
else if (offset_string.is_undefined()) {
// i. Set offsetBehaviour to wall.
offset_behavior = OffsetBehavior::Wall;
}
// h. Set matchBehaviour to match minutes.
match_behavior = MatchBehavior::Minutes;
// See NOTE above about why this is done.
result = move(parsed_result.date_time);
}
// 8. If timeZone is not undefined, then
if (!time_zone.is_undefined()) {
// a. Set timeZone to ? ToTemporalTimeZone(timeZone).
time_zone = TRY(to_temporal_time_zone(global_object, time_zone));
double offset_ns;
// b. If offsetBehaviour is option, then
if (offset_behavior == OffsetBehavior::Option) {
// i. Set offsetString to ? ToString(offsetString).
// NOTE: offsetString is not used after this path, so we don't need to put this into the original offset_string which is of type JS::Value.
auto actual_offset_string = TRY(offset_string.to_string(global_object));
// ii. Let offsetNs be ? ParseTimeZoneOffsetString(offsetString).
offset_ns = TRY(parse_time_zone_offset_string(global_object, actual_offset_string));
}
// c. Else,
else {
// i. Let offsetNs be 0.
offset_ns = 0;
}
// d. Let epochNanoseconds be ? InterpretISODateTimeOffset(result.[[Year]], result.[[Month]], result.[[Day]], result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]], offsetBehaviour, offsetNs, timeZone, "compatible", "reject", matchBehaviour).
auto* epoch_nanoseconds = TRY(interpret_iso_date_time_offset(global_object, result.year, result.month, result.day, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond, offset_behavior, offset_ns, time_zone, "compatible"sv, "reject"sv, match_behavior));
// e. Return ! CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar).
return MUST(create_temporal_zoned_date_time(global_object, *epoch_nanoseconds, time_zone.as_object(), *calendar));
}
// 9. Return ? CreateTemporalDateTime(result.[[Year]], result.[[Month]], result.[[Day]], result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]], 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, *calendar));
}
// 13.22 ValidateTemporalUnitRange ( largestUnit, smallestUnit ), https://tc39.es/proposal-temporal/#sec-temporal-validatetemporalunitrange
ThrowCompletionOr<void> validate_temporal_unit_range(GlobalObject& global_object, StringView largest_unit, StringView smallest_unit)
{
@ -1114,6 +1261,39 @@ ThrowCompletionOr<TemporalMonthDay> parse_temporal_month_day_string(GlobalObject
return TemporalMonthDay { .year = year, .month = result.month, .day = result.day, .calendar = move(result.calendar) };
}
// 13.42 ParseTemporalRelativeToString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalrelativetostring
ThrowCompletionOr<TemporalZonedDateTime> parse_temporal_relative_to_string(GlobalObject& global_object, String const& iso_string)
{
// 1. Assert: Type(isoString) is String.
// 2. If isoString does not satisfy the syntax of a TemporalRelativeToString (see 13.33), then
// a. Throw a RangeError exception.
// TODO
// 3. Let result be ! ParseISODateTime(isoString).
auto result = MUST(parse_iso_date_time(global_object, iso_string));
bool z;
Optional<String> offset;
Optional<String> time_zone;
// TODO: 4. If isoString satisfies the syntax of a TemporalZonedDateTimeString (see 13.33), then
// a. Let timeZoneResult be ! ParseTemporalTimeZoneString(isoString).
// b. Let z be timeZoneResult.[[Z]].
// c. Let offset be timeZoneResult.[[Offset]].
// d. Let timeZone be timeZoneResult.[[Name]].
// TODO: 5. Else,
// a. Let z be false.
z = false;
// b. Let offset be undefined. (NOTE: It already is)
// c. Let timeZone be undefined. (NOTE: It already is)
// 6. Return the Record { [[Year]]: result.[[Year]], [[Month]]: result.[[Month]], [[Day]]: result.[[Day]], [[Hour]]: result.[[Hour]], [[Minute]]: result.[[Minute]], [[Second]]: result.[[Second]], [[Millisecond]]: result.[[Millisecond]], [[Microsecond]]: result.[[Microsecond]], [[Nanosecond]]: result.[[Nanosecond]], [[Calendar]]: result.[[Calendar]], [[TimeZoneZ]]: z, [[TimeZoneOffset]]: offset, [[TimeZoneIANAName]]: timeZone }.
return TemporalZonedDateTime { .date_time = move(result), .time_zone = { .z = z, .offset = move(offset), .name = move(time_zone) } };
}
// 13.43 ParseTemporalTimeString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaltimestring
ThrowCompletionOr<TemporalTime> parse_temporal_time_string(GlobalObject& global_object, [[maybe_unused]] String const& iso_string)
{

View file

@ -113,6 +113,7 @@ ThrowCompletionOr<u64> to_temporal_date_time_rounding_increment(GlobalObject&, O
ThrowCompletionOr<SecondsStringPrecision> to_seconds_string_precision(GlobalObject&, Object const& normalized_options);
ThrowCompletionOr<String> to_largest_temporal_unit(GlobalObject&, Object const& normalized_options, Vector<StringView> const& disallowed_units, String const& fallback, Optional<String> auto_value);
ThrowCompletionOr<Optional<String>> to_smallest_temporal_unit(GlobalObject&, Object const& normalized_options, Vector<StringView> const& disallowed_units, Optional<String> fallback);
ThrowCompletionOr<Value> to_relative_temporal_object(GlobalObject&, Object const& options);
ThrowCompletionOr<void> validate_temporal_unit_range(GlobalObject&, StringView largest_unit, StringView smallest_unit);
String larger_of_two_temporal_units(StringView, StringView);
ThrowCompletionOr<Object*> merge_largest_unit_option(GlobalObject&, Object& options, String largest_unit);
@ -131,6 +132,7 @@ ThrowCompletionOr<TemporalDate> parse_temporal_date_string(GlobalObject&, String
ThrowCompletionOr<ISODateTime> parse_temporal_date_time_string(GlobalObject&, String const& iso_string);
ThrowCompletionOr<TemporalDuration> parse_temporal_duration_string(GlobalObject&, String const& iso_string);
ThrowCompletionOr<TemporalMonthDay> parse_temporal_month_day_string(GlobalObject&, String const& iso_string);
ThrowCompletionOr<TemporalZonedDateTime> parse_temporal_relative_to_string(GlobalObject&, String const& iso_string);
ThrowCompletionOr<TemporalTime> parse_temporal_time_string(GlobalObject&, String const& iso_string);
ThrowCompletionOr<TemporalTimeZone> parse_temporal_time_zone_string(GlobalObject&, String const& iso_string);
ThrowCompletionOr<TemporalYearMonth> parse_temporal_year_month_string(GlobalObject&, String const& iso_string);

View file

@ -161,7 +161,7 @@ ThrowCompletionOr<PlainDate*> calendar_date_add(GlobalObject& global_object, Obj
}
// 12.1.8 CalendarDateUntil ( calendar, one, two, options [ , dateUntil ] ), https://tc39.es/proposal-temporal/#sec-temporal-calendardateuntil
ThrowCompletionOr<Duration*> calendar_date_until(GlobalObject& global_object, Object& calendar, PlainDate& one, PlainDate& two, Object& options, FunctionObject* date_until)
ThrowCompletionOr<Duration*> calendar_date_until(GlobalObject& global_object, Object& calendar, Value one, Value two, Object& options, FunctionObject* date_until)
{
auto& vm = global_object.vm();
@ -172,7 +172,7 @@ ThrowCompletionOr<Duration*> calendar_date_until(GlobalObject& global_object, Ob
date_until = TRY(Value(&calendar).get_method(global_object, vm.names.dateUntil));
// 3. Let duration be ? Call(dateUntil, calendar, « one, two, options »).
auto duration = TRY(call(global_object, date_until ?: js_undefined(), &calendar, &one, &two, &options));
auto duration = TRY(call(global_object, date_until ?: js_undefined(), &calendar, one, two, &options));
// 4. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
auto* duration_object = TRY(duration.to_object(global_object));

View file

@ -37,7 +37,7 @@ Calendar* get_iso8601_calendar(GlobalObject&);
ThrowCompletionOr<Vector<String>> calendar_fields(GlobalObject&, Object& calendar, Vector<StringView> const& field_names);
ThrowCompletionOr<Object*> calendar_merge_fields(GlobalObject&, Object& calendar, Object& fields, Object& additional_fields);
ThrowCompletionOr<PlainDate*> calendar_date_add(GlobalObject&, Object& calendar, Value date, Duration&, Object* options, FunctionObject* date_add = nullptr);
ThrowCompletionOr<Duration*> calendar_date_until(GlobalObject&, Object& calendar, PlainDate& one, PlainDate& two, Object& options, FunctionObject* date_until = nullptr);
ThrowCompletionOr<Duration*> calendar_date_until(GlobalObject&, Object& calendar, Value one, Value two, Object& options, FunctionObject* date_until = nullptr);
ThrowCompletionOr<double> calendar_year(GlobalObject&, Object& calendar, Object& date_like);
ThrowCompletionOr<double> calendar_month(GlobalObject&, Object& calendar, Object& date_like);
ThrowCompletionOr<String> calendar_month_code(GlobalObject&, Object& calendar, Object& date_like);

View file

@ -262,6 +262,34 @@ Duration* create_negated_temporal_duration(GlobalObject& global_object, Duration
return MUST(create_temporal_duration(global_object, -duration.years(), -duration.months(), -duration.weeks(), -duration.days(), -duration.hours(), -duration.minutes(), -duration.seconds(), -duration.milliseconds(), -duration.microseconds(), -duration.nanoseconds()));
}
// 7.5.9 CalculateOffsetShift ( relativeTo, y, mon, w, d, h, min, s, ms, mus, ns ), https://tc39.es/proposal-temporal/#sec-temporal-calculateoffsetshift
ThrowCompletionOr<double> calculate_offset_shift(GlobalObject& global_object, Value relative_to_value, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds)
{
// 1. If Type(relativeTo) is not Object or relativeTo does not have an [[InitializedTemporalZonedDateTime]] internal slot, return 0.
if (!relative_to_value.is_object() || !is<ZonedDateTime>(relative_to_value.as_object()))
return 0.0;
auto& relative_to = static_cast<ZonedDateTime&>(relative_to_value.as_object());
// 2. Let instant be ! CreateTemporalInstant(relativeTo.[[Nanoseconds]]).
auto* instant = MUST(create_temporal_instant(global_object, relative_to.nanoseconds()));
// 3. Let offsetBefore be ? GetOffsetNanosecondsFor(relativeTo.[[TimeZone]], instant).
auto offset_before = TRY(get_offset_nanoseconds_for(global_object, &relative_to.time_zone(), *instant));
// 4. Let after be ? AddZonedDateTime(relativeTo.[[Nanoseconds]], relativeTo.[[TimeZone]], relativeTo.[[Calendar]], y, mon, w, d, h, min, s, ms, mus, ns).
auto* after = TRY(add_zoned_date_time(global_object, relative_to.nanoseconds(), &relative_to.time_zone(), relative_to.calendar(), years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds));
// 5. Let instantAfter be ! CreateTemporalInstant(after).
auto* instant_after = MUST(create_temporal_instant(global_object, *after));
// 6. Let offsetAfter be ? GetOffsetNanosecondsFor(relativeTo.[[TimeZone]], instantAfter).
auto offset_after = TRY(get_offset_nanoseconds_for(global_object, &relative_to.time_zone(), *instant_after));
// 7. Return offsetAfter offsetBefore.
return offset_after - offset_before;
}
// 7.5.10 TotalDurationNanoseconds ( days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, offsetShift ), https://tc39.es/proposal-temporal/#sec-temporal-totaldurationnanoseconds
BigInt* total_duration_nanoseconds(GlobalObject& global_object, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, BigInt const& nanoseconds, double offset_shift)
{
@ -441,6 +469,192 @@ ThrowCompletionOr<BalancedDuration> balance_duration(GlobalObject& global_object
return BalancedDuration { .days = days, .hours = hours * sign, .minutes = minutes * sign, .seconds = seconds * sign, .milliseconds = milliseconds * sign, .microseconds = microseconds * sign, .nanoseconds = result_nanoseconds * sign };
}
// 7.5.12 UnbalanceDurationRelative ( years, months, weeks, days, largestUnit, relativeTo ), https://tc39.es/proposal-temporal/#sec-temporal-unbalancedurationrelative
ThrowCompletionOr<UnbalancedDuration> unbalance_duration_relative(GlobalObject& global_object, double years, double months, double weeks, double days, String const& largest_unit, Value relative_to)
{
auto& vm = global_object.vm();
// 1. If largestUnit is "year", or years, months, weeks, and days are all 0, then
if (largest_unit == "year"sv || (years == 0 && months == 0 && weeks == 0 && days == 0)) {
// a. Return the Record { [[Years]]: years, [[Months]]: months, [[Weeks]]: weeks, [[Days]]: days }.
return UnbalancedDuration { .years = years, .months = months, .weeks = weeks, .days = days };
}
// 2. Let sign be ! DurationSign(years, months, weeks, days, 0, 0, 0, 0, 0, 0).
auto sign = duration_sign(years, months, weeks, days, 0, 0, 0, 0, 0, 0);
// 3. Assert: sign ≠ 0.
VERIFY(sign != 0);
// 4. Let oneYear be ! CreateTemporalDuration(sign, 0, 0, 0, 0, 0, 0, 0, 0, 0).
auto* one_year = MUST(create_temporal_duration(global_object, sign, 0, 0, 0, 0, 0, 0, 0, 0, 0));
// 5. Let oneMonth be ! CreateTemporalDuration(0, sign, 0, 0, 0, 0, 0, 0, 0, 0).
auto* one_month = MUST(create_temporal_duration(global_object, 0, sign, 0, 0, 0, 0, 0, 0, 0, 0));
// 6. Let oneWeek be ! CreateTemporalDuration(0, 0, sign, 0, 0, 0, 0, 0, 0, 0).
auto* one_week = MUST(create_temporal_duration(global_object, 0, 0, sign, 0, 0, 0, 0, 0, 0, 0));
Object* calendar;
// 7. If relativeTo is not undefined, then
if (!relative_to.is_undefined()) {
// a. Set relativeTo to ? ToTemporalDateTime(relativeTo).
PlainDateTime* relative_to_plain_date_time = TRY(to_temporal_date_time(global_object, relative_to));
relative_to = relative_to_plain_date_time;
// b. Let calendar be relativeTo.[[Calendar]].
calendar = &relative_to_plain_date_time->calendar();
}
// 8. Else,
else {
// a. Let calendar be undefined.
calendar = nullptr;
}
// 9. If largestUnit is "month", then
if (largest_unit == "month"sv) {
// a. If calendar is undefined, then
if (!calendar) {
// i. Throw a RangeError exception.
return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalMissingStartingPoint, "months");
}
// b. Let dateAdd be ? GetMethod(calendar, "dateAdd").
auto* date_add = TRY(Value(calendar).get_method(global_object, vm.names.dateAdd));
// c. Let dateUntil be ? GetMethod(calendar, "dateUntil").
auto* date_until = TRY(Value(calendar).get_method(global_object, vm.names.dateUntil));
// d. Repeat, while years ≠ 0,
while (years != 0) {
// i. Let addOptions be ! OrdinaryObjectCreate(null).
auto* add_options = Object::create(global_object, nullptr);
// ii. Let newRelativeTo be ? CalendarDateAdd(calendar, relativeTo, oneYear, addOptions, dateAdd).
auto* new_relative_to = TRY(calendar_date_add(global_object, *calendar, relative_to, *one_year, add_options, date_add));
// iii. Let untilOptions be ! OrdinaryObjectCreate(null).
auto* until_options = Object::create(global_object, nullptr);
// iv. Perform ! CreateDataPropertyOrThrow(untilOptions, "largestUnit", "month").
MUST(until_options->create_data_property_or_throw(vm.names.largestUnit, js_string(vm, "month"sv)));
// v. Let untilResult be ? CalendarDateUntil(calendar, relativeTo, newRelativeTo, untilOptions, dateUntil).
auto* until_result = TRY(calendar_date_until(global_object, *calendar, relative_to, new_relative_to, *until_options, date_until));
// vi. Let oneYearMonths be untilResult.[[Months]].
auto one_year_months = until_result->months();
// vii. Set relativeTo to newRelativeTo.
relative_to = new_relative_to;
// viii. Set years to years sign.
years -= sign;
// ix. Set months to months + oneYearMonths.
months += one_year_months;
}
}
// 10. Else if largestUnit is "week", then
else if (largest_unit == "week"sv) {
// a. If calendar is undefined, then
if (!calendar) {
// i. Throw a RangeError exception.
return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalMissingStartingPoint, "weeks");
}
// b. Repeat, while years ≠ 0,
while (years != 0) {
// i. Let moveResult be ? MoveRelativeDate(calendar, relativeTo, oneYear).
auto move_result = TRY(move_relative_date(global_object, *calendar, verify_cast<PlainDateTime>(relative_to.as_object()), *one_year));
// ii. Set relativeTo to moveResult.[[RelativeTo]].
relative_to = move_result.relative_to.cell();
// iii. Set days to days + moveResult.[[Days]].
days += move_result.days;
// iv. Set years to years sign.
years -= sign;
}
// c. Repeat, while months ≠ 0,
while (months != 0) {
// i. Let moveResult be ? MoveRelativeDate(calendar, relativeTo, oneMonth).
auto move_result = TRY(move_relative_date(global_object, *calendar, verify_cast<PlainDateTime>(relative_to.as_object()), *one_month));
// ii. Set relativeTo to moveResult.[[RelativeTo]].
relative_to = move_result.relative_to.cell();
// iii. Set days to days + moveResult.[[Days]].
days += move_result.days;
// iv. Set months to months sign.
months -= sign;
}
}
// 11. Else,
else {
// a. If any of years, months, and weeks are not zero, then
if (years != 0 || months != 0 || weeks != 0) {
// i. If calendar is undefined, then
if (!calendar) {
// i. Throw a RangeError exception.
return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalMissingStartingPoint, "calendar units");
}
// ii. Repeat, while years ≠ 0,
while (years != 0) {
// 1. Let moveResult be ? MoveRelativeDate(calendar, relativeTo, oneYear).
auto move_result = TRY(move_relative_date(global_object, *calendar, verify_cast<PlainDateTime>(relative_to.as_object()), *one_year));
// 2. Set relativeTo to moveResult.[[RelativeTo]].
relative_to = move_result.relative_to.cell();
// 3. Set days to days + moveResult.[[Days]].
days += move_result.days;
// 4. Set years to years sign.
years -= sign;
}
// iii. Repeat, while months ≠ 0,
while (months != 0) {
// 1. Let moveResult be ? MoveRelativeDate(calendar, relativeTo, oneMonth).
auto move_result = TRY(move_relative_date(global_object, *calendar, verify_cast<PlainDateTime>(relative_to.as_object()), *one_month));
// 2. Set relativeTo to moveResult.[[RelativeTo]].
relative_to = move_result.relative_to.cell();
// 3. Set days to days +moveResult.[[Days]].
days += move_result.days;
// 4. Set months to months sign.
months -= sign;
}
// iv. Repeat, while weeks ≠ 0,
while (weeks != 0) {
// 1. Let moveResult be ? MoveRelativeDate(calendar, relativeTo, oneWeek).
auto move_result = TRY(move_relative_date(global_object, *calendar, verify_cast<PlainDateTime>(relative_to.as_object()), *one_week));
// 2. Set relativeTo to moveResult.[[RelativeTo]].
relative_to = move_result.relative_to.cell();
// 3. Set days to days + moveResult.[[Days]].
days += move_result.days;
// 4. Set weeks to weeks sign.
weeks -= sign;
}
}
}
// 12. Return the Record { [[Years]]: years, [[Months]]: months, [[Weeks]]: weeks, [[Days]]: days }.
return UnbalancedDuration { .years = years, .months = months, .weeks = weeks, .days = days };
}
// 7.5.16 MoveRelativeDate ( calendar, relativeTo, duration ), https://tc39.es/proposal-temporal/#sec-temporal-moverelativedate
ThrowCompletionOr<MoveRelativeDateResult> move_relative_date(GlobalObject& global_object, Object& calendar, PlainDateTime& relative_to, Duration& duration)
{
@ -615,7 +829,7 @@ ThrowCompletionOr<RoundedDuration> round_duration(GlobalObject& global_object, d
MUST(until_options->create_data_property_or_throw(vm.names.largestUnit, js_string(vm, "year"sv)));
// p. Let timePassed be ? CalendarDateUntil(calendar, relativeTo, daysLater, untilOptions).
auto* time_passed = TRY(calendar_date_until(global_object, *calendar, *relative_to_date, *days_later, *until_options));
auto* time_passed = TRY(calendar_date_until(global_object, *calendar, relative_to_date, days_later, *until_options));
// q. Let yearsPassed be timePassed.[[Years]].
auto years_passed = time_passed->years();

View file

@ -109,6 +109,14 @@ struct RoundedDuration {
double remainder;
};
// Used by UnbalanceDurationRelative to temporarily hold values
struct UnbalancedDuration {
double years;
double months;
double weeks;
double days;
};
// Table 7: Properties of a TemporalDurationLike, https://tc39.es/proposal-temporal/#table-temporal-temporaldurationlike-properties
template<typename StructT, typename ValueT>
@ -141,8 +149,10 @@ bool is_valid_duration(double years, double months, double weeks, double days, d
ThrowCompletionOr<PartialDuration> to_partial_duration(GlobalObject&, Value temporal_duration_like);
ThrowCompletionOr<Duration*> create_temporal_duration(GlobalObject&, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, FunctionObject const* new_target = nullptr);
Duration* create_negated_temporal_duration(GlobalObject& global_object, Duration const& duration);
ThrowCompletionOr<double> calculate_offset_shift(GlobalObject&, Value relative_to_value, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds);
BigInt* total_duration_nanoseconds(GlobalObject&, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, BigInt const& nanoseconds, double offset_shift);
ThrowCompletionOr<BalancedDuration> balance_duration(GlobalObject&, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, BigInt const& nanoseconds, String const& largest_unit, Object* relative_to = nullptr);
ThrowCompletionOr<UnbalancedDuration> unbalance_duration_relative(GlobalObject&, double years, double months, double weeks, double days, String const& largest_unit, Value relative_to);
ThrowCompletionOr<MoveRelativeDateResult> move_relative_date(GlobalObject&, Object& calendar, PlainDateTime& relative_to, Duration& duration);
ThrowCompletionOr<ZonedDateTime*> move_relative_zoned_date_time(GlobalObject&, ZonedDateTime&, double years, double months, double weeks, double days);
ThrowCompletionOr<RoundedDuration> round_duration(GlobalObject&, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, u32 increment, StringView unit, StringView rounding_mode, Object* relative_to_object = nullptr);

View file

@ -397,7 +397,7 @@ ThrowCompletionOr<TemporalDuration> difference_iso_date_time(GlobalObject& globa
auto* until_options = TRY(merge_largest_unit_option(global_object, *options, move(date_largest_unit)));
// 12. Let dateDifference be ? CalendarDateUntil(calendar, date1, date2, untilOptions).
auto* date_difference = TRY(calendar_date_until(global_object, calendar, *date1, *date2, *until_options));
auto* date_difference = TRY(calendar_date_until(global_object, calendar, date1, date2, *until_options));
// 13. Let balanceResult be ? BalanceDuration(dateDifference.[[Days]], timeDifference.[[Hours]], timeDifference.[[Minutes]], timeDifference.[[Seconds]], timeDifference.[[Milliseconds]], timeDifference.[[Microseconds]], timeDifference.[[Nanoseconds]], largestUnit).
auto balance_result_ = TRY(balance_duration(global_object, date_difference->days(), time_difference.hours, time_difference.minutes, time_difference.seconds, time_difference.milliseconds, time_difference.microseconds, *js_bigint(vm, { (i32)time_difference.nanoseconds }), largest_unit));