diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp index 0da9c1c056..7f112ebc75 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp @@ -560,6 +560,26 @@ Optional parse_temporal_date_string(GlobalObject& global_object, S return TemporalDate { .year = result->year, .month = result->month, .day = result->day, .calendar = move(result->calendar) }; } +// 13.39 ParseTemporalDateTimeString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldatetimestring +Optional parse_temporal_date_time_string(GlobalObject& global_object, String const& iso_string) +{ + auto& vm = global_object.vm(); + + // 1. Assert: Type(isoString) is String. + + // 2. If isoString does not satisfy the syntax of a TemporalDateTimeString (see 13.33), then + // a. Throw a RangeError exception. + // TODO + + // 3. Let result be ? ParseISODateTime(isoString). + auto result = parse_iso_date_time(global_object, iso_string); + if (vm.exception()) + return {}; + + // 4. Return result. + return result; +} + // 13.40 ParseTemporalDurationString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldurationstring Optional parse_temporal_duration_string(GlobalObject& global_object, String const& iso_string) { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h index 212631edd5..825d58d565 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h @@ -73,6 +73,7 @@ Optional parse_iso_date_time(GlobalObject&, String const& iso_strin Optional parse_temporal_instant_string(GlobalObject&, String const& iso_string); Optional parse_temporal_calendar_string(GlobalObject&, String const& iso_string); Optional parse_temporal_date_string(GlobalObject&, String const& iso_string); +Optional parse_temporal_date_time_string(GlobalObject&, String const& iso_string); Optional parse_temporal_duration_string(GlobalObject&, String const& iso_string); Optional parse_temporal_time_zone_string(GlobalObject&, String const& iso_string); double to_positive_integer_or_infinity(GlobalObject&, Value argument); diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp index b8d6bc4410..2d81fb5acc 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp @@ -7,10 +7,15 @@ #include #include #include +#include +#include +#include #include #include #include #include +#include +#include namespace JS::Temporal { @@ -93,6 +98,143 @@ bool iso_date_time_within_limits(GlobalObject& global_object, i32 year, u8 month return true; } +// 5.5.3 InterpretTemporalDateTimeFields ( calendar, fields, options ), https://tc39.es/proposal-temporal/#sec-temporal-interprettemporaldatetimefields +Optional interpret_temporal_date_time_fields(GlobalObject& global_object, Object& calendar, Object& fields, Object& options) +{ + auto& vm = global_object.vm(); + + Optional time_result; + + // 1. Let timeResult be ? ToTemporalTimeRecord(fields). + time_result = to_temporal_time_record(global_object, fields); + if (vm.exception()) + return {}; + + // 2. Let temporalDate be ? DateFromFields(calendar, fields, options). + auto* temporal_date = date_from_fields(global_object, calendar, fields, options); + if (vm.exception()) + return {}; + + // 3. Let overflow be ? ToTemporalOverflow(options). + auto overflow = to_temporal_overflow(global_object, options); + if (vm.exception()) + return {}; + + // 4. Let timeResult be ? RegulateTime(timeResult.[[Hour]], timeResult.[[Minute]], timeResult.[[Second]], timeResult.[[Millisecond]], timeResult.[[Microsecond]], timeResult.[[Nanosecond]], overflow). + time_result = regulate_time(global_object, time_result->hour, time_result->minute, time_result->second, time_result->millisecond, time_result->microsecond, time_result->nanosecond, *overflow); + if (vm.exception()) + return {}; + + // 5. Return the Record { [[Year]]: temporalDate.[[ISOYear]], [[Month]]: temporalDate.[[ISOMonth]], [[Day]]: temporalDate.[[ISODay]], [[Hour]]: timeResult.[[Hour]], [[Minute]]: timeResult.[[Minute]], [[Second]]: timeResult.[[Second]], [[Millisecond]]: timeResult.[[Millisecond]], [[Microsecond]]: timeResult.[[Microsecond]], [[Nanosecond]]: timeResult.[[Nanosecond]] }. + return ISODateTime { + .year = temporal_date->iso_year(), + .month = temporal_date->iso_month(), + .day = temporal_date->iso_day(), + .hour = static_cast(time_result->hour), + .minute = static_cast(time_result->minute), + .second = static_cast(time_result->second), + .millisecond = static_cast(time_result->millisecond), + .microsecond = static_cast(time_result->microsecond), + .nanosecond = static_cast(time_result->nanosecond), + }; +} + +// 5.5.4 ToTemporalDateTime ( item [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaldatetime +PlainDateTime* to_temporal_date_time(GlobalObject& global_object, Value item, Object* options) +{ + auto& vm = global_object.vm(); + + // 1. If options is not present, set options to ! OrdinaryObjectCreate(null). + if (!options) + options = Object::create(global_object, nullptr); + + Object* calendar; + ISODateTime result; + + // 2. If Type(item) is Object, then + if (item.is_object()) { + auto& item_object = item.as_object(); + + // a. If item has an [[InitializedTemporalDateTime]] internal slot, then + if (is(item_object)) { + // i. Return item. + return &static_cast(item_object); + } + + // b. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then + if (is(item_object)) { + auto& zoned_date_time = static_cast(item_object); + + // i. Let instant be ! CreateTemporalInstant(item.[[Nanoseconds]]). + auto* instant = create_temporal_instant(global_object, zoned_date_time.nanoseconds()); + + // ii. Return ? BuiltinTimeZoneGetPlainDateTimeFor(item.[[TimeZone]], instant, item.[[Calendar]]). + return builtin_time_zone_get_plain_date_time_for(global_object, &zoned_date_time.time_zone(), *instant, zoned_date_time.calendar()); + } + + // c. If item has an [[InitializedTemporalDate]] internal slot, then + if (is(item_object)) { + auto& plain_date = static_cast(item_object); + + // i. Return ? CreateTemporalDateTime(item.[[ISOYear]], item.[[ISOMonth]], item.[[ISODay]], 0, 0, 0, 0, 0, 0, item.[[Calendar]]). + return create_temporal_date_time(global_object, plain_date.iso_year(), plain_date.iso_month(), plain_date.iso_day(), 0, 0, 0, 0, 0, 0, plain_date.calendar()); + } + + // d. Let calendar be ? GetTemporalCalendarWithISODefault(item). + calendar = get_temporal_calendar_with_iso_default(global_object, item_object); + if (vm.exception()) + return {}; + + // e. Let fieldNames be ? CalendarFields(calendar, « "day", "hour", "microsecond", "millisecond", "minute", "month", "monthCode", "nanosecond", "second", "year" »). + auto field_names = calendar_fields(global_object, *calendar, { "day"sv, "hour"sv, "microsecond"sv, "millisecond"sv, "minute"sv, "month"sv, "monthCode"sv, "nanosecond"sv, "second"sv, "year"sv }); + if (vm.exception()) + return {}; + + // f. Let fields be ? PrepareTemporalFields(item, fieldNames, «»). + auto* fields = prepare_temporal_fields(global_object, item_object, field_names, {}); + if (vm.exception()) + return {}; + + // g. Let result be ? InterpretTemporalDateTimeFields(calendar, fields, options). + auto maybe_result = interpret_temporal_date_time_fields(global_object, *calendar, *fields, *options); + if (vm.exception()) + return {}; + result = move(*maybe_result); + } + // 3. Else, + else { + // a. Perform ? ToTemporalOverflow(options). + (void)to_temporal_overflow(global_object, *options); + if (vm.exception()) + return {}; + + // b. Let string be ? ToString(item). + auto string = item.to_string(global_object); + if (vm.exception()) + return {}; + + // c. Let result be ? ParseTemporalDateTimeString(string). + auto maybe_result = parse_temporal_date_time_string(global_object, string); + if (vm.exception()) + return {}; + result = move(*maybe_result); + + // d. Assert: ! IsValidISODate(result.[[Year]], result.[[Month]], result.[[Day]]) is true. + VERIFY(is_valid_iso_date(result.year, result.month, result.day)); + + // e. Assert: ! IsValidTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]) is true. + VERIFY(is_valid_time(result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond)); + + // f. Let calendar be ? ToTemporalCalendarWithISODefault(result.[[Calendar]]). + calendar = to_temporal_calendar_with_iso_default(global_object, result.calendar.has_value() ? js_string(vm, *result.calendar) : js_undefined()); + if (vm.exception()) + return {}; + } + + // 4. Return ? CreateTemporalDateTime(result.[[Year]], result.[[Month]], result.[[Day]], result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]], calendar). + return create_temporal_date_time(global_object, result.year, result.month, result.day, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond, *calendar); +} + // 5.5.5 BalanceISODateTime ( year, month, day, hour, minute, second, millisecond, microsecond, nanosecond ), https://tc39.es/proposal-temporal/#sec-temporal-balanceisodatetime ISODateTime balance_iso_date_time(i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, i64 nanosecond) { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h index 9836d0ea7b..0467be387b 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h @@ -7,6 +7,7 @@ #pragma once +#include #include #include @@ -49,6 +50,8 @@ private: BigInt* get_epoch_from_iso_parts(GlobalObject&, i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond); bool iso_date_time_within_limits(GlobalObject&, i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond); +Optional interpret_temporal_date_time_fields(GlobalObject&, Object& calendar, Object& fields, Object& options); +PlainDateTime* to_temporal_date_time(GlobalObject&, Value item, Object* options = nullptr); ISODateTime balance_iso_date_time(i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, i64 nanosecond); PlainDateTime* create_temporal_date_time(GlobalObject&, i32 iso_year, u8 iso_month, u8 iso_day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, Object& calendar, FunctionObject* new_target = nullptr); diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTimeConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTimeConstructor.cpp index 26706094a5..a86526866b 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTimeConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTimeConstructor.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -28,6 +29,9 @@ void PlainDateTimeConstructor::initialize(GlobalObject& global_object) // 5.2.1 Temporal.PlainDateTime.prototype, https://tc39.es/proposal-temporal/#sec-temporal-plaindatetime-prototype define_direct_property(vm.names.prototype, global_object.temporal_plain_date_time_prototype(), 0); + u8 attr = Attribute::Writable | Attribute::Configurable; + define_native_function(vm.names.from, from, 1, attr); + define_direct_property(vm.names.length, Value(3), Attribute::Configurable); } @@ -110,4 +114,31 @@ Value PlainDateTimeConstructor::construct(FunctionObject& new_target) return create_temporal_date_time(global_object, iso_year, iso_month, iso_day, hour, minute, second, millisecond, microsecond, nanosecond, *calendar, &new_target); } +// 5.2.2 Temporal.PlainDateTime.from ( item [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.from +JS_DEFINE_NATIVE_FUNCTION(PlainDateTimeConstructor::from) +{ + auto item = vm.argument(0); + + // 1. Set options to ? GetOptionsObject(options). + auto* options = get_options_object(global_object, vm.argument(1)); + if (vm.exception()) + return {}; + + // 2. If Type(item) is Object and item has an [[InitializedTemporalDateTime]] internal slot, then + if (item.is_object() && is(item.as_object())) { + auto& plain_date_time = static_cast(item.as_object()); + + // a. Perform ? ToTemporalOverflow(options). + (void)to_temporal_overflow(global_object, *options); + if (vm.exception()) + return {}; + + // b. Return ? CreateTemporalDateTime(item.[[ISOYear]], item.[[ISOMonth]], item.[[ISODay]], item.[[ISOHour]], item.[[ISOMinute]], item.[[ISOSecond]], item.[[ISOMillisecond]], item.[[ISOMicrosecond]], item.[[ISONanosecond]], item.[[Calendar]]). + return create_temporal_date_time(global_object, plain_date_time.iso_year(), plain_date_time.iso_month(), plain_date_time.iso_day(), plain_date_time.iso_hour(), plain_date_time.iso_minute(), plain_date_time.iso_second(), plain_date_time.iso_millisecond(), plain_date_time.iso_microsecond(), plain_date_time.iso_nanosecond(), plain_date_time.calendar()); + } + + // 3. Return ? ToTemporalDateTime(item, options). + return to_temporal_date_time(global_object, item, options); +} + } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTimeConstructor.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTimeConstructor.h index a4263ed7ff..bd87641517 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTimeConstructor.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTimeConstructor.h @@ -23,6 +23,8 @@ public: private: virtual bool has_constructor() const override { return true; } + + JS_DECLARE_NATIVE_FUNCTION(from); }; } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.cpp index 7e4813379b..e30e4056cd 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -33,8 +34,41 @@ void PlainTime::visit_edges(Visitor& visitor) visitor.visit(&m_calendar); } +// 4.5.4 RegulateTime ( hour, minute, second, millisecond, microsecond, nanosecond, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-regulatetime +Optional regulate_time(GlobalObject& global_object, double hour, double minute, double second, double millisecond, double microsecond, double nanosecond, StringView overflow) +{ + auto& vm = global_object.vm(); + + // 1. Assert: hour, minute, second, millisecond, microsecond and nanosecond are integers. + // NOTE: As the spec is currently written this assertion can fail, these are either integers _or_ infinity. + // See https://github.com/tc39/proposal-temporal/issues/1672. + + // 2. Assert: overflow is either "constrain" or "reject". + // NOTE: Asserted by the VERIFY_NOT_REACHED at the end + + // 3. If overflow is "constrain", then + if (overflow == "constrain"sv) { + // a. Return ! ConstrainTime(hour, minute, second, millisecond, microsecond, nanosecond). + return constrain_time(hour, minute, second, millisecond, microsecond, nanosecond); + } + + // 4. If overflow is "reject", then + if (overflow == "reject"sv) { + // a. If ! IsValidTime(hour, minute, second, millisecond, microsecond, nanosecond) is false, throw a RangeError exception. + if (!is_valid_time(hour, minute, second, millisecond, microsecond, nanosecond)) { + vm.throw_exception(global_object, ErrorType::TemporalInvalidPlainTime); + return {}; + } + + // b. Return the Record { [[Hour]]: hour, [[Minute]]: minute, [[Second]]: second, [[Millisecond]]: millisecond, [[Microsecond]]: microsecond, [[Nanosecond]]: nanosecond }. + return TemporalTime { .hour = hour, .minute = minute, .second = second, .millisecond = millisecond, .microsecond = microsecond, .nanosecond = nanosecond }; + } + + VERIFY_NOT_REACHED(); +} + // 4.5.5 IsValidTime ( hour, minute, second, millisecond, microsecond, nanosecond ), https://tc39.es/proposal-temporal/#sec-temporal-isvalidtime -bool is_valid_time(u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond) +bool is_valid_time(double hour, double minute, double second, double millisecond, double microsecond, double nanosecond) { // 1. Assert: hour, minute, second, millisecond, microsecond, and nanosecond are integers. @@ -131,6 +165,33 @@ DaysAndTime balance_time(i64 hour, i64 minute, i64 second, i64 millisecond, i64 }; } +// 4.5.7 ConstrainTime ( hour, minute, second, millisecond, microsecond, nanosecond ), https://tc39.es/proposal-temporal/#sec-temporal-constraintime +TemporalTime constrain_time(double hour, double minute, double second, double millisecond, double microsecond, double nanosecond) +{ + // 1. Assert: hour, minute, second, millisecond, microsecond, and nanosecond are integers. + + // 2. Set hour to ! ConstrainToRange(hour, 0, 23). + hour = constrain_to_range(hour, 0, 23); + + // 3. Set minute to ! ConstrainToRange(minute, 0, 59). + minute = constrain_to_range(minute, 0, 59); + + // 4. Set second to ! ConstrainToRange(second, 0, 59). + second = constrain_to_range(second, 0, 59); + + // 5. Set millisecond to ! ConstrainToRange(millisecond, 0, 999). + millisecond = constrain_to_range(millisecond, 0, 999); + + // 6. Set microsecond to ! ConstrainToRange(microsecond, 0, 999). + microsecond = constrain_to_range(microsecond, 0, 999); + + // 7. Set nanosecond to ! ConstrainToRange(nanosecond, 0, 999). + nanosecond = constrain_to_range(nanosecond, 0, 999); + + // 8. Return the Record { [[Hour]]: hour, [[Minute]]: minute, [[Second]]: second, [[Millisecond]]: millisecond, [[Microsecond]]: microsecond, [[Nanosecond]]: nanosecond }. + return TemporalTime { .hour = hour, .minute = minute, .second = second, .millisecond = millisecond, .microsecond = microsecond, .nanosecond = nanosecond }; +} + // 4.5.8 CreateTemporalTime ( hour, minute, second, millisecond, microsecond, nanosecond [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporaltime PlainTime* create_temporal_time(GlobalObject& global_object, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, FunctionObject* new_target) { @@ -164,4 +225,43 @@ PlainTime* create_temporal_time(GlobalObject& global_object, u8 hour, u8 minute, return object; } +// 4.5.9 ToTemporalTimeRecord ( temporalTimeLike ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaltimerecord +Optional to_temporal_time_record(GlobalObject& global_object, Object& temporal_time_like) +{ + auto& vm = global_object.vm(); + + // 1. Assert: Type(temporalTimeLike) is Object. + + // 2. Let result be the Record { [[Hour]]: undefined, [[Minute]]: undefined, [[Second]]: undefined, [[Millisecond]]: undefined, [[Microsecond]]: undefined, [[Nanosecond]]: undefined }. + auto result = TemporalTime {}; + + // 3. For each row of Table 3, except the header row, in table order, do + for (auto& [internal_slot, property] : temporal_time_like_properties(vm)) { + // a. Let property be the Property value of the current row. + + // b. Let value be ? Get(temporalTimeLike, property). + auto value = temporal_time_like.get(property); + if (vm.exception()) + return {}; + + // c. If value is undefined, then + if (value.is_undefined()) { + // i. Throw a TypeError exception. + vm.throw_exception(global_object, ErrorType::TemporalMissingRequiredProperty, property); + return {}; + } + + // d. Set value to ? ToIntegerOrInfinity(value). + auto value_number = value.to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + + // e. Set result's internal slot whose name is the Internal Slot value of the current row to value. + result.*internal_slot = value_number; + } + + // 4. Return result. + return result; +} + } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.h index fabf4c10df..03b7964e69 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.h @@ -7,6 +7,7 @@ #pragma once +#include #include namespace JS::Temporal { @@ -50,8 +51,41 @@ struct DaysAndTime { u16 nanosecond; }; -bool is_valid_time(u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond); +struct TemporalTime { + double hour; + double minute; + double second; + double millisecond; + double microsecond; + double nanosecond; +}; + +// Table 3: Properties of a TemporalTimeLike, https://tc39.es/proposal-temporal/#table-temporal-temporaltimelike-properties + +template +struct TemporalTimeLikeProperty { + ValueT StructT::*internal_slot { nullptr }; + PropertyName property; +}; + +template +auto temporal_time_like_properties = [](VM& vm) { + using PropertyT = TemporalTimeLikeProperty; + return AK::Array { + PropertyT { &StructT::hour, vm.names.hour }, + PropertyT { &StructT::microsecond, vm.names.microsecond }, + PropertyT { &StructT::millisecond, vm.names.millisecond }, + PropertyT { &StructT::minute, vm.names.minute }, + PropertyT { &StructT::nanosecond, vm.names.nanosecond }, + PropertyT { &StructT::second, vm.names.second }, + }; +}; + +Optional regulate_time(GlobalObject&, double hour, double minute, double second, double millisecond, double microsecond, double nanosecond, StringView overflow); +bool is_valid_time(double hour, double minute, double second, double millisecond, double microsecond, double nanosecond); DaysAndTime balance_time(i64 hour, i64 minute, i64 second, i64 millisecond, i64 microsecond, i64 nanosecond); +TemporalTime constrain_time(double hour, double minute, double second, double millisecond, double microsecond, double nanosecond); PlainTime* create_temporal_time(GlobalObject&, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, FunctionObject* new_target = nullptr); +Optional to_temporal_time_record(GlobalObject&, Object& temporal_time_like); } diff --git a/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.from.js b/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.from.js new file mode 100644 index 0000000000..dcc0c733a8 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.from.js @@ -0,0 +1,172 @@ +describe("correct behavior", () => { + test("length is 1", () => { + expect(Temporal.PlainDateTime.from).toHaveLength(1); + }); + + test("PlainDate instance argument", () => { + const plainDate = new Temporal.PlainDate(2021, 7, 6); + const plainDateTime = Temporal.PlainDateTime.from(plainDate); + expect(plainDateTime.year).toBe(2021); + expect(plainDateTime.month).toBe(7); + expect(plainDateTime.day).toBe(6); + expect(plainDateTime.hour).toBe(0); + expect(plainDateTime.minute).toBe(0); + expect(plainDateTime.second).toBe(0); + expect(plainDateTime.millisecond).toBe(0); + expect(plainDateTime.microsecond).toBe(0); + expect(plainDateTime.nanosecond).toBe(0); + }); + + test("PlainDateTime instance argument", () => { + const plainDateTime_ = new Temporal.PlainDateTime(2021, 7, 6, 18, 14, 47); + const plainDateTime = Temporal.PlainDateTime.from(plainDateTime_); + expect(plainDateTime).not.toBe(plainDateTime_); + expect(plainDateTime.year).toBe(2021); + expect(plainDateTime.month).toBe(7); + expect(plainDateTime.day).toBe(6); + expect(plainDateTime.hour).toBe(18); + expect(plainDateTime.minute).toBe(14); + expect(plainDateTime.second).toBe(47); + expect(plainDateTime.millisecond).toBe(0); + expect(plainDateTime.microsecond).toBe(0); + expect(plainDateTime.nanosecond).toBe(0); + }); + + test("ZonedDateTime instance argument", () => { + const timeZone = new Temporal.TimeZone("UTC"); + const zonedDateTime = new Temporal.ZonedDateTime(1625614921000000000n, timeZone); + const plainDateTime = Temporal.PlainDateTime.from(zonedDateTime); + expect(plainDateTime.year).toBe(2021); + expect(plainDateTime.month).toBe(7); + expect(plainDateTime.day).toBe(6); + expect(plainDateTime.hour).toBe(23); + expect(plainDateTime.minute).toBe(42); + expect(plainDateTime.second).toBe(1); + expect(plainDateTime.millisecond).toBe(0); + expect(plainDateTime.microsecond).toBe(0); + expect(plainDateTime.nanosecond).toBe(0); + }); + + test("fields object argument", () => { + const object = { + year: 2021, + month: 7, + day: 6, + hour: 23, + minute: 42, + second: 1, + millisecond: 0, + microsecond: 0, + nanosecond: 0, + }; + const plainDateTime = Temporal.PlainDateTime.from(object); + expect(plainDateTime.year).toBe(2021); + expect(plainDateTime.month).toBe(7); + expect(plainDateTime.day).toBe(6); + expect(plainDateTime.hour).toBe(23); + expect(plainDateTime.minute).toBe(42); + expect(plainDateTime.second).toBe(1); + expect(plainDateTime.millisecond).toBe(0); + expect(plainDateTime.microsecond).toBe(0); + expect(plainDateTime.nanosecond).toBe(0); + }); + + test("with 'constrain' overflow option", () => { + const object = { + year: 0, + month: 1, + day: 1, + hour: 24, + minute: 60, + second: 60, + millisecond: 1000, + microsecond: 1000, + nanosecond: 1000, + }; + const plainDateTime = Temporal.PlainDateTime.from(object, { overflow: "constrain" }); + expect(plainDateTime.year).toBe(0); + expect(plainDateTime.month).toBe(1); + expect(plainDateTime.day).toBe(1); + expect(plainDateTime.hour).toBe(23); + expect(plainDateTime.minute).toBe(59); + expect(plainDateTime.second).toBe(59); + expect(plainDateTime.millisecond).toBe(999); + expect(plainDateTime.microsecond).toBe(999); + expect(plainDateTime.nanosecond).toBe(999); + }); + + // Un-skip once ParseISODateTime & ParseTemporalDateString are implemented + test.skip("PlainDateTime string argument", () => { + const plainDateTime = Temporal.PlainDateTime.from("2021-07-06T23:42:01Z"); + expect(plainDateTime.year).toBe(2021); + expect(plainDateTime.month).toBe(7); + expect(plainDateTime.day).toBe(6); + expect(plainDateTime.hour).toBe(23); + expect(plainDateTime.minute).toBe(42); + expect(plainDateTime.second).toBe(1); + expect(plainDateTime.millisecond).toBe(0); + expect(plainDateTime.microsecond).toBe(0); + expect(plainDateTime.nanosecond).toBe(0); + }); +}); + +describe("errors", () => { + test("missing fields", () => { + expect(() => { + Temporal.PlainDateTime.from({}); + }).toThrowWithMessage(TypeError, "Required property year is missing or undefined"); + expect(() => { + Temporal.PlainDateTime.from({ year: 0 }); + }).toThrowWithMessage(TypeError, "Required property month is missing or undefined"); + expect(() => { + Temporal.PlainDateTime.from({ year: 0, month: 1 }); + }).toThrowWithMessage(TypeError, "Required property day is missing or undefined"); + }); + + test("with 'reject' overflow option", () => { + const values = [ + [{ year: 1234567, month: 1, day: 1 }, "Invalid plain date"], + [{ year: 0, month: 13, day: 1 }, "Invalid plain date"], + [{ year: 0, month: 1, day: 32 }, "Invalid plain date"], + [{ year: 0, month: 1, day: 1, hour: 24 }, "Invalid plain time"], + [{ year: 0, month: 1, day: 1, hour: 0, minute: 60 }, "Invalid plain time"], + [{ year: 0, month: 1, day: 1, hour: 0, minute: 0, second: 60 }, "Invalid plain time"], + [ + { year: 0, month: 1, day: 1, hour: 0, minute: 0, second: 0, millisecond: 1000 }, + "Invalid plain time", + ], + [ + { + year: 0, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0, + millisecond: 0, + microsecond: 1000, + }, + "Invalid plain time", + ], + [ + { + year: 0, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0, + millisecond: 0, + microsecond: 0, + nanosecond: 1000, + }, + "Invalid plain time", + ], + ]; + for (const [object, error] of values) { + expect(() => { + Temporal.PlainDateTime.from(object, { overflow: "reject" }); + }).toThrowWithMessage(RangeError, error); + } + }); +});