LibJS: Implement Temporal.*.prototype.toZonedDateTimeISO and friends

This commit is contained in:
Timothy Flynn 2024-11-25 09:49:25 -05:00 committed by Andreas Kling
parent 18f95434bc
commit c0150acc5e
Notes: github-actions[bot] 2024-11-26 10:04:02 +00:00
12 changed files with 318 additions and 0 deletions

View file

@ -10,6 +10,7 @@
#include <LibJS/Runtime/Temporal/Duration.h>
#include <LibJS/Runtime/Temporal/InstantPrototype.h>
#include <LibJS/Runtime/Temporal/TimeZone.h>
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
namespace JS::Temporal {
@ -44,6 +45,7 @@ void InstantPrototype::initialize(Realm& realm)
define_native_function(realm, vm.names.toLocaleString, to_locale_string, 0, attr);
define_native_function(realm, vm.names.toJSON, to_json, 0, attr);
define_native_function(realm, vm.names.valueOf, value_of, 0, attr);
define_native_function(realm, vm.names.toZonedDateTimeISO, to_zoned_date_time_iso, 1, attr);
}
// 8.3.3 get Temporal.Instant.prototype.epochMilliseconds, https://tc39.es/proposal-temporal/#sec-get-temporal.instant.prototype.epochmilliseconds
@ -321,4 +323,18 @@ JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::value_of)
return vm.throw_completion<TypeError>(ErrorType::Convert, "Temporal.Instant", "a primitive value");
}
// 8.3.15 Temporal.Instant.prototype.toZonedDateTimeISO ( timeZone ), https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tozoneddatetimeiso
JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::to_zoned_date_time_iso)
{
// 1. Let instant be the this value.
// 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
auto instant = TRY(typed_this_object(vm));
// 3. Set timeZone to ? ToTemporalTimeZoneIdentifier(timeZone).
auto time_zone = TRY(to_temporal_time_zone_identifier(vm, vm.argument(0)));
// 4. Return ! CreateTemporalZonedDateTime(instant.[[EpochNanoseconds]], timeZone, "iso8601").
return MUST(create_temporal_zoned_date_time(vm, instant->epoch_nanoseconds(), move(time_zone), "iso8601"_string));
}
}

View file

@ -35,6 +35,7 @@ private:
JS_DECLARE_NATIVE_FUNCTION(to_locale_string);
JS_DECLARE_NATIVE_FUNCTION(to_json);
JS_DECLARE_NATIVE_FUNCTION(value_of);
JS_DECLARE_NATIVE_FUNCTION(to_zoned_date_time_iso);
};
}

View file

@ -12,6 +12,7 @@
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
#include <LibJS/Runtime/Temporal/PlainTime.h>
#include <LibJS/Runtime/Temporal/TimeZone.h>
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
namespace JS::Temporal {
@ -36,6 +37,7 @@ void Now::initialize(Realm& realm)
define_native_function(realm, vm.names.timeZoneId, time_zone_id, 0, attr);
define_native_function(realm, vm.names.instant, instant, 0, attr);
define_native_function(realm, vm.names.plainDateTimeISO, plain_date_time_iso, 0, attr);
define_native_function(realm, vm.names.zonedDateTimeISO, zoned_date_time_iso, 0, attr);
define_native_function(realm, vm.names.plainDateISO, plain_date_iso, 0, attr);
define_native_function(realm, vm.names.plainTimeISO, plain_time_iso, 0, attr);
}
@ -69,6 +71,30 @@ JS_DEFINE_NATIVE_FUNCTION(Now::plain_date_time_iso)
return MUST(create_temporal_date_time(vm, iso_date_time, "iso8601"_string));
}
// 2.2.4 Temporal.Now.zonedDateTimeISO ( [ temporalTimeZoneLike ] ), https://tc39.es/proposal-temporal/#sec-temporal.now.zoneddatetimeiso
JS_DEFINE_NATIVE_FUNCTION(Now::zoned_date_time_iso)
{
auto temporal_time_zone_like = vm.argument(0);
String time_zone;
// 1. If temporalTimeZoneLike is undefined, then
if (temporal_time_zone_like.is_undefined()) {
// a. Let timeZone be SystemTimeZoneIdentifier().
time_zone = system_time_zone_identifier();
}
// 2. Else,
else {
// a. Let timeZone be ? ToTemporalTimeZoneIdentifier(temporalTimeZoneLike).
time_zone = TRY(to_temporal_time_zone_identifier(vm, temporal_time_zone_like));
}
// 3. Let ns be SystemUTCEpochNanoseconds().
auto nanoseconds = system_utc_epoch_nanoseconds(vm);
// 4. Return ! CreateTemporalZonedDateTime(ns, timeZone, "iso8601").
return MUST(create_temporal_zoned_date_time(vm, BigInt::create(vm, move(nanoseconds)), move(time_zone), "iso8601"_string));
}
// 2.2.5 Temporal.Now.plainDateISO ( [ temporalTimeZoneLike ] ), https://tc39.es/proposal-temporal/#sec-temporal.now.plaindateiso
JS_DEFINE_NATIVE_FUNCTION(Now::plain_date_iso)
{

View file

@ -27,6 +27,7 @@ private:
JS_DECLARE_NATIVE_FUNCTION(time_zone_id);
JS_DECLARE_NATIVE_FUNCTION(instant);
JS_DECLARE_NATIVE_FUNCTION(plain_date_time_iso);
JS_DECLARE_NATIVE_FUNCTION(zoned_date_time_iso);
JS_DECLARE_NATIVE_FUNCTION(plain_date_iso);
JS_DECLARE_NATIVE_FUNCTION(plain_time_iso);
};

View file

@ -14,6 +14,8 @@
#include <LibJS/Runtime/Temporal/PlainMonthDay.h>
#include <LibJS/Runtime/Temporal/PlainTime.h>
#include <LibJS/Runtime/Temporal/PlainYearMonth.h>
#include <LibJS/Runtime/Temporal/TimeZone.h>
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
namespace JS::Temporal {
@ -62,6 +64,7 @@ void PlainDatePrototype::initialize(Realm& realm)
define_native_function(realm, vm.names.since, since, 1, attr);
define_native_function(realm, vm.names.equals, equals, 1, attr);
define_native_function(realm, vm.names.toPlainDateTime, to_plain_date_time, 0, attr);
define_native_function(realm, vm.names.toZonedDateTime, to_zoned_date_time, 1, attr);
define_native_function(realm, vm.names.toString, to_string, 0, attr);
define_native_function(realm, vm.names.toLocaleString, to_locale_string, 0, attr);
define_native_function(realm, vm.names.toJSON, to_json, 0, attr);
@ -384,6 +387,76 @@ JS_DEFINE_NATIVE_FUNCTION(PlainDatePrototype::to_plain_date_time)
return TRY(create_temporal_date_time(vm, iso_date_time, temporal_date->calendar()));
}
// 3.3.29 Temporal.PlainDate.prototype.toZonedDateTime ( item ), https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.tozoneddatetime
JS_DEFINE_NATIVE_FUNCTION(PlainDatePrototype::to_zoned_date_time)
{
auto item = vm.argument(0);
// 1. Let temporalDate be the this value.
// 2. Perform ? RequireInternalSlot(temporalDate, [[InitializedTemporalDate]]).
auto temporal_date = TRY(typed_this_object(vm));
String time_zone;
Value temporal_time;
// 3. If item is an Object, then
if (item.is_object()) {
// a. Let timeZoneLike be ? Get(item, "timeZone").
auto time_zone_like = TRY(item.as_object().get(vm.names.timeZone));
// b. If timeZoneLike is undefined, then
if (time_zone_like.is_undefined()) {
// i. Let timeZone be ? ToTemporalTimeZoneIdentifier(item).
time_zone = TRY(to_temporal_time_zone_identifier(vm, item));
// ii. Let temporalTime be undefined.
temporal_time = js_undefined();
}
// c. Else,
else {
// i. Let timeZone be ? ToTemporalTimeZoneIdentifier(timeZoneLike).
time_zone = TRY(to_temporal_time_zone_identifier(vm, time_zone_like));
// ii. Let temporalTime be ? Get(item, "plainTime").
temporal_time = TRY(item.as_object().get(vm.names.plainTime));
}
}
// 4. Else,
else {
// a. Let timeZone be ? ToTemporalTimeZoneIdentifier(item).
time_zone = TRY(to_temporal_time_zone_identifier(vm, item));
// b. Let temporalTime be undefined.
temporal_time = js_undefined();
}
Crypto::SignedBigInteger epoch_nanoseconds;
// 5. If temporalTime is undefined, then
if (temporal_time.is_undefined()) {
// a. Let epochNs be ? GetStartOfDay(timeZone, temporalDate.[[ISODate]]).
epoch_nanoseconds = TRY(get_start_of_day(vm, time_zone, temporal_date->iso_date()));
}
// 6. Else,
else {
// a. Set temporalTime to ? ToTemporalTime(temporalTime).
auto plain_temporal_time = TRY(to_temporal_time(vm, temporal_time));
// b. Let isoDateTime be CombineISODateAndTimeRecord(temporalDate.[[ISODate]], temporalTime.[[Time]]).
auto iso_date_time = combine_iso_date_and_time_record(temporal_date->iso_date(), plain_temporal_time->time());
// c. If ISODateTimeWithinLimits(isoDateTime) is false, throw a RangeError exception.
if (!iso_date_time_within_limits(iso_date_time))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidISODateTime);
// d. Let epochNs be ? GetEpochNanosecondsFor(timeZone, isoDateTime, COMPATIBLE).
epoch_nanoseconds = TRY(get_epoch_nanoseconds_for(vm, time_zone, iso_date_time, Disambiguation::Compatible));
}
// 7. Return ! CreateTemporalZonedDateTime(epochNs, timeZone, temporalDate.[[Calendar]]).
return MUST(create_temporal_zoned_date_time(vm, BigInt::create(vm, move(epoch_nanoseconds)), move(time_zone), temporal_date->calendar()));
}
// 3.3.30 Temporal.PlainDate.prototype.toString ( [ options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.tostring
JS_DEFINE_NATIVE_FUNCTION(PlainDatePrototype::to_string)
{

View file

@ -49,6 +49,7 @@ private:
JS_DECLARE_NATIVE_FUNCTION(since);
JS_DECLARE_NATIVE_FUNCTION(equals);
JS_DECLARE_NATIVE_FUNCTION(to_plain_date_time);
JS_DECLARE_NATIVE_FUNCTION(to_zoned_date_time);
JS_DECLARE_NATIVE_FUNCTION(to_string);
JS_DECLARE_NATIVE_FUNCTION(to_locale_string);
JS_DECLARE_NATIVE_FUNCTION(to_json);

View file

@ -12,6 +12,8 @@
#include <LibJS/Runtime/Temporal/PlainDate.h>
#include <LibJS/Runtime/Temporal/PlainDateTimePrototype.h>
#include <LibJS/Runtime/Temporal/PlainTime.h>
#include <LibJS/Runtime/Temporal/TimeZone.h>
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
namespace JS::Temporal {
@ -69,6 +71,7 @@ void PlainDateTimePrototype::initialize(Realm& realm)
define_native_function(realm, vm.names.toLocaleString, to_locale_string, 0, attr);
define_native_function(realm, vm.names.toJSON, to_json, 0, attr);
define_native_function(realm, vm.names.valueOf, value_of, 0, attr);
define_native_function(realm, vm.names.toZonedDateTime, to_zoned_date_time, 1, attr);
define_native_function(realm, vm.names.toPlainDate, to_plain_date, 0, attr);
define_native_function(realm, vm.names.toPlainTime, to_plain_time, 0, attr);
}
@ -558,6 +561,32 @@ JS_DEFINE_NATIVE_FUNCTION(PlainDateTimePrototype::value_of)
return vm.throw_completion<TypeError>(ErrorType::Convert, "Temporal.PlainDateTime", "a primitive value");
}
// 5.3.38 Temporal.PlainDateTime.prototype.toZonedDateTime ( temporalTimeZoneLike [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.tozoneddatetime
JS_DEFINE_NATIVE_FUNCTION(PlainDateTimePrototype::to_zoned_date_time)
{
auto temporal_time_zone_like = vm.argument(0);
auto options = vm.argument(1);
// 1. Let dateTime be the this value.
// 2. Perform ? RequireInternalSlot(dateTime, [[InitializedTemporalDateTime]]).
auto date_time = TRY(typed_this_object(vm));
// 3. Let timeZone be ? ToTemporalTimeZoneIdentifier(temporalTimeZoneLike).
auto time_zone = TRY(to_temporal_time_zone_identifier(vm, temporal_time_zone_like));
// 4. Let resolvedOptions be ? GetOptionsObject(options).
auto resolved_options = TRY(get_options_object(vm, options));
// 5. Let disambiguation be ? GetTemporalDisambiguationOption(resolvedOptions).
auto disambiguation = TRY(get_temporal_disambiguation_option(vm, resolved_options));
// 6. Let epochNs be ? GetEpochNanosecondsFor(timeZone, dateTime.[[ISODateTime]], disambiguation).
auto epoch_nanoseconds = TRY(get_epoch_nanoseconds_for(vm, time_zone, date_time->iso_date_time(), disambiguation));
// 7. Return ! CreateTemporalZonedDateTime(epochNs, timeZone, dateTime.[[Calendar]]).
return MUST(create_temporal_zoned_date_time(vm, BigInt::create(vm, move(epoch_nanoseconds)), move(time_zone), date_time->calendar()));
}
// 5.3.39 Temporal.PlainDateTime.prototype.toPlainDate ( ), https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.toplaindate
JS_DEFINE_NATIVE_FUNCTION(PlainDateTimePrototype::to_plain_date)
{

View file

@ -58,6 +58,7 @@ private:
JS_DECLARE_NATIVE_FUNCTION(to_locale_string);
JS_DECLARE_NATIVE_FUNCTION(to_json);
JS_DECLARE_NATIVE_FUNCTION(value_of);
JS_DECLARE_NATIVE_FUNCTION(to_zoned_date_time);
JS_DECLARE_NATIVE_FUNCTION(to_plain_date);
JS_DECLARE_NATIVE_FUNCTION(to_plain_time);
};

View file

@ -0,0 +1,36 @@
describe("correct behavior", () => {
test("length is 1", () => {
expect(Temporal.Instant.prototype.toZonedDateTimeISO).toHaveLength(1);
});
test("basic functionality", () => {
const instant = new Temporal.Instant(1625614921123456789n);
const zonedDateTime = instant.toZonedDateTimeISO("UTC");
expect(zonedDateTime.year).toBe(2021);
expect(zonedDateTime.month).toBe(7);
expect(zonedDateTime.day).toBe(6);
expect(zonedDateTime.hour).toBe(23);
expect(zonedDateTime.minute).toBe(42);
expect(zonedDateTime.second).toBe(1);
expect(zonedDateTime.millisecond).toBe(123);
expect(zonedDateTime.microsecond).toBe(456);
expect(zonedDateTime.nanosecond).toBe(789);
expect(zonedDateTime.calendarId).toBe("iso8601");
expect(zonedDateTime.timeZoneId).toBe("UTC");
});
test("custom time zone object", () => {
const timeZone = "UTC";
const instant = new Temporal.Instant(1625614921123456789n);
const zonedDateTime = instant.toZonedDateTimeISO(timeZone);
expect(zonedDateTime.timeZoneId).toBe(timeZone);
});
});
describe("errors", () => {
test("this value must be a Temporal.Instant object", () => {
expect(() => {
Temporal.Instant.prototype.toZonedDateTimeISO.call("foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.Instant");
});
});

View file

@ -0,0 +1,19 @@
describe("correct behavior", () => {
test("length is 0", () => {
expect(Temporal.Now.zonedDateTimeISO).toHaveLength(0);
});
test("basic functionality", () => {
const zonedDateTime = Temporal.Now.zonedDateTimeISO();
expect(zonedDateTime).toBeInstanceOf(Temporal.ZonedDateTime);
expect(zonedDateTime.calendarId).toBe("iso8601");
});
test("with time zone", () => {
const timeZone = "UTC";
const zonedDateTime = Temporal.Now.zonedDateTimeISO(timeZone);
expect(zonedDateTime).toBeInstanceOf(Temporal.ZonedDateTime);
expect(zonedDateTime.calendarId).toBe("iso8601");
expect(zonedDateTime.timeZoneId).toBe(timeZone);
});
});

View file

@ -0,0 +1,85 @@
describe("correct behavior", () => {
test("length is 1", () => {
expect(Temporal.PlainDate.prototype.toZonedDateTime).toHaveLength(1);
});
test("basic functionality - time zone", () => {
// 3.b. in the spec
const plainDate = new Temporal.PlainDate(2021, 7, 6);
const timeZone = "UTC";
const zonedDateTime = plainDate.toZonedDateTime(timeZone);
expect(zonedDateTime.year).toBe(2021);
expect(zonedDateTime.month).toBe(7);
expect(zonedDateTime.day).toBe(6);
expect(zonedDateTime.hour).toBe(0);
expect(zonedDateTime.minute).toBe(0);
expect(zonedDateTime.second).toBe(0);
expect(zonedDateTime.millisecond).toBe(0);
expect(zonedDateTime.microsecond).toBe(0);
expect(zonedDateTime.nanosecond).toBe(0);
expect(zonedDateTime.calendarId).toBe(plainDate.calendarId);
expect(zonedDateTime.timeZoneId).toBe(timeZone);
});
test("basic functionality - time zone like object", () => {
// 3.c. in the spec
const plainDate = new Temporal.PlainDate(2021, 7, 6);
const timeZone = "UTC";
const zonedDateTime = plainDate.toZonedDateTime(timeZone);
expect(zonedDateTime.year).toBe(2021);
expect(zonedDateTime.month).toBe(7);
expect(zonedDateTime.day).toBe(6);
expect(zonedDateTime.hour).toBe(0);
expect(zonedDateTime.minute).toBe(0);
expect(zonedDateTime.second).toBe(0);
expect(zonedDateTime.millisecond).toBe(0);
expect(zonedDateTime.microsecond).toBe(0);
expect(zonedDateTime.nanosecond).toBe(0);
expect(zonedDateTime.calendarId).toBe(plainDate.calendarId);
expect(zonedDateTime.timeZoneId).toBe(timeZone);
});
test("basic functionality - time zone like object and plain time", () => {
// 3.c. in the spec
const plainDate = new Temporal.PlainDate(2021, 7, 6);
const plainTime = new Temporal.PlainTime(18, 14, 47, 123, 456, 789);
const timeZone = "UTC";
const zonedDateTime = plainDate.toZonedDateTime({ timeZone, plainTime });
expect(zonedDateTime.year).toBe(2021);
expect(zonedDateTime.month).toBe(7);
expect(zonedDateTime.day).toBe(6);
expect(zonedDateTime.hour).toBe(18);
expect(zonedDateTime.minute).toBe(14);
expect(zonedDateTime.second).toBe(47);
expect(zonedDateTime.millisecond).toBe(123);
expect(zonedDateTime.microsecond).toBe(456);
expect(zonedDateTime.nanosecond).toBe(789);
expect(zonedDateTime.calendarId).toBe(plainDate.calendarId);
expect(zonedDateTime.timeZoneId).toBe(timeZone);
});
test("basic functionality - time zone identifier", () => {
// 4. in the spec
const plainDate = new Temporal.PlainDate(2021, 7, 6);
const zonedDateTime = plainDate.toZonedDateTime("UTC");
expect(zonedDateTime.year).toBe(2021);
expect(zonedDateTime.month).toBe(7);
expect(zonedDateTime.day).toBe(6);
expect(zonedDateTime.hour).toBe(0);
expect(zonedDateTime.minute).toBe(0);
expect(zonedDateTime.second).toBe(0);
expect(zonedDateTime.millisecond).toBe(0);
expect(zonedDateTime.microsecond).toBe(0);
expect(zonedDateTime.nanosecond).toBe(0);
expect(zonedDateTime.calendarId).toBe(plainDate.calendarId);
expect(zonedDateTime.timeZoneId).toBe("UTC");
});
});
describe("errors", () => {
test("this value must be a Temporal.PlainDate object", () => {
expect(() => {
Temporal.PlainDate.prototype.toZonedDateTime.call("foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainDate");
});
});

View file

@ -0,0 +1,30 @@
describe("correct behavior", () => {
test("length is 1", () => {
expect(Temporal.PlainDateTime.prototype.toZonedDateTime).toHaveLength(1);
});
test("basic functionality", () => {
const plainDateTime = new Temporal.PlainDateTime(2021, 7, 6, 18, 14, 47, 123, 456, 789);
const timeZone = "UTC";
const zonedDateTime = plainDateTime.toZonedDateTime(timeZone);
expect(zonedDateTime.year).toBe(2021);
expect(zonedDateTime.month).toBe(7);
expect(zonedDateTime.day).toBe(6);
expect(zonedDateTime.hour).toBe(18);
expect(zonedDateTime.minute).toBe(14);
expect(zonedDateTime.second).toBe(47);
expect(zonedDateTime.millisecond).toBe(123);
expect(zonedDateTime.microsecond).toBe(456);
expect(zonedDateTime.nanosecond).toBe(789);
expect(zonedDateTime.calendarId).toBe(plainDateTime.calendarId);
expect(zonedDateTime.timeZoneId).toBe(timeZone);
});
});
describe("errors", () => {
test("this value must be a Temporal.PlainDateTime object", () => {
expect(() => {
Temporal.PlainDateTime.prototype.toZonedDateTime.call("foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainDateTime");
});
});