summaryrefslogtreecommitdiff
path: root/Userland/Libraries
diff options
context:
space:
mode:
authorLinus Groh <mail@linusgroh.de>2021-08-07 00:31:08 +0100
committerLinus Groh <mail@linusgroh.de>2021-08-07 13:10:35 +0100
commitb38f1fb0718cd3d9f58854064b0c538b27aa4a5a (patch)
treefe6ac4cbd6152c7993684fb494802609740a98a2 /Userland/Libraries
parent8ffad70504a2d882b28137a7cf6c27571173314b (diff)
downloadserenity-b38f1fb0718cd3d9f58854064b0c538b27aa4a5a.zip
LibJS: Implement Temporal.Instant.prototype.add()
Diffstat (limited to 'Userland/Libraries')
-rw-r--r--Userland/Libraries/LibJS/Runtime/ErrorTypes.h3
-rw-r--r--Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp54
-rw-r--r--Userland/Libraries/LibJS/Runtime/Temporal/Duration.h1
-rw-r--r--Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp29
-rw-r--r--Userland/Libraries/LibJS/Runtime/Temporal/Instant.h1
-rw-r--r--Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.cpp27
-rw-r--r--Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.h2
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.prototype.add.js59
8 files changed, 173 insertions, 3 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h
index 6086108a80..f704f8fbc5 100644
--- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h
+++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h
@@ -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") \
diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp
index 5d5cec2d0c..2a84daa75f 100644
--- a/Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp
@@ -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;
+}
+
}
diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Duration.h b/Userland/Libraries/LibJS/Runtime/Temporal/Duration.h
index ac3489a8bf..e5e9e094fc 100644
--- a/Userland/Libraries/LibJS/Runtime/Temporal/Duration.h
+++ b/Userland/Libraries/LibJS/Runtime/Temporal/Duration.h
@@ -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);
}
diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp
index 02f88828de..834d3a7cbe 100644
--- a/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp
@@ -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)
{
diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Instant.h b/Userland/Libraries/LibJS/Runtime/Temporal/Instant.h
index 65a6f252a5..2ea0efafc2 100644
--- a/Userland/Libraries/LibJS/Runtime/Temporal/Instant.h
+++ b/Userland/Libraries/LibJS/Runtime/Temporal/Instant.h
@@ -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);
}
diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.cpp
index e395710f43..d749a8182c 100644
--- a/Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.cpp
@@ -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)
{
diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.h b/Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.h
index c5b9d7e64e..55360e6dd6 100644
--- a/Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.h
+++ b/Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.h
@@ -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);
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.prototype.add.js b/Userland/Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.prototype.add.js
new file mode 100644
index 0000000000..015c94dc6b
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.prototype.add.js
@@ -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`
+ );
+ }
+ });
+});