diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimeConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimeConstructor.cpp index 4cee1b0ceb..ffc5320069 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimeConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimeConstructor.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include @@ -28,6 +29,9 @@ void ZonedDateTimeConstructor::initialize(GlobalObject& global_object) // 6.2.1 Temporal.ZonedDateTime.prototype, https://tc39.es/proposal-temporal/#sec-temporal-zoneddatetime-prototype define_direct_property(vm.names.prototype, global_object.temporal_zoned_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(2), Attribute::Configurable); } @@ -64,4 +68,33 @@ ThrowCompletionOr ZonedDateTimeConstructor::construct(FunctionObject& n return TRY(create_temporal_zoned_date_time(global_object, *epoch_nanoseconds, *time_zone, *calendar, &new_target)); } +// 6.2.2 Temporal.ZonedDateTime.from ( item [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.from +JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimeConstructor::from) +{ + // 1. Set options to ? GetOptionsObject(options). + auto* options = TRY(get_options_object(global_object, vm.argument(1))); + + auto item = vm.argument(0); + + // 2. If Type(item) is Object and item has an [[InitializedTemporalZonedDateTime]] internal slot, then + if (item.is_object() && is(item.as_object())) { + auto& item_object = static_cast(item.as_object()); + + // a. Perform ? ToTemporalOverflow(options). + (void)TRY(to_temporal_overflow(global_object, *options)); + + // b. Perform ? ToTemporalDisambiguation(options). + (void)TRY(to_temporal_disambiguation(global_object, *options)); + + // c. Perform ? ToTemporalOffset(options, "reject"). + (void)TRY(to_temporal_offset(global_object, *options, "reject")); + + // d. Return ! CreateTemporalZonedDateTime(item.[[Nanoseconds]], item.[[TimeZone]], item.[[Calendar]]). + return MUST(create_temporal_zoned_date_time(global_object, item_object.nanoseconds(), item_object.time_zone(), item_object.calendar())); + } + + // 3. Return ? ToTemporalZonedDateTime(item, options). + return TRY(to_temporal_zoned_date_time(global_object, item, options)); +} + } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimeConstructor.h b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimeConstructor.h index 1db77736b2..b18744d597 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimeConstructor.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimeConstructor.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/Tests/builtins/Temporal/ZonedDateTime/ZonedDateTime.from.js b/Userland/Libraries/LibJS/Tests/builtins/Temporal/ZonedDateTime/ZonedDateTime.from.js new file mode 100644 index 0000000000..7e9f9f9538 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Temporal/ZonedDateTime/ZonedDateTime.from.js @@ -0,0 +1,151 @@ +describe("correct behavior", () => { + test("length is 1", () => { + expect(Temporal.ZonedDateTime.from).toHaveLength(1); + }); + + test("ZonedDateTime instance argument", () => { + const timeZone = new Temporal.TimeZone("UTC"); + const calendar = new Temporal.Calendar("iso8601"); + const zonedDateTime = new Temporal.ZonedDateTime(1627318123456789000n, timeZone, calendar); + const createdZoneDateTime = Temporal.ZonedDateTime.from(zonedDateTime); + + expect(createdZoneDateTime).toBeInstanceOf(Temporal.ZonedDateTime); + expect(createdZoneDateTime).not.toBe(zonedDateTime); + expect(createdZoneDateTime.timeZone).toBe(timeZone); + expect(createdZoneDateTime.calendar).toBe(calendar); + expect(createdZoneDateTime.epochNanoseconds).toBe(1627318123456789000n); + }); + + test("PlainDate instance argument", () => { + const timeZone = new Temporal.TimeZone("UTC"); + const calendar = new Temporal.Calendar("iso8601"); + const plainDate = new Temporal.PlainDate(2021, 11, 7, calendar); + plainDate.timeZone = timeZone; + const createdZoneDateTime = Temporal.ZonedDateTime.from(plainDate); + + expect(createdZoneDateTime).toBeInstanceOf(Temporal.ZonedDateTime); + expect(createdZoneDateTime.timeZone).toBe(timeZone); + expect(createdZoneDateTime.calendar).toBe(calendar); + expect(createdZoneDateTime.year).toBe(2021); + expect(createdZoneDateTime.month).toBe(11); + expect(createdZoneDateTime.day).toBe(7); + }); + + test("PlainDateTime instance argument", () => { + const timeZone = new Temporal.TimeZone("UTC"); + const calendar = new Temporal.Calendar("iso8601"); + const plainDateTime = new Temporal.PlainDateTime( + 2021, + 11, + 7, + 0, + 20, + 5, + 100, + 200, + 300, + calendar + ); + plainDateTime.timeZone = timeZone; + const createdZoneDateTime = Temporal.ZonedDateTime.from(plainDateTime); + + expect(createdZoneDateTime).toBeInstanceOf(Temporal.ZonedDateTime); + expect(createdZoneDateTime.timeZone).toBe(timeZone); + expect(createdZoneDateTime.calendar).toBe(calendar); + expect(createdZoneDateTime.year).toBe(2021); + expect(createdZoneDateTime.month).toBe(11); + expect(createdZoneDateTime.day).toBe(7); + expect(createdZoneDateTime.hour).toBe(0); + expect(createdZoneDateTime.minute).toBe(20); + expect(createdZoneDateTime.second).toBe(5); + expect(createdZoneDateTime.millisecond).toBe(100); + expect(createdZoneDateTime.microsecond).toBe(200); + expect(createdZoneDateTime.nanosecond).toBe(300); + }); + + test("ZonedDateTime-like argument", () => { + const timeZone = new Temporal.TimeZone("UTC"); + const calendar = new Temporal.Calendar("iso8601"); + const zdtLike = { + timeZone, + calendar, + year: 2021, + month: 11, + day: 7, + hour: 0, + minute: 20, + second: 5, + millisecond: 100, + microsecond: 200, + nanosecond: 300, + }; + const createdZoneDateTime = Temporal.ZonedDateTime.from(zdtLike); + + expect(createdZoneDateTime).toBeInstanceOf(Temporal.ZonedDateTime); + expect(createdZoneDateTime.timeZone).toBe(timeZone); + expect(createdZoneDateTime.calendar).toBe(calendar); + expect(createdZoneDateTime.year).toBe(2021); + expect(createdZoneDateTime.month).toBe(11); + expect(createdZoneDateTime.day).toBe(7); + expect(createdZoneDateTime.hour).toBe(0); + expect(createdZoneDateTime.minute).toBe(20); + expect(createdZoneDateTime.second).toBe(5); + expect(createdZoneDateTime.millisecond).toBe(100); + expect(createdZoneDateTime.microsecond).toBe(200); + expect(createdZoneDateTime.nanosecond).toBe(300); + }); + + // FIXME: Enable when parse_iso_date_time is implemented. + test.skip("from string", () => { + const zonedDateTime = Temporal.ZonedDateTime.from( + "2021-11-07T00:20:05.100200300+00:00[UTC][u-ca=iso8601]" + ); + + expect(zonedDateTime).toBeInstanceOf(Temporal.ZonedDateTime); + expect(zonedDateTime.timeZone).toBeInstanceOf(Temporal.TimeZone); + expect(zonedDateTime.timeZone.id).toBe("UTC"); + expect(zonedDateTime.calendar).toBeInstanceOf(Temporal.Calendar); + expect(zonedDateTime.calendar.id).toBe("iso8601"); + expect(createdZoneDateTime.year).toBe(2021); + expect(createdZoneDateTime.month).toBe(11); + expect(createdZoneDateTime.day).toBe(7); + expect(createdZoneDateTime.hour).toBe(0); + expect(createdZoneDateTime.minute).toBe(20); + expect(createdZoneDateTime.second).toBe(5); + expect(createdZoneDateTime.millisecond).toBe(100); + expect(createdZoneDateTime.microsecond).toBe(200); + expect(createdZoneDateTime.nanosecond).toBe(300); + expect(createdZoneDateTime.offset).toBe("+00:00"); + expect(createdZoneDateTime.offsetNanoseconds).toBe(0); + }); +}); + +describe("errors", () => { + test("requires timeZone property", () => { + expect(() => { + Temporal.ZonedDateTime.from({}); + }).toThrowWithMessage(TypeError, "Required property timeZone is missing or undefined"); + }); + + test("requires year property", () => { + expect(() => { + Temporal.ZonedDateTime.from({ timeZone: new Temporal.TimeZone("UTC") }); + }).toThrowWithMessage(TypeError, "Required property year is missing or undefined"); + }); + + test("requires month property", () => { + expect(() => { + Temporal.ZonedDateTime.from({ timeZone: new Temporal.TimeZone("UTC"), year: 2021 }); + }).toThrowWithMessage(TypeError, "Required property month is missing or undefined"); + }); + + test("requires day property", () => { + expect(() => { + Temporal.ZonedDateTime.from({ + timeZone: new Temporal.TimeZone("UTC"), + year: 2021, + month: 11, + }); + }).toThrowWithMessage(TypeError, "Required property day is missing or undefined"); + }); +});