ladybird/Libraries/LibJS/Runtime/Temporal/Duration.cpp
2025-03-01 14:49:20 +01:00

1529 lines
77 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Math.h>
#include <AK/NumericLimits.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Date.h>
#include <LibJS/Runtime/Intrinsics.h>
#include <LibJS/Runtime/Realm.h>
#include <LibJS/Runtime/Temporal/Calendar.h>
#include <LibJS/Runtime/Temporal/Duration.h>
#include <LibJS/Runtime/Temporal/DurationConstructor.h>
#include <LibJS/Runtime/Temporal/Instant.h>
#include <LibJS/Runtime/Temporal/PlainDate.h>
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
#include <LibJS/Runtime/Temporal/TimeZone.h>
#include <LibJS/Runtime/VM.h>
#include <LibJS/Runtime/ValueInlines.h>
#include <math.h>
namespace JS::Temporal {
GC_DEFINE_ALLOCATOR(Duration);
// 7 Temporal.Duration Objects, https://tc39.es/proposal-temporal/#sec-temporal-duration-objects
Duration::Duration(double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, Object& prototype)
: Object(ConstructWithPrototypeTag::Tag, prototype)
, m_years(years)
, m_months(months)
, m_weeks(weeks)
, m_days(days)
, m_hours(hours)
, m_minutes(minutes)
, m_seconds(seconds)
, m_milliseconds(milliseconds)
, m_microseconds(microseconds)
, m_nanoseconds(nanoseconds)
{
auto fields = AK::Array {
&Duration::m_years,
&Duration::m_months,
&Duration::m_weeks,
&Duration::m_days,
&Duration::m_hours,
&Duration::m_minutes,
&Duration::m_seconds,
&Duration::m_milliseconds,
&Duration::m_microseconds,
&Duration::m_nanoseconds,
};
// NOTE: The spec stores these fields as mathematical values. VERIFY() that we have finite, integral values in them,
// and normalize any negative zeros caused by floating point math. This is usually done using (𝔽(value)) at
// the call site.
for (auto const& field : fields) {
auto& value = this->*field;
VERIFY(isfinite(value));
// FIXME: test-js contains a small number of cases where a Temporal.Duration is constructed with a non-integral
// double. Eliminate these and VERIFY(trunc(value) == value) instead.
if (trunc(value) != value)
value = trunc(value);
else if (bit_cast<u64>(value) == NEGATIVE_ZERO_BITS)
value = 0;
}
}
// maxTimeDuration = 2**53 × 10**9 - 1 = 9,007,199,254,740,991,999,999,999
TimeDuration const MAX_TIME_DURATION = "9007199254740991999999999"_sbigint;
// 7.5.4 ZeroDateDuration ( ), https://tc39.es/proposal-temporal/#sec-temporal-zerodateduration
DateDuration zero_date_duration(VM& vm)
{
// 1. Return ! CreateDateDurationRecord(0, 0, 0, 0).
return MUST(create_date_duration_record(vm, 0, 0, 0, 0));
}
// 7.5.5 ToInternalDurationRecord ( duration ), https://tc39.es/proposal-temporal/#sec-temporal-tointernaldurationrecord
InternalDuration to_internal_duration_record(VM& vm, Duration const& duration)
{
// 1. Let dateDuration be ! CreateDateDurationRecord(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]]).
auto date_duration = MUST(create_date_duration_record(vm, duration.years(), duration.months(), duration.weeks(), duration.days()));
// 2. Let timeDuration be TimeDurationFromComponents(duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]]).
auto time_duration = time_duration_from_components(duration.hours(), duration.minutes(), duration.seconds(), duration.milliseconds(), duration.microseconds(), duration.nanoseconds());
// 3. Return CombineDateAndTimeDuration(dateDuration, timeDuration).
return combine_date_and_time_duration(date_duration, move(time_duration));
}
// 7.5.6 ToInternalDurationRecordWith24HourDays ( duration ), https://tc39.es/proposal-temporal/#sec-temporal-tointernaldurationrecordwith24hourdays
InternalDuration to_internal_duration_record_with_24_hour_days(VM& vm, Duration const& duration)
{
// 1. Let timeDuration be TimeDurationFromComponents(duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]]).
auto time_duration = time_duration_from_components(duration.hours(), duration.minutes(), duration.seconds(), duration.milliseconds(), duration.microseconds(), duration.nanoseconds());
// 2. Set timeDuration to ! Add24HourDaysToTimeDuration(timeDuration, duration.[[Days]]).
time_duration = MUST(add_24_hour_days_to_time_duration(vm, time_duration, duration.days()));
// 3. Let dateDuration be ! CreateDateDurationRecord(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], 0).
auto date_duration = MUST(create_date_duration_record(vm, duration.years(), duration.months(), duration.weeks(), 0));
// 4. Return CombineDateAndTimeDuration(dateDuration, timeDuration).
return combine_date_and_time_duration(date_duration, move(time_duration));
}
// 7.5.7 ToDateDurationRecordWithoutTime ( duration ), https://tc39.es/proposal-temporal/#sec-temporal-todatedurationrecordwithouttime
DateDuration to_date_duration_record_without_time(VM& vm, Duration const& duration)
{
// 1. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration).
auto internal_duration = to_internal_duration_record_with_24_hour_days(vm, duration);
// 2. Let days be truncate(internalDuration.[[Time]] / nsPerDay).
auto days = internal_duration.time.divided_by(NANOSECONDS_PER_DAY).quotient;
// 3. Return ! CreateDateDurationRecord(internalDuration.[[Date]].[[Years]], internalDuration.[[Date]].[[Months]], internalDuration.[[Date]].[[Weeks]], days).
return MUST(create_date_duration_record(vm, duration.years(), duration.months(), duration.weeks(), days.to_double()));
}
// 7.5.8 TemporalDurationFromInternal ( internalDuration, largestUnit ), https://tc39.es/proposal-temporal/#sec-temporal-temporaldurationfrominternal
ThrowCompletionOr<GC::Ref<Duration>> temporal_duration_from_internal(VM& vm, InternalDuration const& internal_duration, Unit largest_unit)
{
// 1. Let days, hours, minutes, seconds, milliseconds, and microseconds be 0.
double days = 0;
double hours = 0;
double minutes = 0;
double seconds = 0;
double milliseconds = 0;
double microseconds = 0;
// 2. Let sign be TimeDurationSign(internalDuration.[[Time]]).
auto sign = time_duration_sign(internal_duration.time);
// 3. Let nanoseconds be abs(internalDuration.[[Time]]).
auto const& absolute_nanoseconds = internal_duration.time.unsigned_value();
double nanoseconds = 0;
// 4. If TemporalUnitCategory(largestUnit) is date, then
if (temporal_unit_category(largest_unit) == UnitCategory::Date) {
// a. Set microseconds to floor(nanoseconds / 1000).
auto nanoseconds_division_result = absolute_nanoseconds.divided_by(NANOSECONDS_PER_MICROSECOND);
// b. Set nanoseconds to nanoseconds modulo 1000.
nanoseconds = nanoseconds_division_result.remainder.to_double();
// c. Set milliseconds to floor(microseconds / 1000).
auto microseconds_division_result = nanoseconds_division_result.quotient.divided_by(MICROSECONDS_PER_MILLISECOND);
// d. Set microseconds to microseconds modulo 1000.
microseconds = microseconds_division_result.remainder.to_double();
// e. Set seconds to floor(milliseconds / 1000).
auto milliseconds_division_result = microseconds_division_result.quotient.divided_by(MILLISECONDS_PER_SECOND);
// f. Set milliseconds to milliseconds modulo 1000.
milliseconds = milliseconds_division_result.remainder.to_double();
// g. Set minutes to floor(seconds / 60).
auto seconds_division_result = milliseconds_division_result.quotient.divided_by(SECONDS_PER_MINUTE);
// h. Set seconds to seconds modulo 60.
seconds = seconds_division_result.remainder.to_double();
// i. Set hours to floor(minutes / 60).
auto minutes_division_result = seconds_division_result.quotient.divided_by(MINUTES_PER_HOUR);
// j. Set minutes to minutes modulo 60.
minutes = minutes_division_result.remainder.to_double();
// k. Set days to floor(hours / 24).
auto hours_division_result = minutes_division_result.quotient.divided_by(HOURS_PER_DAY);
days = hours_division_result.quotient.to_double();
// l. Set hours to hours modulo 24.
hours = hours_division_result.remainder.to_double();
}
// 5. Else if largestUnit is hour, then
else if (largest_unit == Unit::Hour) {
// a. Set microseconds to floor(nanoseconds / 1000).
auto nanoseconds_division_result = absolute_nanoseconds.divided_by(NANOSECONDS_PER_MICROSECOND);
// b. Set nanoseconds to nanoseconds modulo 1000.
nanoseconds = nanoseconds_division_result.remainder.to_double();
// c. Set milliseconds to floor(microseconds / 1000).
auto microseconds_division_result = nanoseconds_division_result.quotient.divided_by(MICROSECONDS_PER_MILLISECOND);
// d. Set microseconds to microseconds modulo 1000.
microseconds = microseconds_division_result.remainder.to_double();
// e. Set seconds to floor(milliseconds / 1000).
auto milliseconds_division_result = microseconds_division_result.quotient.divided_by(MILLISECONDS_PER_SECOND);
// f. Set milliseconds to milliseconds modulo 1000.
milliseconds = milliseconds_division_result.remainder.to_double();
// g. Set minutes to floor(seconds / 60).
auto seconds_division_result = milliseconds_division_result.quotient.divided_by(SECONDS_PER_MINUTE);
// h. Set seconds to seconds modulo 60.
seconds = seconds_division_result.remainder.to_double();
// i. Set hours to floor(minutes / 60).
auto minutes_division_result = seconds_division_result.quotient.divided_by(MINUTES_PER_HOUR);
hours = minutes_division_result.quotient.to_double();
// j. Set minutes to minutes modulo 60.
minutes = minutes_division_result.remainder.to_double();
}
// 6. Else if largestUnit is minute, then
else if (largest_unit == Unit::Minute) {
// a. Set microseconds to floor(nanoseconds / 1000).
auto nanoseconds_division_result = absolute_nanoseconds.divided_by(Crypto::UnsignedBigInteger(NANOSECONDS_PER_MICROSECOND));
// b. Set nanoseconds to nanoseconds modulo 1000.
nanoseconds = nanoseconds_division_result.remainder.to_double();
// c. Set milliseconds to floor(microseconds / 1000).
auto microseconds_division_result = nanoseconds_division_result.quotient.divided_by(MICROSECONDS_PER_MILLISECOND);
// d. Set microseconds to microseconds modulo 1000.
microseconds = microseconds_division_result.remainder.to_double();
// e. Set seconds to floor(milliseconds / 1000).
auto milliseconds_division_result = microseconds_division_result.quotient.divided_by(MILLISECONDS_PER_SECOND);
// f. Set milliseconds to milliseconds modulo 1000.
milliseconds = milliseconds_division_result.remainder.to_double();
// g. Set minutes to floor(seconds / 60).
auto seconds_division_result = milliseconds_division_result.quotient.divided_by(SECONDS_PER_MINUTE);
minutes = seconds_division_result.quotient.to_double();
// h. Set seconds to seconds modulo 60.
seconds = seconds_division_result.remainder.to_double();
}
// 7. Else if largestUnit is second, then
else if (largest_unit == Unit::Second) {
// a. Set microseconds to floor(nanoseconds / 1000).
auto nanoseconds_division_result = absolute_nanoseconds.divided_by(NANOSECONDS_PER_MICROSECOND);
// b. Set nanoseconds to nanoseconds modulo 1000.
nanoseconds = nanoseconds_division_result.remainder.to_double();
// c. Set milliseconds to floor(microseconds / 1000).
auto microseconds_division_result = nanoseconds_division_result.quotient.divided_by(MICROSECONDS_PER_MILLISECOND);
// d. Set microseconds to microseconds modulo 1000.
microseconds = microseconds_division_result.remainder.to_double();
// e. Set seconds to floor(milliseconds / 1000).
auto milliseconds_division_result = microseconds_division_result.quotient.divided_by(MILLISECONDS_PER_SECOND);
seconds = milliseconds_division_result.quotient.to_double();
// f. Set milliseconds to milliseconds modulo 1000.
milliseconds = milliseconds_division_result.remainder.to_double();
}
// 8. Else if largestUnit is millisecond, then
else if (largest_unit == Unit::Millisecond) {
// a. Set microseconds to floor(nanoseconds / 1000).
auto nanoseconds_division_result = absolute_nanoseconds.divided_by(NANOSECONDS_PER_MICROSECOND);
// b. Set nanoseconds to nanoseconds modulo 1000.
nanoseconds = nanoseconds_division_result.remainder.to_double();
// c. Set milliseconds to floor(microseconds / 1000).
auto microseconds_division_result = nanoseconds_division_result.quotient.divided_by(MICROSECONDS_PER_MILLISECOND);
milliseconds = microseconds_division_result.quotient.to_double();
// d. Set microseconds to microseconds modulo 1000.
microseconds = microseconds_division_result.remainder.to_double();
}
// 9. Else if largestUnit is microsecond, then
else if (largest_unit == Unit::Microsecond) {
// a. Set microseconds to floor(nanoseconds / 1000).
auto nanoseconds_division_result = absolute_nanoseconds.divided_by(NANOSECONDS_PER_MICROSECOND);
microseconds = nanoseconds_division_result.quotient.to_double();
// b. Set nanoseconds to nanoseconds modulo 1000.
nanoseconds = nanoseconds_division_result.remainder.to_double();
}
// 10. Else,
else {
// a. Assert: largestUnit is nanosecond.
VERIFY(largest_unit == Unit::Nanosecond);
nanoseconds = absolute_nanoseconds.to_double();
}
// 11. NOTE: When largestUnit is millisecond, microsecond, or nanosecond, milliseconds, microseconds, or nanoseconds
// may be an unsafe integer. In this case, care must be taken when implementing the calculation using floating
// point arithmetic. It can be implemented in C++ using std::fma(). String manipulation will also give an exact
// result, since the multiplication is by a power of 10.
// 12. Return ? CreateTemporalDuration(internalDuration.[[Date]].[[Years]], internalDuration.[[Date]].[[Months]], internalDuration.[[Date]].[[Weeks]], internalDuration.[[Date]].[[Days]] + days × sign, hours × sign, minutes × sign, seconds × sign, milliseconds × sign, microseconds × sign, nanoseconds × sign).
return TRY(create_temporal_duration(vm, internal_duration.date.years, internal_duration.date.months, internal_duration.date.weeks, internal_duration.date.days + (days * sign), hours * sign, minutes * sign, seconds * sign, milliseconds * sign, microseconds * sign, nanoseconds * sign));
}
// 7.5.9 CreateDateDurationRecord ( years, months, weeks, days ), https://tc39.es/proposal-temporal/#sec-temporal-createdatedurationrecord
ThrowCompletionOr<DateDuration> create_date_duration_record(VM& vm, double years, double months, double weeks, double days)
{
// 1. If IsValidDuration(years, months, weeks, days, 0, 0, 0, 0, 0, 0) is false, throw a RangeError exception.
if (!is_valid_duration(years, months, weeks, days, 0, 0, 0, 0, 0, 0))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidDuration);
// 2. Return Date Duration Record { [[Years]]: (𝔽(years)), [[Months]]: (𝔽(months)), [[Weeks]]: (𝔽(weeks)), [[Days]]: (𝔽(days)) }.
return DateDuration { years, months, weeks, days };
}
// 7.5.10 AdjustDateDurationRecord ( dateDuration, days [ , weeks [ , months ] ] ), https://tc39.es/proposal-temporal/#sec-temporal-adjustdatedurationrecord
ThrowCompletionOr<DateDuration> adjust_date_duration_record(VM& vm, DateDuration const& date_duration, double days, Optional<double> weeks, Optional<double> months)
{
// 1. If weeks is not present, set weeks to dateDuration.[[Weeks]].
if (!weeks.has_value())
weeks = date_duration.weeks;
// 2. If months is not present, set months to dateDuration.[[Months]].
if (!months.has_value())
months = date_duration.months;
// 3. Return ? CreateDateDurationRecord(dateDuration.[[Years]], months, weeks, days).
return TRY(create_date_duration_record(vm, date_duration.years, *months, *weeks, days));
}
// 7.5.11 CombineDateAndTimeDuration ( dateDuration, timeDuration ), https://tc39.es/proposal-temporal/#sec-temporal-combinedateandtimeduration
InternalDuration combine_date_and_time_duration(DateDuration date_duration, TimeDuration time_duration)
{
// 1. Let dateSign be DateDurationSign(dateDuration).
auto date_sign = date_duration_sign(date_duration);
// 2. Let timeSign be TimeDurationSign(timeDuration).
auto time_sign = time_duration_sign(time_duration);
// 3. Assert: If dateSign ≠ 0 and timeSign ≠ 0, dateSign = timeSign.
if (date_sign != 0 && time_sign != 0)
VERIFY(date_sign == time_sign);
// 4. Return Internal Duration Record { [[Date]]: dateDuration, [[Time]]: timeDuration }.
return InternalDuration { date_duration, move(time_duration) };
}
// 7.5.12 ToTemporalDuration ( item ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalduration
ThrowCompletionOr<GC::Ref<Duration>> to_temporal_duration(VM& vm, Value item)
{
// 1. If item is an Object and item has an [[InitializedTemporalDuration]] internal slot, then
if (item.is_object() && is<Duration>(item.as_object())) {
auto const& duration = static_cast<Duration const&>(item.as_object());
// a. Return ! CreateTemporalDuration(item.[[Years]], item.[[Months]], item.[[Weeks]], item.[[Days]], item.[[Hours]], item.[[Minutes]], item.[[Seconds]], item.[[Milliseconds]], item.[[Microseconds]], item.[[Nanoseconds]]).
return MUST(create_temporal_duration(vm, duration.years(), duration.months(), duration.weeks(), duration.days(), duration.hours(), duration.minutes(), duration.seconds(), duration.milliseconds(), duration.microseconds(), duration.nanoseconds()));
}
// 2. If item is not an Object, then
if (!item.is_object()) {
// a. If item is not a String, throw a TypeError exception.
if (!item.is_string())
return vm.throw_completion<TypeError>(ErrorType::NotAString, item);
// b. Return ? ParseTemporalDurationString(item).
return TRY(parse_temporal_duration_string(vm, item.as_string().utf8_string_view()));
}
// 3. Let result be a new Partial Duration Record with each field set to 0.
auto result = PartialDuration::zero();
// 4. Let partial be ? ToTemporalPartialDurationRecord(item).
auto partial = TRY(to_temporal_partial_duration_record(vm, item));
// 5. If partial.[[Years]] is not undefined, set result.[[Years]] to partial.[[Years]].
if (partial.years.has_value())
result.years = *partial.years;
// 6. If partial.[[Months]] is not undefined, set result.[[Months]] to partial.[[Months]].
if (partial.months.has_value())
result.months = *partial.months;
// 7. If partial.[[Weeks]] is not undefined, set result.[[Weeks]] to partial.[[Weeks]].
if (partial.weeks.has_value())
result.weeks = *partial.weeks;
// 8. If partial.[[Days]] is not undefined, set result.[[Days]] to partial.[[Days]].
if (partial.days.has_value())
result.days = *partial.days;
// 9. If partial.[[Hours]] is not undefined, set result.[[Hours]] to partial.[[Hours]].
if (partial.hours.has_value())
result.hours = *partial.hours;
// 10. If partial.[[Minutes]] is not undefined, set result.[[Minutes]] to partial.[[Minutes]].
if (partial.minutes.has_value())
result.minutes = *partial.minutes;
// 11. If partial.[[Seconds]] is not undefined, set result.[[Seconds]] to partial.[[Seconds]].
if (partial.seconds.has_value())
result.seconds = *partial.seconds;
// 12. If partial.[[Milliseconds]] is not undefined, set result.[[Milliseconds]] to partial.[[Milliseconds]].
if (partial.milliseconds.has_value())
result.milliseconds = *partial.milliseconds;
// 13. If partial.[[Microseconds]] is not undefined, set result.[[Microseconds]] to partial.[[Microseconds]].
if (partial.microseconds.has_value())
result.microseconds = *partial.microseconds;
// 14. If partial.[[Nanoseconds]] is not undefined, set result.[[Nanoseconds]] to partial.[[Nanoseconds]].
if (partial.nanoseconds.has_value())
result.nanoseconds = *partial.nanoseconds;
// 15. Return ? CreateTemporalDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]).
return TRY(create_temporal_duration(vm, *result.years, *result.months, *result.weeks, *result.days, *result.hours, *result.minutes, *result.seconds, *result.milliseconds, *result.microseconds, *result.nanoseconds));
}
// 7.5.13 DurationSign ( duration ), https://tc39.es/proposal-temporal/#sec-durationsign
i8 duration_sign(Duration const& duration)
{
// 1. For each value v of « duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]] », do
for (auto value : { duration.years(), duration.months(), duration.weeks(), duration.days(), duration.hours(), duration.minutes(), duration.seconds(), duration.milliseconds(), duration.microseconds(), duration.nanoseconds() }) {
// a. If v < 0, return -1.
if (value < 0)
return -1;
// b. If v > 0, return 1.
if (value > 0)
return 1;
}
// 2. Return 0.
return 0;
}
// 7.5.14 DateDurationSign ( dateDuration ), https://tc39.es/proposal-temporal/#sec-temporal-datedurationsign
i8 date_duration_sign(DateDuration const& date_duration)
{
// 1. For each value v of « dateDuration.[[Years]], dateDuration.[[Months]], dateDuration.[[Weeks]], dateDuration.[[Days]] », do
for (auto value : { date_duration.years, date_duration.months, date_duration.weeks, date_duration.days }) {
// a. If v < 0, return -1.
if (value < 0)
return -1;
// b. If v > 0, return 1.
if (value > 0)
return 1;
}
// 2. Return 0.
return 0;
}
// 7.5.15 InternalDurationSign ( internalDuration ), https://tc39.es/proposal-temporal/#sec-temporal-internaldurationsign
i8 internal_duration_sign(InternalDuration const& internal_duration)
{
// 1. Let dateSign be DateDurationSign(internalDuration.[[Date]]).
auto date_sign = date_duration_sign(internal_duration.date);
// 2. If dateSign ≠ 0, return dateSign.
if (date_sign != 0)
return date_sign;
// 3. Return TimeDurationSign(internalDuration.[[Time]]).
return time_duration_sign(internal_duration.time);
}
// 7.5.16 IsValidDuration ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds ), https://tc39.es/proposal-temporal/#sec-isvalidduration
bool is_valid_duration(double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds)
{
// 1. Let sign be 0.
auto sign = 0;
// 2. For each value v of « years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds », do
for (auto value : { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds }) {
// a. If 𝔽(v) is not finite, return false.
if (!isfinite(value))
return false;
// b. If v < 0, then
if (value < 0) {
// i. If sign > 0, return false.
if (sign > 0)
return false;
// ii. Set sign to -1.
sign = -1;
}
// c. Else if v > 0, then
else if (value > 0) {
// i. If sign < 0, return false.
if (sign < 0)
return false;
// ii. Set sign to 1.
sign = 1;
}
}
// 3. If abs(years) ≥ 2**32, return false.
if (AK::fabs(years) > NumericLimits<u32>::max())
return false;
// 4. If abs(months) ≥ 2**32, return false.
if (AK::fabs(months) > NumericLimits<u32>::max())
return false;
// 5. If abs(weeks) ≥ 2**32, return false.
if (AK::fabs(weeks) > NumericLimits<u32>::max())
return false;
// 6. Let normalizedSeconds be days × 86,400 + hours × 3600 + minutes × 60 + seconds + (𝔽(milliseconds)) × 10**-3 + (𝔽(microseconds)) × 10**-6 + (𝔽(nanoseconds)) × 10**-9.
// 7. NOTE: The above step cannot be implemented directly using floating-point arithmetic. Multiplying by 10**-3,
// 10**-6, and 10**-9 respectively may be imprecise when milliseconds, microseconds, or nanoseconds is an
// unsafe integer. This multiplication can be implemented in C++ with an implementation of std::remquo()
// with sufficient bits in the quotient. String manipulation will also give an exact result, since the
// multiplication is by a power of 10.
auto normalized_seconds = TimeDuration { days }.multiplied_by(NANOSECONDS_PER_DAY);
normalized_seconds = normalized_seconds.plus(TimeDuration { hours }.multiplied_by(NANOSECONDS_PER_HOUR));
normalized_seconds = normalized_seconds.plus(TimeDuration { minutes }.multiplied_by(NANOSECONDS_PER_MINUTE));
normalized_seconds = normalized_seconds.plus(TimeDuration { seconds }.multiplied_by(NANOSECONDS_PER_SECOND));
normalized_seconds = normalized_seconds.plus(TimeDuration { milliseconds }.multiplied_by(NANOSECONDS_PER_MILLISECOND));
normalized_seconds = normalized_seconds.plus(TimeDuration { microseconds }.multiplied_by(NANOSECONDS_PER_MICROSECOND));
normalized_seconds = normalized_seconds.plus(TimeDuration { nanoseconds });
// 8. If abs(normalizedSeconds) ≥ 2**53, return false.
if (normalized_seconds.unsigned_value() > MAX_TIME_DURATION.unsigned_value())
return false;
// 9. Return true.
return true;
}
// 7.5.17 DefaultTemporalLargestUnit ( duration ), https://tc39.es/proposal-temporal/#sec-temporal-defaulttemporallargestunit
Unit default_temporal_largest_unit(Duration const& duration)
{
// 1. If duration.[[Years]] ≠ 0, return YEAR.
if (duration.years() != 0)
return Unit::Year;
// 2. If duration.[[Months]] ≠ 0, return MONTH.
if (duration.months() != 0)
return Unit::Month;
// 3. If duration.[[Weeks]] ≠ 0, return WEEK.
if (duration.weeks() != 0)
return Unit::Week;
// 4. If duration.[[Days]] ≠ 0, return DAY.
if (duration.days() != 0)
return Unit::Day;
// 5. If duration.[[Hours]] ≠ 0, return HOUR.
if (duration.hours() != 0)
return Unit::Hour;
// 6. If duration.[[Minutes]] ≠ 0, return MINUTE.
if (duration.minutes() != 0)
return Unit::Minute;
// 7. If duration.[[Seconds]] ≠ 0, return SECOND.
if (duration.seconds() != 0)
return Unit::Second;
// 8. If duration.[[Milliseconds]] ≠ 0, return MILLISECOND.
if (duration.milliseconds() != 0)
return Unit::Millisecond;
// 9. If duration.[[Microseconds]] ≠ 0, return MICROSECOND.
if (duration.microseconds() != 0)
return Unit::Microsecond;
// 10. Return NANOSECOND.
return Unit::Nanosecond;
}
// 7.5.18 ToTemporalPartialDurationRecord ( temporalDurationLike ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalpartialdurationrecord
ThrowCompletionOr<PartialDuration> to_temporal_partial_duration_record(VM& vm, Value temporal_duration_like)
{
// 1. If temporalDurationLike is not an Object, then
if (!temporal_duration_like.is_object()) {
// a. Throw a TypeError exception.
return vm.throw_completion<TypeError>(ErrorType::NotAnObject, temporal_duration_like);
}
// 2. Let result be a new partial Duration Record with each field set to undefined.
PartialDuration result {};
// 3. NOTE: The following steps read properties and perform independent validation in alphabetical order.
auto to_integral_if_defined = [&vm, &temporal_duration = temporal_duration_like.as_object()](auto const& property, auto& field) -> ThrowCompletionOr<void> {
if (auto value = TRY(temporal_duration.get(property)); !value.is_undefined())
field = TRY(to_integer_if_integral(vm, value, ErrorType::TemporalInvalidDurationPropertyValueNonIntegral, property, value));
return {};
};
// 4. Let days be ? Get(temporalDurationLike, "days").
// 5. If days is not undefined, set result.[[Days]] to ? ToIntegerIfIntegral(days).
TRY(to_integral_if_defined(vm.names.days, result.days));
// 6. Let hours be ? Get(temporalDurationLike, "hours").
// 7. If hours is not undefined, set result.[[Hours]] to ? ToIntegerIfIntegral(hours).
TRY(to_integral_if_defined(vm.names.hours, result.hours));
// 8. Let microseconds be ? Get(temporalDurationLike, "microseconds").
// 9. If microseconds is not undefined, set result.[[Microseconds]] to ? ToIntegerIfIntegral(microseconds).
TRY(to_integral_if_defined(vm.names.microseconds, result.microseconds));
// 10. Let milliseconds be ? Get(temporalDurationLike, "milliseconds").
// 11. If milliseconds is not undefined, set result.[[Milliseconds]] to ? ToIntegerIfIntegral(milliseconds).
TRY(to_integral_if_defined(vm.names.milliseconds, result.milliseconds));
// 12. Let minutes be ? Get(temporalDurationLike, "minutes").
// 13. If minutes is not undefined, set result.[[Minutes]] to ? ToIntegerIfIntegral(minutes).
TRY(to_integral_if_defined(vm.names.minutes, result.minutes));
// 14. Let months be ? Get(temporalDurationLike, "months").
// 15. If months is not undefined, set result.[[Months]] to ? ToIntegerIfIntegral(months).
TRY(to_integral_if_defined(vm.names.months, result.months));
// 16. Let nanoseconds be ? Get(temporalDurationLike, "nanoseconds").
// 17. If nanoseconds is not undefined, set result.[[Nanoseconds]] to ? ToIntegerIfIntegral(nanoseconds).
TRY(to_integral_if_defined(vm.names.nanoseconds, result.nanoseconds));
// 18. Let seconds be ? Get(temporalDurationLike, "seconds").
// 19. If seconds is not undefined, set result.[[Seconds]] to ? ToIntegerIfIntegral(seconds).
TRY(to_integral_if_defined(vm.names.seconds, result.seconds));
// 20. Let weeks be ? Get(temporalDurationLike, "weeks").
// 21. If weeks is not undefined, set result.[[Weeks]] to ? ToIntegerIfIntegral(weeks).
TRY(to_integral_if_defined(vm.names.weeks, result.weeks));
// 22. Let years be ? Get(temporalDurationLike, "years").
// 23. If years is not undefined, set result.[[Years]] to ? ToIntegerIfIntegral(years).
TRY(to_integral_if_defined(vm.names.years, result.years));
// 24. If years is undefined, and months is undefined, and weeks is undefined, and days is undefined, and hours is
// undefined, and minutes is undefined, and seconds is undefined, and milliseconds is undefined, and microseconds
// is undefined, and nanoseconds is undefined, throw a TypeError exception.
if (!result.any_field_defined())
return vm.throw_completion<TypeError>(ErrorType::TemporalInvalidDurationLikeObject);
// 25. Return result.
return result;
}
// 7.5.19 CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporalduration
ThrowCompletionOr<GC::Ref<Duration>> create_temporal_duration(VM& vm, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, GC::Ptr<FunctionObject> new_target)
{
auto& realm = *vm.current_realm();
// 1. If IsValidDuration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds) is false, throw a RangeError exception.
if (!is_valid_duration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidDuration);
// 2. If newTarget is not present, set newTarget to %Temporal.Duration%.
if (!new_target)
new_target = realm.intrinsics().temporal_duration_constructor();
// 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.Duration.prototype%", « [[InitializedTemporalDuration]], [[Years]], [[Months]], [[Weeks]], [[Days]], [[Hours]], [[Minutes]], [[Seconds]], [[Milliseconds]], [[Microseconds]], [[Nanoseconds]] »).
// 4. Set object.[[Years]] to (𝔽(years)).
// 5. Set object.[[Months]] to (𝔽(months)).
// 6. Set object.[[Weeks]] to (𝔽(weeks)).
// 7. Set object.[[Days]] to (𝔽(days)).
// 8. Set object.[[Hours]] to (𝔽(hours)).
// 9. Set object.[[Minutes]] to (𝔽(minutes)).
// 10. Set object.[[Seconds]] to (𝔽(seconds)).
// 11. Set object.[[Milliseconds]] to (𝔽(milliseconds)).
// 12. Set object.[[Microseconds]] to (𝔽(microseconds)).
// 13. Set object.[[Nanoseconds]] to (𝔽(nanoseconds)).
auto object = TRY(ordinary_create_from_constructor<Duration>(vm, *new_target, &Intrinsics::temporal_duration_prototype, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds));
// 14. Return object.
return object;
}
// 7.5.20 CreateNegatedTemporalDuration ( duration ), https://tc39.es/proposal-temporal/#sec-temporal-createnegatedtemporalduration
GC::Ref<Duration> create_negated_temporal_duration(VM& vm, Duration const& duration)
{
// 1. Return ! CreateTemporalDuration(-duration.[[Years]], -duration.[[Months]], -duration.[[Weeks]], -duration.[[Days]], -duration.[[Hours]], -duration.[[Minutes]], -duration.[[Seconds]], -duration.[[Milliseconds]], -duration.[[Microseconds]], -duration.[[Nanoseconds]]).
return MUST(create_temporal_duration(vm, -duration.years(), -duration.months(), -duration.weeks(), -duration.days(), -duration.hours(), -duration.minutes(), -duration.seconds(), -duration.milliseconds(), -duration.microseconds(), -duration.nanoseconds()));
}
// 7.5.21 TimeDurationFromComponents ( hours, minutes, seconds, milliseconds, microseconds, nanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-timedurationfromcomponents
TimeDuration time_duration_from_components(double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds)
{
// 1. Set minutes to minutes + hours × 60.
auto total_minutes = TimeDuration { minutes }.plus(TimeDuration { hours }.multiplied_by(60_bigint));
// 2. Set seconds to seconds + minutes × 60.
auto total_seconds = TimeDuration { seconds }.plus(total_minutes.multiplied_by(60_bigint));
// 3. Set milliseconds to milliseconds + seconds × 1000.
auto total_milliseconds = TimeDuration { milliseconds }.plus(total_seconds.multiplied_by(1000_bigint));
// 4. Set microseconds to microseconds + milliseconds × 1000.
auto total_microseconds = TimeDuration { microseconds }.plus(total_milliseconds.multiplied_by(1000_bigint));
// 5. Set nanoseconds to nanoseconds + microseconds × 1000.
auto total_nanoseconds = TimeDuration { nanoseconds }.plus(total_microseconds.multiplied_by(1000_bigint));
// 6. Assert: abs(nanoseconds) ≤ maxTimeDuration.
VERIFY(total_nanoseconds.unsigned_value() <= MAX_TIME_DURATION.unsigned_value());
// 7. Return nanoseconds.
return total_nanoseconds;
}
// 7.5.22 AddTimeDuration ( one, two ), https://tc39.es/proposal-temporal/#sec-temporal-addtimeduration
ThrowCompletionOr<TimeDuration> add_time_duration(VM& vm, TimeDuration const& one, TimeDuration const& two)
{
// 1. Let result be one + two.
auto result = one.plus(two);
// 2. If abs(result) > maxTimeDuration, throw a RangeError exception.
if (result.unsigned_value() > MAX_TIME_DURATION.unsigned_value())
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidDuration);
// 3. Return result.
return result;
}
// 7.5.23 Add24HourDaysToTimeDuration ( d, days ), https://tc39.es/proposal-temporal/#sec-temporal-add24hourdaystonormalizedtimeduration
ThrowCompletionOr<TimeDuration> add_24_hour_days_to_time_duration(VM& vm, TimeDuration const& time_duration, double days)
{
// 1. Let result be d + days × nsPerDay.
auto result = time_duration.plus(TimeDuration { days }.multiplied_by(NANOSECONDS_PER_DAY));
// 2. If abs(result) > maxTimeDuration, throw a RangeError exception.
if (result.unsigned_value() > MAX_TIME_DURATION.unsigned_value())
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidDuration);
// 3. Return result.
return result;
}
// 7.5.24 AddTimeDurationToEpochNanoseconds ( d, epochNs ), https://tc39.es/proposal-temporal/#sec-temporal-addtimedurationtoepochnanoseconds
Crypto::SignedBigInteger add_time_duration_to_epoch_nanoseconds(TimeDuration const& duration, Crypto::SignedBigInteger const& epoch_nanoseconds)
{
// 1. Return epochNs + (d).
return epoch_nanoseconds.plus(duration);
}
// 7.5.25 CompareTimeDuration ( one, two ), https://tc39.es/proposal-temporal/#sec-temporal-comparetimeduration
i8 compare_time_duration(TimeDuration const& one, TimeDuration const& two)
{
// 1. If one > two, return 1.
if (one > two)
return 1;
// 2. If one < two, return -1.
if (one < two)
return -1;
// 3. Return 0.
return 0;
}
// 7.5.26 TimeDurationFromEpochNanosecondsDifference ( one, two ), https://tc39.es/proposal-temporal/#sec-temporal-timedurationfromepochnanosecondsdifference
TimeDuration time_duration_from_epoch_nanoseconds_difference(Crypto::SignedBigInteger const& one, Crypto::SignedBigInteger const& two)
{
// 1. Let result be (one) - (two).
auto result = one.minus(two);
// 2. Assert: abs(result) ≤ maxTimeDuration.
VERIFY(result.unsigned_value() <= MAX_TIME_DURATION.unsigned_value());
// 3. Return result.
return result;
}
// 7.5.27 RoundTimeDurationToIncrement ( d, increment, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-roundtimedurationtoincrement
ThrowCompletionOr<TimeDuration> round_time_duration_to_increment(VM& vm, TimeDuration const& duration, Crypto::UnsignedBigInteger const& increment, RoundingMode rounding_mode)
{
// 1. Let rounded be RoundNumberToIncrement(d, increment, roundingMode).
auto rounded = round_number_to_increment(duration, increment, rounding_mode);
// 2. If abs(rounded) > maxTimeDuration, throw a RangeError exception.
if (rounded.unsigned_value() > MAX_TIME_DURATION.unsigned_value())
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidDuration);
// 3. Return rounded.
return rounded;
}
// 7.5.28 TimeDurationSign ( d ), https://tc39.es/proposal-temporal/#sec-temporal-timedurationsign
i8 time_duration_sign(TimeDuration const& time_duration)
{
// 1. If d < 0, return -1.
if (time_duration.is_negative())
return -1;
// 2. If d > 0, return 1.
if (time_duration.is_positive())
return 1;
// 3. Return 0.
return 0;
}
// 7.5.29 DateDurationDays ( dateDuration, plainRelativeTo ), https://tc39.es/proposal-temporal/#sec-temporal-datedurationdays
ThrowCompletionOr<double> date_duration_days(VM& vm, DateDuration const& date_duration, PlainDate const& plain_relative_to)
{
// 1. Let yearsMonthsWeeksDuration be ! AdjustDateDurationRecord(dateDuration, 0).
auto years_months_weeks_duration = MUST(adjust_date_duration_record(vm, date_duration, 0));
// 2. If DateDurationSign(yearsMonthsWeeksDuration) = 0, return dateDuration.[[Days]].
if (date_duration_sign(years_months_weeks_duration) == 0)
return date_duration.days;
// 3. Let later be ? CalendarDateAdd(plainRelativeTo.[[Calendar]], plainRelativeTo.[[ISODate]], yearsMonthsWeeksDuration, CONSTRAIN).
auto later = TRY(calendar_date_add(vm, plain_relative_to.calendar(), plain_relative_to.iso_date(), years_months_weeks_duration, Overflow::Constrain));
// 4. Let epochDays1 be ISODateToEpochDays(plainRelativeTo.[[ISODate]].[[Year]], plainRelativeTo.[[ISODate]].[[Month]] - 1, plainRelativeTo.[[ISODate]].[[Day]]).
auto epoch_days1 = iso_date_to_epoch_days(plain_relative_to.iso_date().year, plain_relative_to.iso_date().month - 1, plain_relative_to.iso_date().day);
// 5. Let epochDays2 be ISODateToEpochDays(later.[[Year]], later.[[Month]] - 1, later.[[Day]]).
auto epoch_days2 = iso_date_to_epoch_days(later.year, later.month - 1, later.day);
// 6. Let yearsMonthsWeeksInDays be epochDays2 - epochDays1.
auto years_months_weeks_in_days = epoch_days2 - epoch_days1;
// 7. Return dateDuration.[[Days]] + yearsMonthsWeeksInDays.
return date_duration.days + years_months_weeks_in_days;
}
// 7.5.30 RoundTimeDuration ( timeDuration, increment, unit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-roundtimeduration
ThrowCompletionOr<TimeDuration> round_time_duration(VM& vm, TimeDuration const& time_duration, Crypto::UnsignedBigInteger const& increment, Unit unit, RoundingMode rounding_mode)
{
// 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);
// 2. Return ? RoundTimeDurationToIncrement(timeDuration, divisor × increment, roundingMode).
return TRY(round_time_duration_to_increment(vm, time_duration, divisor.multiplied_by(increment), rounding_mode));
}
// 7.5.31 TotalTimeDuration ( timeDuration, unit ), https://tc39.es/proposal-temporal/#sec-temporal-totaltimeduration
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);
// 2. NOTE: The following step cannot be implemented directly using floating-point arithmetic when 𝔽(timeDuration) is
// not a safe integer. The division can be implemented in C++ with the __float128 type if the compiler supports it,
// or with software emulation such as in the SoftFP library.
// 3. Return timeDuration / divisor.
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
ThrowCompletionOr<CalendarNudgeResult> nudge_to_calendar_unit(VM& vm, i8 sign, InternalDuration const& duration, Crypto::SignedBigInteger const& dest_epoch_ns, ISODateTime const& iso_date_time, Optional<StringView> time_zone, StringView calendar, u64 increment, Unit unit, RoundingMode rounding_mode)
{
DateDuration start_duration;
DateDuration end_duration;
double r1 = 0;
double r2 = 0;
// 1. If unit is YEAR, then
if (unit == Unit::Year) {
// a. Let years be RoundNumberToIncrement(duration.[[Date]].[[Years]], increment, TRUNC).
auto years = round_number_to_increment(duration.date.years, increment, RoundingMode::Trunc);
// b. Let r1 be years.
r1 = years;
// c. Let r2 be years + increment × sign.
r2 = years + static_cast<double>(increment) * sign;
// d. Let startDuration be ? CreateDateDurationRecord(r1, 0, 0, 0).
start_duration = TRY(create_date_duration_record(vm, r1, 0, 0, 0));
// e. Let endDuration be ? CreateDateDurationRecord(r2, 0, 0, 0).
end_duration = TRY(create_date_duration_record(vm, r2, 0, 0, 0));
}
// 2. Else if unit is MONTH, then
else if (unit == Unit::Month) {
// a. Let months be RoundNumberToIncrement(duration.[[Date]].[[Months]], increment, TRUNC).
auto months = round_number_to_increment(duration.date.months, increment, RoundingMode::Trunc);
// b. Let r1 be months.
r1 = months;
// c. Let r2 be months + increment × sign.
r2 = months + static_cast<double>(increment) * sign;
// d. Let startDuration be ? AdjustDateDurationRecord(duration.[[Date]], 0, 0, r1).
start_duration = TRY(adjust_date_duration_record(vm, duration.date, 0, 0, r1));
// e. Let endDuration be ? AdjustDateDurationRecord(duration.[[Date]], 0, 0, r2).
end_duration = TRY(adjust_date_duration_record(vm, duration.date, 0, 0, r2));
}
// 3. Else if unit is WEEK, then
else if (unit == Unit::Week) {
// a. Let yearsMonths be ! AdjustDateDurationRecord(duration.[[Date]], 0, 0).
auto years_months = MUST(adjust_date_duration_record(vm, duration.date, 0, 0));
// b. Let weeksStart be ? CalendarDateAdd(calendar, isoDateTime.[[ISODate]], yearsMonths, CONSTRAIN).
auto weeks_start = TRY(calendar_date_add(vm, calendar, iso_date_time.iso_date, years_months, Overflow::Constrain));
// c. Let weeksEnd be BalanceISODate(weeksStart.[[Year]], weeksStart.[[Month]], weeksStart.[[Day]] + duration.[[Date]].[[Days]]).
auto weeks_end = balance_iso_date(weeks_start.year, weeks_start.month, static_cast<double>(weeks_start.day) + duration.date.days);
// d. Let untilResult be CalendarDateUntil(calendar, weeksStart, weeksEnd, WEEK).
auto until_result = calendar_date_until(vm, calendar, weeks_start, weeks_end, Unit::Week);
// e. Let weeks be RoundNumberToIncrement(duration.[[Date]].[[Weeks]] + untilResult.[[Weeks]], increment, TRUNC).
auto weeks = round_number_to_increment(duration.date.weeks + until_result.weeks, increment, RoundingMode::Trunc);
// f. Let r1 be weeks.
r1 = weeks;
// g. Let r2 be weeks + increment × sign.
r2 = weeks + static_cast<double>(increment) * sign;
// h. Let startDuration be ? AdjustDateDurationRecord(duration.[[Date]], 0, r1).
start_duration = TRY(adjust_date_duration_record(vm, duration.date, 0, r1));
// i. Let endDuration be ? AdjustDateDurationRecord(duration.[[Date]], 0, r2).
end_duration = TRY(adjust_date_duration_record(vm, duration.date, 0, r2));
}
// 4. Else,
else {
// a. Assert: unit is DAY.
VERIFY(unit == Unit::Day);
// b. Let days be RoundNumberToIncrement(duration.[[Date]].[[Days]], increment, TRUNC).
auto days = round_number_to_increment(duration.date.days, increment, RoundingMode::Trunc);
// c. Let r1 be days.
r1 = days;
// d. Let r2 be days + increment × sign.
r2 = days + static_cast<double>(increment) * sign;
// e. Let startDuration be ? AdjustDateDurationRecord(duration.[[Date]], r1).
start_duration = TRY(adjust_date_duration_record(vm, duration.date, r1));
// f. Let endDuration be ? AdjustDateDurationRecord(duration.[[Date]], r2).
end_duration = TRY(adjust_date_duration_record(vm, duration.date, r2));
}
// 5. Assert: If sign is 1, r1 ≥ 0 and r1 < r2.
if (sign == 1)
VERIFY(r1 >= 0 && r1 < r2);
// 6. Assert: If sign is -1, r1 ≤ 0 and r1 > r2.
else if (sign == -1)
VERIFY(r1 <= 0 && r1 > r2);
// 7. Let start be ? CalendarDateAdd(calendar, isoDateTime.[[ISODate]], startDuration, CONSTRAIN).
auto start = TRY(calendar_date_add(vm, calendar, iso_date_time.iso_date, start_duration, Overflow::Constrain));
// 8. Let end be ? CalendarDateAdd(calendar, isoDateTime.[[ISODate]], endDuration, CONSTRAIN).
auto end = TRY(calendar_date_add(vm, calendar, iso_date_time.iso_date, end_duration, Overflow::Constrain));
// 9. Let startDateTime be CombineISODateAndTimeRecord(start, isoDateTime.[[Time]]).
auto start_date_time = combine_iso_date_and_time_record(start, iso_date_time.time);
// 10. Let endDateTime be CombineISODateAndTimeRecord(end, isoDateTime.[[Time]]).
auto end_date_time = combine_iso_date_and_time_record(end, iso_date_time.time);
Crypto::SignedBigInteger start_epoch_ns;
Crypto::SignedBigInteger end_epoch_ns;
// 11. If timeZone is UNSET, then
if (!time_zone.has_value()) {
// a. Let startEpochNs be GetUTCEpochNanoseconds(startDateTime).
start_epoch_ns = get_utc_epoch_nanoseconds(start_date_time);
// b. Let endEpochNs be GetUTCEpochNanoseconds(endDateTime).
end_epoch_ns = get_utc_epoch_nanoseconds(end_date_time);
}
// 12. Else,
else {
// a. Let startEpochNs be ? GetEpochNanosecondsFor(timeZone, startDateTime, COMPATIBLE).
start_epoch_ns = TRY(get_epoch_nanoseconds_for(vm, *time_zone, start_date_time, Disambiguation::Compatible));
// b. Let endEpochNs be ? GetEpochNanosecondsFor(timeZone, endDateTime, COMPATIBLE).
end_epoch_ns = TRY(get_epoch_nanoseconds_for(vm, *time_zone, end_date_time, Disambiguation::Compatible));
}
// 13. If sign is 1, then
if (sign == 1) {
// a. Assert: startEpochNs ≤ destEpochNs ≤ endEpochNs.
VERIFY(start_epoch_ns <= dest_epoch_ns);
VERIFY(dest_epoch_ns <= end_epoch_ns);
}
// 14. Else,
else {
// a. Assert: endEpochNs ≤ destEpochNs ≤ startEpochNs.
VERIFY(end_epoch_ns <= dest_epoch_ns);
VERIFY(dest_epoch_ns <= start_epoch_ns);
}
// 15. Assert: startEpochNs ≠ endEpochNs.
VERIFY(start_epoch_ns != end_epoch_ns);
// 16. Let progress be (destEpochNs - startEpochNs) / (endEpochNs - startEpochNs).
auto progress_numerator = dest_epoch_ns.minus(start_epoch_ns);
auto progress_denominator = end_epoch_ns.minus(start_epoch_ns);
auto progress_equals_one = progress_numerator == progress_denominator;
// 17. Let total be r1 + progress × increment × sign.
auto total_numerator = progress_numerator.multiplied_by(Crypto::UnsignedBigInteger { increment });
if (sign == -1)
total_numerator.negate();
if (progress_denominator.is_negative())
total_numerator.negate();
auto total_mv = Crypto::BigFraction { Crypto::SignedBigInteger { r1 } } + Crypto::BigFraction { move(total_numerator), progress_denominator.unsigned_value() };
auto total = total_mv.to_double();
// 18. NOTE: The above two steps cannot be implemented directly using floating-point arithmetic. This division can be
// implemented as if expressing the denominator and numerator of total as two time durations, and performing one
// division operation with a floating-point result.
// 19. Assert: 0 ≤ progress ≤ 1.
// 20. If sign < 0, let isNegative be NEGATIVE; else let isNegative be POSITIVE.
auto is_negative = sign < 0 ? Sign::Negative : Sign::Positive;
// 21. Let unsignedRoundingMode be GetUnsignedRoundingMode(roundingMode, isNegative).
auto unsigned_rounding_mode = get_unsigned_rounding_mode(rounding_mode, is_negative);
double rounded_unit = 0;
// 22. If progress = 1, then
if (progress_equals_one) {
// a. Let roundedUnit be abs(r2).
rounded_unit = fabs(r2);
}
// 23. Else,
else {
// a. Assert: abs(r1) ≤ abs(total) < abs(r2).
VERIFY(fabs(r1) <= fabs(total));
VERIFY(fabs(total) <= fabs(r2));
// b. Let roundedUnit be ApplyUnsignedRoundingMode(abs(total), abs(r1), abs(r2), unsignedRoundingMode).
rounded_unit = apply_unsigned_rounding_mode(fabs(total), fabs(r1), fabs(r2), unsigned_rounding_mode);
}
auto did_expand_calendar_unit = false;
DateDuration result_duration;
Crypto::SignedBigInteger nudged_epoch_ns;
// 24. If roundedUnit is abs(r2), then
if (rounded_unit == fabs(r2)) {
// a. Let didExpandCalendarUnit be true.
did_expand_calendar_unit = true;
// b. Let resultDuration be endDuration.
result_duration = end_duration;
// c. Let nudgedEpochNs be endEpochNs.
nudged_epoch_ns = move(end_epoch_ns);
}
// 25. Else,
else {
// a. Let didExpandCalendarUnit be false.
did_expand_calendar_unit = false;
// b. Let resultDuration be startDuration.
result_duration = start_duration;
// c. Let nudgedEpochNs be startEpochNs.
nudged_epoch_ns = move(start_epoch_ns);
}
// 26. Set resultDuration to CombineDateAndTimeDuration(resultDuration, 0).
auto result_date_and_time_duration = combine_date_and_time_duration(result_duration, TimeDuration { 0 });
// 27. Let nudgeResult be Duration Nudge Result Record { [[Duration]]: resultDuration, [[NudgedEpochNs]]: nudgedEpochNs, [[DidExpandCalendarUnit]]: didExpandCalendarUnit }.
DurationNudgeResult nudge_result { .duration = move(result_date_and_time_duration), .nudged_epoch_ns = move(nudged_epoch_ns), .did_expand_calendar_unit = did_expand_calendar_unit };
// 28. Return the Record { [[NudgeResult]]: nudgeResult, [[Total]]: total }.
return CalendarNudgeResult { .nudge_result = move(nudge_result), .total = move(total_mv) };
}
// 7.5.34 NudgeToZonedTime ( sign, duration, isoDateTime, timeZone, calendar, increment, unit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-nudgetozonedtime
ThrowCompletionOr<DurationNudgeResult> nudge_to_zoned_time(VM& vm, i8 sign, InternalDuration const& duration, ISODateTime const& iso_date_time, StringView time_zone, StringView calendar, u64 increment, Unit unit, RoundingMode rounding_mode)
{
// 1. Let start be ? CalendarDateAdd(calendar, isoDateTime.[[ISODate]], duration.[[Date]], CONSTRAIN).
auto start = TRY(calendar_date_add(vm, calendar, iso_date_time.iso_date, duration.date, Overflow::Constrain));
// 2. Let startDateTime be CombineISODateAndTimeRecord(start, isoDateTime.[[Time]]).
auto start_date_time = combine_iso_date_and_time_record(start, iso_date_time.time);
// 3. Let endDate be BalanceISODate(start.[[Year]], start.[[Month]], start.[[Day]] + sign).
auto end_date = balance_iso_date(start.year, start.month, static_cast<double>(start.day) + sign);
// 4. Let endDateTime be CombineISODateAndTimeRecord(endDate, isoDateTime.[[Time]]).
auto end_date_time = combine_iso_date_and_time_record(end_date, iso_date_time.time);
// 5. Let startEpochNs be ? GetEpochNanosecondsFor(timeZone, startDateTime, COMPATIBLE).
auto start_epoch_ns = TRY(get_epoch_nanoseconds_for(vm, time_zone, start_date_time, Disambiguation::Compatible));
// 6. Let endEpochNs be ? GetEpochNanosecondsFor(timeZone, endDateTime, COMPATIBLE).
auto end_epoch_ns = TRY(get_epoch_nanoseconds_for(vm, time_zone, end_date_time, Disambiguation::Compatible));
// 7. Let daySpan be TimeDurationFromEpochNanosecondsDifference(endEpochNs, startEpochNs).
auto day_span = time_duration_from_epoch_nanoseconds_difference(end_epoch_ns, start_epoch_ns);
// 8. Assert: TimeDurationSign(daySpan) = sign.
VERIFY(time_duration_sign(day_span) == sign);
// 9. Let unitLength be the value in the "Length in Nanoseconds" column of the row of Table 21 whose "Value" column contains unit.
auto const& unit_length = temporal_unit_length_in_nanoseconds(unit);
// 10. Let roundedTimeDuration be ? RoundTimeDurationToIncrement(duration.[[Time]], increment × unitLength, roundingMode).
auto unit_length_multiplied_by_increment = unit_length.multiplied_by(Crypto::UnsignedBigInteger { increment });
auto rounded_time_duration = TRY(round_time_duration_to_increment(vm, duration.time, unit_length_multiplied_by_increment, rounding_mode));
// 11. Let beyondDaySpan be ! AddTimeDuration(roundedTimeDuration, -daySpan).
day_span.negate();
auto beyond_day_span = MUST(add_time_duration(vm, rounded_time_duration, day_span));
auto did_round_beyond_day = false;
Crypto::SignedBigInteger nudged_epoch_ns;
i8 day_delta = 0;
// 12. If TimeDurationSign(beyondDaySpan) ≠ -sign, then
if (time_duration_sign(beyond_day_span) != -sign) {
// a. Let didRoundBeyondDay be true.
did_round_beyond_day = true;
// b. Let dayDelta be sign.
day_delta = sign;
// c. Set roundedTimeDuration to ? RoundTimeDurationToIncrement(beyondDaySpan, increment × unitLength, roundingMode).
rounded_time_duration = TRY(round_time_duration_to_increment(vm, beyond_day_span, unit_length_multiplied_by_increment, rounding_mode));
// d. Let nudgedEpochNs be AddTimeDurationToEpochNanoseconds(roundedTimeDuration, endEpochNs).
nudged_epoch_ns = add_time_duration_to_epoch_nanoseconds(rounded_time_duration, end_epoch_ns);
}
// 13. Else,
else {
// a. Let didRoundBeyondDay be false.
did_round_beyond_day = false;
// b. Let dayDelta be 0.
day_delta = 0;
// c. Let nudgedEpochNs be AddTimeDurationToEpochNanoseconds(roundedTimeDuration, startEpochNs).
nudged_epoch_ns = add_time_duration_to_epoch_nanoseconds(rounded_time_duration, start_epoch_ns);
}
// 14. Let dateDuration be ! AdjustDateDurationRecord(duration.[[Date]], duration.[[Date]].[[Days]] + dayDelta).
auto date_duration = MUST(adjust_date_duration_record(vm, duration.date, duration.date.days + day_delta));
// 15. Let resultDuration be CombineDateAndTimeDuration(dateDuration, roundedTimeDuration).
auto result_duration = combine_date_and_time_duration(date_duration, move(rounded_time_duration));
// 16. Return Duration Nudge Result Record { [[Duration]]: resultDuration, [[NudgedEpochNs]]: nudgedEpochNs, [[DidExpandCalendarUnit]]: didRoundBeyondDay }.
return DurationNudgeResult { .duration = move(result_duration), .nudged_epoch_ns = move(nudged_epoch_ns), .did_expand_calendar_unit = did_round_beyond_day };
}
// 7.5.35 NudgeToDayOrTime ( duration, destEpochNs, largestUnit, increment, smallestUnit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-nudgetodayortime
ThrowCompletionOr<DurationNudgeResult> nudge_to_day_or_time(VM& vm, InternalDuration const& duration, Crypto::SignedBigInteger const& dest_epoch_ns, Unit largest_unit, u64 increment, Unit smallest_unit, RoundingMode rounding_mode)
{
// 1. 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));
// 2. Let unitLength be the value in the "Length in Nanoseconds" column of the row of Table 21 whose "Value" column contains smallestUnit.
auto const& unit_length = temporal_unit_length_in_nanoseconds(smallest_unit);
// 3. Let roundedTime be ? RoundTimeDurationToIncrement(timeDuration, unitLength × increment, roundingMode).
auto unit_length_multiplied_by_increment = unit_length.multiplied_by(Crypto::UnsignedBigInteger { increment });
auto rounded_time = TRY(round_time_duration_to_increment(vm, time_duration, unit_length_multiplied_by_increment, rounding_mode));
// 4. Let diffTime be ! AddTimeDuration(roundedTime, -timeDuration).
time_duration.negate();
auto diff_time = MUST(add_time_duration(vm, rounded_time, time_duration));
time_duration.negate();
// 5. Let wholeDays be truncate(TotalTimeDuration(timeDuration, 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).to_double());
// 7. Let dayDelta be roundedWholeDays - wholeDays.
auto day_delta = rounded_whole_days - whole_days;
// 8. If dayDelta < 0, let dayDeltaSign be -1; else if dayDelta > 0, let dayDeltaSign be 1; else let dayDeltaSign be 0.
auto day_delta_sign = day_delta < 0 ? -1 : (day_delta > 0 ? 1 : 0);
// 9. If dayDeltaSign = TimeDurationSign(timeDuration), let didExpandDays be true; else let didExpandDays be false.
auto did_expand_days = day_delta_sign == time_duration_sign(time_duration);
// 10. Let nudgedEpochNs be AddTimeDurationToEpochNanoseconds(diffTime, destEpochNs).
auto nudged_epoch_ns = add_time_duration_to_epoch_nanoseconds(diff_time, dest_epoch_ns);
// 11. Let days be 0.
double days = 0;
// 12. Let remainder be roundedTime.
TimeDuration remainder;
// 13. If TemporalUnitCategory(largestUnit) is DATE, then
if (temporal_unit_category(largest_unit) == UnitCategory::Date) {
// a. Set days to roundedWholeDays.
days = rounded_whole_days;
// b. Set remainder to ! AddTimeDuration(roundedTime, TimeDurationFromComponents(-roundedWholeDays * HoursPerDay, 0, 0, 0, 0, 0)).
remainder = MUST(add_time_duration(vm, rounded_time, time_duration_from_components(-rounded_whole_days * JS::hours_per_day, 0, 0, 0, 0, 0)));
} else {
remainder = move(rounded_time);
}
// 14. Let dateDuration be ! AdjustDateDurationRecord(duration.[[Date]], days).
auto date_duration = MUST(adjust_date_duration_record(vm, duration.date, days));
// 15. Let resultDuration be CombineDateAndTimeDuration(dateDuration, remainder).
auto result_duration = combine_date_and_time_duration(date_duration, move(remainder));
// 16. Return Duration Nudge Result Record { [[Duration]]: resultDuration, [[NudgedEpochNs]]: nudgedEpochNs, [[DidExpandCalendarUnit]]: didExpandDays }.
return DurationNudgeResult { .duration = move(result_duration), .nudged_epoch_ns = move(nudged_epoch_ns), .did_expand_calendar_unit = did_expand_days };
}
// 7.5.36 BubbleRelativeDuration ( sign, duration, nudgedEpochNs, isoDateTime, timeZone, calendar, largestUnit, smallestUnit ), https://tc39.es/proposal-temporal/#sec-temporal-bubblerelativeduration
ThrowCompletionOr<InternalDuration> bubble_relative_duration(VM& vm, i8 sign, InternalDuration duration, Crypto::SignedBigInteger const& nudged_epoch_ns, ISODateTime const& iso_date_time, Optional<StringView> time_zone, StringView calendar, Unit largest_unit, Unit smallest_unit)
{
// 1. If smallestUnit is largestUnit, return duration.
if (smallest_unit == largest_unit)
return duration;
// 2. Let largestUnitIndex be the ordinal index of the row of Table 21 whose "Value" column contains largestUnit.
auto largest_unit_index = to_underlying(largest_unit);
// 3. Let smallestUnitIndex be the ordinal index of the row of Table 21 whose "Value" column contains smallestUnit.
auto smallest_unit_index = to_underlying(smallest_unit);
// 4. Let unitIndex be smallestUnitIndex - 1.
auto unit_index = smallest_unit_index - 1;
// 5. Let done be false.
auto done = false;
// 6. Repeat, while unitIndex ≥ largestUnitIndex and done is false,
while (unit_index >= largest_unit_index && !done) {
// a. Let unit be the value in the "Value" column of Table 21 in the row whose ordinal index is unitIndex.
auto unit = static_cast<Unit>(unit_index);
// b. If unit is not WEEK, or largestUnit is WEEK, then
if (unit != Unit::Week || largest_unit == Unit::Week) {
DateDuration end_duration;
// i. If unit is YEAR, then
if (unit == Unit::Year) {
// 1. Let years be duration.[[Date]].[[Years]] + sign.
auto years = duration.date.years + sign;
// 2. Let endDuration be ? CreateDateDurationRecord(years, 0, 0, 0).
end_duration = TRY(create_date_duration_record(vm, years, 0, 0, 0));
}
// ii. Else if unit is MONTH, then
else if (unit == Unit::Month) {
// 1. Let months be duration.[[Date]].[[Months]] + sign.
auto months = duration.date.months + sign;
// 2. Let endDuration be ? AdjustDateDurationRecord(duration.[[Date]], 0, 0, months).
end_duration = TRY(adjust_date_duration_record(vm, duration.date, 0, 0, months));
}
// iii. Else,
else {
// 1. Assert: unit is WEEK.
VERIFY(unit == Unit::Week);
// 2. Let weeks be duration.[[Date]].[[Weeks]] + sign.
auto weeks = duration.date.weeks + sign;
// 3. Let endDuration be ? AdjustDateDurationRecord(duration.[[Date]], 0, weeks).
end_duration = TRY(adjust_date_duration_record(vm, duration.date, 0, weeks));
}
// iv. Let end be ? CalendarDateAdd(calendar, isoDateTime.[[ISODate]], endDuration, CONSTRAIN).
auto end = TRY(calendar_date_add(vm, calendar, iso_date_time.iso_date, end_duration, Overflow::Constrain));
// v. Let endDateTime be CombineISODateAndTimeRecord(end, isoDateTime.[[Time]]).
auto end_date_time = combine_iso_date_and_time_record(end, iso_date_time.time);
Crypto::SignedBigInteger end_epoch_ns;
// vi. If timeZone is UNSET, then
if (!time_zone.has_value()) {
// 1. Let endEpochNs be GetUTCEpochNanoseconds(endDateTime).
end_epoch_ns = get_utc_epoch_nanoseconds(end_date_time);
}
// vii. Else,
else {
// 1. Let endEpochNs be ? GetEpochNanosecondsFor(timeZone, endDateTime, COMPATIBLE).
end_epoch_ns = TRY(get_epoch_nanoseconds_for(vm, *time_zone, end_date_time, Disambiguation::Compatible));
}
// viii. Let beyondEnd be nudgedEpochNs - endEpochNs.
auto beyond_end = nudged_epoch_ns.minus(end_epoch_ns);
// ix. If beyondEnd < 0, let beyondEndSign be -1; else if beyondEnd > 0, let beyondEndSign be 1; else let beyondEndSign be 0.
auto beyond_end_sign = beyond_end.is_negative() ? -1 : (beyond_end.is_positive() ? 1 : 0);
// x. If beyondEndSign ≠ -sign, then
if (beyond_end_sign != -sign) {
// 1. Set duration to CombineDateAndTimeDuration(endDuration, 0).
duration = combine_date_and_time_duration(end_duration, TimeDuration { 0 });
}
// xi. Else,
else {
// 1. Set done to true.
done = true;
}
}
// c. Set unitIndex to unitIndex - 1.
--unit_index;
}
// 7. Return duration.
return duration;
}
// 7.5.37 RoundRelativeDuration ( duration, destEpochNs, isoDateTime, timeZone, calendar, largestUnit, increment, smallestUnit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-roundrelativeduration
ThrowCompletionOr<InternalDuration> round_relative_duration(VM& vm, InternalDuration duration, Crypto::SignedBigInteger const& dest_epoch_ns, ISODateTime const& iso_date_time, Optional<StringView> time_zone, StringView calendar, Unit largest_unit, u64 increment, Unit smallest_unit, RoundingMode rounding_mode)
{
// 1. Let irregularLengthUnit be false.
auto irregular_length_unit = false;
// 2. If IsCalendarUnit(smallestUnit) is true, set irregularLengthUnit to true.
if (is_calendar_unit(smallest_unit))
irregular_length_unit = true;
// 3. If timeZone is not UNSET and smallestUnit is DAY, set irregularLengthUnit to true.
if (time_zone.has_value() && smallest_unit == Unit::Day)
irregular_length_unit = true;
// 4. If InternalDurationSign(duration) < 0, let sign be -1; else let sign be 1.
i8 sign = internal_duration_sign(duration) < 0 ? -1 : 1;
DurationNudgeResult nudge_result;
// 5. If irregularLengthUnit is true, then
if (irregular_length_unit) {
// a. Let record be ? NudgeToCalendarUnit(sign, duration, destEpochNs, isoDateTime, timeZone, calendar, increment, smallestUnit, roundingMode).
auto record = TRY(nudge_to_calendar_unit(vm, sign, duration, dest_epoch_ns, iso_date_time, time_zone, calendar, increment, smallest_unit, rounding_mode));
// b. Let nudgeResult be record.[[NudgeResult]].
nudge_result = move(record.nudge_result);
}
// 6. Else if timeZone is not UNSET, then
else if (time_zone.has_value()) {
// a. Let nudgeResult be ? NudgeToZonedTime(sign, duration, isoDateTime, timeZone, calendar, increment, smallestUnit, roundingMode).
nudge_result = TRY(nudge_to_zoned_time(vm, sign, duration, iso_date_time, *time_zone, calendar, increment, smallest_unit, rounding_mode));
}
// 7. Else,
else {
// a. Let nudgeResult be ? NudgeToDayOrTime(duration, destEpochNs, largestUnit, increment, smallestUnit, roundingMode).
nudge_result = TRY(nudge_to_day_or_time(vm, duration, dest_epoch_ns, largest_unit, increment, smallest_unit, rounding_mode));
}
// 8. Set duration to nudgeResult.[[Duration]].
duration = move(nudge_result.duration);
// 9. If nudgeResult.[[DidExpandCalendarUnit]] is true and smallestUnit is not WEEK, then
if (nudge_result.did_expand_calendar_unit && smallest_unit != Unit::Week) {
// a. Let startUnit be LargerOfTwoTemporalUnits(smallestUnit, DAY).
auto start_unit = larger_of_two_temporal_units(smallest_unit, Unit::Day);
// b. Set duration to ? BubbleRelativeDuration(sign, duration, nudgeResult.[[NudgedEpochNs]], isoDateTime, timeZone, calendar, largestUnit, startUnit).
duration = TRY(bubble_relative_duration(vm, sign, move(duration), nudge_result.nudged_epoch_ns, iso_date_time, time_zone, calendar, largest_unit, start_unit));
}
// 10. Return duration.
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, Crypto::SignedBigInteger 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)
{
// 1. Let sign be DurationSign(duration).
auto sign = duration_sign(duration);
// 2. Let datePart be the empty String.
StringBuilder date_part;
// 3. If duration.[[Years]] ≠ 0, then
if (duration.years() != 0) {
// a. Set datePart to the string concatenation of abs(duration.[[Years]]) formatted as a decimal number and the
// code unit 0x0059 (LATIN CAPITAL LETTER Y).
date_part.appendff("{}Y", AK::fabs(duration.years()));
}
// 4. If duration.[[Months]] ≠ 0, then
if (duration.months() != 0) {
// a. Set datePart to the string concatenation of datePart, abs(duration.[[Months]]) formatted as a decimal number,
// and the code unit 0x004D (LATIN CAPITAL LETTER M).
date_part.appendff("{}M", AK::fabs(duration.months()));
}
// 5. If duration.[[Weeks]] ≠ 0, then
if (duration.weeks() != 0) {
// a. Set datePart to the string concatenation of datePart, abs(duration.[[Weeks]]) formatted as a decimal number,
// and the code unit 0x0057 (LATIN CAPITAL LETTER W).
date_part.appendff("{}W", AK::fabs(duration.weeks()));
}
// 6. If duration.[[Days]] ≠ 0, then
if (duration.days() != 0) {
// a. Set datePart to the string concatenation of datePart, abs(duration.[[Days]]) formatted as a decimal number,
// and the code unit 0x0044 (LATIN CAPITAL LETTER D).
date_part.appendff("{}D", AK::fabs(duration.days()));
}
// 7. Let timePart be the empty String.
StringBuilder time_part;
// 8. If duration.[[Hours]] ≠ 0, then
if (duration.hours() != 0) {
// a. Set timePart to the string concatenation of abs(duration.[[Hours]]) formatted as a decimal number and the
// code unit 0x0048 (LATIN CAPITAL LETTER H).
time_part.appendff("{}H", AK::fabs(duration.hours()));
}
// 9. If duration.[[Minutes]] ≠ 0, then
if (duration.minutes() != 0) {
// a. Set timePart to the string concatenation of timePart, abs(duration.[[Minutes]]) formatted as a decimal number,
// and the code unit 0x004D (LATIN CAPITAL LETTER M).
time_part.appendff("{}M", AK::fabs(duration.minutes()));
}
// 10. Let zeroMinutesAndHigher be false.
auto zero_minutes_and_higher = false;
// 11. If DefaultTemporalLargestUnit(duration) is SECOND, MILLISECOND, MICROSECOND, or NANOSECOND, set zeroMinutesAndHigher to true.
if (auto unit = default_temporal_largest_unit(duration); unit == Unit::Second || unit == Unit::Millisecond || unit == Unit::Microsecond || unit == Unit::Nanosecond)
zero_minutes_and_higher = true;
// 12. Let secondsDuration be TimeDurationFromComponents(0, 0, duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]]).
auto seconds_duration = time_duration_from_components(0, 0, duration.seconds(), duration.milliseconds(), duration.microseconds(), duration.nanoseconds());
// 13. If secondsDuration ≠ 0, or zeroMinutesAndHigher is true, or precision is not auto, then
if (!seconds_duration.is_zero() || zero_minutes_and_higher || !precision.has<Auto>()) {
auto division_result = seconds_duration.divided_by(NANOSECONDS_PER_SECOND);
// a. Let secondsPart be abs(truncate(secondsDuration / 10**9)) formatted as a decimal number.
auto seconds_part = MUST(division_result.quotient.unsigned_value().to_base(10));
// b. Let subSecondsPart be FormatFractionalSeconds(abs(remainder(secondsDuration, 10**9)), precision).
auto sub_seconds_part = format_fractional_seconds(division_result.remainder.unsigned_value().to_u64(), precision);
// c. Set timePart to the string concatenation of timePart, secondsPart, subSecondsPart, and the code unit
// 0x0053 (LATIN CAPITAL LETTER S).
time_part.appendff("{}{}S", seconds_part, sub_seconds_part);
}
// 14. Let signPart be the code unit 0x002D (HYPHEN-MINUS) if sign < 0, and otherwise the empty String.
auto sign_part = sign < 0 ? "-"sv : ""sv;
// 15. Let result be the string concatenation of signPart, the code unit 0x0050 (LATIN CAPITAL LETTER P) and datePart.
StringBuilder result;
result.appendff("{}P{}", sign_part, date_part.string_view());
// 16. If timePart is not the empty String, then
if (!time_part.is_empty()) {
// a. Set result to the string concatenation of result, the code unit 0x0054 (LATIN CAPITAL LETTER T), and timePart.
result.appendff("T{}", time_part.string_view());
}
// 17. Return result.
return MUST(result.to_string());
}
// 7.5.40 AddDurations ( operation, duration, other ), https://tc39.es/proposal-temporal/#sec-temporal-adddurations
ThrowCompletionOr<GC::Ref<Duration>> add_durations(VM& vm, ArithmeticOperation operation, Duration const& duration, Value other_value)
{
// 1. Set other to ? ToTemporalDuration(other).
auto other = TRY(to_temporal_duration(vm, other_value));
// 2. If operation is subtract, set other to CreateNegatedTemporalDuration(other).
if (operation == ArithmeticOperation::Subtract)
other = create_negated_temporal_duration(vm, other);
// 3. Let largestUnit1 be DefaultTemporalLargestUnit(duration).
auto largest_unit1 = default_temporal_largest_unit(duration);
// 4. Let largestUnit2 be DefaultTemporalLargestUnit(other).
auto largest_unit2 = default_temporal_largest_unit(other);
// 5. Let largestUnit be LargerOfTwoTemporalUnits(largestUnit1, largestUnit2).
auto largest_unit = larger_of_two_temporal_units(largest_unit1, largest_unit2);
// 6. If IsCalendarUnit(largestUnit) is true, throw a RangeError exception.
if (is_calendar_unit(largest_unit))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidLargestUnit, "a calendar unit"sv);
// 7. Let d1 be ToInternalDurationRecordWith24HourDays(duration).
auto duration1 = to_internal_duration_record_with_24_hour_days(vm, duration);
// 8. Let d2 be ToInternalDurationRecordWith24HourDays(other).
auto duration2 = to_internal_duration_record_with_24_hour_days(vm, other);
// 9. Let timeResult be ? AddTimeDuration(d1.[[Time]], d2.[[Time]]).
auto time_result = TRY(add_time_duration(vm, duration1.time, duration2.time));
// 10. Let result be CombineDateAndTimeDuration(ZeroDateDuration(), timeResult).
auto result = combine_date_and_time_duration(zero_date_duration(vm), move(time_result));
// 11. Return ? TemporalDurationFromInternal(result, largestUnit).
return TRY(temporal_duration_from_internal(vm, result, largest_unit));
}
}