LibJS: Begin implementing the relativeTo option of Duration.round

This commit is contained in:
Timothy Flynn 2024-11-22 18:16:57 -05:00 committed by Andreas Kling
commit 70ad66d3c0
Notes: github-actions[bot] 2024-11-23 13:47:01 +00:00
6 changed files with 219 additions and 9 deletions

View file

@ -5,8 +5,12 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/Temporal/Calendar.h>
#include <LibJS/Runtime/Temporal/Duration.h>
#include <LibJS/Runtime/Temporal/DurationPrototype.h>
#include <LibJS/Runtime/Temporal/PlainDate.h>
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
#include <LibJS/Runtime/Temporal/PlainTime.h>
namespace JS::Temporal {
@ -272,7 +276,7 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::round)
// 10. Let relativeToRecord be ? GetTemporalRelativeToOption(roundTo).
// 11. Let zonedRelativeTo be relativeToRecord.[[ZonedRelativeTo]].
// 12. Let plainRelativeTo be relativeToRecord.[[PlainRelativeTo]].
auto [zoned_relative_to, plain_relative_to] = TRY(get_temporal_relative_to_option(vm, *round_to));
auto [plain_relative_to, zoned_relative_to] = TRY(get_temporal_relative_to_option(vm, *round_to));
// 13. Let roundingIncrement be ? GetRoundingIncrementOption(roundTo).
auto rounding_increment = TRY(get_rounding_increment_option(vm, *round_to));
@ -362,13 +366,26 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::round)
// a. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration).
auto internal_duration = to_internal_duration_record_with_24_hour_days(vm, 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. Set internalDuration to ? DifferencePlainDateTimeWithRounding(isoDateTime, targetDateTime, calendar, largestUnit, roundingIncrement, smallestUnit, roundingMode).
// 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. Set internalDuration to ? DifferencePlainDateTimeWithRounding(isoDateTime, targetDateTime, calendar, largestUnit, roundingIncrement, smallestUnit, roundingMode).
internal_duration = TRY(difference_plain_date_time_with_rounding(vm, iso_date_time, target_date_time, calendar, largest_unit_value, rounding_increment, smallest_unit_value, rounding_mode));
// i. Return ? TemporalDurationFromInternal(internalDuration, largestUnit).
return TRY(temporal_duration_from_internal(vm, internal_duration, largest_unit_value));

View file

@ -6,8 +6,8 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
#include <LibJS/Runtime/Temporal/Calendar.h>
#include <LibJS/Runtime/Temporal/Duration.h>
#include <LibJS/Runtime/Temporal/Instant.h>
#include <LibJS/Runtime/Temporal/PlainDate.h>
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
@ -81,4 +81,90 @@ ISODateTime balance_iso_date_time(double year, double month, double day, double
return combine_iso_date_and_time_record(balanced_date, balanced_time);
}
// 5.5.10 CompareISODateTime ( isoDateTime1, isoDateTime2 ), https://tc39.es/proposal-temporal/#sec-temporal-compareisodatetime
i8 compare_iso_date_time(ISODateTime const& iso_date_time1, ISODateTime const& iso_date_time2)
{
// 1. Let dateResult be CompareISODate(isoDateTime1.[[ISODate]], isoDateTime2.[[ISODate]]).
auto date_result = compare_iso_date(iso_date_time1.iso_date, iso_date_time2.iso_date);
// 2. If dateResult ≠ 0, return dateResult.
if (date_result != 0)
return date_result;
// 3. Return CompareTimeRecord(isoDateTime1.[[Time]], isoDateTime2.[[Time]]).
return compare_time_record(iso_date_time1.time, iso_date_time2.time);
}
// 5.5.12 DifferenceISODateTime ( isoDateTime1, isoDateTime2, calendar, largestUnit ), https://tc39.es/proposal-temporal/#sec-temporal-differenceisodatetime
ThrowCompletionOr<InternalDuration> difference_iso_date_time(VM& vm, ISODateTime const& iso_date_time1, ISODateTime const& iso_date_time2, StringView calendar, Unit largest_unit)
{
// 1. Assert: ISODateTimeWithinLimits(isoDateTime1) is true.
VERIFY(iso_date_time_within_limits(iso_date_time1));
// 2. Assert: ISODateTimeWithinLimits(isoDateTime2) is true.
VERIFY(iso_date_time_within_limits(iso_date_time2));
// 3. Let timeDuration be DifferenceTime(isoDateTime1.[[Time]], isoDateTime2.[[Time]]).
auto time_duration = difference_time(iso_date_time1.time, iso_date_time2.time);
// 4. Let timeSign be TimeDurationSign(timeDuration).
auto time_sign = time_duration_sign(time_duration);
// 5. Let dateSign be CompareISODate(isoDateTime2.[[ISODate]], isoDateTime1.[[ISODate]]).
auto date_sign = compare_iso_date(iso_date_time2.iso_date, iso_date_time1.iso_date);
// 6. Let adjustedDate be isoDateTime2.[[ISODate]].
auto adjusted_date = iso_date_time2.iso_date;
// 7. If timeSign = -dateSign, then
if (time_sign == -date_sign) {
// a. Set adjustedDate to BalanceISODate(adjustedDate.[[Year]], adjustedDate.[[Month]], adjustedDate.[[Day]] + timeSign).
adjusted_date = balance_iso_date(adjusted_date.year, adjusted_date.month, static_cast<double>(adjusted_date.day) + time_sign);
// b. Set timeDuration to ? Add24HourDaysToTimeDuration(timeDuration, -timeSign).
time_duration = TRY(add_24_hour_days_to_time_duration(vm, time_duration, -time_sign));
}
// 8. Let dateLargestUnit be LargerOfTwoTemporalUnits(DAY, largestUnit).
auto date_largest_unit = larger_of_two_temporal_units(Unit::Day, largest_unit);
// 9. Let dateDifference be CalendarDateUntil(calendar, isoDateTime1.[[ISODate]], adjustedDate, dateLargestUnit).
auto date_difference = calendar_date_until(vm, calendar, iso_date_time1.iso_date, adjusted_date, date_largest_unit);
// 10. If largestUnit is not dateLargestUnit, then
if (largest_unit != date_largest_unit) {
// a. Set timeDuration to ? Add24HourDaysToTimeDuration(timeDuration, dateDifference.[[Days]]).
time_duration = TRY(add_24_hour_days_to_time_duration(vm, time_duration, date_difference.days));
// b. Set dateDifference.[[Days]] to 0.
date_difference.days = 0;
}
// 11. Return ? CombineDateAndTimeDuration(dateDifference, timeDuration).
return TRY(combine_date_and_time_duration(vm, date_difference, move(time_duration)));
}
// 5.5.13 DifferencePlainDateTimeWithRounding ( isoDateTime1, isoDateTime2, calendar, largestUnit, roundingIncrement, smallestUnit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-differenceplaindatetimewithrounding
ThrowCompletionOr<InternalDuration> difference_plain_date_time_with_rounding(VM& vm, ISODateTime const& iso_date_time1, ISODateTime const& iso_date_time2, StringView calendar, Unit largest_unit, u64 rounding_increment, Unit smallest_unit, RoundingMode rounding_mode)
{
// 1. If CompareISODateTime(isoDateTime1, isoDateTime2) = 0, then
if (compare_iso_date_time(iso_date_time1, iso_date_time2) == 0) {
// a. Return ! CombineDateAndTimeDuration(ZeroDateDuration(), 0).
return MUST(combine_date_and_time_duration(vm, zero_date_duration(vm), TimeDuration { 0 }));
}
// 2. Let diff be ? DifferenceISODateTime(isoDateTime1, isoDateTime2, calendar, largestUnit).
auto diff = TRY(difference_iso_date_time(vm, iso_date_time1, iso_date_time2, calendar, largest_unit));
// 3. If smallestUnit is NANOSECOND and roundingIncrement = 1, return diff.
if (smallest_unit == Unit::Nanosecond && rounding_increment == 1)
return diff;
// 4. Let destEpochNs be GetUTCEpochNanoseconds(isoDateTime2).
auto dest_epoch_ns = get_utc_epoch_nanoseconds(iso_date_time2);
// 5. Return ? RoundRelativeDuration(diff, destEpochNs, isoDateTime1, UNSET, calendar, largestUnit, roundingIncrement, smallestUnit, roundingMode).
return TRY(round_relative_duration(vm, diff, dest_epoch_ns, iso_date_time1, {}, calendar, largest_unit, rounding_increment, smallest_unit, rounding_mode));
}
}

View file

@ -8,6 +8,7 @@
#pragma once
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
#include <LibJS/Runtime/Temporal/ISORecords.h>
namespace JS::Temporal {
@ -16,5 +17,8 @@ ISODateTime combine_iso_date_and_time_record(ISODate, Time);
bool iso_date_time_within_limits(ISODateTime);
ThrowCompletionOr<ISODateTime> interpret_temporal_date_time_fields(VM&, StringView calendar, CalendarFields&, Overflow);
ISODateTime balance_iso_date_time(double year, double month, double day, double hour, double minute, double second, double millisecond, double microsecond, double nanosecond);
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);
}

View file

@ -8,6 +8,8 @@
#include <AK/Assertions.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Temporal/Duration.h>
#include <LibJS/Runtime/Temporal/Instant.h>
#include <LibJS/Runtime/Temporal/PlainTime.h>
#include <math.h>
@ -46,6 +48,37 @@ Time noon_time_record()
return { .days = 0, .hour = 12, .minute = 0, .second = 0, .millisecond = 0, .microsecond = 0, .nanosecond = 0 };
}
// 4.5.5 DifferenceTime ( time1, time2 ), https://tc39.es/proposal-temporal/#sec-temporal-differencetime
TimeDuration difference_time(Time const& time1, Time const& time2)
{
// 1. Let hours be time2.[[Hour]] - time1.[[Hour]].
auto hours = static_cast<double>(time2.hour) - static_cast<double>(time1.hour);
// 2. Let minutes be time2.[[Minute]] - time1.[[Minute]].
auto minutes = static_cast<double>(time2.minute) - static_cast<double>(time1.minute);
// 3. Let seconds be time2.[[Second]] - time1.[[Second]].
auto seconds = static_cast<double>(time2.second) - static_cast<double>(time1.second);
// 4. Let milliseconds be time2.[[Millisecond]] - time1.[[Millisecond]].
auto milliseconds = static_cast<double>(time2.millisecond) - static_cast<double>(time1.millisecond);
// 5. Let microseconds be time2.[[Microsecond]] - time1.[[Microsecond]].
auto microseconds = static_cast<double>(time2.microsecond) - static_cast<double>(time1.microsecond);
// 6. Let nanoseconds be time2.[[Nanosecond]] - time1.[[Nanosecond]].
auto nanoseconds = static_cast<double>(time2.nanosecond) - static_cast<double>(time1.nanosecond);
// 7. Let timeDuration be TimeDurationFromComponents(hours, minutes, seconds, milliseconds, microseconds, nanoseconds).
auto time_duration = time_duration_from_components(hours, minutes, seconds, milliseconds, microseconds, nanoseconds);
// 8. Assert: abs(timeDuration) < nsPerDay.
VERIFY(time_duration.unsigned_value() < NANOSECONDS_PER_DAY);
// 9. Return timeDuration.
return time_duration;
}
// 4.5.8 RegulateTime ( hour, minute, second, millisecond, microsecond, nanosecond, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-regulatetime
ThrowCompletionOr<Time> regulate_time(VM& vm, double hour, double minute, double second, double millisecond, double microsecond, double nanosecond, Overflow overflow)
{
@ -172,4 +205,62 @@ Time balance_time(double hour, double minute, double second, double millisecond,
return create_time_record(hour, minute, second, millisecond, microsecond, nanosecond, delta_days);
}
// 4.5.14 CompareTimeRecord ( time1, time2 ), https://tc39.es/proposal-temporal/#sec-temporal-comparetimerecord
i8 compare_time_record(Time const& time1, Time const& time2)
{
// 1. If time1.[[Hour]] > time2.[[Hour]], return 1.
if (time1.hour > time2.hour)
return 1;
// 2. If time1.[[Hour]] < time2.[[Hour]], return -1.
if (time1.hour < time2.hour)
return -1;
// 3. If time1.[[Minute]] > time2.[[Minute]], return 1.
if (time1.minute > time2.minute)
return 1;
// 4. If time1.[[Minute]] < time2.[[Minute]], return -1.
if (time1.minute < time2.minute)
return -1;
// 5. If time1.[[Second]] > time2.[[Second]], return 1.
if (time1.second > time2.second)
return 1;
// 6. If time1.[[Second]] < time2.[[Second]], return -1.
if (time1.second < time2.second)
return -1;
// 7. If time1.[[Millisecond]] > time2.[[Millisecond]], return 1.
if (time1.millisecond > time2.millisecond)
return 1;
// 8. If time1.[[Millisecond]] < time2.[[Millisecond]], return -1.
if (time1.millisecond < time2.millisecond)
return -1;
// 9. If time1.[[Microsecond]] > time2.[[Microsecond]], return 1.
if (time1.microsecond > time2.microsecond)
return 1;
// 10. If time1.[[Microsecond]] < time2.[[Microsecond]], return -1.
if (time1.microsecond < time2.microsecond)
return -1;
// 11. If time1.[[Nanosecond]] > time2.[[Nanosecond]], return 1.
if (time1.nanosecond > time2.nanosecond)
return 1;
// 12. If time1.[[Nanosecond]] < time2.[[Nanosecond]], return -1.
if (time1.nanosecond < time2.nanosecond)
return -1;
// 13. Return 0.
return 0;
}
// 4.5.15 AddTime ( time, timeDuration ), https://tc39.es/proposal-temporal/#sec-temporal-addtime
Time add_time(Time const& time, TimeDuration const& time_duration)
{
auto nanoseconds = time_duration.plus(TimeDuration { static_cast<i64>(time.nanosecond) });
// 1. Return BalanceTime(time.[[Hour]], time.[[Minute]], time.[[Second]], time.[[Millisecond]], time.[[Microsecond]], time.[[Nanosecond]] + timeDuration).
return balance_time(time.hour, time.minute, time.second, time.millisecond, time.microsecond, nanoseconds.to_double());
}
}

View file

@ -16,8 +16,11 @@ namespace JS::Temporal {
Time create_time_record(double hour, double minute, double second, double millisecond, double microsecond, double nanosecond, double delta_days = 0);
Time midnight_time_record();
Time noon_time_record();
TimeDuration difference_time(Time const&, Time const&);
ThrowCompletionOr<Time> regulate_time(VM&, double hour, double minute, double second, double millisecond, double microsecond, double nanosecond, Overflow);
bool is_valid_time(double hour, double minute, double second, double millisecond, double microsecond, double nanosecond);
Time balance_time(double hour, double minute, double second, double millisecond, double microsecond, double nanosecond);
i8 compare_time_record(Time const&, Time const&);
Time add_time(Time const&, TimeDuration const& time_duration);
}

View file

@ -89,6 +89,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.round({ largestUnit: "months", relativeTo });
expect(result.months).toBe(1);
});
});
});
describe("errors", () => {