LibJS: Implement mathematical Temporal.Duration prototypes

Includes:
Temporal.Duration.prototype.negated
Temporal.Duration.prototype.abs
Temporal.Duration.prototype.add
Temporal.Duration.prototype.subtract
This commit is contained in:
Timothy Flynn 2024-11-18 12:48:10 -05:00 committed by Tim Flynn
parent 55c81482b0
commit a80523be18
Notes: github-actions[bot] 2024-11-21 00:06:00 +00:00
11 changed files with 591 additions and 0 deletions

View file

@ -261,6 +261,7 @@
M(TemporalInvalidInstantString, "Invalid instant string '{}'") \ M(TemporalInvalidInstantString, "Invalid instant string '{}'") \
M(TemporalInvalidISODate, "Invalid ISO date") \ M(TemporalInvalidISODate, "Invalid ISO date") \
M(TemporalInvalidISODateTime, "Invalid ISO date time") \ M(TemporalInvalidISODateTime, "Invalid ISO date time") \
M(TemporalInvalidLargestUnit, "Largest unit must not be {}") \
M(TemporalInvalidMonthCode, "Invalid month code") \ M(TemporalInvalidMonthCode, "Invalid month code") \
M(TemporalInvalidMonthDayString, "Invalid month day string '{}'") \ M(TemporalInvalidMonthDayString, "Invalid month day string '{}'") \
M(TemporalInvalidMonthDayStringUTCDesignator, "Invalid month day string '{}': must not contain a UTC designator") \ M(TemporalInvalidMonthDayStringUTCDesignator, "Invalid month day string '{}': must not contain a UTC designator") \

View file

@ -55,6 +55,26 @@ ThrowCompletionOr<RelativeTo> get_temporal_relative_to_option(VM& vm, Object con
return RelativeTo { .plain_relative_to = {}, .zoned_relative_to = {} }; return RelativeTo { .plain_relative_to = {}, .zoned_relative_to = {} };
} }
// 13.19 LargerOfTwoTemporalUnits ( u1, u2 ), https://tc39.es/proposal-temporal/#sec-temporal-largeroftwotemporalunits
Unit larger_of_two_temporal_units(Unit unit1, Unit unit2)
{
// 1. For each row of Table 21, except the header row, in table order, do
for (auto const& row : temporal_units) {
// a. Let unit be the value in the "Value" column of the row.
auto unit = row.value;
// b. If u1 is unit, return unit.
if (unit1 == unit)
return unit;
// c. If u2 is unit, return unit.
if (unit2 == unit)
return unit;
}
VERIFY_NOT_REACHED();
}
// 13.20 IsCalendarUnit ( unit ), https://tc39.es/proposal-temporal/#sec-temporal-iscalendarunit // 13.20 IsCalendarUnit ( unit ), https://tc39.es/proposal-temporal/#sec-temporal-iscalendarunit
bool is_calendar_unit(Unit unit) bool is_calendar_unit(Unit unit)
{ {

View file

@ -18,6 +18,11 @@
namespace JS::Temporal { namespace JS::Temporal {
enum class ArithmeticOperation {
Add,
Subtract,
};
// https://tc39.es/proposal-temporal/#sec-temporal-units // https://tc39.es/proposal-temporal/#sec-temporal-units
enum class Unit { enum class Unit {
Year, Year,
@ -56,6 +61,7 @@ struct RelativeTo {
}; };
ThrowCompletionOr<RelativeTo> get_temporal_relative_to_option(VM&, Object const& options); ThrowCompletionOr<RelativeTo> get_temporal_relative_to_option(VM&, Object const& options);
Unit larger_of_two_temporal_units(Unit, Unit);
bool is_calendar_unit(Unit); bool is_calendar_unit(Unit);
UnitCategory temporal_unit_category(Unit); UnitCategory temporal_unit_category(Unit);
ThrowCompletionOr<GC::Ref<Duration>> parse_temporal_duration_string(VM&, StringView iso_string); ThrowCompletionOr<GC::Ref<Duration>> parse_temporal_duration_string(VM&, StringView iso_string);

View file

@ -88,6 +88,200 @@ InternalDuration to_internal_duration_record(VM& vm, Duration const& duration)
return MUST(combine_date_and_time_duration(vm, date_duration, move(time_duration))); return MUST(combine_date_and_time_duration(vm, 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 MUST(combine_date_and_time_duration(vm, date_duration, move(time_duration)));
}
// 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 // 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) ThrowCompletionOr<DateDuration> create_date_duration_record(VM& vm, double years, double months, double weeks, double days)
{ {
@ -432,6 +626,13 @@ ThrowCompletionOr<GC::Ref<Duration>> create_temporal_duration(VM& vm, double yea
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 // 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) TimeDuration time_duration_from_components(double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds)
{ {
@ -457,6 +658,20 @@ TimeDuration time_duration_from_components(double hours, double minutes, double
return total_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 // 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) ThrowCompletionOr<TimeDuration> add_24_hour_days_to_time_duration(VM& vm, TimeDuration const& time_duration, double days)
{ {
@ -501,4 +716,43 @@ i8 time_duration_sign(TimeDuration const& time_duration)
return 0; return 0;
} }
// 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 = MUST(combine_date_and_time_duration(vm, zero_date_duration(vm), move(time_result)));
// 11. Return ? TemporalDurationFromInternal(result, largestUnit).
return TRY(temporal_duration_from_internal(vm, result, largest_unit));
}
} }

View file

@ -102,6 +102,8 @@ struct InternalDuration {
DateDuration zero_date_duration(VM&); DateDuration zero_date_duration(VM&);
InternalDuration to_internal_duration_record(VM&, Duration const&); InternalDuration to_internal_duration_record(VM&, Duration const&);
InternalDuration to_internal_duration_record_with_24_hour_days(VM&, Duration const&);
ThrowCompletionOr<GC::Ref<Duration>> temporal_duration_from_internal(VM&, InternalDuration const&, Unit largest_unit);
ThrowCompletionOr<DateDuration> create_date_duration_record(VM&, double years, double months, double weeks, double days); ThrowCompletionOr<DateDuration> create_date_duration_record(VM&, double years, double months, double weeks, double days);
ThrowCompletionOr<InternalDuration> combine_date_and_time_duration(VM&, DateDuration, TimeDuration); ThrowCompletionOr<InternalDuration> combine_date_and_time_duration(VM&, DateDuration, TimeDuration);
ThrowCompletionOr<GC::Ref<Duration>> to_temporal_duration(VM&, Value); ThrowCompletionOr<GC::Ref<Duration>> to_temporal_duration(VM&, Value);
@ -111,9 +113,12 @@ bool is_valid_duration(double years, double months, double weeks, double days, d
Unit default_temporal_largest_unit(Duration const&); Unit default_temporal_largest_unit(Duration const&);
ThrowCompletionOr<PartialDuration> to_temporal_partial_duration_record(VM&, Value temporal_duration_like); ThrowCompletionOr<PartialDuration> to_temporal_partial_duration_record(VM&, Value temporal_duration_like);
ThrowCompletionOr<GC::Ref<Duration>> create_temporal_duration(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 = {}); ThrowCompletionOr<GC::Ref<Duration>> create_temporal_duration(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 = {});
GC::Ref<Duration> create_negated_temporal_duration(VM&, Duration const&);
TimeDuration time_duration_from_components(double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds); TimeDuration time_duration_from_components(double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds);
ThrowCompletionOr<TimeDuration> add_time_duration(VM&, TimeDuration const&, TimeDuration const&);
ThrowCompletionOr<TimeDuration> add_24_hour_days_to_time_duration(VM&, TimeDuration const&, double days); ThrowCompletionOr<TimeDuration> add_24_hour_days_to_time_duration(VM&, TimeDuration const&, double days);
i8 compare_time_duration(TimeDuration const&, TimeDuration const&); i8 compare_time_duration(TimeDuration const&, TimeDuration const&);
i8 time_duration_sign(TimeDuration const&); i8 time_duration_sign(TimeDuration const&);
ThrowCompletionOr<GC::Ref<Duration>> add_durations(VM&, ArithmeticOperation, Duration const&, Value);
} }

View file

@ -37,6 +37,10 @@ void DurationPrototype::initialize(Realm& realm)
u8 attr = Attribute::Writable | Attribute::Configurable; u8 attr = Attribute::Writable | Attribute::Configurable;
define_native_function(realm, vm.names.with, with, 1, attr); define_native_function(realm, vm.names.with, with, 1, attr);
define_native_function(realm, vm.names.negated, negated, 0, attr);
define_native_function(realm, vm.names.abs, abs, 0, attr);
define_native_function(realm, vm.names.add, add, 1, attr);
define_native_function(realm, vm.names.subtract, subtract, 1, attr);
} }
// 7.3.3 get Temporal.Duration.prototype.years, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.years // 7.3.3 get Temporal.Duration.prototype.years, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.years
@ -162,4 +166,52 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::with)
return TRY(create_temporal_duration(vm, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds)); return TRY(create_temporal_duration(vm, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds));
} }
// 7.3.16 Temporal.Duration.prototype.negated ( ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.negated
JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::negated)
{
// 1. Let duration be the this value.
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
auto duration = TRY(typed_this_object(vm));
// 3. Return CreateNegatedTemporalDuration(duration).
return create_negated_temporal_duration(vm, duration);
}
// 7.3.17 Temporal.Duration.prototype.abs ( ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.abs
JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::abs)
{
// 1. Let duration be the this value.
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
auto duration = TRY(typed_this_object(vm));
// 3. Return ! CreateTemporalDuration(abs(duration.[[Years]]), abs(duration.[[Months]]), abs(duration.[[Weeks]]), abs(duration.[[Days]]), abs(duration.[[Hours]]), abs(duration.[[Minutes]]), abs(duration.[[Seconds]]), abs(duration.[[Milliseconds]]), abs(duration.[[Microseconds]]), abs(duration.[[Nanoseconds]])).
return MUST(create_temporal_duration(vm, fabs(duration->years()), fabs(duration->months()), fabs(duration->weeks()), fabs(duration->days()), fabs(duration->hours()), fabs(duration->minutes()), fabs(duration->seconds()), fabs(duration->milliseconds()), fabs(duration->microseconds()), fabs(duration->nanoseconds())));
}
// 7.3.18 Temporal.Duration.prototype.add ( other ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.add
JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::add)
{
auto other = vm.argument(0);
// 1. Let duration be the this value.
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
auto duration = TRY(typed_this_object(vm));
// 3. Return ? AddDurations(ADD, duration, other).
return TRY(add_durations(vm, ArithmeticOperation::Add, duration, other));
}
// 7.3.19 Temporal.Duration.prototype.subtract ( other ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.subtract
JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::subtract)
{
auto other = vm.argument(0);
// 1. Let duration be the this value.
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
auto duration = TRY(typed_this_object(vm));
// 3. Return ? AddDurations(SUBTRACT, duration, other).
return TRY(add_durations(vm, ArithmeticOperation::Subtract, duration, other));
}
} }

View file

@ -31,6 +31,10 @@ private:
JS_DECLARE_NATIVE_FUNCTION(sign_getter); JS_DECLARE_NATIVE_FUNCTION(sign_getter);
JS_DECLARE_NATIVE_FUNCTION(blank_getter); JS_DECLARE_NATIVE_FUNCTION(blank_getter);
JS_DECLARE_NATIVE_FUNCTION(with); JS_DECLARE_NATIVE_FUNCTION(with);
JS_DECLARE_NATIVE_FUNCTION(negated);
JS_DECLARE_NATIVE_FUNCTION(abs);
JS_DECLARE_NATIVE_FUNCTION(add);
JS_DECLARE_NATIVE_FUNCTION(subtract);
}; };
} }

View file

@ -0,0 +1,53 @@
const DURATION_PROPERTIES = [
"years",
"months",
"weeks",
"days",
"hours",
"minutes",
"seconds",
"milliseconds",
"microseconds",
"nanoseconds",
];
describe("correct behavior", () => {
test("length is 0", () => {
expect(Temporal.Duration.prototype.abs).toHaveLength(0);
});
test("basic functionality", () => {
let absoluteDuration;
absoluteDuration = new Temporal.Duration(123).abs();
expect(absoluteDuration.years).toBe(123);
absoluteDuration = new Temporal.Duration(-123).abs();
expect(absoluteDuration.years).toBe(123);
});
test("each property is made absolute", () => {
let values;
let duration;
values = Array(DURATION_PROPERTIES.length).fill(-1);
duration = new Temporal.Duration(...values).abs();
for (const property of DURATION_PROPERTIES) {
expect(duration[property]).toBe(1);
}
values = Array(DURATION_PROPERTIES.length).fill(1);
duration = new Temporal.Duration(...values).abs();
for (const property of DURATION_PROPERTIES) {
expect(duration[property]).toBe(1);
}
});
});
describe("errors", () => {
test("this value must be a Temporal.Duration object", () => {
expect(() => {
Temporal.Duration.prototype.abs.call("foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.Duration");
});
});

View file

@ -0,0 +1,77 @@
describe("correct behavior", () => {
test("length is 1", () => {
expect(Temporal.Duration.prototype.add).toHaveLength(1);
});
function checkCommonResults(durationResult) {
expect(durationResult.years).toBe(0);
expect(durationResult.months).toBe(0);
expect(durationResult.weeks).toBe(0);
expect(durationResult.days).toBe(3);
expect(durationResult.hours).toBe(3);
expect(durationResult.minutes).toBe(3);
expect(durationResult.seconds).toBe(3);
expect(durationResult.milliseconds).toBe(3);
expect(durationResult.microseconds).toBe(3);
expect(durationResult.nanoseconds).toBe(3);
}
test("basic functionality", () => {
const duration = new Temporal.Duration(0, 0, 0, 2, 2, 2, 2, 2, 2, 2);
const oneDuration = new Temporal.Duration(0, 0, 0, 1, 1, 1, 1, 1, 1, 1);
const durationResult = duration.add(oneDuration);
checkCommonResults(durationResult);
});
test("from duration-like", () => {
const duration = new Temporal.Duration(0, 0, 0, 2, 2, 2, 2, 2, 2, 2);
const oneDuration = {
days: 1,
hours: 1,
minutes: 1,
seconds: 1,
milliseconds: 1,
microseconds: 1,
nanoseconds: 1,
};
const durationResult = duration.add(oneDuration);
checkCommonResults(durationResult);
});
test("from string", () => {
const duration = new Temporal.Duration(0, 0, 0, 2, 2, 2, 2, 2, 2, 2);
const oneDuration = "P1DT1H1M1.001001001S";
const durationResult = duration.add(oneDuration);
checkCommonResults(durationResult);
});
});
describe("errors", () => {
test("this value must be a Temporal.Duration object", () => {
expect(() => {
Temporal.Duration.prototype.add.call("foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.Duration");
});
test("relativeTo is required when duration has calendar units", () => {
const yearDuration = new Temporal.Duration(1);
const monthDuration = new Temporal.Duration(0, 1);
const weekDuration = new Temporal.Duration(0, 0, 1);
const durationToAdd = { seconds: 1 };
expect(() => {
yearDuration.add(durationToAdd);
}).toThrowWithMessage(RangeError, "Largest unit must not be a calendar unit");
expect(() => {
monthDuration.add(durationToAdd);
}).toThrowWithMessage(RangeError, "Largest unit must not be a calendar unit");
expect(() => {
weekDuration.add(durationToAdd);
}).toThrowWithMessage(RangeError, "Largest unit must not be a calendar unit");
});
});

View file

@ -0,0 +1,42 @@
const DURATION_PROPERTIES = [
"years",
"months",
"weeks",
"days",
"hours",
"minutes",
"seconds",
"milliseconds",
"microseconds",
"nanoseconds",
];
describe("correct behavior", () => {
test("length is 0", () => {
expect(Temporal.Duration.prototype.negated).toHaveLength(0);
});
test("basic functionality", () => {
const negativeDuration = new Temporal.Duration(123).negated();
expect(negativeDuration.years).toBe(-123);
const positiveDuration = new Temporal.Duration(-123).negated();
expect(positiveDuration.years).toBe(123);
});
test("each property is negated", () => {
const values = Array(DURATION_PROPERTIES.length).fill(1);
const duration = new Temporal.Duration(...values).negated();
for (const property of DURATION_PROPERTIES) {
expect(duration[property]).toBe(-1);
}
});
});
describe("errors", () => {
test("this value must be a Temporal.Duration object", () => {
expect(() => {
Temporal.Duration.prototype.negated.call("foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.Duration");
});
});

View file

@ -0,0 +1,77 @@
describe("correct behavior", () => {
test("length is 1", () => {
expect(Temporal.Duration.prototype.subtract).toHaveLength(1);
});
function checkCommonResults(durationResult) {
expect(durationResult.years).toBe(0);
expect(durationResult.months).toBe(0);
expect(durationResult.weeks).toBe(0);
expect(durationResult.days).toBe(1);
expect(durationResult.hours).toBe(1);
expect(durationResult.minutes).toBe(1);
expect(durationResult.seconds).toBe(1);
expect(durationResult.milliseconds).toBe(1);
expect(durationResult.microseconds).toBe(1);
expect(durationResult.nanoseconds).toBe(1);
}
test("basic functionality", () => {
const duration = new Temporal.Duration(0, 0, 0, 2, 2, 2, 2, 2, 2, 2);
const oneDuration = new Temporal.Duration(0, 0, 0, 1, 1, 1, 1, 1, 1, 1);
const durationResult = duration.subtract(oneDuration);
checkCommonResults(durationResult);
});
test("from duration-like", () => {
const duration = new Temporal.Duration(0, 0, 0, 2, 2, 2, 2, 2, 2, 2);
const oneDuration = {
days: 1,
hours: 1,
minutes: 1,
seconds: 1,
milliseconds: 1,
microseconds: 1,
nanoseconds: 1,
};
const durationResult = duration.subtract(oneDuration);
checkCommonResults(durationResult);
});
test("from string", () => {
const duration = new Temporal.Duration(0, 0, 0, 2, 2, 2, 2, 2, 2, 2);
const oneDuration = "P1DT1H1M1.001001001S";
const durationResult = duration.subtract(oneDuration);
checkCommonResults(durationResult);
});
});
describe("errors", () => {
test("this value must be a Temporal.Duration object", () => {
expect(() => {
Temporal.Duration.prototype.subtract.call("foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.Duration");
});
test("relativeTo is required when duration has calendar units", () => {
const yearDuration = new Temporal.Duration(1);
const monthDuration = new Temporal.Duration(0, 1);
const weekDuration = new Temporal.Duration(0, 0, 1);
const durationToSubtract = { seconds: 1 };
expect(() => {
yearDuration.subtract(durationToSubtract);
}).toThrowWithMessage(RangeError, "Largest unit must not be a calendar unit");
expect(() => {
monthDuration.subtract(durationToSubtract);
}).toThrowWithMessage(RangeError, "Largest unit must not be a calendar unit");
expect(() => {
weekDuration.subtract(durationToSubtract);
}).toThrowWithMessage(RangeError, "Largest unit must not be a calendar unit");
});
});