LibJS: Implement the Temporal.PlainTime constructor
And the simple Temporal.PlainTime.prototype getters, so that the constructed Temporal.PlainTime may actually be validated.
This commit is contained in:
parent
971f794127
commit
66365fef57
Notes:
github-actions[bot]
2024-11-24 00:38:03 +00:00
Author: https://github.com/trflynn89 Commit: https://github.com/LadybirdBrowser/ladybird/commit/66365fef578 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2535
22 changed files with 693 additions and 1 deletions
|
@ -219,10 +219,12 @@ set(SOURCES
|
|||
Runtime/Temporal/PlainMonthDay.cpp
|
||||
Runtime/Temporal/PlainMonthDayConstructor.cpp
|
||||
Runtime/Temporal/PlainMonthDayPrototype.cpp
|
||||
Runtime/Temporal/PlainTime.cpp
|
||||
Runtime/Temporal/PlainTimeConstructor.cpp
|
||||
Runtime/Temporal/PlainTimePrototype.cpp
|
||||
Runtime/Temporal/PlainYearMonth.cpp
|
||||
Runtime/Temporal/PlainYearMonthConstructor.cpp
|
||||
Runtime/Temporal/PlainYearMonthPrototype.cpp
|
||||
Runtime/Temporal/PlainTime.cpp
|
||||
Runtime/Temporal/Temporal.cpp
|
||||
Runtime/Temporal/TimeZone.cpp
|
||||
Runtime/TypedArray.cpp
|
||||
|
|
|
@ -91,6 +91,7 @@
|
|||
__JS_ENUMERATE(Duration, duration, DurationPrototype, DurationConstructor) \
|
||||
__JS_ENUMERATE(PlainDate, plain_date, PlainDatePrototype, PlainDateConstructor) \
|
||||
__JS_ENUMERATE(PlainMonthDay, plain_month_day, PlainMonthDayPrototype, PlainMonthDayConstructor) \
|
||||
__JS_ENUMERATE(PlainTime, plain_time, PlainTimePrototype, PlainTimeConstructor) \
|
||||
__JS_ENUMERATE(PlainYearMonth, plain_year_month, PlainYearMonthPrototype, PlainYearMonthConstructor)
|
||||
|
||||
#define JS_ENUMERATE_BUILTIN_NAMESPACE_OBJECTS \
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
#include <LibJS/Runtime/Temporal/Duration.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDate.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainMonthDay.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainTime.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainYearMonth.h>
|
||||
#include <LibJS/Runtime/TypedArray.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
|
@ -856,6 +857,13 @@ ErrorOr<void> print_temporal_plain_month_day(JS::PrintContext& print_context, JS
|
|||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> print_temporal_plain_time(JS::PrintContext& print_context, JS::Temporal::PlainTime const& plain_time, HashTable<JS::Object*>&)
|
||||
{
|
||||
TRY(print_type(print_context, "Temporal.PlainTime"sv));
|
||||
TRY(js_out(print_context, " \033[34;1m{:02}:{:02}:{:02}.{:03}{:03}{:03}\033[0m", plain_time.time().hour, plain_time.time().minute, plain_time.time().second, plain_time.time().millisecond, plain_time.time().microsecond, plain_time.time().nanosecond));
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> print_temporal_plain_year_month(JS::PrintContext& print_context, JS::Temporal::PlainYearMonth const& plain_year_month, HashTable<JS::Object*>& seen_objects)
|
||||
{
|
||||
TRY(print_type(print_context, "Temporal.PlainYearMonth"sv));
|
||||
|
@ -986,6 +994,8 @@ ErrorOr<void> print_value(JS::PrintContext& print_context, JS::Value value, Hash
|
|||
return print_temporal_plain_date(print_context, static_cast<JS::Temporal::PlainDate&>(object), seen_objects);
|
||||
if (is<JS::Temporal::PlainMonthDay>(object))
|
||||
return print_temporal_plain_month_day(print_context, static_cast<JS::Temporal::PlainMonthDay&>(object), seen_objects);
|
||||
if (is<JS::Temporal::PlainTime>(object))
|
||||
return print_temporal_plain_time(print_context, static_cast<JS::Temporal::PlainTime&>(object), seen_objects);
|
||||
if (is<JS::Temporal::PlainYearMonth>(object))
|
||||
return print_temporal_plain_year_month(print_context, static_cast<JS::Temporal::PlainYearMonth&>(object), seen_objects);
|
||||
return print_object(print_context, object, seen_objects);
|
||||
|
|
|
@ -278,6 +278,7 @@
|
|||
M(TemporalInvalidRelativeToStringUTCDesignatorWithoutBracketedTimeZone, "Invalid relativeTo string '{}': must not contain a UTC " \
|
||||
"designator without bracketed time zone") \
|
||||
M(TemporalInvalidTime, "Invalid time") \
|
||||
M(TemporalInvalidTimeLikeField, "Invalid value {} for time field '{}'") \
|
||||
M(TemporalInvalidTimeString, "Invalid time string '{}'") \
|
||||
M(TemporalInvalidTimeStringUTCDesignator, "Invalid time string '{}': must not contain a UTC designator") \
|
||||
M(TemporalInvalidTimeZoneName, "Invalid time zone name '{}'") \
|
||||
|
|
|
@ -105,6 +105,8 @@
|
|||
#include <LibJS/Runtime/Temporal/PlainDatePrototype.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainMonthDayConstructor.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainMonthDayPrototype.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainTimeConstructor.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainTimePrototype.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainYearMonthConstructor.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainYearMonthPrototype.h>
|
||||
#include <LibJS/Runtime/Temporal/Temporal.h>
|
||||
|
|
|
@ -628,6 +628,8 @@ ThrowCompletionOr<bool> is_partial_temporal_object(VM& vm, Value value)
|
|||
return false;
|
||||
if (is<PlainMonthDay>(object))
|
||||
return false;
|
||||
if (is<PlainTime>(object))
|
||||
return false;
|
||||
if (is<PlainYearMonth>(object))
|
||||
return false;
|
||||
|
||||
|
|
|
@ -11,10 +11,20 @@
|
|||
#include <LibJS/Runtime/Temporal/Duration.h>
|
||||
#include <LibJS/Runtime/Temporal/Instant.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainTime.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainTimeConstructor.h>
|
||||
#include <math.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(PlainTime);
|
||||
|
||||
// 4 Temporal.PlainTime Objects, https://tc39.es/proposal-temporal/#sec-temporal-plaintime-objects
|
||||
PlainTime::PlainTime(Time const& time, Object& prototype)
|
||||
: Object(ConstructWithPrototypeTag::Tag, prototype)
|
||||
, m_time(time)
|
||||
{
|
||||
}
|
||||
|
||||
// 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.
|
||||
static TimeDuration big_floor(TimeDuration const& numerator, Crypto::UnsignedBigInteger const& denominator)
|
||||
|
@ -93,6 +103,83 @@ TimeDuration difference_time(Time const& time1, Time const& time2)
|
|||
return time_duration;
|
||||
}
|
||||
|
||||
// 4.5.6 ToTemporalTime ( item [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaltime
|
||||
ThrowCompletionOr<GC::Ref<PlainTime>> to_temporal_time(VM& vm, Value item, Value options)
|
||||
{
|
||||
// 1. If options is not present, set options to undefined.
|
||||
|
||||
Time time;
|
||||
|
||||
// 2. If item is an Object, then
|
||||
if (item.is_object()) {
|
||||
auto const& object = item.as_object();
|
||||
|
||||
// a. If item has an [[InitializedTemporalTime]] internal slot, then
|
||||
if (is<PlainTime>(object)) {
|
||||
auto const& plain_time = static_cast<PlainTime const&>(object);
|
||||
|
||||
// i. Let resolvedOptions be ? GetOptionsObject(options).
|
||||
auto resolved_options = TRY(get_options_object(vm, options));
|
||||
|
||||
// ii. Perform ? GetTemporalOverflowOption(resolvedOptions).
|
||||
TRY(get_temporal_overflow_option(vm, resolved_options));
|
||||
|
||||
// iii. Return ! CreateTemporalTime(item.[[Time]]).
|
||||
return MUST(create_temporal_time(vm, plain_time.time()));
|
||||
}
|
||||
|
||||
// FIXME: b. If item has an [[InitializedTemporalDateTime]] internal slot, then
|
||||
// FIXME: i. Let resolvedOptions be ? GetOptionsObject(options).
|
||||
// FIXME: ii. Perform ? GetTemporalOverflowOption(resolvedOptions).
|
||||
// FIXME: iii. Return ! CreateTemporalTime(item.[[ISODateTime]].[[Time]]).
|
||||
|
||||
// FIXME: c. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then
|
||||
// FIXME: i. Let isoDateTime be GetISODateTimeFor(item.[[TimeZone]], item.[[EpochNanoseconds]]).
|
||||
// FIXME: ii. Let resolvedOptions be ? GetOptionsObject(options).
|
||||
// FIXME: iii. Perform ? GetTemporalOverflowOption(resolvedOptions).
|
||||
// FIXME: iv. Return ! CreateTemporalTime(isoDateTime.[[Time]]).
|
||||
|
||||
// d. Let result be ? ToTemporalTimeRecord(item).
|
||||
auto result = TRY(to_temporal_time_record(vm, object));
|
||||
|
||||
// e. Let resolvedOptions be ? GetOptionsObject(options).
|
||||
auto resolved_options = TRY(get_options_object(vm, options));
|
||||
|
||||
// f. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
|
||||
auto overflow = TRY(get_temporal_overflow_option(vm, resolved_options));
|
||||
|
||||
// g. Set result to ? RegulateTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]], overflow).
|
||||
time = TRY(regulate_time(vm, *result.hour, *result.minute, *result.second, *result.millisecond, *result.microsecond, *result.nanosecond, overflow));
|
||||
}
|
||||
// 3. Else,
|
||||
else {
|
||||
// a. If item is not a String, throw a TypeError exception.
|
||||
if (!item.is_string())
|
||||
return vm.throw_completion<TypeError>(ErrorType::TemporalInvalidPlainTime);
|
||||
|
||||
// b. Let parseResult be ? ParseISODateTime(item, « TemporalTimeString »).
|
||||
auto parse_result = TRY(parse_iso_date_time(vm, item.as_string().utf8_string_view(), { { Production::TemporalTimeString } }));
|
||||
|
||||
// c. Assert: parseResult.[[Time]] is not START-OF-DAY.
|
||||
VERIFY(!parse_result.time.has<ParsedISODateTime::StartOfDay>());
|
||||
|
||||
// d. Set result to parseResult.[[Time]].
|
||||
time = parse_result.time.get<Time>();
|
||||
|
||||
// e. NOTE: A successful parse using TemporalTimeString guarantees absence of ambiguity with respect to any
|
||||
// ISO 8601 date-only, year-month, or month-day representation.
|
||||
|
||||
// f. Let resolvedOptions be ? GetOptionsObject(options).
|
||||
auto resolved_options = TRY(get_options_object(vm, options));
|
||||
|
||||
// g. Perform ? GetTemporalOverflowOption(resolvedOptions).
|
||||
TRY(get_temporal_overflow_option(vm, resolved_options));
|
||||
}
|
||||
|
||||
// 4. Return ! CreateTemporalTime(result).
|
||||
return MUST(create_temporal_time(vm, time));
|
||||
}
|
||||
|
||||
// 4.5.8 RegulateTime ( hour, minute, second, millisecond, microsecond, nanosecond, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-regulatetime
|
||||
ThrowCompletionOr<Time> regulate_time(VM& vm, double hour, double minute, double second, double millisecond, double microsecond, double nanosecond, Overflow overflow)
|
||||
{
|
||||
|
@ -262,6 +349,98 @@ Time balance_time(double hour, double minute, double second, double millisecond,
|
|||
return create_time_record(hour, minute, second, millisecond, microsecond, nanosecond, delta_days);
|
||||
}
|
||||
|
||||
// 4.5.11 CreateTemporalTime ( time [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporaltime
|
||||
ThrowCompletionOr<GC::Ref<PlainTime>> create_temporal_time(VM& vm, Time const& time, GC::Ptr<FunctionObject> new_target)
|
||||
{
|
||||
auto& realm = *vm.current_realm();
|
||||
|
||||
// 1. If newTarget is not present, set newTarget to %Temporal.PlainTime%.
|
||||
if (!new_target)
|
||||
new_target = realm.intrinsics().temporal_plain_time_constructor();
|
||||
|
||||
// 2. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.PlainTime.prototype%", « [[InitializedTemporalTime]], [[Time]] »).
|
||||
// 3. Set object.[[Time]] to time.
|
||||
auto object = TRY(ordinary_create_from_constructor<PlainTime>(vm, *new_target, &Intrinsics::temporal_plain_time_prototype, time));
|
||||
|
||||
// 4. Return object.
|
||||
return object;
|
||||
}
|
||||
|
||||
// 4.5.12 ToTemporalTimeRecord ( temporalTimeLike [ , completeness ] ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaltimerecord
|
||||
ThrowCompletionOr<TemporalTimeLike> to_temporal_time_record(VM& vm, Object const& temporal_time_like, Completeness completeness)
|
||||
{
|
||||
// 1. If completeness is not present, set completeness to COMPLETE.
|
||||
|
||||
TemporalTimeLike result;
|
||||
|
||||
// 2. If completeness is COMPLETE, then
|
||||
if (completeness == Completeness::Complete) {
|
||||
// a. Let result be a new TemporalTimeLike Record with each field set to 0.
|
||||
result = TemporalTimeLike::zero();
|
||||
}
|
||||
// 3. Else,
|
||||
else {
|
||||
// a. Let result be a new TemporalTimeLike Record with each field set to UNSET.
|
||||
}
|
||||
|
||||
// 4. Let any be false.
|
||||
auto any = false;
|
||||
|
||||
auto apply_field = [&](auto const& key, auto& result_field) -> ThrowCompletionOr<void> {
|
||||
auto field = TRY(temporal_time_like.get(key));
|
||||
if (field.is_undefined())
|
||||
return {};
|
||||
|
||||
result_field = TRY(to_integer_with_truncation(vm, field, ErrorType::TemporalInvalidTimeLikeField, field, key));
|
||||
any = true;
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
// 5. Let hour be ? Get(temporalTimeLike, "hour").
|
||||
// 6. If hour is not undefined, then
|
||||
// a. Set result.[[Hour]] to ? ToIntegerWithTruncation(hour).
|
||||
// b. Set any to true.
|
||||
TRY(apply_field(vm.names.hour, result.hour));
|
||||
|
||||
// 7. Let microsecond be ? Get(temporalTimeLike, "microsecond").
|
||||
// 8. If microsecond is not undefined, then
|
||||
// a. Set result.[[Microsecond]] to ? ToIntegerWithTruncation(microsecond).
|
||||
// b. Set any to true.
|
||||
TRY(apply_field(vm.names.microsecond, result.microsecond));
|
||||
|
||||
// 9. Let millisecond be ? Get(temporalTimeLike, "millisecond").
|
||||
// 10. If millisecond is not undefined, then
|
||||
// a. Set result.[[Millisecond]] to ? ToIntegerWithTruncation(millisecond).
|
||||
// b. Set any to true.
|
||||
TRY(apply_field(vm.names.millisecond, result.millisecond));
|
||||
|
||||
// 11. Let minute be ? Get(temporalTimeLike, "minute").
|
||||
// 12. If minute is not undefined, then
|
||||
// a. Set result.[[Minute]] to ? ToIntegerWithTruncation(minute).
|
||||
// b. Set any to true.
|
||||
TRY(apply_field(vm.names.minute, result.minute));
|
||||
|
||||
// 13. Let nanosecond be ? Get(temporalTimeLike, "nanosecond").
|
||||
// 14. If nanosecond is not undefined, then
|
||||
// a. Set result.[[Nanosecond]] to ? ToIntegerWithTruncation(nanosecond).
|
||||
// b. Set any to true.
|
||||
TRY(apply_field(vm.names.nanosecond, result.nanosecond));
|
||||
|
||||
// 15. Let second be ? Get(temporalTimeLike, "second").
|
||||
// 16. If second is not undefined, then
|
||||
// a. Set result.[[Second]] to ? ToIntegerWithTruncation(second).
|
||||
// b. Set any to true.
|
||||
TRY(apply_field(vm.names.second, result.second));
|
||||
|
||||
// 17. If any is false, throw a TypeError exception.
|
||||
if (!any)
|
||||
return vm.throw_completion<TypeError>(ErrorType::TemporalInvalidTime);
|
||||
|
||||
// 18. Return result.
|
||||
return result;
|
||||
}
|
||||
|
||||
// 4.5.14 CompareTimeRecord ( time1, time2 ), https://tc39.es/proposal-temporal/#sec-temporal-comparetimerecord
|
||||
i8 compare_time_record(Time const& time1, Time const& time2)
|
||||
{
|
||||
|
|
|
@ -13,14 +13,52 @@
|
|||
|
||||
namespace JS::Temporal {
|
||||
|
||||
class PlainTime final : public Object {
|
||||
JS_OBJECT(PlainTime, Object);
|
||||
GC_DECLARE_ALLOCATOR(PlainTime);
|
||||
|
||||
public:
|
||||
virtual ~PlainTime() override = default;
|
||||
|
||||
[[nodiscard]] Time const& time() const { return m_time; }
|
||||
|
||||
private:
|
||||
PlainTime(Time const&, Object& prototype);
|
||||
|
||||
Time m_time; // [[Time]]
|
||||
};
|
||||
|
||||
// Table 5: TemporalTimeLike Record Fields, https://tc39.es/proposal-temporal/#table-temporal-temporaltimelike-record-fields
|
||||
struct TemporalTimeLike {
|
||||
static TemporalTimeLike zero()
|
||||
{
|
||||
return { .hour = 0, .minute = 0, .second = 0, .millisecond = 0, .microsecond = 0, .nanosecond = 0 };
|
||||
}
|
||||
|
||||
Optional<double> hour;
|
||||
Optional<double> minute;
|
||||
Optional<double> second;
|
||||
Optional<double> millisecond;
|
||||
Optional<double> microsecond;
|
||||
Optional<double> nanosecond;
|
||||
};
|
||||
|
||||
enum class Completeness {
|
||||
Complete,
|
||||
Partial,
|
||||
};
|
||||
|
||||
Time create_time_record(double hour, double minute, double second, double millisecond, double microsecond, double nanosecond, double delta_days = 0);
|
||||
Time midnight_time_record();
|
||||
Time noon_time_record();
|
||||
TimeDuration difference_time(Time const&, Time const&);
|
||||
ThrowCompletionOr<GC::Ref<PlainTime>> to_temporal_time(VM&, Value item, Value options = js_undefined());
|
||||
ThrowCompletionOr<Time> regulate_time(VM&, double hour, double minute, double second, double millisecond, double microsecond, double nanosecond, Overflow);
|
||||
bool is_valid_time(double hour, double minute, double second, double millisecond, double microsecond, double nanosecond);
|
||||
Time balance_time(double hour, double minute, double second, double millisecond, double microsecond, double nanosecond);
|
||||
Time balance_time(double hour, double minute, double second, double millisecond, double microsecond, TimeDuration const& nanosecond);
|
||||
ThrowCompletionOr<GC::Ref<PlainTime>> create_temporal_time(VM&, Time const&, GC::Ptr<FunctionObject> new_target = {});
|
||||
ThrowCompletionOr<TemporalTimeLike> to_temporal_time_record(VM&, Object const& temporal_time_like, Completeness = Completeness::Complete);
|
||||
i8 compare_time_record(Time const&, Time const&);
|
||||
Time add_time(Time const&, TimeDuration const& time_duration);
|
||||
|
||||
|
|
108
Libraries/LibJS/Runtime/Temporal/PlainTimeConstructor.cpp
Normal file
108
Libraries/LibJS/Runtime/Temporal/PlainTimeConstructor.cpp
Normal file
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainTime.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainTimeConstructor.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(PlainTimeConstructor);
|
||||
|
||||
// 4.1 The Temporal.PlainTime Constructor, https://tc39.es/proposal-temporal/#sec-temporal-plaintime-constructor
|
||||
PlainTimeConstructor::PlainTimeConstructor(Realm& realm)
|
||||
: NativeFunction(realm.vm().names.PlainTime.as_string(), realm.intrinsics().function_prototype())
|
||||
{
|
||||
}
|
||||
|
||||
void PlainTimeConstructor::initialize(Realm& realm)
|
||||
{
|
||||
Base::initialize(realm);
|
||||
|
||||
auto& vm = this->vm();
|
||||
|
||||
// 4.2.1 Temporal.PlainTime.prototype, https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype
|
||||
define_direct_property(vm.names.prototype, realm.intrinsics().temporal_plain_time_prototype(), 0);
|
||||
|
||||
u8 attr = Attribute::Writable | Attribute::Configurable;
|
||||
define_native_function(realm, vm.names.from, from, 1, attr);
|
||||
define_native_function(realm, vm.names.compare, compare, 2, attr);
|
||||
|
||||
define_direct_property(vm.names.length, Value(0), Attribute::Configurable);
|
||||
}
|
||||
|
||||
// 4.1.1 Temporal.PlainTime ( [ hour [ , minute [ , second [ , millisecond [ , microsecond [ , nanosecond ] ] ] ] ] ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaintime
|
||||
ThrowCompletionOr<Value> PlainTimeConstructor::call()
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
|
||||
// 1. If NewTarget is undefined, then
|
||||
// a. Throw a TypeError exception.
|
||||
return vm.throw_completion<TypeError>(ErrorType::ConstructorWithoutNew, "Temporal.PlainTime");
|
||||
}
|
||||
|
||||
// 4.1.1 Temporal.PlainTime ( [ hour [ , minute [ , second [ , millisecond [ , microsecond [ , nanosecond ] ] ] ] ] ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaintime
|
||||
ThrowCompletionOr<GC::Ref<Object>> PlainTimeConstructor::construct(FunctionObject& new_target)
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
|
||||
auto next_integer_argument = [&, index = 0]() mutable -> ThrowCompletionOr<double> {
|
||||
if (auto value = vm.argument(index++); !value.is_undefined())
|
||||
return to_integer_with_truncation(vm, value, ErrorType::TemporalInvalidPlainTime);
|
||||
return 0;
|
||||
};
|
||||
|
||||
// 2. If hour is undefined, set hour to 0; else set hour to ? ToIntegerWithTruncation(hour).
|
||||
auto hour = TRY(next_integer_argument());
|
||||
|
||||
// 3. If minute is undefined, set minute to 0; else set minute to ? ToIntegerWithTruncation(minute).
|
||||
auto minute = TRY(next_integer_argument());
|
||||
|
||||
// 4. If second is undefined, set second to 0; else set second to ? ToIntegerWithTruncation(second).
|
||||
auto second = TRY(next_integer_argument());
|
||||
|
||||
// 5. If millisecond is undefined, set millisecond to 0; else set millisecond to ? ToIntegerWithTruncation(millisecond).
|
||||
auto millisecond = TRY(next_integer_argument());
|
||||
|
||||
// 6. If microsecond is undefined, set microsecond to 0; else set microsecond to ? ToIntegerWithTruncation(microsecond).
|
||||
auto microsecond = TRY(next_integer_argument());
|
||||
|
||||
// 7. If nanosecond is undefined, set nanosecond to 0; else set nanosecond to ? ToIntegerWithTruncation(nanosecond).
|
||||
auto nanosecond = TRY(next_integer_argument());
|
||||
|
||||
// 8. If IsValidTime(hour, minute, second, millisecond, microsecond, nanosecond) is false, throw a RangeError exception.
|
||||
if (!is_valid_time(hour, minute, second, millisecond, microsecond, nanosecond))
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidPlainTime);
|
||||
|
||||
// 9. Let time be CreateTimeRecord(hour, minute, second, millisecond, microsecond, nanosecond).
|
||||
auto time = create_time_record(hour, minute, second, millisecond, microsecond, nanosecond);
|
||||
|
||||
// 10. Return ? CreateTemporalTime(time, NewTarget).
|
||||
return TRY(create_temporal_time(vm, time, new_target));
|
||||
}
|
||||
|
||||
// 4.2.2 Temporal.PlainTime.from ( item [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaintime.from
|
||||
JS_DEFINE_NATIVE_FUNCTION(PlainTimeConstructor::from)
|
||||
{
|
||||
// 4. Return ? ToTemporalTime(item, overflow).
|
||||
return TRY(to_temporal_time(vm, vm.argument(0), vm.argument(1)));
|
||||
}
|
||||
|
||||
// 4.2.3 Temporal.PlainTime.compare ( one, two ), https://tc39.es/proposal-temporal/#sec-temporal.plaintime.compare
|
||||
JS_DEFINE_NATIVE_FUNCTION(PlainTimeConstructor::compare)
|
||||
{
|
||||
// 1. Set one to ? ToTemporalTime(one).
|
||||
auto one = TRY(to_temporal_time(vm, vm.argument(0)));
|
||||
|
||||
// 2. Set two to ? ToTemporalTime(two).
|
||||
auto two = TRY(to_temporal_time(vm, vm.argument(1)));
|
||||
|
||||
// 3. Return 𝔽(CompareTimeRecord(one.[[Time]], two.[[Time]])).
|
||||
return compare_time_record(one->time(), two->time());
|
||||
}
|
||||
|
||||
}
|
34
Libraries/LibJS/Runtime/Temporal/PlainTimeConstructor.h
Normal file
34
Libraries/LibJS/Runtime/Temporal/PlainTimeConstructor.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Runtime/NativeFunction.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
class PlainTimeConstructor final : public NativeFunction {
|
||||
JS_OBJECT(PlainTimeConstructor, NativeFunction);
|
||||
GC_DECLARE_ALLOCATOR(PlainTimeConstructor);
|
||||
|
||||
public:
|
||||
virtual void initialize(Realm&) override;
|
||||
virtual ~PlainTimeConstructor() override = default;
|
||||
|
||||
virtual ThrowCompletionOr<Value> call() override;
|
||||
virtual ThrowCompletionOr<GC::Ref<Object>> construct(FunctionObject& new_target) override;
|
||||
|
||||
private:
|
||||
explicit PlainTimeConstructor(Realm&);
|
||||
|
||||
virtual bool has_constructor() const override { return true; }
|
||||
|
||||
JS_DECLARE_NATIVE_FUNCTION(from);
|
||||
JS_DECLARE_NATIVE_FUNCTION(compare);
|
||||
};
|
||||
|
||||
}
|
65
Libraries/LibJS/Runtime/Temporal/PlainTimePrototype.cpp
Normal file
65
Libraries/LibJS/Runtime/Temporal/PlainTimePrototype.cpp
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/Temporal/PlainTimePrototype.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(PlainTimePrototype);
|
||||
|
||||
// 4.3 Properties of the Temporal.PlainTime Prototype Object, https://tc39.es/proposal-temporal/#sec-properties-of-the-temporal-plaintime-prototype-object
|
||||
PlainTimePrototype::PlainTimePrototype(Realm& realm)
|
||||
: PrototypeObject(realm.intrinsics().object_prototype())
|
||||
{
|
||||
}
|
||||
|
||||
void PlainTimePrototype::initialize(Realm& realm)
|
||||
{
|
||||
Base::initialize(realm);
|
||||
|
||||
auto& vm = this->vm();
|
||||
|
||||
// 4.3.2 Temporal.PlainTime.prototype[ %Symbol.toStringTag% ], https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype-%symbol.tostringtag%
|
||||
define_direct_property(vm.well_known_symbol_to_string_tag(), PrimitiveString::create(vm, "Temporal.PlainTime"_string), Attribute::Configurable);
|
||||
|
||||
define_native_accessor(realm, vm.names.hour, hour_getter, {}, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.minute, minute_getter, {}, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.second, second_getter, {}, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.millisecond, millisecond_getter, {}, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.microsecond, microsecond_getter, {}, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.nanosecond, nanosecond_getter, {}, Attribute::Configurable);
|
||||
}
|
||||
|
||||
// 4.3.3 get Temporal.PlainTime.prototype.hour, https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.hour
|
||||
// 4.3.4 get Temporal.PlainTime.prototype.minute, https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.minute
|
||||
// 4.3.5 get Temporal.PlainTime.prototype.second, https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.second
|
||||
// 4.3.6 get Temporal.PlainTime.prototype.millisecond, https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.millisecond
|
||||
// 4.3.7 get Temporal.PlainTime.prototype.microsecond, https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.microsecond
|
||||
// 4.3.8 get Temporal.PlainTime.prototype.nanosecond, https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.microsecond
|
||||
#define JS_ENUMERATE_PLAIN_TIME_FIELDS \
|
||||
__JS_ENUMERATE(hour) \
|
||||
__JS_ENUMERATE(minute) \
|
||||
__JS_ENUMERATE(second) \
|
||||
__JS_ENUMERATE(millisecond) \
|
||||
__JS_ENUMERATE(microsecond) \
|
||||
__JS_ENUMERATE(nanosecond)
|
||||
|
||||
#define __JS_ENUMERATE(field) \
|
||||
JS_DEFINE_NATIVE_FUNCTION(PlainTimePrototype::field##_getter) \
|
||||
{ \
|
||||
/* 1. Let temporalTime be the this value. */ \
|
||||
/* 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]). */ \
|
||||
auto temporal_time = TRY(typed_this_object(vm)); \
|
||||
\
|
||||
/* 3. Return 𝔽(temporalTime.[[Time]].[[<field>]]). */ \
|
||||
return temporal_time->time().field; \
|
||||
}
|
||||
JS_ENUMERATE_PLAIN_TIME_FIELDS
|
||||
#undef __JS_ENUMERATE
|
||||
|
||||
}
|
34
Libraries/LibJS/Runtime/Temporal/PlainTimePrototype.h
Normal file
34
Libraries/LibJS/Runtime/Temporal/PlainTimePrototype.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Runtime/PrototypeObject.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainTime.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
class PlainTimePrototype final : public PrototypeObject<PlainTimePrototype, PlainTime> {
|
||||
JS_PROTOTYPE_OBJECT(PlainTimePrototype, PlainTime, Temporal.PlainTime);
|
||||
GC_DECLARE_ALLOCATOR(PlainTimePrototype);
|
||||
|
||||
public:
|
||||
virtual void initialize(Realm&) override;
|
||||
virtual ~PlainTimePrototype() override = default;
|
||||
|
||||
private:
|
||||
explicit PlainTimePrototype(Realm&);
|
||||
|
||||
JS_DECLARE_NATIVE_FUNCTION(hour_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(minute_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(second_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(millisecond_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(microsecond_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(nanosecond_getter);
|
||||
};
|
||||
|
||||
}
|
|
@ -9,6 +9,7 @@
|
|||
#include <LibJS/Runtime/Temporal/DurationConstructor.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDateConstructor.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainMonthDayConstructor.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainTimeConstructor.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainYearMonthConstructor.h>
|
||||
#include <LibJS/Runtime/Temporal/Temporal.h>
|
||||
|
||||
|
@ -35,6 +36,7 @@ void Temporal::initialize(Realm& realm)
|
|||
define_intrinsic_accessor(vm.names.Duration, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_duration_constructor(); });
|
||||
define_intrinsic_accessor(vm.names.PlainDate, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_plain_date_constructor(); });
|
||||
define_intrinsic_accessor(vm.names.PlainMonthDay, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_plain_month_day_constructor(); });
|
||||
define_intrinsic_accessor(vm.names.PlainTime, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_plain_time_constructor(); });
|
||||
define_intrinsic_accessor(vm.names.PlainYearMonth, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_plain_year_month_constructor(); });
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
describe("correct behavior", () => {
|
||||
test("length is 2", () => {
|
||||
expect(Temporal.PlainTime.compare).toHaveLength(2);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
const plainTime1 = new Temporal.PlainTime(16, 38, 40, 1, 2, 3);
|
||||
expect(Temporal.PlainTime.compare(plainTime1, plainTime1)).toBe(0);
|
||||
const plainTime2 = new Temporal.PlainTime(16, 39, 5, 0, 1, 2);
|
||||
expect(Temporal.PlainTime.compare(plainTime1, plainTime2)).toBe(-1);
|
||||
expect(Temporal.PlainTime.compare(plainTime2, plainTime1)).toBe(1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,67 @@
|
|||
describe("correct behavior", () => {
|
||||
test("length is 1", () => {
|
||||
expect(Temporal.PlainTime.from).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("PlainTime instance argument", () => {
|
||||
const plainTime = new Temporal.PlainTime(18, 45, 37, 1, 2, 3);
|
||||
const createdPlainTime = Temporal.PlainTime.from(plainTime);
|
||||
expect(createdPlainTime.hour).toBe(18);
|
||||
expect(createdPlainTime.minute).toBe(45);
|
||||
expect(createdPlainTime.second).toBe(37);
|
||||
expect(createdPlainTime.millisecond).toBe(1);
|
||||
expect(createdPlainTime.microsecond).toBe(2);
|
||||
expect(createdPlainTime.nanosecond).toBe(3);
|
||||
});
|
||||
|
||||
test("PlainTime string argument", () => {
|
||||
const createdPlainTime = Temporal.PlainTime.from("2021-08-27T18:44:11");
|
||||
expect(createdPlainTime.hour).toBe(18);
|
||||
expect(createdPlainTime.minute).toBe(44);
|
||||
expect(createdPlainTime.second).toBe(11);
|
||||
expect(createdPlainTime.millisecond).toBe(0);
|
||||
expect(createdPlainTime.microsecond).toBe(0);
|
||||
expect(createdPlainTime.nanosecond).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("string must not contain a UTC designator", () => {
|
||||
expect(() => {
|
||||
Temporal.PlainTime.from("2021-07-06T23:42:01Z");
|
||||
}).toThrowWithMessage(RangeError, "Invalid ISO date time");
|
||||
});
|
||||
|
||||
test("extended year must not be negative zero", () => {
|
||||
expect(() => {
|
||||
Temporal.PlainTime.from("-000000-01-01T00:00:00");
|
||||
}).toThrowWithMessage(RangeError, "Invalid ISO date time");
|
||||
expect(() => {
|
||||
Temporal.PlainTime.from("−000000-01-01T00:00:00"); // U+2212
|
||||
}).toThrowWithMessage(RangeError, "Invalid ISO date time");
|
||||
});
|
||||
|
||||
test("ambiguous string must contain a time designator", () => {
|
||||
const values = [
|
||||
// YYYY-MM or HHMM-UU
|
||||
"2021-12",
|
||||
// MMDD or HHMM
|
||||
"1214",
|
||||
"0229",
|
||||
"1130",
|
||||
// MM-DD or HH-UU
|
||||
"12-14",
|
||||
// YYYYMM or HHMMSS
|
||||
"202112",
|
||||
];
|
||||
for (const value of values) {
|
||||
expect(() => {
|
||||
Temporal.PlainTime.from(value);
|
||||
}).toThrowWithMessage(RangeError, "Invalid ISO date time");
|
||||
|
||||
expect(() => {
|
||||
Temporal.PlainTime.from(`T${value}`);
|
||||
}).not.toThrow();
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,50 @@
|
|||
describe("errors", () => {
|
||||
test("called without new", () => {
|
||||
expect(() => {
|
||||
Temporal.PlainTime();
|
||||
}).toThrowWithMessage(
|
||||
TypeError,
|
||||
"Temporal.PlainTime constructor must be called with 'new'"
|
||||
);
|
||||
});
|
||||
|
||||
test("cannot pass Infinity", () => {
|
||||
for (let i = 0; i < 6; ++i) {
|
||||
const args = Array(6).fill(0);
|
||||
|
||||
args[i] = Infinity;
|
||||
expect(() => {
|
||||
new Temporal.PlainTime(...args);
|
||||
}).toThrowWithMessage(RangeError, "Invalid plain time");
|
||||
|
||||
args[i] = -Infinity;
|
||||
expect(() => {
|
||||
new Temporal.PlainTime(...args);
|
||||
}).toThrowWithMessage(RangeError, "Invalid plain time");
|
||||
}
|
||||
});
|
||||
|
||||
test("cannot pass invalid ISO time", () => {
|
||||
const badValues = [24, 60, 60, 1000, 1000, 1000];
|
||||
for (let i = 0; i < 6; ++i) {
|
||||
const args = [0, 0, 0, 0, 0, 0];
|
||||
args[i] = badValues[i];
|
||||
expect(() => {
|
||||
new Temporal.PlainTime(...args);
|
||||
}).toThrowWithMessage(RangeError, "Invalid plain time");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("normal behavior", () => {
|
||||
test("length is 0", () => {
|
||||
expect(Temporal.PlainTime).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
const plainTime = new Temporal.PlainTime(19, 46, 32, 123, 456, 789);
|
||||
expect(typeof plainTime).toBe("object");
|
||||
expect(plainTime).toBeInstanceOf(Temporal.PlainTime);
|
||||
expect(Object.getPrototypeOf(plainTime)).toBe(Temporal.PlainTime.prototype);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
describe("correct behavior", () => {
|
||||
test("basic functionality", () => {
|
||||
const plainTime = new Temporal.PlainTime(12);
|
||||
expect(plainTime.hour).toBe(12);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.PlainTime object", () => {
|
||||
expect(() => {
|
||||
Reflect.get(Temporal.PlainTime.prototype, "hour", "foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainTime");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
describe("correct behavior", () => {
|
||||
test("basic functionality", () => {
|
||||
const plainTime = new Temporal.PlainTime(0, 0, 0, 0, 12);
|
||||
expect(plainTime.microsecond).toBe(12);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.PlainTime object", () => {
|
||||
expect(() => {
|
||||
Reflect.get(Temporal.PlainTime.prototype, "microsecond", "foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainTime");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
describe("correct behavior", () => {
|
||||
test("basic functionality", () => {
|
||||
const plainTime = new Temporal.PlainTime(0, 0, 0, 12);
|
||||
expect(plainTime.millisecond).toBe(12);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.PlainTime object", () => {
|
||||
expect(() => {
|
||||
Reflect.get(Temporal.PlainTime.prototype, "millisecond", "foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainTime");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
describe("correct behavior", () => {
|
||||
test("basic functionality", () => {
|
||||
const plainTime = new Temporal.PlainTime(0, 12);
|
||||
expect(plainTime.minute).toBe(12);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.PlainTime object", () => {
|
||||
expect(() => {
|
||||
Reflect.get(Temporal.PlainTime.prototype, "minute", "foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainTime");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
describe("correct behavior", () => {
|
||||
test("basic functionality", () => {
|
||||
const plainTime = new Temporal.PlainTime(0, 0, 0, 0, 0, 12);
|
||||
expect(plainTime.nanosecond).toBe(12);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.PlainTime object", () => {
|
||||
expect(() => {
|
||||
Reflect.get(Temporal.PlainTime.prototype, "nanosecond", "foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainTime");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
describe("correct behavior", () => {
|
||||
test("basic functionality", () => {
|
||||
const plainTime = new Temporal.PlainTime(0, 0, 12);
|
||||
expect(plainTime.second).toBe(12);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.PlainTime object", () => {
|
||||
expect(() => {
|
||||
Reflect.get(Temporal.PlainTime.prototype, "second", "foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainTime");
|
||||
});
|
||||
});
|
Loading…
Add table
Reference in a new issue