diff --git a/Libraries/LibJS/CMakeLists.txt b/Libraries/LibJS/CMakeLists.txt index a32237ebaa3..0e2667b3dd7 100644 --- a/Libraries/LibJS/CMakeLists.txt +++ b/Libraries/LibJS/CMakeLists.txt @@ -211,6 +211,8 @@ set(SOURCES Runtime/Temporal/DurationConstructor.cpp Runtime/Temporal/DurationPrototype.cpp Runtime/Temporal/Instant.cpp + Runtime/Temporal/InstantConstructor.cpp + Runtime/Temporal/InstantPrototype.cpp Runtime/Temporal/ISO8601.cpp Runtime/Temporal/PlainDate.cpp Runtime/Temporal/PlainDateConstructor.cpp diff --git a/Libraries/LibJS/Forward.h b/Libraries/LibJS/Forward.h index 8633193c81a..bf8156703cf 100644 --- a/Libraries/LibJS/Forward.h +++ b/Libraries/LibJS/Forward.h @@ -89,6 +89,7 @@ #define JS_ENUMERATE_TEMPORAL_OBJECTS \ __JS_ENUMERATE(Duration, duration, DurationPrototype, DurationConstructor) \ + __JS_ENUMERATE(Instant, instant, InstantPrototype, InstantConstructor) \ __JS_ENUMERATE(PlainDate, plain_date, PlainDatePrototype, PlainDateConstructor) \ __JS_ENUMERATE(PlainDateTime, plain_date_time, PlainDateTimePrototype, PlainDateTimeConstructor) \ __JS_ENUMERATE(PlainMonthDay, plain_month_day, PlainMonthDayPrototype, PlainMonthDayConstructor) \ diff --git a/Libraries/LibJS/Print.cpp b/Libraries/LibJS/Print.cpp index fa9ea2468f9..f0dc6fd8016 100644 --- a/Libraries/LibJS/Print.cpp +++ b/Libraries/LibJS/Print.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -840,6 +841,14 @@ ErrorOr print_temporal_duration(JS::PrintContext& print_context, JS::Tempo return {}; } +ErrorOr print_temporal_instant(JS::PrintContext& print_context, JS::Temporal::Instant const& instant, HashTable& seen_objects) +{ + TRY(print_type(print_context, "Temporal.Instant"sv)); + TRY(js_out(print_context, " ")); + TRY(print_value(print_context, instant.epoch_nanoseconds(), seen_objects)); + return {}; +} + ErrorOr print_temporal_plain_date(JS::PrintContext& print_context, JS::Temporal::PlainDate const& plain_date, HashTable& seen_objects) { TRY(print_type(print_context, "Temporal.PlainDate"sv)); @@ -1000,6 +1009,8 @@ ErrorOr print_value(JS::PrintContext& print_context, JS::Value value, Hash return print_intl_duration_format(print_context, static_cast(object), seen_objects); if (is(object)) return print_temporal_duration(print_context, static_cast(object), seen_objects); + if (is(object)) + return print_temporal_instant(print_context, static_cast(object), seen_objects); if (is(object)) return print_temporal_plain_date(print_context, static_cast(object), seen_objects); if (is(object)) diff --git a/Libraries/LibJS/Runtime/Intrinsics.cpp b/Libraries/LibJS/Runtime/Intrinsics.cpp index e7b6e92ab81..451a60cfd6c 100644 --- a/Libraries/LibJS/Runtime/Intrinsics.cpp +++ b/Libraries/LibJS/Runtime/Intrinsics.cpp @@ -101,6 +101,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp b/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp index d53ea4af59b..91e4f27d2c0 100644 --- a/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp +++ b/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp @@ -1754,4 +1754,19 @@ Crypto::SignedBigInteger get_utc_epoch_nanoseconds(ISODateTime const& iso_date_t iso_date_time.time.nanosecond); } +// AD-HOC +// FIXME: We should add a generic floor() method to our BigInt classes. But for now, since we know we are only dividing +// by powers of 10, we can implement a very situationally specific method to compute the floor of a division. +Crypto::SignedBigInteger big_floor(Crypto::SignedBigInteger const& numerator, Crypto::UnsignedBigInteger const& denominator) +{ + auto result = numerator.divided_by(denominator); + + if (result.remainder.is_zero()) + return result.quotient; + if (!result.quotient.is_negative() && result.remainder.is_positive()) + return result.quotient; + + return result.quotient.minus(Crypto::SignedBigInteger { 1 }); +} + } diff --git a/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h b/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h index 7b171cd66ea..054773e3506 100644 --- a/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h +++ b/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h @@ -261,4 +261,6 @@ ThrowCompletionOr get_rounding_mode_option(VM&, Object const& opti ThrowCompletionOr get_rounding_increment_option(VM&, Object const& options); Crypto::SignedBigInteger get_utc_epoch_nanoseconds(ISODateTime const&); +Crypto::SignedBigInteger big_floor(Crypto::SignedBigInteger const& numerator, Crypto::UnsignedBigInteger const& denominator); + } diff --git a/Libraries/LibJS/Runtime/Temporal/Instant.cpp b/Libraries/LibJS/Runtime/Temporal/Instant.cpp index 80c31945668..3cba89fa729 100644 --- a/Libraries/LibJS/Runtime/Temporal/Instant.cpp +++ b/Libraries/LibJS/Runtime/Temporal/Instant.cpp @@ -7,10 +7,35 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include namespace JS::Temporal { +GC_DEFINE_ALLOCATOR(Instant); + +// 8 Temporal.Instant Objects, https://tc39.es/proposal-temporal/#sec-temporal-instant-objects +Instant::Instant(BigInt const& epoch_nanoseconds, Object& prototype) + : Object(ConstructWithPrototypeTag::Tag, prototype) + , m_epoch_nanoseconds(epoch_nanoseconds) +{ +} + +void Instant::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_epoch_nanoseconds); +} + // nsMaxInstant = 10**8 × nsPerDay = 8.64 × 10**21 Crypto::SignedBigInteger const NANOSECONDS_MAX_INSTANT = "8640000000000000000000"_sbigint; @@ -47,4 +72,96 @@ bool is_valid_epoch_nanoseconds(Crypto::SignedBigInteger const& epoch_nanosecond return true; } +// 8.5.2 CreateTemporalInstant ( epochNanoseconds [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-isvalidepochnanoseconds +ThrowCompletionOr> create_temporal_instant(VM& vm, BigInt const& epoch_nanoseconds, GC::Ptr new_target) +{ + auto& realm = *vm.current_realm(); + + // 1. Assert: IsValidEpochNanoseconds(epochNanoseconds) is true. + VERIFY(is_valid_epoch_nanoseconds(epoch_nanoseconds.big_integer())); + + // 2. If newTarget is not present, set newTarget to %Temporal.Instant%. + if (!new_target) + new_target = realm.intrinsics().temporal_instant_constructor(); + + // 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.Instant.prototype%", « [[InitializedTemporalInstant]], [[EpochNanoseconds]] »). + // 4. Set object.[[EpochNanoseconds]] to epochNanoseconds. + auto object = TRY(ordinary_create_from_constructor(vm, *new_target, &Intrinsics::temporal_instant_prototype, epoch_nanoseconds)); + + // 5. Return object. + return object; +} + +// 8.5.3 ToTemporalInstant ( item ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalinstant +ThrowCompletionOr> to_temporal_instant(VM& vm, Value item) +{ + // 1. If item is an Object, then + if (item.is_object()) { + auto const& object = item.as_object(); + + // a. If item has an [[InitializedTemporalInstant]] or [[InitializedTemporalZonedDateTime]] internal slot, then + // FIXME: Handle ZonedDateTime. + if (is(object)) { + // i. Return ! CreateTemporalInstant(item.[[EpochNanoseconds]]). + return MUST(create_temporal_instant(vm, static_cast(object).epoch_nanoseconds())); + } + + // b. NOTE: This use of ToPrimitive allows Instant-like objects to be converted. + // c. Set item to ? ToPrimitive(item, STRING). + item = TRY(item.to_primitive(vm, Value::PreferredType::String)); + } + + // 2. If item is not a String, throw a TypeError exception. + if (!item.is_string()) + return vm.throw_completion(ErrorType::TemporalInvalidInstantString, item); + + // 3. Let parsed be ? ParseISODateTime(item, « TemporalInstantString »). + auto parsed = TRY(parse_iso_date_time(vm, item.as_string().utf8_string_view(), { { Production::TemporalInstantString } })); + + // 4. Assert: Either parsed.[[TimeZone]].[[OffsetString]] is not empty or parsed.[[TimeZone]].[[Z]] is true, but not both. + auto const& offset_string = parsed.time_zone.offset_string; + auto z_designator = parsed.time_zone.z_designator; + + VERIFY(offset_string.has_value() || z_designator); + VERIFY(!offset_string.has_value() || !z_designator); + + // 5. If parsed.[[TimeZone]].[[Z]] is true, let offsetNanoseconds be 0; otherwise, let offsetNanoseconds be + // ! ParseDateTimeUTCOffset(parsed.[[TimeZone]].[[OffsetString]]). + auto offset_nanoseconds = z_designator ? 0.0 : parse_date_time_utc_offset(*offset_string); + + // 6. If parsed.[[Time]] is START-OF-DAY, let time be MidnightTimeRecord(); else let time be parsed.[[Time]]. + auto time = parsed.time.has() ? midnight_time_record() : parsed.time.get