LibJS: Begin implementing the relativeTo option of Duration.total

This commit is contained in:
Timothy Flynn 2024-11-22 18:38:53 -05:00 committed by Andreas Kling
commit d0149d8fc0
Notes: github-actions[bot] 2024-11-23 13:46:56 +00:00
6 changed files with 89 additions and 18 deletions

View file

@ -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)
{

View file

@ -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);

View file

@ -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

View file

@ -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));
}
}

View file

@ -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);
}

View file

@ -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", () => {