Forráskód Böngészése

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

Luke Wilde 3 éve
szülő
commit
29072f4b09

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

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

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

@@ -225,6 +225,7 @@
     M(TemporalInvalidUnitRange, "Invalid unit range, {} is larger than {}")                                                             \
     M(TemporalInvalidUnitRange, "Invalid unit range, {} is larger than {}")                                                             \
     M(TemporalInvalidZonedDateTimeOffset, "Invalid offset for the provided date and time in the current time zone")                     \
     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(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(TemporalObjectMustHaveOneOf, "Object must have at least one of the following properties: {}")                                     \
     M(TemporalObjectMustNotHave, "Object must not have a defined {} property")                                                          \
     M(TemporalObjectMustNotHave, "Object must not have a defined {} property")                                                          \
     M(TemporalPropertyMustBeFinite, "Property must not be Infinity")                                                                    \
     M(TemporalPropertyMustBeFinite, "Property must not be Infinity")                                                                    \

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

@@ -1,6 +1,7 @@
 /*
 /*
  * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
  * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
  * Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
  * Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
+ * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
  *
  *
  * SPDX-License-Identifier: BSD-2-Clause
  * SPDX-License-Identifier: BSD-2-Clause
  */
  */
@@ -514,6 +515,152 @@ ThrowCompletionOr<Optional<String>> to_smallest_temporal_unit(GlobalObject& glob
     return smallest_unit;
     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
 // 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)
 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) };
     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
 // 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)
 ThrowCompletionOr<TemporalTime> parse_temporal_time_string(GlobalObject& global_object, [[maybe_unused]] String const& iso_string)
 {
 {

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

@@ -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<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<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<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);
 ThrowCompletionOr<void> validate_temporal_unit_range(GlobalObject&, StringView largest_unit, StringView smallest_unit);
 String larger_of_two_temporal_units(StringView, StringView);
 String larger_of_two_temporal_units(StringView, StringView);
 ThrowCompletionOr<Object*> merge_largest_unit_option(GlobalObject&, Object& options, String largest_unit);
 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<ISODateTime> parse_temporal_date_time_string(GlobalObject&, String const& iso_string);
 ThrowCompletionOr<TemporalDuration> parse_temporal_duration_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<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<TemporalTime> parse_temporal_time_string(GlobalObject&, String const& iso_string);
 ThrowCompletionOr<TemporalTimeZone> parse_temporal_time_zone_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);
 ThrowCompletionOr<TemporalYearMonth> parse_temporal_year_month_string(GlobalObject&, String const& iso_string);

+ 2 - 2
Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp

@@ -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
 // 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();
     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));
         date_until = TRY(Value(&calendar).get_method(global_object, vm.names.dateUntil));
 
 
     // 3. Let duration be ? Call(dateUntil, calendar, « one, two, options »).
     // 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]]).
     // 4. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
     auto* duration_object = TRY(duration.to_object(global_object));
     auto* duration_object = TRY(duration.to_object(global_object));

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

@@ -37,7 +37,7 @@ Calendar* get_iso8601_calendar(GlobalObject&);
 ThrowCompletionOr<Vector<String>> calendar_fields(GlobalObject&, Object& calendar, Vector<StringView> const& field_names);
 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<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<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_year(GlobalObject&, Object& calendar, Object& date_like);
 ThrowCompletionOr<double> calendar_month(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);
 ThrowCompletionOr<String> calendar_month_code(GlobalObject&, Object& calendar, Object& date_like);

+ 215 - 1
Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp

@@ -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()));
     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
 // 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)
 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 };
     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
 // 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)
 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)));
         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).
         // 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]].
         // q. Let yearsPassed be timePassed.[[Years]].
         auto years_passed = time_passed->years();
         auto years_passed = time_passed->years();

+ 10 - 0
Userland/Libraries/LibJS/Runtime/Temporal/Duration.h

@@ -109,6 +109,14 @@ struct RoundedDuration {
     double remainder;
     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
 // Table 7: Properties of a TemporalDurationLike, https://tc39.es/proposal-temporal/#table-temporal-temporaldurationlike-properties
 
 
 template<typename StructT, typename ValueT>
 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<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);
 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);
 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);
 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<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<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<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);
 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);

+ 1 - 1
Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp

@@ -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)));
     auto* until_options = TRY(merge_largest_unit_option(global_object, *options, move(date_largest_unit)));
 
 
     // 12. Let dateDifference be ? CalendarDateUntil(calendar, date1, date2, untilOptions).
     // 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).
     // 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));
     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));