Bläddra i källkod

LibJS: Begin implementing the relativeTo option of Duration.total

Timothy Flynn 8 månader sedan
förälder
incheckning
d0149d8fc0

+ 26 - 5
Libraries/LibJS/Runtime/Temporal/Duration.cpp

@@ -834,7 +834,7 @@ ThrowCompletionOr<TimeDuration> round_time_duration(VM& vm, TimeDuration const&
 }
 
 // 7.5.31 TotalTimeDuration ( timeDuration, unit ), https://tc39.es/proposal-temporal/#sec-temporal-totaltimeduration
-double total_time_duration(TimeDuration const& time_duration, Unit unit)
+Crypto::BigFraction total_time_duration(TimeDuration const& time_duration, Unit unit)
 {
     // 1. Let divisor be the value in the "Length in Nanoseconds" column of the row of Table 21 whose "Value" column contains unit.
     auto const& divisor = temporal_unit_length_in_nanoseconds(unit);
@@ -844,8 +844,7 @@ double total_time_duration(TimeDuration const& time_duration, Unit unit)
     //    or with software emulation such as in the SoftFP library.
 
     // 3. Return timeDuration / divisor.
-    auto result = Crypto::BigFraction { time_duration } / Crypto::BigFraction { Crypto::SignedBigInteger { divisor } };
-    return result.to_double();
+    return Crypto::BigFraction { time_duration } / Crypto::BigFraction { Crypto::SignedBigInteger { divisor } };
 }
 
 // 7.5.33 NudgeToCalendarUnit ( sign, duration, destEpochNs, isoDateTime, timeZone, calendar, increment, unit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-nudgetocalendarunit
@@ -1175,10 +1174,10 @@ ThrowCompletionOr<DurationNudgeResult> nudge_to_day_or_time(VM& vm, InternalDura
     time_duration.negate();
 
     // 5. Let wholeDays be truncate(TotalTimeDuration(timeDuration, DAY)).
-    auto whole_days = trunc(total_time_duration(time_duration, Unit::Day));
+    auto whole_days = trunc(total_time_duration(time_duration, Unit::Day).to_double());
 
     // 6. Let roundedWholeDays be truncate(TotalTimeDuration(roundedTime, DAY)).
-    auto rounded_whole_days = trunc(total_time_duration(rounded_time, Unit::Day));
+    auto rounded_whole_days = trunc(total_time_duration(rounded_time, Unit::Day).to_double());
 
     // 7. Let dayDelta be roundedWholeDays - wholeDays.
     auto day_delta = rounded_whole_days - whole_days;
@@ -1374,6 +1373,28 @@ ThrowCompletionOr<InternalDuration> round_relative_duration(VM& vm, InternalDura
     return duration;
 }
 
+// 7.5.38 TotalRelativeDuration ( duration, destEpochNs, isoDateTime, timeZone, calendar, unit ), https://tc39.es/proposal-temporal/#sec-temporal-totalrelativeduration
+ThrowCompletionOr<Crypto::BigFraction> total_relative_duration(VM& vm, InternalDuration const& duration, TimeDuration const& dest_epoch_ns, ISODateTime const& iso_date_time, Optional<StringView> time_zone, StringView calendar, Unit unit)
+{
+    // 1. If IsCalendarUnit(unit) is true, or timeZone is not UNSET and unit is DAY, then
+    if (is_calendar_unit(unit) || (time_zone.has_value() && unit == Unit::Day)) {
+        // a. Let sign be InternalDurationSign(duration).
+        auto sign = internal_duration_sign(duration);
+
+        // b. Let record be ? NudgeToCalendarUnit(sign, duration, destEpochNs, isoDateTime, timeZone, calendar, 1, unit, TRUNC).
+        auto record = TRY(nudge_to_calendar_unit(vm, sign, duration, dest_epoch_ns, iso_date_time, time_zone, calendar, 1, unit, RoundingMode::Trunc));
+
+        // c. Return record.[[Total]].
+        return record.total;
+    }
+
+    // 2. Let timeDuration be ! Add24HourDaysToTimeDuration(duration.[[Time]], duration.[[Date]].[[Days]]).
+    auto time_duration = MUST(add_24_hour_days_to_time_duration(vm, duration.time, duration.date.days));
+
+    // 3. Return TotalTimeDuration(timeDuration, unit).
+    return total_time_duration(time_duration, unit);
+}
+
 // 7.5.39 TemporalDurationToString ( duration, precision ), https://tc39.es/proposal-temporal/#sec-temporal-temporaldurationtostring
 String temporal_duration_to_string(Duration const& duration, Precision precision)
 {

+ 2 - 1
Libraries/LibJS/Runtime/Temporal/Duration.h

@@ -136,12 +136,13 @@ ThrowCompletionOr<TimeDuration> round_time_duration_to_increment(VM&, TimeDurati
 i8 time_duration_sign(TimeDuration const&);
 ThrowCompletionOr<double> date_duration_days(VM&, DateDuration const&, PlainDate const&);
 ThrowCompletionOr<TimeDuration> round_time_duration(VM&, TimeDuration const&, Crypto::UnsignedBigInteger const& increment, Unit, RoundingMode);
+Crypto::BigFraction total_time_duration(TimeDuration const&, Unit);
 ThrowCompletionOr<CalendarNudgeResult> nudge_to_calendar_unit(VM&, i8 sign, InternalDuration const&, TimeDuration const& dest_epoch_ns, ISODateTime const&, Optional<StringView> time_zone, StringView calendar, u64 increment, Unit, RoundingMode);
 ThrowCompletionOr<DurationNudgeResult> nudge_to_zoned_time(VM&, i8 sign, InternalDuration const&, ISODateTime const&, StringView time_zone, StringView calendar, u64 increment, Unit, RoundingMode);
 ThrowCompletionOr<DurationNudgeResult> nudge_to_day_or_time(VM&, InternalDuration const&, TimeDuration const& dest_epoch_ns, Unit largest_unit, u64 increment, Unit smallest_unit, RoundingMode);
 ThrowCompletionOr<InternalDuration> bubble_relative_duration(VM&, i8 sign, InternalDuration, TimeDuration const& nudged_epoch_ns, ISODateTime const&, Optional<StringView> time_zone, StringView calendar, Unit largest_unit, Unit smallest_unit);
 ThrowCompletionOr<InternalDuration> round_relative_duration(VM&, InternalDuration, TimeDuration const& dest_epoch_ns, ISODateTime const&, Optional<StringView> time_zone, StringView calendar, Unit largest_unit, u64 increment, Unit smallest_unit, RoundingMode);
-double total_time_duration(TimeDuration const&, Unit);
+ThrowCompletionOr<Crypto::BigFraction> total_relative_duration(VM&, InternalDuration const&, TimeDuration const&, ISODateTime const&, Optional<StringView> time_zone, StringView calendar, Unit);
 String temporal_duration_to_string(Duration const&, Precision);
 ThrowCompletionOr<GC::Ref<Duration>> add_durations(VM&, ArithmeticOperation, Duration const&, Value);
 

+ 27 - 12
Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp

@@ -409,7 +409,7 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::round)
         auto fractional_days = total_time_duration(internal_duration.time, Unit::Day);
 
         // b. Let days be RoundNumberToIncrement(fractionalDays, roundingIncrement, roundingMode).
-        auto days = round_number_to_increment(fractional_days, rounding_increment, rounding_mode);
+        auto days = round_number_to_increment(fractional_days.to_double(), rounding_increment, rounding_mode);
 
         // c. Let dateDuration be ? CreateDateDurationRecord(0, 0, 0, days).
         auto date_duration = TRY(create_date_duration_record(vm, 0, 0, 0, days));
@@ -470,12 +470,12 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::total)
     // 7. Let relativeToRecord be ? GetTemporalRelativeToOption(totalOf).
     // 8. Let zonedRelativeTo be relativeToRecord.[[ZonedRelativeTo]].
     // 9. Let plainRelativeTo be relativeToRecord.[[PlainRelativeTo]].
-    auto [zoned_relative_to, plain_relative_to] = TRY(get_temporal_relative_to_option(vm, *total_of));
+    auto [plain_relative_to, zoned_relative_to] = TRY(get_temporal_relative_to_option(vm, *total_of));
 
     // 10. Let unit be ? GetTemporalUnitValuedOption(totalOf, "unit", DATETIME, REQUIRED).
     auto unit = TRY(get_temporal_unit_valued_option(vm, *total_of, vm.names.unit, UnitGroup::DateTime, Required {})).get<Unit>();
 
-    double total = 0;
+    Crypto::BigFraction total;
 
     // 11. If zonedRelativeTo is not undefined, then
     if (zoned_relative_to) {
@@ -488,14 +488,29 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::total)
     }
     // 12. Else if plainRelativeTo is not undefined, then
     else if (plain_relative_to) {
-        // FIXME: a. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration).
-        // FIXME: b. Let targetTime be AddTime(MidnightTimeRecord(), internalDuration.[[Time]]).
-        // FIXME: c. Let calendar be plainRelativeTo.[[Calendar]].
-        // FIXME: d. Let dateDuration be ! AdjustDateDurationRecord(internalDuration.[[Date]], targetTime.[[Days]]).
-        // FIXME: e. Let targetDate be ? CalendarDateAdd(calendar, plainRelativeTo.[[ISODate]], dateDuration, constrain).
-        // FIXME: f. Let isoDateTime be CombineISODateAndTimeRecord(plainRelativeTo.[[ISODate]], MidnightTimeRecord()).
-        // FIXME: g. Let targetDateTime be CombineISODateAndTimeRecord(targetDate, targetTime).
-        // FIXME: h. Let total be ? DifferencePlainDateTimeWithTotal(isoDateTime, targetDateTime, calendar, unit).
+        // a. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration).
+        auto internal_duration = to_internal_duration_record_with_24_hour_days(vm, duration);
+
+        // b. Let targetTime be AddTime(MidnightTimeRecord(), internalDuration.[[Time]]).
+        auto target_time = add_time(midnight_time_record(), internal_duration.time);
+
+        // c. Let calendar be plainRelativeTo.[[Calendar]].
+        auto const& calendar = plain_relative_to->calendar();
+
+        // d. Let dateDuration be ! AdjustDateDurationRecord(internalDuration.[[Date]], targetTime.[[Days]]).
+        auto date_duration = MUST(adjust_date_duration_record(vm, internal_duration.date, target_time.days));
+
+        // e. Let targetDate be ? CalendarDateAdd(calendar, plainRelativeTo.[[ISODate]], dateDuration, CONSTRAIN).
+        auto target_date = TRY(calendar_date_add(vm, calendar, plain_relative_to->iso_date(), date_duration, Overflow::Constrain));
+
+        // f. Let isoDateTime be CombineISODateAndTimeRecord(plainRelativeTo.[[ISODate]], MidnightTimeRecord()).
+        auto iso_date_time = combine_iso_date_and_time_record(plain_relative_to->iso_date(), midnight_time_record());
+
+        // g. Let targetDateTime be CombineISODateAndTimeRecord(targetDate, targetTime).
+        auto target_date_time = combine_iso_date_and_time_record(target_date, target_time);
+
+        // h. Let total be ? DifferencePlainDateTimeWithTotal(isoDateTime, targetDateTime, calendar, unit).
+        total = TRY(difference_plain_date_time_with_total(vm, iso_date_time, target_date_time, calendar, unit));
     }
     // 13. Else,
     else {
@@ -516,7 +531,7 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::total)
     }
 
     // 14. Return 𝔽(total).
-    return total;
+    return total.to_double();
 }
 
 // 7.3.22 Temporal.Duration.prototype.toString ( [ options ] ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.tostring

+ 23 - 0
Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp

@@ -167,4 +167,27 @@ ThrowCompletionOr<InternalDuration> difference_plain_date_time_with_rounding(VM&
     return TRY(round_relative_duration(vm, diff, dest_epoch_ns, iso_date_time1, {}, calendar, largest_unit, rounding_increment, smallest_unit, rounding_mode));
 }
 
+// 5.5.14 DifferencePlainDateTimeWithTotal ( isoDateTime1, isoDateTime2, calendar, unit ), https://tc39.es/proposal-temporal/#sec-temporal-differenceplaindatetimewithtotal
+ThrowCompletionOr<Crypto::BigFraction> difference_plain_date_time_with_total(VM& vm, ISODateTime const& iso_date_time1, ISODateTime const& iso_date_time2, StringView calendar, Unit unit)
+{
+    // 1. If CompareISODateTime(isoDateTime1, isoDateTime2) = 0, then
+    if (compare_iso_date_time(iso_date_time1, iso_date_time2) == 0) {
+        // a. Return 0.
+        return Crypto::BigFraction {};
+    }
+
+    // 2. Let diff be ? DifferenceISODateTime(isoDateTime1, isoDateTime2, calendar, unit).
+    auto diff = TRY(difference_iso_date_time(vm, iso_date_time1, iso_date_time2, calendar, unit));
+
+    // 3. If unit is NANOSECOND, return diff.[[Time]].
+    if (unit == Unit::Nanosecond)
+        return move(diff.time);
+
+    // 4. Let destEpochNs be GetUTCEpochNanoseconds(isoDateTime2).
+    auto dest_epoch_ns = get_utc_epoch_nanoseconds(iso_date_time2);
+
+    // 5. Return ? TotalRelativeDuration(diff, destEpochNs, isoDateTime1, UNSET, calendar, unit).
+    return TRY(total_relative_duration(vm, diff, dest_epoch_ns, iso_date_time1, {}, calendar, unit));
+}
+
 }

+ 2 - 0
Libraries/LibJS/Runtime/Temporal/PlainDateTime.h

@@ -8,6 +8,7 @@
 
 #pragma once
 
+#include <LibCrypto/BigFraction/BigFraction.h>
 #include <LibJS/Runtime/Temporal/AbstractOperations.h>
 #include <LibJS/Runtime/Temporal/ISORecords.h>
 
@@ -20,5 +21,6 @@ ISODateTime balance_iso_date_time(double year, double month, double day, double
 i8 compare_iso_date_time(ISODateTime const&, ISODateTime const&);
 ThrowCompletionOr<InternalDuration> difference_iso_date_time(VM&, ISODateTime const&, ISODateTime const&, StringView calendar, Unit largest_unit);
 ThrowCompletionOr<InternalDuration> difference_plain_date_time_with_rounding(VM&, ISODateTime const&, ISODateTime const&, StringView calendar, Unit largest_unit, u64 rounding_increment, Unit smallest_unit, RoundingMode);
+ThrowCompletionOr<Crypto::BigFraction> difference_plain_date_time_with_total(VM&, ISODateTime const&, ISODateTime const&, StringView calendar, Unit);
 
 }

+ 9 - 0
Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.prototype.total.js

@@ -16,6 +16,15 @@ describe("correct behavior", () => {
             }
         }
     });
+
+    test("relative to plain date", () => {
+        const duration = new Temporal.Duration(0, 0, 0, 31);
+
+        ["2000-01-01", "2000-01-01T00:00", "2000-01-01T00:00[u-ca=iso8601]"].forEach(relativeTo => {
+            const result = duration.total({ unit: "months", relativeTo });
+            expect(result).toBe(1);
+        });
+    });
 });
 
 describe("errors", () => {