mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-26 01:20:25 +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(toPlainYearMonth) \
|
||||
P(toString) \
|
||||
P(total) \
|
||||
P(toTemporalInstant) \
|
||||
P(toTimeString) \
|
||||
P(toUpperCase) \
|
||||
|
|
|
@ -515,6 +515,32 @@ ThrowCompletionOr<Optional<String>> to_smallest_temporal_unit(GlobalObject& glob
|
|||
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
|
||||
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<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<String> to_temporal_duration_total_unit(GlobalObject& global_object, Object const& normalized_options);
|
||||
ThrowCompletionOr<Value> to_relative_temporal_object(GlobalObject&, Object const& options);
|
||||
ThrowCompletionOr<void> validate_temporal_unit_range(GlobalObject&, StringView largest_unit, StringView smallest_unit);
|
||||
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.
|
||||
|
||||
// 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
|
||||
if (unit.is_one_of("year"sv, "month"sv, "week"sv) && !relative_to_object) {
|
||||
// a. Throw a RangeError exception.
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Temporal/Duration.h>
|
||||
#include <LibJS/Runtime/Temporal/DurationPrototype.h>
|
||||
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
|
||||
#include <math.h>
|
||||
|
||||
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.negated, negated, 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.toJSON, to_json, 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())));
|
||||
}
|
||||
|
||||
// 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
|
||||
JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::to_string)
|
||||
{
|
||||
|
|
|
@ -35,6 +35,7 @@ private:
|
|||
JS_DECLARE_NATIVE_FUNCTION(with);
|
||||
JS_DECLARE_NATIVE_FUNCTION(negated);
|
||||
JS_DECLARE_NATIVE_FUNCTION(abs);
|
||||
JS_DECLARE_NATIVE_FUNCTION(total);
|
||||
JS_DECLARE_NATIVE_FUNCTION(to_string);
|
||||
JS_DECLARE_NATIVE_FUNCTION(to_json);
|
||||
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