mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-29 11:00:29 +00:00
LibJS: Implement Temporal.Duration.prototype.total()
This commit is contained in:
parent
656efe5d6c
commit
dbe70e7c55
Notes:
sideshowbarker
2024-07-18 01:11:17 +09:00
Author: https://github.com/linusg Commit: https://github.com/SerenityOS/serenity/commit/dbe70e7c55b Pull-request: https://github.com/SerenityOS/serenity/pull/10909 Reviewed-by: https://github.com/IdanHo ✅
7 changed files with 245 additions and 0 deletions
|
@ -456,6 +456,7 @@ namespace JS {
|
||||||
P(toPlainTime) \
|
P(toPlainTime) \
|
||||||
P(toPlainYearMonth) \
|
P(toPlainYearMonth) \
|
||||||
P(toString) \
|
P(toString) \
|
||||||
|
P(total) \
|
||||||
P(toTemporalInstant) \
|
P(toTemporalInstant) \
|
||||||
P(toTimeString) \
|
P(toTimeString) \
|
||||||
P(toUpperCase) \
|
P(toUpperCase) \
|
||||||
|
|
|
@ -515,6 +515,32 @@ ThrowCompletionOr<Optional<String>> to_smallest_temporal_unit(GlobalObject& glob
|
||||||
return smallest_unit;
|
return smallest_unit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 13.19 ToTemporalDurationTotalUnit ( normalizedOptions ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaldurationtotalunit
|
||||||
|
ThrowCompletionOr<String> to_temporal_duration_total_unit(GlobalObject& global_object, Object const& normalized_options)
|
||||||
|
{
|
||||||
|
auto& vm = global_object.vm();
|
||||||
|
|
||||||
|
// 1. Let unit be ? GetOption(normalizedOptions, "unit", « String », « "year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds", "microsecond", "microseconds", "nanosecond", "nanoseconds" », undefined).
|
||||||
|
auto unit_value = TRY(get_option(global_object, normalized_options, vm.names.unit, { OptionType::String }, { "year"sv, "years"sv, "month"sv, "months"sv, "week"sv, "weeks"sv, "day"sv, "days"sv, "hour"sv, "hours"sv, "minute"sv, "minutes"sv, "second"sv, "seconds"sv, "millisecond"sv, "milliseconds"sv, "microsecond"sv, "microseconds"sv, "nanosecond"sv, "nanoseconds"sv }, js_undefined()));
|
||||||
|
|
||||||
|
// 2. If unit is undefined, then
|
||||||
|
if (unit_value.is_undefined()) {
|
||||||
|
// a. Throw a RangeError exception.
|
||||||
|
return vm.throw_completion<RangeError>(global_object, ErrorType::IsUndefined, "unit option value"sv);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto unit = unit_value.as_string().string();
|
||||||
|
|
||||||
|
// 3. If unit is in the Plural column of Table 12, then
|
||||||
|
if (auto singular_unit = plural_to_singular_units.get(unit); singular_unit.has_value()) {
|
||||||
|
// a. Set unit to the corresponding Singular value of the same row.
|
||||||
|
unit = *singular_unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Return unit.
|
||||||
|
return unit;
|
||||||
|
}
|
||||||
|
|
||||||
// 13.21 ToRelativeTemporalObject ( options ), https://tc39.es/proposal-temporal/#sec-temporal-torelativetemporalobject
|
// 13.21 ToRelativeTemporalObject ( options ), https://tc39.es/proposal-temporal/#sec-temporal-torelativetemporalobject
|
||||||
ThrowCompletionOr<Value> to_relative_temporal_object(GlobalObject& global_object, Object const& options)
|
ThrowCompletionOr<Value> to_relative_temporal_object(GlobalObject& global_object, Object const& options)
|
||||||
{
|
{
|
||||||
|
|
|
@ -113,6 +113,7 @@ ThrowCompletionOr<u64> to_temporal_date_time_rounding_increment(GlobalObject&, O
|
||||||
ThrowCompletionOr<SecondsStringPrecision> to_seconds_string_precision(GlobalObject&, Object const& normalized_options);
|
ThrowCompletionOr<SecondsStringPrecision> to_seconds_string_precision(GlobalObject&, Object const& normalized_options);
|
||||||
ThrowCompletionOr<String> to_largest_temporal_unit(GlobalObject&, Object const& normalized_options, Vector<StringView> const& disallowed_units, String const& fallback, Optional<String> auto_value);
|
ThrowCompletionOr<String> to_largest_temporal_unit(GlobalObject&, Object const& normalized_options, Vector<StringView> const& disallowed_units, String const& fallback, Optional<String> auto_value);
|
||||||
ThrowCompletionOr<Optional<String>> to_smallest_temporal_unit(GlobalObject&, Object const& normalized_options, Vector<StringView> const& disallowed_units, Optional<String> fallback);
|
ThrowCompletionOr<Optional<String>> to_smallest_temporal_unit(GlobalObject&, Object const& normalized_options, Vector<StringView> const& disallowed_units, Optional<String> fallback);
|
||||||
|
ThrowCompletionOr<String> to_temporal_duration_total_unit(GlobalObject& global_object, Object const& normalized_options);
|
||||||
ThrowCompletionOr<Value> to_relative_temporal_object(GlobalObject&, Object const& options);
|
ThrowCompletionOr<Value> to_relative_temporal_object(GlobalObject&, Object const& options);
|
||||||
ThrowCompletionOr<void> validate_temporal_unit_range(GlobalObject&, StringView largest_unit, StringView smallest_unit);
|
ThrowCompletionOr<void> validate_temporal_unit_range(GlobalObject&, StringView largest_unit, StringView smallest_unit);
|
||||||
String larger_of_two_temporal_units(StringView, StringView);
|
String larger_of_two_temporal_units(StringView, StringView);
|
||||||
|
|
|
@ -704,6 +704,9 @@ ThrowCompletionOr<RoundedDuration> round_duration(GlobalObject& global_object, d
|
||||||
|
|
||||||
// 2. Let years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, and increment each be the mathematical values of themselves.
|
// 2. Let years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, and increment each be the mathematical values of themselves.
|
||||||
|
|
||||||
|
// FIXME: assuming "smallestUnit" as the option name here leads to confusing error messages in some cases:
|
||||||
|
// > new Temporal.Duration().total({ unit: "month" })
|
||||||
|
// Uncaught exception: [RangeError] month is not a valid value for option smallestUnit
|
||||||
// 3. If unit is "year", "month", or "week", and relativeTo is undefined, then
|
// 3. If unit is "year", "month", or "week", and relativeTo is undefined, then
|
||||||
if (unit.is_one_of("year"sv, "month"sv, "week"sv) && !relative_to_object) {
|
if (unit.is_one_of("year"sv, "month"sv, "week"sv) && !relative_to_object) {
|
||||||
// a. Throw a RangeError exception.
|
// a. Throw a RangeError exception.
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||||
#include <LibJS/Runtime/Temporal/Duration.h>
|
#include <LibJS/Runtime/Temporal/Duration.h>
|
||||||
#include <LibJS/Runtime/Temporal/DurationPrototype.h>
|
#include <LibJS/Runtime/Temporal/DurationPrototype.h>
|
||||||
|
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
namespace JS::Temporal {
|
namespace JS::Temporal {
|
||||||
|
@ -45,6 +46,7 @@ void DurationPrototype::initialize(GlobalObject& global_object)
|
||||||
define_native_function(vm.names.with, with, 1, attr);
|
define_native_function(vm.names.with, with, 1, attr);
|
||||||
define_native_function(vm.names.negated, negated, 0, attr);
|
define_native_function(vm.names.negated, negated, 0, attr);
|
||||||
define_native_function(vm.names.abs, abs, 0, attr);
|
define_native_function(vm.names.abs, abs, 0, attr);
|
||||||
|
define_native_function(vm.names.total, total, 1, attr);
|
||||||
define_native_function(vm.names.toString, to_string, 0, attr);
|
define_native_function(vm.names.toString, to_string, 0, attr);
|
||||||
define_native_function(vm.names.toJSON, to_json, 0, attr);
|
define_native_function(vm.names.toJSON, to_json, 0, attr);
|
||||||
define_native_function(vm.names.toLocaleString, to_locale_string, 0, attr);
|
define_native_function(vm.names.toLocaleString, to_locale_string, 0, attr);
|
||||||
|
@ -286,6 +288,110 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::abs)
|
||||||
return TRY(create_temporal_duration(global_object, 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())));
|
return TRY(create_temporal_duration(global_object, 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.21 Temporal.Duration.prototype.total ( options ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.total
|
||||||
|
JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::total)
|
||||||
|
{
|
||||||
|
// 1. Let duration be the this value.
|
||||||
|
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
|
||||||
|
auto* duration = TRY(typed_this_object(global_object));
|
||||||
|
|
||||||
|
// 3. If options is undefined, throw a TypeError exception.
|
||||||
|
if (vm.argument(0).is_undefined())
|
||||||
|
return vm.throw_completion<TypeError>(global_object, ErrorType::TemporalMissingOptionsObject);
|
||||||
|
|
||||||
|
// 4. Set options to ? GetOptionsObject(options).
|
||||||
|
auto* options = TRY(get_options_object(global_object, vm.argument(0)));
|
||||||
|
|
||||||
|
// 5. Let relativeTo be ? ToRelativeTemporalObject(options).
|
||||||
|
auto relative_to = TRY(to_relative_temporal_object(global_object, *options));
|
||||||
|
|
||||||
|
// 6. Let unit be ? ToTemporalDurationTotalUnit(options).
|
||||||
|
auto unit = TRY(to_temporal_duration_total_unit(global_object, *options));
|
||||||
|
|
||||||
|
// 7. Let unbalanceResult be ? UnbalanceDurationRelative(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], unit, relativeTo).
|
||||||
|
auto unbalance_result = TRY(unbalance_duration_relative(global_object, duration->years(), duration->months(), duration->weeks(), duration->days(), unit, relative_to));
|
||||||
|
|
||||||
|
// 8. Let intermediate be undefined.
|
||||||
|
ZonedDateTime* intermediate = nullptr;
|
||||||
|
|
||||||
|
// 9. If relativeTo has an [[InitializedTemporalZonedDateTime]] internal slot, then
|
||||||
|
if (relative_to.is_object() && is<ZonedDateTime>(relative_to.as_object())) {
|
||||||
|
// a. Set intermediate to ? MoveRelativeZonedDateTime(relativeTo, unbalanceResult.[[Years]], unbalanceResult.[[Months]], unbalanceResult.[[Weeks]], 0).
|
||||||
|
intermediate = TRY(move_relative_zoned_date_time(global_object, static_cast<ZonedDateTime&>(relative_to.as_object()), unbalance_result.years, unbalance_result.months, unbalance_result.weeks, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 10. Let balanceResult be ? BalanceDuration(unbalanceResult.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], unit, intermediate).
|
||||||
|
auto balance_result = TRY(balance_duration(global_object, unbalance_result.days, duration->hours(), duration->minutes(), duration->seconds(), duration->milliseconds(), duration->microseconds(), *js_bigint(vm, Crypto::SignedBigInteger::create_from(duration->nanoseconds())), unit, intermediate));
|
||||||
|
|
||||||
|
// 11. Let roundResult be ? RoundDuration(unbalanceResult.[[Years]], unbalanceResult.[[Months]], unbalanceResult.[[Weeks]], balanceResult.[[Days]], balanceResult.[[Hours]], balanceResult.[[Minutes]], balanceResult.[[Seconds]], balanceResult.[[Milliseconds]], balanceResult.[[Microseconds]], balanceResult.[[Nanoseconds]], 1, unit, "trunc", relativeTo).
|
||||||
|
auto round_result = TRY(round_duration(global_object, unbalance_result.years, unbalance_result.months, unbalance_result.weeks, balance_result.days, balance_result.hours, balance_result.minutes, balance_result.seconds, balance_result.milliseconds, balance_result.microseconds, balance_result.nanoseconds, 1, unit, "trunc"sv, relative_to.is_object() ? &relative_to.as_object() : nullptr));
|
||||||
|
|
||||||
|
double whole;
|
||||||
|
|
||||||
|
// 12. If unit is "year", then
|
||||||
|
if (unit == "year"sv) {
|
||||||
|
// a. Let whole be roundResult.[[Years]].
|
||||||
|
whole = round_result.years;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 13. If unit is "month", then
|
||||||
|
if (unit == "month"sv) {
|
||||||
|
// a. Let whole be roundResult.[[Months]].
|
||||||
|
whole = round_result.months;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 14. If unit is "week", then
|
||||||
|
if (unit == "week"sv) {
|
||||||
|
// a. Let whole be roundResult.[[Weeks]].
|
||||||
|
whole = round_result.weeks;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 15. If unit is "day", then
|
||||||
|
if (unit == "day"sv) {
|
||||||
|
// a. Let whole be roundResult.[[Days]].
|
||||||
|
whole = round_result.days;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 16. If unit is "hour", then
|
||||||
|
if (unit == "hour"sv) {
|
||||||
|
// a. Let whole be roundResult.[[Hours]].
|
||||||
|
whole = round_result.hours;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 17. If unit is "minute", then
|
||||||
|
if (unit == "minute"sv) {
|
||||||
|
// a. Let whole be roundResult.[[Minutes]].
|
||||||
|
whole = round_result.minutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 18. If unit is "second", then
|
||||||
|
if (unit == "second"sv) {
|
||||||
|
// a. Let whole be roundResult.[[Seconds]].
|
||||||
|
whole = round_result.seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 19. If unit is "millisecond", then
|
||||||
|
if (unit == "millisecond"sv) {
|
||||||
|
// a. Let whole be roundResult.[[Milliseconds]].
|
||||||
|
whole = round_result.milliseconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 20. If unit is "microsecond", then
|
||||||
|
if (unit == "microsecond"sv) {
|
||||||
|
// a. Let whole be roundResult.[[Microseconds]].
|
||||||
|
whole = round_result.microseconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 21. If unit is "nanosecond", then
|
||||||
|
if (unit == "nanosecond"sv) {
|
||||||
|
// a. Let whole be roundResult.[[Nanoseconds]].
|
||||||
|
whole = round_result.nanoseconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 22. Return whole + roundResult.[[Remainder]].
|
||||||
|
return whole + round_result.remainder;
|
||||||
|
}
|
||||||
|
|
||||||
// 7.3.22 Temporal.Duration.prototype.toString ( [ options ] ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.tostring
|
// 7.3.22 Temporal.Duration.prototype.toString ( [ options ] ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.tostring
|
||||||
JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::to_string)
|
JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::to_string)
|
||||||
{
|
{
|
||||||
|
|
|
@ -35,6 +35,7 @@ private:
|
||||||
JS_DECLARE_NATIVE_FUNCTION(with);
|
JS_DECLARE_NATIVE_FUNCTION(with);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(negated);
|
JS_DECLARE_NATIVE_FUNCTION(negated);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(abs);
|
JS_DECLARE_NATIVE_FUNCTION(abs);
|
||||||
|
JS_DECLARE_NATIVE_FUNCTION(total);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(to_string);
|
JS_DECLARE_NATIVE_FUNCTION(to_string);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(to_json);
|
JS_DECLARE_NATIVE_FUNCTION(to_json);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(to_locale_string);
|
JS_DECLARE_NATIVE_FUNCTION(to_locale_string);
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
describe("correct behavior", () => {
|
||||||
|
test("basic functionality", () => {
|
||||||
|
{
|
||||||
|
const duration = new Temporal.Duration(0, 0, 0, 0, 1, 2, 3, 4, 5, 6);
|
||||||
|
const relativeTo = new Temporal.PlainDate(1970, 1, 1);
|
||||||
|
const values = [
|
||||||
|
[{ unit: "year", relativeTo }, 0.0001180556825534627],
|
||||||
|
[{ unit: "month", relativeTo }, 0.0013900104558714158],
|
||||||
|
[{ unit: "week", relativeTo }, 0.006155760590287699],
|
||||||
|
[{ unit: "day", relativeTo }, 0.04309032413201389],
|
||||||
|
[{ unit: "hour" }, 1.034167779168333],
|
||||||
|
[{ unit: "minute" }, 62.0500667501],
|
||||||
|
[{ unit: "second" }, 3723.00400500600017],
|
||||||
|
[{ unit: "millisecond" }, 3723004.005005999933928],
|
||||||
|
[{ unit: "microsecond" }, 3723004005.006000041961669],
|
||||||
|
[{ unit: "nanosecond" }, 3723004005006],
|
||||||
|
];
|
||||||
|
for (const [arg, expected] of values) {
|
||||||
|
const matcher = Number.isInteger(expected) ? "toBe" : "toBeCloseTo";
|
||||||
|
expect(duration.total(arg))[matcher](expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const duration = new Temporal.Duration(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
|
||||||
|
const relativeTo = new Temporal.PlainDate(1970, 1, 1);
|
||||||
|
const values = [
|
||||||
|
[{ unit: "year", relativeTo }, 1.2307194003046997],
|
||||||
|
[{ unit: "month", relativeTo }, 14.813309068103722],
|
||||||
|
[{ unit: "week", relativeTo }, 64.17322587303077],
|
||||||
|
[{ unit: "day", relativeTo }, 449.21258111121534],
|
||||||
|
[{ unit: "hour", relativeTo }, 10781.101946669169],
|
||||||
|
[{ unit: "minute", relativeTo }, 646866.1168001501],
|
||||||
|
[{ unit: "second", relativeTo }, 38811967.00800901],
|
||||||
|
[{ unit: "millisecond", relativeTo }, 38811967008.00901],
|
||||||
|
[{ unit: "microsecond", relativeTo }, 38811967008009.01],
|
||||||
|
[{ unit: "nanosecond", relativeTo }, 38811967008009010],
|
||||||
|
];
|
||||||
|
for (const [arg, expected] of values) {
|
||||||
|
const matcher = Number.isInteger(expected) ? "toBe" : "toBeCloseTo";
|
||||||
|
expect(duration.total(arg))[matcher](expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const relativeTo = new Temporal.PlainDate(1970, 1, 1);
|
||||||
|
const units = [
|
||||||
|
"year",
|
||||||
|
"month",
|
||||||
|
"week",
|
||||||
|
"day",
|
||||||
|
"hour",
|
||||||
|
"minute",
|
||||||
|
"second",
|
||||||
|
"millisecond",
|
||||||
|
"microsecond",
|
||||||
|
"nanosecond",
|
||||||
|
];
|
||||||
|
for (let i = 0; i < 10; ++i) {
|
||||||
|
const args = [0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||||
|
args[i] = 123;
|
||||||
|
const unit = units[i];
|
||||||
|
const duration = new Temporal.Duration(...args);
|
||||||
|
expect(duration.total({ unit, relativeTo })).toBe(123);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("errors", () => {
|
||||||
|
test("this value must be a Temporal.Duration object", () => {
|
||||||
|
expect(() => {
|
||||||
|
Temporal.Duration.prototype.total.call("foo");
|
||||||
|
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.Duration");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("missing options object", () => {
|
||||||
|
const duration = new Temporal.Duration();
|
||||||
|
expect(() => {
|
||||||
|
duration.total();
|
||||||
|
}).toThrowWithMessage(TypeError, "Required options object is missing or undefined");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("missing unit option", () => {
|
||||||
|
const duration = new Temporal.Duration();
|
||||||
|
expect(() => {
|
||||||
|
duration.total({});
|
||||||
|
}).toThrowWithMessage(RangeError, "unit option value is undefined");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("invalid unit option", () => {
|
||||||
|
const duration = new Temporal.Duration();
|
||||||
|
expect(() => {
|
||||||
|
duration.total({ unit: "foo" });
|
||||||
|
}).toThrowWithMessage(RangeError, "foo is not a valid value for option unit");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("relativeTo is required when duration has calendar units", () => {
|
||||||
|
const duration = new Temporal.Duration(1);
|
||||||
|
expect(() => {
|
||||||
|
duration.total({ unit: "second" });
|
||||||
|
}).toThrowWithMessage(
|
||||||
|
RangeError,
|
||||||
|
"A starting point is required for balancing calendar units"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in a new issue