LibJS: Move regulating and balancing logic into ISODateSurpasses

This is an editorial change in the Temporal proposal. See:
eddb77f
This commit is contained in:
Timothy Flynn 2025-08-05 10:28:13 -04:00 committed by Tim Flynn
commit 3920194bca
Notes: github-actions[bot] 2025-08-05 15:19:24 +00:00
3 changed files with 54 additions and 27 deletions

View file

@ -395,8 +395,8 @@ DateDuration calendar_date_until(VM& vm, StringView calendar, ISODate one, ISODa
if (candidate_years != 0)
candidate_years -= sign;
// d.ii. Repeat, while ISODateSurpasses(sign, one.[[Year]] + candidateYears, one.[[Month]], one.[[Day]], two) is false,
while (!iso_date_surpasses(sign, static_cast<double>(one.year) + candidate_years, one.month, one.day, two)) {
// d.ii. Repeat, while ISODateSurpasses(sign, one, two, candidateYears, 0, 0, 0) is false,
while (!iso_date_surpasses(vm, sign, one, two, candidate_years, 0, 0, 0)) {
// 1. Set years to candidateYears.
years = candidate_years;
@ -407,19 +407,13 @@ DateDuration calendar_date_until(VM& vm, StringView calendar, ISODate one, ISODa
// f.i. Let candidateMonths be sign.
double candidate_months = sign;
// f.ii. Let intermediate be BalanceISOYearMonth(one.[[Year]] + years, one.[[Month]] + candidateMonths).
auto intermediate = balance_iso_year_month(static_cast<double>(one.year) + years, static_cast<double>(one.month) + candidate_months);
// f.iii. Repeat, while ISODateSurpasses(sign, intermediate.[[Year]], intermediate.[[Month]], one.[[Day]], two) is false,
while (!iso_date_surpasses(sign, intermediate.year, intermediate.month, one.day, two)) {
// f.ii. Repeat, while ISODateSurpasses(sign, one, two, years, candidateMonths, 0, 0) is false,
while (!iso_date_surpasses(vm, sign, one, two, years, candidate_months, 0, 0)) {
// 1. Set months to candidateMonths.
months = candidate_months;
// 2. Set candidateMonths to candidateMonths + sign.
candidate_months += sign;
// 3. Set intermediate to BalanceISOYearMonth(intermediate.[[Year]], intermediate.[[Month]] + sign).
intermediate = balance_iso_year_month(intermediate.year, static_cast<double>(intermediate.month) + sign);
}
if (largest_unit == Unit::Month) {
@ -428,28 +422,24 @@ DateDuration calendar_date_until(VM& vm, StringView calendar, ISODate one, ISODa
}
}
// g. Set intermediate to BalanceISOYearMonth(one.[[Year]] + years, one.[[Month]] + months).
auto intermediate = balance_iso_year_month(static_cast<double>(one.year) + years, static_cast<double>(one.month) + months);
// h. Let constrained be ! RegulateISODate(intermediate.[[Year]], intermediate.[[Month]], one.[[Day]], CONSTRAIN).
auto constrained = MUST(regulate_iso_date(vm, intermediate.year, intermediate.month, one.day, Overflow::Constrain));
// i. Let weeks be 0.
// g. Let weeks be 0.
double weeks = 0;
// OPTIMIZATION: If the largestUnit is DAY, we do not want to enter an ISODateSurpasses loop. The loop would have
// us increment the intermediate ISOYearMonth one day at time, which will take an extremely long
// time if the difference is a large number of years. Instead, we can compute the day difference,
// and convert to weeks if needed.
auto year_month = balance_iso_year_month(static_cast<double>(one.year) + years, static_cast<double>(one.month) + months);
auto regulated_date = MUST(regulate_iso_date(vm, year_month.year, year_month.month, one.day, Overflow::Constrain));
auto days = iso_date_to_epoch_days(two.year, two.month - 1, two.day) - iso_date_to_epoch_days(constrained.year, constrained.month - 1, constrained.day);
auto days = iso_date_to_epoch_days(two.year, two.month - 1, two.day) - iso_date_to_epoch_days(regulated_date.year, regulated_date.month - 1, regulated_date.day);
if (largest_unit == Unit::Week) {
weeks = trunc(days / 7.0);
days = fmod(days, 7.0);
}
// o. Return ! CreateDateDurationRecord(years, months, weeks, days).
// l. Return ! CreateDateDurationRecord(years, months, weeks, days).
return MUST(create_date_duration_record(vm, years, months, weeks, days));
}

View file

@ -18,6 +18,7 @@
#include <LibJS/Runtime/Temporal/PlainDateConstructor.h>
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
#include <LibJS/Runtime/Temporal/PlainTime.h>
#include <LibJS/Runtime/Temporal/PlainYearMonth.h>
#include <LibJS/Runtime/Temporal/TimeZone.h>
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
@ -165,29 +166,65 @@ ThrowCompletionOr<GC::Ref<PlainDate>> to_temporal_date(VM& vm, Value item, Value
return TRY(create_temporal_date(vm, iso_date, move(calendar)));
}
// 3.5.5 ISODateSurpasses ( sign, y1, m1, d1, isoDate2 ), https://tc39.es/proposal-temporal/#sec-temporal-isodatesurpasses
bool iso_date_surpasses(i8 sign, double year1, double month1, double day1, ISODate iso_date2)
// 3.5.5 ISODateSurpasses ( sign, baseDate, isoDate2, years, months, weeks, days ), https://tc39.es/proposal-temporal/#sec-temporal-isodatesurpasses
bool iso_date_surpasses(VM& vm, i8 sign, ISODate base_date, ISODate iso_date2, double years, double months, double weeks, double days)
{
// 1. If y1 ≠ isoDate2.[[Year]], then
// 1. Let yearMonth be BalanceISOYearMonth(baseDate.[[Year]] + years, baseDate.[[Month]] + months).
auto year_month = balance_iso_year_month(static_cast<double>(base_date.year) + years, static_cast<double>(base_date.month) + months);
i32 year1 = 0;
u8 month1 = 0;
u8 day1 = 0;
// 2. If weeks is not 0 or days is not 0, then
if (weeks != 0 || days != 0) {
// a. Let regulatedDate be ! RegulateISODate(yearMonth.[[Year]], yearMonth.[[Month]], baseDate.[[Day]], CONSTRAIN).
auto regulated_date = MUST(regulate_iso_date(vm, year_month.year, year_month.month, base_date.day, Overflow::Constrain));
// b. Let balancedDate be BalanceISODate(regulatedDate.[[Year]], regulatedDate.[[Month]], regulatedDate.[[Day]] + 7 * weeks + days).
auto balanced_date = balance_iso_date(regulated_date.year, regulated_date.month, static_cast<double>(regulated_date.day) + (7 * weeks) + days);
// c. Let y1 be balancedDate.[[Year]].
year1 = balanced_date.year;
// d. Let m1 be balancedDate.[[Month]].
month1 = balanced_date.month;
// e. Let d1 be balancedDate.[[Day]].
day1 = balanced_date.day;
}
// 3. Else,
else {
// a. Let y1 be yearMonth.[[Year]].
year1 = year_month.year;
// b. Let m1 be yearMonth.[[Month]].
month1 = year_month.month;
// c. Let d1 be baseDate.[[Day]].
day1 = base_date.day;
}
// 4. If y1 ≠ isoDate2.[[Year]], then
if (year1 != iso_date2.year) {
// a. If sign × (y1 - isoDate2.[[Year]]) > 0, return true.
if (sign * (year1 - iso_date2.year) > 0)
return true;
}
// 2. Else if m1 ≠ isoDate2.[[Month]], then
// 5. Else if m1 ≠ isoDate2.[[Month]], then
else if (month1 != iso_date2.month) {
// a. If sign × (m1 - isoDate2.[[Month]]) > 0, return true.
if (sign * (month1 - iso_date2.month) > 0)
return true;
}
// 3. Else if d1 ≠ isoDate2.[[Day]], then
// 6. Else if d1 ≠ isoDate2.[[Day]], then
else if (day1 != iso_date2.day) {
// a. If sign × (d1 - isoDate2.[[Day]]) > 0, return true.
if (sign * (day1 - iso_date2.day) > 0)
return true;
}
// 4. Return false.
// 7. Return false.
return false;
}

View file

@ -2,7 +2,7 @@
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
* Copyright (c) 2024-2025, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -37,7 +37,7 @@ private:
ISODate create_iso_date_record(double year, double month, double day);
ThrowCompletionOr<GC::Ref<PlainDate>> to_temporal_date(VM& vm, Value item, Value options = js_undefined());
ThrowCompletionOr<GC::Ref<PlainDate>> create_temporal_date(VM&, ISODate, String calendar, GC::Ptr<FunctionObject> new_target = {});
bool iso_date_surpasses(i8 sign, double year1, double month1, double day1, ISODate iso_date2);
bool iso_date_surpasses(VM&, i8 sign, ISODate base_date, ISODate iso_date2, double years, double months, double weeks, double days);
ThrowCompletionOr<ISODate> regulate_iso_date(VM& vm, double year, double month, double day, Overflow overflow);
bool is_valid_iso_date(double year, double month, double day);
ISODate balance_iso_date(double year, double month, double day);