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:
Timothy Flynn 2024-11-23 10:21:21 -05:00 committed by Tim Flynn
parent 971f794127
commit 66365fef57
Notes: github-actions[bot] 2024-11-24 00:38:03 +00:00
22 changed files with 693 additions and 1 deletions

View file

@ -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

View file

@ -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 \

View file

@ -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);

View file

@ -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 '{}'") \

View file

@ -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>

View file

@ -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;

View file

@ -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)
{

View file

@ -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);

View 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());
}
}

View 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);
};
}

View 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
}

View 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);
};
}

View file

@ -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(); });
}

View file

@ -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);
});
});

View file

@ -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();
}
});
});

View file

@ -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);
});
});

View file

@ -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");
});
});

View file

@ -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");
});
});

View file

@ -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");
});
});

View file

@ -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");
});
});

View file

@ -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");
});
});

View file

@ -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");
});
});