diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp index f3a0112d97..4337fbd9d3 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2021-2023, Linus Groh * Copyright (c) 2021, Luke Wilde + * Copyright (c) 2024, Shannon Booth * * SPDX-License-Identifier: BSD-2-Clause */ @@ -1188,321 +1189,371 @@ ThrowCompletionOr move_relative_zoned_date_time(VM& vm, ZonedDat return MUST(create_temporal_zoned_date_time(vm, *intermediate_ns, zoned_date_time.time_zone(), zoned_date_time.calendar())); } -// 7.5.25 RoundDuration ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, increment, unit, roundingMode [ , relativeTo ] ), https://tc39.es/proposal-temporal/#sec-temporal-roundduration -ThrowCompletionOr round_duration(VM& vm, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, u32 increment, StringView unit, StringView rounding_mode, Object* relative_to_object) +// 7.5.27 RoundDuration ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, increment, unit, roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ , timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] ), https://tc39.es/proposal-temporal/#sec-temporal-roundduration +// FIXME: Support calendarRec, zonedRelativeTo, timeZoneRec, precalculatedPlainDateTime +ThrowCompletionOr round_duration(VM& vm, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, u32 increment, StringView unit, StringView rounding_mode, Object* plain_relative_to_object) { auto& realm = *vm.current_realm(); - Object* calendar = nullptr; - double fractional_seconds = 0; + Object* calendar = nullptr; // FIXME: Should come from calendarRec - // 1. If relativeTo is not present, set relativeTo to undefined. - // NOTE: `relative_to_object` and `relative_to` in the various code paths below are all the same as far as the - // spec is concerned, but the latter is more strictly typed for convenience. - PlainDate* relative_to = nullptr; + double fractional_seconds = 0; + double fractional_days = 0; + + // FIXME: 1. Assert: If either of plainRelativeTo or zonedRelativeTo are present and not undefined, calendarRec is not undefined. + // FIXME: 2. Assert: If zonedRelativeTo is present and not undefined, timeZoneRec is not undefined. + + // 3. If plainRelativeTo is not present, set plainRelativeTo to undefined. + // NOTE: `plain_relative_to_object` and `plain_relative_to` in the various code paths below are all the same as far as the + // spec is concerned, but the latter is more strictly typed for convenience. + PlainDate* plain_relative_to = nullptr; + + // FIXME: 4. If zonedRelativeTo is not present, set zonedRelativeTo to undefined. + ZonedDateTime* zoned_relative_to = nullptr; + + // FIXME: 5. If precalculatedPlainDateTime is not present, set precalculatedPlainDateTime to undefined. // FIXME: assuming "smallestUnit" as the option name here leads to confusing error messages in some cases: // > new Temporal.Duration().total({ unit: "month" }) // Uncaught exception: [RangeError] month is not a valid value for option smallestUnit - // 2. If unit is "year", "month", or "week", and relativeTo is undefined, then - if (unit.is_one_of("year"sv, "month"sv, "week"sv) && !relative_to_object) { - // a. Throw a RangeError exception. - return vm.throw_completion(ErrorType::OptionIsNotValidValue, unit, "smallestUnit"sv); + // 6. If unit is "year", "month", or "week", then + if (unit.is_one_of("year"sv, "month"sv, "week"sv)) { + // a. If plainRelativeTo is undefined, throw a RangeError exception. + if (!plain_relative_to_object) + return vm.throw_completion(ErrorType::OptionIsNotValidValue, unit, "smallestUnit"sv); + + // FIXME: b. Assert: CalendarMethodsRecordHasLookedUp(calendarRec, dateAdd) is true. + // FIXME: c. Assert: CalendarMethodsRecordHasLookedUp(calendarRec, dateUntil) is true. } - // 3. Let zonedRelativeTo be undefined. - ZonedDateTime* zoned_relative_to = nullptr; - - // 4. If relativeTo is not undefined, then - if (relative_to_object) { - // a. If relativeTo has an [[InitializedTemporalZonedDateTime]] internal slot, then - if (is(relative_to_object)) { - auto* relative_to_zoned_date_time = static_cast(relative_to_object); - - // i. Set zonedRelativeTo to relativeTo. + // FIXME: AD-HOC, should be coming from arguments given to this function. + if (plain_relative_to_object) { + if (is(plain_relative_to_object)) { + auto* relative_to_zoned_date_time = static_cast(plain_relative_to_object); zoned_relative_to = relative_to_zoned_date_time; - - // ii. Set relativeTo to ? ToTemporalDate(relativeTo). - relative_to = TRY(to_temporal_date(vm, relative_to_object)); + plain_relative_to = TRY(to_temporal_date(vm, plain_relative_to_object)); + } else { + VERIFY(is(plain_relative_to_object)); + plain_relative_to = verify_cast(plain_relative_to_object); } - // b. Else, - else { - // i. Assert: relativeTo has an [[InitializedTemporalDate]] internal slot. - VERIFY(is(relative_to_object)); - - relative_to = static_cast(relative_to_object); - } - - // c. Let calendar be relativeTo.[[Calendar]]. - calendar = &relative_to->calendar(); + calendar = &plain_relative_to->calendar(); } - // 5. Else, - // a. NOTE: calendar will not be used below. - // 6. If unit is one of "year", "month", "week", or "day", then + // 7. If unit is one of "year", "month", "week", or "day", then if (unit.is_one_of("year"sv, "month"sv, "week"sv, "day"sv)) { - // a. Let nanoseconds be ! TotalDurationNanoseconds(0, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, 0). + + // a. Let nanoseconds be TotalDurationNanoseconds(hours, minutes, seconds, milliseconds, microseconds, nanoseconds). + // FIXME: We shouldn't be passing through 0 days and 0 offset digit auto nanoseconds_bigint = total_duration_nanoseconds(0, hours, minutes, seconds, milliseconds, microseconds, Crypto::SignedBigInteger { nanoseconds }, 0); - // b. Let intermediate be undefined. - ZonedDateTime* intermediate = nullptr; - - // c. If zonedRelativeTo is not undefined, then + // b. If zonedRelativeTo is not undefined, then if (zoned_relative_to) { - // i. Let intermediate be ? MoveRelativeZonedDateTime(zonedRelativeTo, years, months, weeks, days). - intermediate = TRY(move_relative_zoned_date_time(vm, *zoned_relative_to, years, months, weeks, days)); + // i. Let intermediate be ? MoveRelativeZonedDateTime(zonedRelativeTo, calendarRec, timeZoneRec, years, months, weeks, days, precalculatedPlainDateTime). + // FIXME: Pass through calendarRecord, timeZoneRec and precalculatedPlainDateTime + auto* intermediate = TRY(move_relative_zoned_date_time(vm, *zoned_relative_to, years, months, weeks, days)); + + // ii. Let result be ? NanosecondsToDays(nanoseconds, intermediate, timeZoneRec). + // FIXME: Pass through timeZoneRec + auto result = TRY(nanoseconds_to_days(vm, nanoseconds_bigint, intermediate)); + + // iii. Let fractionalDays be days + result.[[Days]] + result.[[Nanoseconds]] / result.[[DayLength]]. + auto nanoseconds_division_result = result.nanoseconds.divided_by(Crypto::UnsignedBigInteger { result.day_length }); + fractional_days = days + result.days + nanoseconds_division_result.quotient.to_double() + nanoseconds_division_result.remainder.to_double() / result.day_length; + } + // c. Else, + else { + // i. Let fractionalDays be days + nanoseconds / nsPerDay. + auto nanoseconds_division_result = nanoseconds_bigint.divided_by(ns_per_day_bigint); + fractional_days = days + nanoseconds_division_result.quotient.to_double() + (nanoseconds_division_result.remainder.to_double() / ns_per_day); } - // d. Let result be ? NanosecondsToDays(nanoseconds, intermediate). - auto result = TRY(nanoseconds_to_days(vm, nanoseconds_bigint, intermediate)); - - // e. Set days to days + result.[[Days]] + result.[[Nanoseconds]] / result.[[DayLength]]. - auto nanoseconds_division_result = result.nanoseconds.divided_by(Crypto::UnsignedBigInteger { result.day_length }); - days += result.days + nanoseconds_division_result.quotient.to_double() + nanoseconds_division_result.remainder.to_double() / result.day_length; - - // f. Set hours, minutes, seconds, milliseconds, microseconds, and nanoseconds to 0. + // d. Set days, hours, minutes, seconds, milliseconds, microseconds, and nanoseconds to 0. + days = 0; hours = 0; minutes = 0; seconds = 0; milliseconds = 0; microseconds = 0; nanoseconds = 0; + + // e. Assert: fractionalSeconds is not used below. } - // 7. Else, + // 8. Else, else { // a. Let fractionalSeconds be nanoseconds × 10^-9 + microseconds × 10^-6 + milliseconds × 10^-3 + seconds. fractional_seconds = nanoseconds * 0.000000001 + microseconds * 0.000001 + milliseconds * 0.001 + seconds; + + // b. Assert: fractionalDays is not used below. } - // 8. Let remainder be undefined. - double remainder = 0; + // 9. Let total be unset. + double total = 0; - // 9. If unit is "year", then + // 10. If unit is "year", then if (unit == "year"sv) { - VERIFY(relative_to); + VERIFY(plain_relative_to); // a. Let yearsDuration be ! CreateTemporalDuration(years, 0, 0, 0, 0, 0, 0, 0, 0, 0). auto* years_duration = MUST(create_temporal_duration(vm, years, 0, 0, 0, 0, 0, 0, 0, 0, 0)); - // b. Let dateAdd be ? GetMethod(calendar, "dateAdd"). + // FIXME: b. Let yearsLater be ? AddDate(calendarRec, plainRelativeTo, yearsDuration). auto date_add = TRY(Value(calendar).get_method(vm, vm.names.dateAdd)); + auto* years_later = TRY(calendar_date_add(vm, *calendar, plain_relative_to, *years_duration, nullptr, date_add)); - // c. Let yearsLater be ? CalendarDateAdd(calendar, relativeTo, yearsDuration, undefined, dateAdd). - auto* years_later = TRY(calendar_date_add(vm, *calendar, relative_to, *years_duration, nullptr, date_add)); - - // d. Let yearsMonthsWeeks be ! CreateTemporalDuration(years, months, weeks, 0, 0, 0, 0, 0, 0, 0). + // c. Let yearsMonthsWeeks be ! CreateTemporalDuration(years, months, weeks, 0, 0, 0, 0, 0, 0, 0). auto* years_months_weeks = MUST(create_temporal_duration(vm, years, months, weeks, 0, 0, 0, 0, 0, 0, 0)); - // e. Let yearsMonthsWeeksLater be ? CalendarDateAdd(calendar, relativeTo, yearsMonthsWeeks, undefined, dateAdd). - auto* years_months_weeks_later = TRY(calendar_date_add(vm, *calendar, relative_to, *years_months_weeks, nullptr, date_add)); + // FIXME: d. Let yearsMonthsWeeksLater be ? AddDate(calendarRec, plainRelativeTo, yearsMonthsWeeks). + auto* years_months_weeks_later = TRY(calendar_date_add(vm, *calendar, plain_relative_to, *years_months_weeks, nullptr, date_add)); - // f. Let monthsWeeksInDays be DaysUntil(yearsLater, yearsMonthsWeeksLater). + // e. Let monthsWeeksInDays be DaysUntil(yearsLater, yearsMonthsWeeksLater). auto months_weeks_in_days = days_until(*years_later, *years_months_weeks_later); - // g. Set relativeTo to yearsLater. - relative_to = years_later; + // f. Set plainRelativeTo to yearsLater. + plain_relative_to = years_later; - // h. Let days be days + monthsWeeksInDays. - days += months_weeks_in_days; + // g. Set fractionalDays to fractionalDays + monthsWeeksInDays. + fractional_days = fractional_days + months_weeks_in_days; - // i. Let wholeDaysDuration be ? CreateTemporalDuration(0, 0, 0, truncate(days), 0, 0, 0, 0, 0, 0). - auto* whole_days_duration = TRY(create_temporal_duration(vm, 0, 0, 0, trunc(days), 0, 0, 0, 0, 0, 0)); + // h. Let isoResult be ! AddISODate(plainRelativeTo.[[ISOYear]]. plainRelativeTo.[[ISOMonth]], plainRelativeTo.[[ISODay]], 0, 0, 0, truncate(fractionalDays), "constrain"). + auto iso_result = MUST(add_iso_date(vm, plain_relative_to->iso_year(), plain_relative_to->iso_month(), plain_relative_to->iso_day(), 0, 0, 0, trunc(fractional_days), "constrain"sv)); - // j. Let wholeDaysLater be ? CalendarDateAdd(calendar, relativeTo, wholeDaysDuration, undefined, dateAdd). - auto* whole_days_later = TRY(calendar_date_add(vm, *calendar, relative_to, *whole_days_duration, nullptr, date_add)); + // i. Let wholeDaysLater be ? CreateTemporalDate(isoResult.[[Year]], isoResult.[[Month]], isoResult.[[Day]], calendarRec.[[Receiver]]). + // FIXME: Pass through receiver from calendarRec + auto* whole_days_later = TRY(create_temporal_date(vm, iso_result.year, iso_result.month, iso_result.day, *calendar)); - // k. Let untilOptions be OrdinaryObjectCreate(null). + // j. Let untilOptions be OrdinaryObjectCreate(null). auto until_options = Object::create(realm, nullptr); - // l. Perform ! CreateDataPropertyOrThrow(untilOptions, "largestUnit", "year"). + // k. Perform ! CreateDataPropertyOrThrow(untilOptions, "largestUnit", "year"). MUST(until_options->create_data_property_or_throw(vm.names.largestUnit, PrimitiveString::create(vm, "year"_string))); - // m. Let timePassed be ? CalendarDateUntil(calendar, relativeTo, wholeDaysLater, untilOptions). - auto* time_passed = TRY(calendar_date_until(vm, *calendar, relative_to, whole_days_later, *until_options)); + // l. Let timePassed be ? DifferenceDate(calendarRec, plainRelativeTo, wholeDaysLater, untilOptions). + // FIXME: Pass through calendarRec + auto time_passed = TRY(difference_date(vm, *calendar, *plain_relative_to, *whole_days_later, *until_options)); - // n. Let yearsPassed be timePassed.[[Years]]. + // m. Let yearsPassed be timePassed.[[Years]]. auto years_passed = time_passed->years(); - // o. Set years to years + yearsPassed. + // n. Set years to years + yearsPassed. years += years_passed; - // p. Let oldRelativeTo be relativeTo. - auto* old_relative_to = relative_to; - - // q. Let yearsDuration be ! CreateTemporalDuration(yearsPassed, 0, 0, 0, 0, 0, 0, 0, 0, 0). + // o. Let yearsDuration be ! CreateTemporalDuration(yearsPassed, 0, 0, 0, 0, 0, 0, 0, 0, 0). years_duration = MUST(create_temporal_duration(vm, years_passed, 0, 0, 0, 0, 0, 0, 0, 0, 0)); - // r. Set relativeTo to ? CalendarDateAdd(calendar, relativeTo, yearsDuration, undefined, dateAdd). - relative_to = TRY(calendar_date_add(vm, *calendar, relative_to, *years_duration, nullptr, date_add)); + // p. Let moveResult be ? MoveRelativeDate(calendarRec, plainRelativeTo, yearsDuration). + // FIXME: Pass through calendarRec instead of date_add + auto move_result = TRY(move_relative_date(vm, *calendar, *plain_relative_to, *years_duration, date_add)); - // s. Let daysPassed be DaysUntil(oldRelativeTo, relativeTo). - auto days_passed = days_until(*old_relative_to, *relative_to); + // q. Set plainRelativeTo to moveResult.[[RelativeTo]]. + auto plain_relative_to = move_result.relative_to; - // t. Set days to days - daysPassed. - days -= days_passed; + // r. Let daysPassed be moveResult.[[Days]]. + auto days_passed = move_result.days; - // u. If days < 0, let sign be -1; else, let sign be 1. - auto sign = days < 0 ? -1 : 1; + // s. Set fractionalDays to fractionalDays - daysPassed. + fractional_days -= days_passed; - // v. Let oneYear be ! CreateTemporalDuration(sign, 0, 0, 0, 0, 0, 0, 0, 0, 0). + // t. If fractionalDays < 0, let sign be -1; else, let sign be 1. + auto sign = fractional_days < 0 ? -1 : 1; + + // u. Let oneYear be ! CreateTemporalDuration(sign, 0, 0, 0, 0, 0, 0, 0, 0, 0). auto* one_year = MUST(create_temporal_duration(vm, sign, 0, 0, 0, 0, 0, 0, 0, 0, 0)); - // w. Let moveResult be ? MoveRelativeDate(calendar, relativeTo, oneYear, dateAdd). - auto move_result = TRY(move_relative_date(vm, *calendar, *relative_to, *one_year, date_add)); + // v. Set moveResult to ? MoveRelativeDate(calendarRec, plainRelativeTo, oneYear). + // FIXME:: pass through calendarRec + move_result = TRY(move_relative_date(vm, *calendar, *plain_relative_to, *one_year, date_add)); - // x. Let oneYearDays be moveResult.[[Days]]. + // w. Let oneYearDays be moveResult.[[Days]]. auto one_year_days = move_result.days; - // y. Let fractionalYears be years + days / abs(oneYearDays). - auto fractional_years = years + days / fabs(one_year_days); + // x. If oneYearDays = 0, throw a RangeError exception. + if (one_year_days == 0) + return vm.throw_completion(ErrorType::TemporalInvalidCalendarFunctionResult, "dateAdd", "result implying a year is zero days long"); + + // y. Let fractionalYears be years + fractionalDays / abs(oneYearDays). + auto fractional_years = years + fractional_days / fabs(one_year_days); // z. Set years to RoundNumberToIncrement(fractionalYears, increment, roundingMode). years = round_number_to_increment(fractional_years, increment, rounding_mode); - // aa. Set remainder to fractionalYears - years. - remainder = fractional_years - years; + // aa. Set total to fractionalYears. + total = fractional_years; - // ab. Set months, weeks, and days to 0. + // ab. Set months and weeks to 0. months = 0; weeks = 0; - days = 0; } // 10. Else if unit is "month", then else if (unit == "month"sv) { - VERIFY(relative_to); + VERIFY(plain_relative_to); // a. Let yearsMonths be ! CreateTemporalDuration(years, months, 0, 0, 0, 0, 0, 0, 0, 0). auto* years_months = MUST(create_temporal_duration(vm, years, months, 0, 0, 0, 0, 0, 0, 0, 0)); - // b. Let dateAdd be ? GetMethod(calendar, "dateAdd"). + // FIXME: b. Let yearsMonthsLater be ? AddDate(calendarRec, plainRelativeTo, yearsMonths). auto date_add = TRY(Value(calendar).get_method(vm, vm.names.dateAdd)); + auto* years_months_later = TRY(calendar_date_add(vm, *calendar, plain_relative_to, *years_months, nullptr, date_add)); - // c. Let yearsMonthsLater be ? CalendarDateAdd(calendar, relativeTo, yearsMonths, undefined, dateAdd). - auto* years_months_later = TRY(calendar_date_add(vm, *calendar, relative_to, *years_months, nullptr, date_add)); - - // d. Let yearsMonthsWeeks be ! CreateTemporalDuration(years, months, weeks, 0, 0, 0, 0, 0, 0, 0). + // c. Let yearsMonthsWeeks be ! CreateTemporalDuration(years, months, weeks, 0, 0, 0, 0, 0, 0, 0). auto* years_months_weeks = MUST(create_temporal_duration(vm, years, months, weeks, 0, 0, 0, 0, 0, 0, 0)); - // e. Let yearsMonthsWeeksLater be ? CalendarDateAdd(calendar, relativeTo, yearsMonthsWeeks, undefined, dateAdd). - auto* years_months_weeks_later = TRY(calendar_date_add(vm, *calendar, relative_to, *years_months_weeks, nullptr, date_add)); + // FIXME: d. Let yearsMonthsWeeksLater be ? AddDate(calendarRec, plainRelativeTo, yearsMonthsWeeks). + auto* years_months_weeks_later = TRY(calendar_date_add(vm, *calendar, plain_relative_to, *years_months_weeks, nullptr, date_add)); - // f. Let weeksInDays be DaysUntil(yearsMonthsLater, yearsMonthsWeeksLater). + // e. Let weeksInDays be DaysUntil(yearsMonthsLater, yearsMonthsWeeksLater). auto weeks_in_days = days_until(*years_months_later, *years_months_weeks_later); - // g. Set relativeTo to yearsMonthsLater. - relative_to = years_months_later; + // f. Set plainRelativeTo to yearsMonthsLater. + plain_relative_to = years_months_later; - // h. Let days be days + weeksInDays. - days += weeks_in_days; + // g. Set fractionalDays to fractionalDays + weeksInDays. + fractional_days += weeks_in_days; - // i. If days < 0, let sign be -1; else, let sign be 1. - auto sign = days < 0 ? -1 : 1; + // h. Let isoResult be ! AddISODate(plainRelativeTo.[[ISOYear]], plainRelativeTo.[[ISOMonth]], plainRelativeTo.[[ISODay]], 0, 0, 0, truncate(fractionalDays), "constrain"). + auto iso_result = MUST(add_iso_date(vm, plain_relative_to->iso_year(), plain_relative_to->iso_month(), plain_relative_to->iso_day(), 0, 0, 0, trunc(fractional_days), "constrain"sv)); - // j. Let oneMonth be ! CreateTemporalDuration(0, sign, 0, 0, 0, 0, 0, 0, 0, 0). + // i. Let wholeDaysLater be ? CreateTemporalDate(isoResult.[[Year]], isoResult.[[Month]], isoResult.[[Day]], calendarRec.[[Receiver]]). + // FIXME: Pass through calendarRec + auto* whole_days_later = TRY(create_temporal_date(vm, iso_result.year, iso_result.month, iso_result.day, *calendar)); // FIXME: receiver + + // j. Let untilOptions be OrdinaryObjectCreate(null). + auto until_options = Object::create(realm, nullptr); + + // k. Perform ! CreateDataPropertyOrThrow(untilOptions, "largestUnit", "month"). + MUST(until_options->create_data_property_or_throw(vm.names.largestUnit, PrimitiveString::create(vm, "month"_string))); + + // l. Let timePassed be ? DifferenceDate(calendarRec, plainRelativeTo, wholeDaysLater, untilOptions). + // FIXME: Pass through receiver from calendarRec + auto time_passed = TRY(difference_date(vm, *calendar, *plain_relative_to, *whole_days_later, *until_options)); + + // m. Let monthsPassed be timePassed.[[Months]]. + auto months_passed = time_passed->months(); + + // n. Set months to months + monthsPassed. + months += months_passed; + + // o. Let monthsPassedDuration be ! CreateTemporalDuration(0, monthsPassed, 0, 0, 0, 0, 0, 0, 0, 0). + auto* months_passed_duration = MUST(create_temporal_duration(vm, 0, months_passed, 0, 0, 0, 0, 0, 0, 0, 0)); + + // p. Let moveResult be ? MoveRelativeDate(calendarRec, plainRelativeTo, monthsPassedDuration). + // FIXME: Pass through calendarRec + auto move_result = TRY(move_relative_date(vm, *calendar, *plain_relative_to, *months_passed_duration, date_add)); + + // q. Set plainRelativeTo to moveResult.[[RelativeTo]]. + plain_relative_to = move_result.relative_to; + + // r. Let daysPassed be moveResult.[[Days]]. + auto days_passed = move_result.days; + + // s. Set fractionalDays to fractionalDays - daysPassed. + fractional_days -= days_passed; + + // t. If fractionalDays < 0, let sign be -1; else, let sign be 1. + auto sign = fractional_days < 0 ? -1 : 1; + + // u. Let oneMonth be ! CreateTemporalDuration(0, sign, 0, 0, 0, 0, 0, 0, 0, 0). auto* one_month = MUST(create_temporal_duration(vm, 0, sign, 0, 0, 0, 0, 0, 0, 0, 0)); - // k. Let moveResult be ? MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd). - auto move_result = TRY(move_relative_date(vm, *calendar, *relative_to, *one_month, date_add)); + // v. Let moveResult be ? MoveRelativeDate(calendarRec, plainRelativeTo, oneMonth). + // FIXME: spec bug, this should be set. + move_result = TRY(move_relative_date(vm, *calendar, *plain_relative_to, *one_month, date_add)); - // l. Set relativeTo to moveResult.[[RelativeTo]]. - relative_to = move_result.relative_to.cell(); - - // m. Let oneMonthDays be moveResult.[[Days]]. + // w. Let oneMonthDays be moveResult.[[Days]]. auto one_month_days = move_result.days; - // n. Repeat, while abs(days) ≥ abs(oneMonthDays), - while (fabs(days) >= fabs(one_month_days)) { - // i. Set months to months + sign. - months += sign; + // x. If oneMonthDays = 0, throw a RangeError exception. + if (one_month_days == 0) + return vm.throw_completion(ErrorType::TemporalInvalidCalendarFunctionResult, "dateAdd", "result implying a month is zero days long"); - // ii. Set days to days - oneMonthDays. - days -= one_month_days; + // y. Let fractionalMonths be months + fractionalDays / abs(oneMonthDays). + auto fractional_months = months + fractional_days / fabs(one_month_days); - // iii. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd). - move_result = TRY(move_relative_date(vm, *calendar, *relative_to, *one_month, date_add)); - - // iv. Set relativeTo to moveResult.[[RelativeTo]]. - relative_to = move_result.relative_to.cell(); - - // v. Set oneMonthDays to moveResult.[[Days]]. - one_month_days = move_result.days; - } - - // o. Let fractionalMonths be months + days / abs(oneMonthDays). - auto fractional_months = months + days / fabs(one_month_days); - - // p. Set months to RoundNumberToIncrement(fractionalMonths, increment, roundingMode). + // z. Set months to RoundNumberToIncrement(fractionalMonths, increment, roundingMode). months = round_number_to_increment(fractional_months, increment, rounding_mode); - // q. Set remainder to fractionalMonths - months. - remainder = fractional_months - months; + // aa. Set total to fractionalMonths. + total = fractional_months; - // r. Set weeks and days to 0. + // ab. Set weeks to 0. weeks = 0; - days = 0; } // 11. Else if unit is "week", then else if (unit == "week"sv) { - VERIFY(relative_to); + VERIFY(plain_relative_to); - // a. If days < 0, let sign be -1; else, let sign be 1. - auto sign = days < 0 ? -1 : 1; + // a. Let isoResult be ! AddISODate(plainRelativeTo.[[ISOYear]], plainRelativeTo.[[ISOMonth]], plainRelativeTo.[[ISODay]], 0, 0, 0, truncate(fractionalDays), "constrain"). + auto iso_result = MUST(add_iso_date(vm, plain_relative_to->iso_year(), plain_relative_to->iso_month(), plain_relative_to->iso_day(), 0, 0, 0, trunc(fractional_days), "constrain"sv)); - // b. Let oneWeek be ! CreateTemporalDuration(0, 0, sign, 0, 0, 0, 0, 0, 0, 0). + // b. Let wholeDaysLater be ? CreateTemporalDate(isoResult.[[Year]], isoResult.[[Month]], isoResult.[[Day]], calendarRec.[[Receiver]]). + // FIXME: Pass through receiver from calendarRec + auto* whole_days_later = TRY(create_temporal_date(vm, iso_result.year, iso_result.month, iso_result.day, *calendar)); + + // c. Let untilOptions be OrdinaryObjectCreate(null). + auto until_options = Object::create(realm, nullptr); + + // d. Perform ! CreateDataPropertyOrThrow(untilOptions, "largestUnit", "week"). + MUST(until_options->create_data_property_or_throw(vm.names.largestUnit, PrimitiveString::create(vm, "month"_string))); + + // e. Let timePassed be ? DifferenceDate(calendarRec, plainRelativeTo, wholeDaysLater, untilOptions). + // FIXME: Pass through calendarRec + auto time_passed = TRY(difference_date(vm, *calendar, *plain_relative_to, *whole_days_later, *until_options)); + + // f. Let weeksPassed be timePassed.[[Weeks]]. + auto weeks_passed = time_passed->weeks(); + + // g. Set weeks to weeks + weeksPassed. + weeks += weeks_passed; + + // h. Let weeksPassedDuration be ! CreateTemporalDuration(0, 0, weeksPassed, 0, 0, 0, 0, 0, 0, 0). + auto* weeks_passed_duration = MUST(create_temporal_duration(vm, 0, 0, weeks_passed, 0, 0, 0, 0, 0, 0, 0)); + + // FIXME: i. Let moveResult be ? MoveRelativeDate(calendarRec, plainRelativeTo, weeksPassedDuration). + auto date_add = TRY(Value(calendar).get_method(vm, vm.names.dateAdd)); + auto move_result = TRY(move_relative_date(vm, *calendar, *plain_relative_to, *weeks_passed_duration, date_add)); + + // j. Set plainRelativeTo to moveResult.[[RelativeTo]]. + plain_relative_to = move_result.relative_to; + + // k. Let daysPassed be moveResult.[[Days]]. + auto days_passed = move_result.days; + + // l. Set fractionalDays to fractionalDays - daysPassed. + fractional_days -= days_passed; + + // m. If fractionalDays < 0, let sign be -1; else, let sign be 1. + auto sign = fractional_days < 0 ? -1 : 1; + + // n. Let oneWeek be ! CreateTemporalDuration(0, 0, sign, 0, 0, 0, 0, 0, 0, 0). auto* one_week = MUST(create_temporal_duration(vm, 0, 0, sign, 0, 0, 0, 0, 0, 0, 0)); - // c. Let dateAdd be ? GetMethod(calendar, "dateAdd"). - auto date_add = TRY(Value(calendar).get_method(vm, vm.names.dateAdd)); + // o. Let moveResult be ? MoveRelativeDate(calendarRec, plainRelativeTo, oneWeek). + // FIXME: spec bug, should be set + move_result = TRY(move_relative_date(vm, *calendar, *plain_relative_to, *one_week, date_add)); - // d. Let moveResult be ? MoveRelativeDate(calendar, relativeTo, oneWeek, dateAdd). - auto move_result = TRY(move_relative_date(vm, *calendar, *relative_to, *one_week, date_add)); - - // e. Set relativeTo to moveResult.[[RelativeTo]]. - relative_to = move_result.relative_to.cell(); - - // f. Let oneWeekDays be moveResult.[[Days]]. + // p. Let oneWeekDays be moveResult.[[Days]]. auto one_week_days = move_result.days; - // g. Repeat, while abs(days) ≥ abs(oneWeekDays), - while (fabs(days) >= fabs(one_week_days)) { - // i. Set weeks to weeks + sign. - weeks += sign; + // q. If oneWeekDays = 0, throw a RangeError exception. + if (one_week_days == 0) + return vm.throw_completion(ErrorType::TemporalInvalidCalendarFunctionResult, "dateAdd", "result implying a month is zero days long"); - // ii. Set days to days - oneWeekDays. - days -= one_week_days; + // r. Let fractionalWeeks be weeks + fractionalDays / abs(oneWeekDays). + auto fractional_weeks = weeks + fractional_days / fabs(one_week_days); - // iii. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneWeek, dateAdd). - move_result = TRY(move_relative_date(vm, *calendar, *relative_to, *one_week, date_add)); - - // iv. Set relativeTo to moveResult.[[RelativeTo]]. - relative_to = move_result.relative_to.cell(); - - // v. Set oneWeekDays to moveResult.[[Days]]. - one_week_days = move_result.days; - } - - // h. Let fractionalWeeks be weeks + days / abs(oneWeekDays). - auto fractional_weeks = weeks + days / fabs(one_week_days); - - // i. Set weeks to RoundNumberToIncrement(fractionalWeeks, increment, roundingMode). + // s. Set weeks to RoundNumberToIncrement(fractionalWeeks, increment, roundingMode). weeks = round_number_to_increment(fractional_weeks, increment, rounding_mode); - // j. Set remainder to fractionalWeeks - weeks. - remainder = fractional_weeks - weeks; - - // k. Set days to 0. - days = 0; + // t. Set total to fractionalWeeks. + total = fractional_weeks; } // 12. Else if unit is "day", then else if (unit == "day"sv) { - // a. Let fractionalDays be days. - auto fractional_days = days; + // a. Set days to RoundNumberToIncrement(fractionalDays, increment, roundingMode). + days = round_number_to_increment(fractional_days, increment, rounding_mode); - // b. Set days to RoundNumberToIncrement(days, increment, roundingMode). - days = round_number_to_increment(days, increment, rounding_mode); - - // c. Set remainder to fractionalDays - days. - remainder = fractional_days - days; + // b. Set total to fractionalDays. + total = fractional_days; } // 13. Else if unit is "hour", then else if (unit == "hour"sv) { @@ -1512,8 +1563,8 @@ ThrowCompletionOr round_duration(VM& vm, double years, double m // b. Set hours to RoundNumberToIncrement(fractionalHours, increment, roundingMode). hours = round_number_to_increment(fractional_hours, increment, rounding_mode); - // c. Set remainder to fractionalHours - hours. - remainder = fractional_hours - hours; + // c. Set total to fractionalHours. + total = fractional_hours; // d. Set minutes, seconds, milliseconds, microseconds, and nanoseconds to 0. minutes = 0; @@ -1530,8 +1581,8 @@ ThrowCompletionOr round_duration(VM& vm, double years, double m // b. Set minutes to RoundNumberToIncrement(fractionalMinutes, increment, roundingMode). minutes = round_number_to_increment(fractional_minutes, increment, rounding_mode); - // c. Set remainder to fractionalMinutes - minutes. - remainder = fractional_minutes - minutes; + // c. Set total to fractionalMinutes. + total = fractional_minutes; // d. Set seconds, milliseconds, microseconds, and nanoseconds to 0. seconds = 0; @@ -1544,8 +1595,8 @@ ThrowCompletionOr round_duration(VM& vm, double years, double m // a. Set seconds to RoundNumberToIncrement(fractionalSeconds, increment, roundingMode). seconds = round_number_to_increment(fractional_seconds, increment, rounding_mode); - // b. Set remainder to fractionalSeconds - seconds. - remainder = fractional_seconds - seconds; + // b. Set total to fractionalSeconds. + total = fractional_seconds; // c. Set milliseconds, microseconds, and nanoseconds to 0. milliseconds = 0; @@ -1560,8 +1611,8 @@ ThrowCompletionOr round_duration(VM& vm, double years, double m // b. Set milliseconds to RoundNumberToIncrement(fractionalMilliseconds, increment, roundingMode). milliseconds = round_number_to_increment(fractional_milliseconds, increment, rounding_mode); - // c. Set remainder to fractionalMilliseconds - milliseconds. - remainder = fractional_milliseconds - milliseconds; + // c. Set total to fractionalMilliseconds. + total = fractional_milliseconds; // d. Set microseconds and nanoseconds to 0. microseconds = 0; @@ -1575,8 +1626,8 @@ ThrowCompletionOr round_duration(VM& vm, double years, double m // b. Set microseconds to RoundNumberToIncrement(fractionalMicroseconds, increment, roundingMode). microseconds = round_number_to_increment(fractional_microseconds, increment, rounding_mode); - // c. Set remainder to fractionalMicroseconds - microseconds. - remainder = fractional_microseconds - microseconds; + // c. Set total to fractionalMicroseconds. + total = fractional_microseconds; // d. Set nanoseconds to 0. nanoseconds = 0; @@ -1586,21 +1637,18 @@ ThrowCompletionOr round_duration(VM& vm, double years, double m // a. Assert: unit is "nanosecond". VERIFY(unit == "nanosecond"sv); - // b. Set remainder to nanoseconds. - remainder = nanoseconds; + // b. Set total to nanoseconds. + total = nanoseconds; // c. Set nanoseconds to RoundNumberToIncrement(nanoseconds, increment, roundingMode). nanoseconds = round_number_to_increment(nanoseconds, increment, rounding_mode); - - // d. Set remainder to remainder - nanoseconds. - remainder -= nanoseconds; } - // 19. Let duration be ? CreateDurationRecord(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). + // 20. Let duration be ? CreateDurationRecord(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). auto duration = TRY(create_duration_record(vm, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds)); - // 20. Return the Record { [[DurationRecord]]: duration, [[Remainder]]: remainder }. - return RoundedDuration { .duration_record = duration, .remainder = remainder }; + // 21. Return the Record { [[DurationRecord]]: duration, [[Total]]: total }. + return RoundedDuration { .duration_record = duration, .total = total }; } // 7.5.26 AdjustRoundedDurationDays ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, increment, unit, roundingMode, relativeTo ), https://tc39.es/proposal-temporal/#sec-temporal-adjustroundeddurationdays diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Duration.h b/Userland/Libraries/LibJS/Runtime/Temporal/Duration.h index dfdeccfe0e..5d2c5bd942 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Duration.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Duration.h @@ -107,7 +107,7 @@ struct MoveRelativeDateResult { // Used by RoundDuration to temporarily hold values struct RoundedDuration { DurationRecord duration_record; - double remainder; + double total; }; // Table 8: Duration Record Fields, https://tc39.es/proposal-temporal/#table-temporal-duration-record-fields diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp index afd5315444..9785540557 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp @@ -445,6 +445,7 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::round) } // 7.3.21 Temporal.Duration.prototype.total ( totalOf ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.total +// FIXME: This is well out of date with the spec. JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::total) { auto& realm = *vm.current_realm(); @@ -501,67 +502,8 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::total) // 12. Let roundRecord be ? RoundDuration(unbalanceResult.[[Years]], unbalanceResult.[[Months]], unbalanceResult.[[Weeks]], balanceResult.[[Days]], balanceResult.[[Hours]], balanceResult.[[Minutes]], balanceResult.[[Seconds]], balanceResult.[[Milliseconds]], balanceResult.[[Microseconds]], balanceResult.[[Nanoseconds]], 1, unit, "trunc", relativeTo). auto round_record = TRY(round_duration(vm, unbalance_result.years, unbalance_result.months, unbalance_result.weeks, balance_result.days, balance_result.hours, balance_result.minutes, balance_result.seconds, balance_result.milliseconds, balance_result.microseconds, balance_result.nanoseconds, 1, *unit, "trunc"sv, relative_to.is_object() ? &relative_to.as_object() : nullptr)); - // 13. Let roundResult be roundRecord.[[DurationRecord]]. - auto& round_result = round_record.duration_record; - - double whole; - - // 14. If unit is "year", then - if (unit == "year"sv) { - // a. Let whole be roundResult.[[Years]]. - whole = round_result.years; - } - // 15. Else if unit is "month", then - else if (unit == "month"sv) { - // a. Let whole be roundResult.[[Months]]. - whole = round_result.months; - } - // 16. Else if unit is "week", then - else if (unit == "week"sv) { - // a. Let whole be roundResult.[[Weeks]]. - whole = round_result.weeks; - } - // 17. Else if unit is "day", then - else if (unit == "day"sv) { - // a. Let whole be roundResult.[[Days]]. - whole = round_result.days; - } - // 18. Else if unit is "hour", then - else if (unit == "hour"sv) { - // a. Let whole be roundResult.[[Hours]]. - whole = round_result.hours; - } - // 19. Else if unit is "minute", then - else if (unit == "minute"sv) { - // a. Let whole be roundResult.[[Minutes]]. - whole = round_result.minutes; - } - // 20. Else if unit is "second", then - else if (unit == "second"sv) { - // a. Let whole be roundResult.[[Seconds]]. - whole = round_result.seconds; - } - // 21. Else if unit is "millisecond", then - else if (unit == "millisecond"sv) { - // a. Let whole be roundResult.[[Milliseconds]]. - whole = round_result.milliseconds; - } - // 22. Else if unit is "microsecond", then - else if (unit == "microsecond"sv) { - // a. Let whole be roundResult.[[Microseconds]]. - whole = round_result.microseconds; - } - // 23. Else, - else { - // a. Assert: unit is "nanosecond". - VERIFY(unit == "nanosecond"sv); - - // b. Let whole be roundResult.[[Nanoseconds]]. - whole = round_result.nanoseconds; - } - - // 24. Return 𝔽(whole + roundRecord.[[Remainder]]). - return Value(whole + round_record.remainder); + // 13. Return 𝔽(roundRecord.[[Total]]). + return Value(round_record.total); } // 7.3.22 Temporal.Duration.prototype.toString ( [ options ] ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.tostring diff --git a/Userland/Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.prototype.round.js b/Userland/Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.prototype.round.js index 28672bfa14..51ea215aab 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.prototype.round.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.prototype.round.js @@ -173,4 +173,24 @@ describe("errors", () => { "A starting point is required for balancing calendar units" ); }); + + test("invalid calendar throws range exception when performing round", () => { + const duration = Temporal.Duration.from({ nanoseconds: 0 }); + + const calendar = new (class extends Temporal.Calendar { + dateAdd(date, duration, options) { + return date; + } + })("iso8601"); + + expect(() => { + duration.round({ + relativeTo: new Temporal.PlainDate(1997, 5, 10, calendar), + smallestUnit: "years", + }); + }).toThrowWithMessage( + RangeError, + "Invalid calendar, dateAdd() function returned result implying a year is zero days long" + ); + }); });