mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-12-02 04:20:28 +00:00
LibJS: Implement Temporal.Instant.prototype.add()
This commit is contained in:
parent
8ffad70504
commit
b38f1fb071
Notes:
sideshowbarker
2024-07-18 07:19:08 +09:00
Author: https://github.com/linusg Commit: https://github.com/SerenityOS/serenity/commit/b38f1fb0718 Pull-request: https://github.com/SerenityOS/serenity/pull/9250 Reviewed-by: https://github.com/IdanHo ✅
8 changed files with 173 additions and 3 deletions
|
@ -171,7 +171,8 @@
|
|||
M(TemporalInvalidCalendarIdentifier, "Invalid calendar identifier '{}'") \
|
||||
M(TemporalInvalidDuration, "Invalid duration") \
|
||||
M(TemporalInvalidDurationLikeObject, "Invalid duration-like object") \
|
||||
M(TemporalInvalidDurationPropertyValue, "Invalid value for duration property '{}': must be an integer, got {}") \
|
||||
M(TemporalInvalidDurationPropertyValueNonIntegral, "Invalid value for duration property '{}': must be an integer, got {}") \
|
||||
M(TemporalInvalidDurationPropertyValueNonZero, "Invalid value for duration property '{}': must be zero, got {}") \
|
||||
M(TemporalInvalidEpochNanoseconds, "Invalid epoch nanoseconds value, must be in range -86400 * 10^17 to 86400 * 10^17") \
|
||||
M(TemporalInvalidISODate, "Invalid ISO date") \
|
||||
M(TemporalInvalidMonthCode, "Invalid month code") \
|
||||
|
|
|
@ -118,7 +118,7 @@ TemporalDuration to_temporal_duration_record(GlobalObject& global_object, Object
|
|||
// e. If floor(val) ≠ val, then
|
||||
if (floor(value.as_double()) != value.as_double()) {
|
||||
// i. Throw a RangeError exception.
|
||||
vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidDurationPropertyValue, property.as_string(), value.to_string_without_side_effects());
|
||||
vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidDurationPropertyValueNonIntegral, property.as_string(), value.to_string_without_side_effects());
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -266,4 +266,56 @@ Duration* create_temporal_duration(GlobalObject& global_object, double years, do
|
|||
return object;
|
||||
}
|
||||
|
||||
// 7.5.19 ToLimitedTemporalDuration ( temporalDurationLike, disallowedFields ),https://tc39.es/proposal-temporal/#sec-temporal-tolimitedtemporalduration
|
||||
Optional<TemporalDuration> to_limited_temporal_duration(GlobalObject& global_object, Value temporal_duration_like, Vector<StringView> const& disallowed_fields)
|
||||
{
|
||||
auto& vm = global_object.vm();
|
||||
|
||||
Optional<TemporalDuration> duration;
|
||||
|
||||
// 1. If Type(temporalDurationLike) is not Object, then
|
||||
if (!temporal_duration_like.is_object()) {
|
||||
// a. Let str be ? ToString(temporalDurationLike).
|
||||
auto str = temporal_duration_like.to_string(global_object);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
|
||||
// b. Let duration be ? ParseTemporalDurationString(str).
|
||||
duration = parse_temporal_duration_string(global_object, str);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
}
|
||||
// 2. Else,
|
||||
else {
|
||||
// a. Let duration be ? ToTemporalDurationRecord(temporalDurationLike).
|
||||
duration = to_temporal_duration_record(global_object, temporal_duration_like.as_object());
|
||||
if (vm.exception())
|
||||
return {};
|
||||
}
|
||||
|
||||
// 3. If ! IsValidDuration(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]]) is false, throw a RangeError exception.
|
||||
if (!is_valid_duration(duration->years, duration->months, duration->weeks, duration->days, duration->hours, duration->minutes, duration->seconds, duration->milliseconds, duration->microseconds, duration->nanoseconds)) {
|
||||
vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidDuration);
|
||||
return {};
|
||||
}
|
||||
|
||||
// 4. For each row of Table 7, except the header row, in table order, do
|
||||
for (auto& [internal_slot, property] : temporal_duration_like_properties<TemporalDuration, double>(vm)) {
|
||||
// a. Let prop be the Property value of the current row.
|
||||
|
||||
// b. Let value be duration's internal slot whose name is the Internal Slot value of the current row.
|
||||
auto value = (*duration).*internal_slot;
|
||||
|
||||
// If value is not 0 and disallowedFields contains prop, then
|
||||
if (value != 0 && disallowed_fields.contains_slow(property.as_string())) {
|
||||
// i. Throw a RangeError exception.
|
||||
vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidDurationPropertyValueNonZero, property.as_string(), value);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Return duration.
|
||||
return duration;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -102,5 +102,6 @@ i8 duration_sign(double years, double months, double weeks, double days, double
|
|||
bool is_valid_duration(double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds);
|
||||
PartialDuration to_partial_duration(GlobalObject&, Value temporal_duration_like);
|
||||
Duration* create_temporal_duration(GlobalObject&, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, FunctionObject* new_target = nullptr);
|
||||
Optional<TemporalDuration> to_limited_temporal_duration(GlobalObject&, Value temporal_duration_like, Vector<StringView> const& disallowed_fields);
|
||||
|
||||
}
|
||||
|
|
|
@ -160,6 +160,35 @@ i32 compare_epoch_nanoseconds(BigInt const& epoch_nanoseconds_one, BigInt const&
|
|||
return 0;
|
||||
}
|
||||
|
||||
// 8.5.6 AddInstant ( epochNanoseconds, hours, minutes, seconds, milliseconds, microseconds, nanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-addinstant
|
||||
BigInt* add_instant(GlobalObject& global_object, BigInt const& epoch_nanoseconds, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds)
|
||||
{
|
||||
auto& vm = global_object.vm();
|
||||
|
||||
// 1. Assert: hours, minutes, seconds, milliseconds, microseconds, and nanoseconds are integer Number values.
|
||||
VERIFY(hours == trunc(hours) && minutes == trunc(minutes) && seconds == trunc(seconds) && milliseconds == trunc(milliseconds) && microseconds == trunc(microseconds) && nanoseconds == trunc(nanoseconds));
|
||||
|
||||
// 2. Let result be epochNanoseconds + ℤ(nanoseconds) + ℤ(microseconds) × 1000ℤ + ℤ(milliseconds) × 10^6ℤ + ℤ(seconds) × 10^9ℤ + ℤ(minutes) × 60ℤ × 10^9ℤ + ℤ(hours) × 3600ℤ × 10^9ℤ.
|
||||
// FIXME: Pretty sure i64's are not sufficient for the extreme cases.
|
||||
auto* result = js_bigint(vm,
|
||||
epoch_nanoseconds.big_integer()
|
||||
.plus(Crypto::SignedBigInteger::create_from((i64)nanoseconds))
|
||||
.plus(Crypto::SignedBigInteger::create_from((i64)microseconds).multiplied_by(Crypto::SignedBigInteger { 1'000 }))
|
||||
.plus(Crypto::SignedBigInteger::create_from((i64)milliseconds).multiplied_by(Crypto::SignedBigInteger { 1'000'000 }))
|
||||
.plus(Crypto::SignedBigInteger::create_from((i64)seconds).multiplied_by(Crypto::SignedBigInteger { 1'000'000'000 }))
|
||||
.plus(Crypto::SignedBigInteger::create_from((i64)minutes).multiplied_by(Crypto::SignedBigInteger { 60 }).multiplied_by(Crypto::SignedBigInteger { 1'000'000'000 }))
|
||||
.plus(Crypto::SignedBigInteger::create_from((i64)hours).multiplied_by(Crypto::SignedBigInteger { 3600 }).multiplied_by(Crypto::SignedBigInteger { 1'000'000'000 })));
|
||||
|
||||
// If ! IsValidEpochNanoseconds(result) is false, throw a RangeError exception.
|
||||
if (!is_valid_epoch_nanoseconds(*result)) {
|
||||
vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidEpochNanoseconds);
|
||||
return {};
|
||||
}
|
||||
|
||||
// 4. Return result.
|
||||
return result;
|
||||
}
|
||||
|
||||
// 8.5.8 RoundTemporalInstant ( ns, increment, unit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-roundtemporalinstant
|
||||
BigInt* round_temporal_instant(GlobalObject& global_object, BigInt const& nanoseconds, u64 increment, String const& unit, String const& rounding_mode)
|
||||
{
|
||||
|
|
|
@ -39,6 +39,7 @@ Instant* create_temporal_instant(GlobalObject&, BigInt& nanoseconds, FunctionObj
|
|||
Instant* to_temporal_instant(GlobalObject&, Value item);
|
||||
BigInt* parse_temporal_instant(GlobalObject&, String const& iso_string);
|
||||
i32 compare_epoch_nanoseconds(BigInt const&, BigInt const&);
|
||||
BigInt* add_instant(GlobalObject&, BigInt const& epoch_nanoseconds, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds);
|
||||
BigInt* round_temporal_instant(GlobalObject&, BigInt const& nanoseconds, u64 increment, String const& unit, String const& rounding_mode);
|
||||
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <LibCrypto/BigInt/UnsignedBigInteger.h>
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Temporal/Duration.h>
|
||||
#include <LibJS/Runtime/Temporal/Instant.h>
|
||||
#include <LibJS/Runtime/Temporal/InstantPrototype.h>
|
||||
|
||||
|
@ -34,6 +35,7 @@ void InstantPrototype::initialize(GlobalObject& global_object)
|
|||
define_native_accessor(vm.names.epochNanoseconds, epoch_nanoseconds_getter, {}, Attribute::Configurable);
|
||||
|
||||
u8 attr = Attribute::Writable | Attribute::Configurable;
|
||||
define_native_function(vm.names.add, add, 1, attr);
|
||||
define_native_function(vm.names.round, round, 1, attr);
|
||||
define_native_function(vm.names.equals, equals, 1, attr);
|
||||
define_native_function(vm.names.valueOf, value_of, 0, attr);
|
||||
|
@ -125,6 +127,31 @@ JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::epoch_nanoseconds_getter)
|
|||
return &ns;
|
||||
}
|
||||
|
||||
// 8.3.7 Temporal.Instant.prototype.add ( temporalDurationLike ), https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.add
|
||||
JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::add)
|
||||
{
|
||||
auto temporal_duration_like = vm.argument(0);
|
||||
|
||||
// 1. Let instant be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
|
||||
auto* instant = typed_this(global_object);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
|
||||
// 3. Let duration be ? ToLimitedTemporalDuration(temporalDurationLike, « "years", "months", "weeks", "days" »).
|
||||
auto duration = to_limited_temporal_duration(global_object, temporal_duration_like, { "years"sv, "months"sv, "weeks"sv, "days"sv });
|
||||
if (vm.exception())
|
||||
return {};
|
||||
|
||||
// 4. Let ns be ? AddInstant(instant.[[Nanoseconds]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]]).
|
||||
auto* ns = add_instant(global_object, instant->nanoseconds(), duration->hours, duration->minutes, duration->seconds, duration->milliseconds, duration->microseconds, duration->nanoseconds);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
|
||||
// 5. Return ! CreateTemporalInstant(ns).
|
||||
return create_temporal_instant(global_object, *ns);
|
||||
}
|
||||
|
||||
// 8.3.11 Temporal.Instant.prototype.round ( options ), https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.round
|
||||
JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::round)
|
||||
{
|
||||
|
|
|
@ -23,7 +23,7 @@ private:
|
|||
JS_DECLARE_NATIVE_FUNCTION(epoch_milliseconds_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(epoch_microseconds_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(epoch_nanoseconds_getter);
|
||||
|
||||
JS_DECLARE_NATIVE_FUNCTION(add);
|
||||
JS_DECLARE_NATIVE_FUNCTION(round);
|
||||
JS_DECLARE_NATIVE_FUNCTION(equals);
|
||||
JS_DECLARE_NATIVE_FUNCTION(value_of);
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
describe("correct behavior", () => {
|
||||
test("length is 1", () => {
|
||||
expect(Temporal.Instant.prototype.add).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
const instant = new Temporal.Instant(1625614921000000000n);
|
||||
const duration = new Temporal.Duration(0, 0, 0, 0, 1, 2, 3, 4, 5, 6);
|
||||
expect(instant.add(duration).epochNanoseconds).toBe(1625618644004005006n);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.Instant object", () => {
|
||||
expect(() => {
|
||||
Temporal.Instant.prototype.add.call("foo");
|
||||
}).toThrowWithMessage(TypeError, "Not a Temporal.Instant");
|
||||
});
|
||||
|
||||
test("invalid nanoseconds value, positive", () => {
|
||||
const instant = new Temporal.Instant(8_640_000_000_000_000_000_000n);
|
||||
const duration = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, 1);
|
||||
expect(() => {
|
||||
instant.add(duration);
|
||||
}).toThrowWithMessage(
|
||||
RangeError,
|
||||
"Invalid epoch nanoseconds value, must be in range -86400 * 10^17 to 86400 * 10^17"
|
||||
);
|
||||
});
|
||||
|
||||
test("invalid nanoseconds value, negative", () => {
|
||||
const instant = new Temporal.Instant(-8_640_000_000_000_000_000_000n);
|
||||
const duration = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, -1);
|
||||
expect(() => {
|
||||
instant.add(duration);
|
||||
}).toThrowWithMessage(
|
||||
RangeError,
|
||||
"Invalid epoch nanoseconds value, must be in range -86400 * 10^17 to 86400 * 10^17"
|
||||
);
|
||||
});
|
||||
|
||||
test("disallowed fields", () => {
|
||||
const instant = new Temporal.Instant(1625614921000000000n);
|
||||
for (const [args, property] of [
|
||||
[[123, 0, 0, 0], "years"],
|
||||
[[0, 123, 0, 0], "months"],
|
||||
[[0, 0, 123, 0], "weeks"],
|
||||
[[0, 0, 0, 123], "days"],
|
||||
]) {
|
||||
const duration = new Temporal.Duration(...args);
|
||||
expect(() => {
|
||||
instant.add(duration);
|
||||
}).toThrowWithMessage(
|
||||
RangeError,
|
||||
`Invalid value for duration property '${property}': must be zero, got 123`
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue