From 3920194bca348affc191b7b0087794630b66ffd9 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Tue, 5 Aug 2025 10:28:13 -0400 Subject: [PATCH] LibJS: Move regulating and balancing logic into ISODateSurpasses This is an editorial change in the Temporal proposal. See: https://github.com/tc39/proposal-temporal/commit/eddb77f --- Libraries/LibJS/Runtime/Temporal/Calendar.cpp | 28 ++++------- .../LibJS/Runtime/Temporal/PlainDate.cpp | 49 ++++++++++++++++--- Libraries/LibJS/Runtime/Temporal/PlainDate.h | 4 +- 3 files changed, 54 insertions(+), 27 deletions(-) diff --git a/Libraries/LibJS/Runtime/Temporal/Calendar.cpp b/Libraries/LibJS/Runtime/Temporal/Calendar.cpp index 74899063108..098bb272c48 100644 --- a/Libraries/LibJS/Runtime/Temporal/Calendar.cpp +++ b/Libraries/LibJS/Runtime/Temporal/Calendar.cpp @@ -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(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(one.year) + years, static_cast(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(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(one.year) + years, static_cast(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(one.year) + years, static_cast(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)); } diff --git a/Libraries/LibJS/Runtime/Temporal/PlainDate.cpp b/Libraries/LibJS/Runtime/Temporal/PlainDate.cpp index e9e3ffb11fd..2511d1addb9 100644 --- a/Libraries/LibJS/Runtime/Temporal/PlainDate.cpp +++ b/Libraries/LibJS/Runtime/Temporal/PlainDate.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -165,29 +166,65 @@ ThrowCompletionOr> 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(base_date.year) + years, static_cast(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(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; } diff --git a/Libraries/LibJS/Runtime/Temporal/PlainDate.h b/Libraries/LibJS/Runtime/Temporal/PlainDate.h index 694a3ed7590..a88510c6f12 100644 --- a/Libraries/LibJS/Runtime/Temporal/PlainDate.h +++ b/Libraries/LibJS/Runtime/Temporal/PlainDate.h @@ -2,7 +2,7 @@ * Copyright (c) 2021, Idan Horowitz * Copyright (c) 2021-2023, Linus Groh * Copyright (c) 2024, Shannon Booth - * Copyright (c) 2024, Tim Flynn + * Copyright (c) 2024-2025, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -37,7 +37,7 @@ private: ISODate create_iso_date_record(double year, double month, double day); ThrowCompletionOr> to_temporal_date(VM& vm, Value item, Value options = js_undefined()); ThrowCompletionOr> create_temporal_date(VM&, ISODate, String calendar, GC::Ptr 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 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);