Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 208 additions & 0 deletions JSTests/stress/temporal-zoneddatetime.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
//@ requireOptions("--useTemporal=1")

function shouldBe(actual, expected) {
if (actual !== expected)
throw new Error(`expected ${expected} but got ${actual}`);
}

function shouldThrow(op, errorConstructor) {
try {
op();
} catch (e) {
if (!(e instanceof errorConstructor))
throw new Error(`threw ${e}, expected ${errorConstructor.name}`);
return;
}
throw new Error(`expected to throw ${errorConstructor.name}`);
}

// Namespace.
shouldBe(typeof Temporal.ZonedDateTime, "function");
shouldBe(Temporal.ZonedDateTime.length, 2);
shouldBe(Temporal.ZonedDateTime.name, "ZonedDateTime");
shouldBe(Temporal.ZonedDateTime.prototype[Symbol.toStringTag], "Temporal.ZonedDateTime");

// Constructor must be called with new.
shouldThrow(() => Temporal.ZonedDateTime(0n, "UTC"), TypeError);

// Basic construction at Unix epoch in UTC.
{
let z = new Temporal.ZonedDateTime(0n, "UTC");
shouldBe(z.epochNanoseconds, 0n);
shouldBe(z.epochMilliseconds, 0);
shouldBe(z.timeZoneId, "UTC");
shouldBe(z.calendarId, "iso8601");
shouldBe(z.offset, "+00:00");
shouldBe(z.offsetNanoseconds, 0);
shouldBe(z.year, 1970);
shouldBe(z.month, 1);
shouldBe(z.monthCode, "M01");
shouldBe(z.day, 1);
shouldBe(z.hour, 0);
shouldBe(z.minute, 0);
shouldBe(z.second, 0);
shouldBe(z.millisecond, 0);
shouldBe(z.microsecond, 0);
shouldBe(z.nanosecond, 0);
shouldBe(z.dayOfWeek, 4); // Thursday
shouldBe(z.dayOfYear, 1);
shouldBe(z.daysInWeek, 7);
shouldBe(z.daysInMonth, 31);
shouldBe(z.daysInYear, 365);
shouldBe(z.monthsInYear, 12);
shouldBe(z.inLeapYear, false);
shouldBe(z.toString(), "1970-01-01T00:00:00+00:00[UTC]");
shouldBe(z.toJSON(), "1970-01-01T00:00:00+00:00[UTC]");
}

// valueOf must throw.
shouldThrow(() => +new Temporal.ZonedDateTime(0n, "UTC"), TypeError);

// Offset time zone.
{
let z = new Temporal.ZonedDateTime(0n, "+05:30");
shouldBe(z.timeZoneId, "+05:30");
shouldBe(z.offset, "+05:30");
shouldBe(z.offsetNanoseconds, 5 * 3600e9 + 30 * 60e9);
shouldBe(z.hour, 5);
shouldBe(z.minute, 30);
shouldBe(z.toString(), "1970-01-01T05:30:00+05:30[+05:30]");
}

// Named time zone (IANA).
{
let z = new Temporal.ZonedDateTime(1700000000000000000n, "America/New_York");
shouldBe(z.timeZoneId, "America/New_York");
shouldBe(z.offset, "-05:00");
shouldBe(z.year, 2023);
shouldBe(z.month, 11);
shouldBe(z.day, 14);
shouldBe(z.hour, 17);
shouldBe(z.minute, 13);
shouldBe(z.second, 20);
}

// Conversions.
{
let z = new Temporal.ZonedDateTime(0n, "UTC");
shouldBe(z.toInstant().epochNanoseconds, 0n);
shouldBe(z.toPlainDate().toString(), "1970-01-01");
shouldBe(z.toPlainTime().toString(), "00:00:00");
shouldBe(z.toPlainDateTime().toString(), "1970-01-01T00:00:00");
}

// withTimeZone preserves the instant.
{
let z = new Temporal.ZonedDateTime(0n, "UTC").withTimeZone("+01:00");
shouldBe(z.epochNanoseconds, 0n);
shouldBe(z.timeZoneId, "+01:00");
shouldBe(z.hour, 1);
}

// compare.
shouldBe(Temporal.ZonedDateTime.compare(
new Temporal.ZonedDateTime(0n, "UTC"),
new Temporal.ZonedDateTime(1n, "UTC")), -1);
shouldBe(Temporal.ZonedDateTime.compare(
new Temporal.ZonedDateTime(1n, "UTC"),
new Temporal.ZonedDateTime(0n, "UTC")), 1);
shouldBe(Temporal.ZonedDateTime.compare(
new Temporal.ZonedDateTime(0n, "UTC"),
new Temporal.ZonedDateTime(0n, "+05:00")), 0);

// equals includes time zone identity.
shouldBe(new Temporal.ZonedDateTime(0n, "UTC").equals(new Temporal.ZonedDateTime(0n, "UTC")), true);
shouldBe(new Temporal.ZonedDateTime(0n, "UTC").equals(new Temporal.ZonedDateTime(0n, "+01:00")), false);

// Invalid inputs.
shouldThrow(() => new Temporal.ZonedDateTime(0n, "Not/AZone"), RangeError);
shouldThrow(() => new Temporal.ZonedDateTime(0n, 42), TypeError);

// Temporal.Now additions.
shouldBe(typeof Temporal.Now.zonedDateTimeISO, "function");
shouldBe(typeof Temporal.Now.plainDateTimeISO, "function");
shouldBe(typeof Temporal.Now.plainDateISO, "function");
shouldBe(typeof Temporal.Now.plainTimeISO, "function");
shouldBe(Temporal.Now.zonedDateTimeISO("UTC") instanceof Temporal.ZonedDateTime, true);
shouldBe(Temporal.Now.plainDateISO("UTC") instanceof Temporal.PlainDate, true);
shouldBe(Temporal.Now.plainTimeISO("UTC") instanceof Temporal.PlainTime, true);
shouldBe(Temporal.Now.plainDateTimeISO("UTC") instanceof Temporal.PlainDateTime, true);

// Instant interop.
{
let z = new Temporal.ZonedDateTime(12345n, "UTC");
shouldBe(Temporal.Instant.from(z).epochNanoseconds, 12345n);
}

// --- from() with property bags + DST disambiguation --------------------

{
// Normal wall time.
let z = Temporal.ZonedDateTime.from({ year: 2024, month: 3, day: 15, hour: 12, timeZone: "America/New_York" });
shouldBe(z.toString(), "2024-03-15T12:00:00-04:00[America/New_York]");
}

{
// DST spring-forward gap: 2024-03-10 02:30 doesn't exist in NY.
// "compatible" (default) picks the later interpretation.
let z = Temporal.ZonedDateTime.from({ year: 2024, month: 3, day: 10, hour: 2, minute: 30, timeZone: "America/New_York" });
shouldBe(z.hour, 3);
shouldBe(z.offset, "-04:00");
// "reject" throws.
shouldThrow(() => Temporal.ZonedDateTime.from(
{ year: 2024, month: 3, day: 10, hour: 2, minute: 30, timeZone: "America/New_York" },
{ disambiguation: "reject" }), RangeError);
}

{
// DST fall-back overlap: 2024-11-03 01:30 occurs twice in NY.
// "compatible"/"earlier" -> -04:00, "later" -> -05:00.
let a = Temporal.ZonedDateTime.from({ year: 2024, month: 11, day: 3, hour: 1, minute: 30, timeZone: "America/New_York" });
shouldBe(a.offset, "-04:00");
let b = Temporal.ZonedDateTime.from(
{ year: 2024, month: 11, day: 3, hour: 1, minute: 30, timeZone: "America/New_York" },
{ disambiguation: "later" });
shouldBe(b.offset, "-05:00");
shouldThrow(() => Temporal.ZonedDateTime.from(
{ year: 2024, month: 11, day: 3, hour: 1, minute: 30, timeZone: "America/New_York" },
{ disambiguation: "reject" }), RangeError);
}

{
// offset option: "reject" when offset doesn't match the time zone.
shouldThrow(() => Temporal.ZonedDateTime.from(
{ year: 2024, month: 3, day: 15, hour: 12, offset: "+09:00", timeZone: "America/New_York" },
{ offset: "reject" }), RangeError);
// "use" takes the provided offset literally.
let z = Temporal.ZonedDateTime.from(
{ year: 2024, month: 3, day: 15, hour: 12, offset: "+00:00", timeZone: "America/New_York" },
{ offset: "use" });
shouldBe(z.epochNanoseconds, BigInt(Date.UTC(2024, 2, 15, 12)) * 1000000n);
}

// startOfDay and hoursInDay, including a DST day.
{
let z = Temporal.ZonedDateTime.from({ year: 2024, month: 3, day: 10, hour: 12, timeZone: "America/New_York" });
shouldBe(z.startOfDay().hour, 0);
shouldBe(z.hoursInDay, 23); // spring-forward
let w = Temporal.ZonedDateTime.from({ year: 2024, month: 11, day: 3, hour: 12, timeZone: "America/New_York" });
shouldBe(w.hoursInDay, 25); // fall-back
let n = new Temporal.ZonedDateTime(0n, "UTC");
shouldBe(n.hoursInDay, 24);
}

// Instant.prototype.toZonedDateTimeISO
{
let z = Temporal.Instant.from("2024-01-01T00:00:00Z").toZonedDateTimeISO("Asia/Tokyo");
shouldBe(z instanceof Temporal.ZonedDateTime, true);
shouldBe(z.toString(), "2024-01-01T09:00:00+09:00[Asia/Tokyo]");
}

// ToTemporalTimeZoneIdentifier extracts from date-time strings.
{
shouldBe(new Temporal.ZonedDateTime(0n, "UTC").withTimeZone("2021-08-19T17:30Z").timeZoneId, "UTC");
shouldBe(new Temporal.ZonedDateTime(0n, "UTC").withTimeZone("2021-08-19T17:30-07:00").timeZoneId, "-07:00");
shouldBe(new Temporal.ZonedDateTime(0n, "UTC").withTimeZone("2021-08-19T17:30[Asia/Tokyo]").timeZoneId, "Asia/Tokyo");
shouldThrow(() => new Temporal.ZonedDateTime(0n, "UTC").withTimeZone("2021-08-19T17:30"), RangeError);
shouldThrow(() => new Temporal.ZonedDateTime(0n, "UTC").withTimeZone("2021-08-19T17:30-07:00:01"), RangeError);
}
5 changes: 5 additions & 0 deletions Source/JavaScriptCore/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ set(JavaScriptCore_OBJECT_LUT_SOURCES
runtime/TemporalPlainYearMonthPrototype.cpp
runtime/TemporalTimeZoneConstructor.cpp
runtime/TemporalTimeZonePrototype.cpp
runtime/TemporalZonedDateTimeConstructor.cpp
runtime/TemporalZonedDateTimePrototype.cpp

wasm/js/JSWebAssembly.cpp
wasm/js/WebAssemblyArrayConstructor.cpp
Expand Down Expand Up @@ -1735,6 +1737,9 @@ set(JavaScriptCore_PRIVATE_FRAMEWORK_HEADERS
runtime/TemporalTimeZone.h
runtime/TemporalTimeZoneConstructor.h
runtime/TemporalTimeZonePrototype.h
runtime/TemporalZonedDateTime.h
runtime/TemporalZonedDateTimeConstructor.h
runtime/TemporalZonedDateTimePrototype.h
runtime/TestRunnerUtils.h
runtime/ThrowScope.h
runtime/ToNativeFromValue.h
Expand Down
2 changes: 2 additions & 0 deletions Source/JavaScriptCore/DerivedSources.make
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,8 @@ OBJECT_LUT_HEADERS = \
TemporalPlainYearMonthPrototype.lut.h \
TemporalTimeZoneConstructor.lut.h \
TemporalTimeZonePrototype.lut.h \
TemporalZonedDateTimeConstructor.lut.h \
TemporalZonedDateTimePrototype.lut.h \
WebAssemblyArrayConstructor.lut.h \
WebAssemblyArrayPrototype.lut.h \
WebAssemblyCompileErrorConstructor.lut.h \
Expand Down
3 changes: 3 additions & 0 deletions Source/JavaScriptCore/Sources.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1127,6 +1127,9 @@ runtime/TemporalPlainYearMonthPrototype.cpp
runtime/TemporalTimeZone.cpp
runtime/TemporalTimeZoneConstructor.cpp
runtime/TemporalTimeZonePrototype.cpp
runtime/TemporalZonedDateTime.cpp
runtime/TemporalZonedDateTimeConstructor.cpp
runtime/TemporalZonedDateTimePrototype.cpp
runtime/TestRunnerUtils.cpp
runtime/ThrowScope.cpp
runtime/TypeLocationCache.cpp
Expand Down
1 change: 1 addition & 0 deletions Source/JavaScriptCore/heap/Heap.h
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ class Heap;
v(temporalPlainDateTimeSpace, cellHeapCellType, TemporalPlainDateTime) \
v(temporalPlainTimeSpace, cellHeapCellType, TemporalPlainTime) \
v(temporalTimeZoneSpace, cellHeapCellType, TemporalTimeZone) \
v(temporalZonedDateTimeSpace, cellHeapCellType, TemporalZonedDateTime) \
v(uint8ArraySpace, cellHeapCellType, JSUint8Array) \
v(uint8ClampedArraySpace, cellHeapCellType, JSUint8ClampedArray) \
v(uint16ArraySpace, cellHeapCellType, JSUint16Array) \
Expand Down
1 change: 1 addition & 0 deletions Source/JavaScriptCore/heap/HeapSubspaceTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
#include "TemporalPlainDateTime.h"
#include "TemporalPlainTime.h"
#include "TemporalTimeZone.h"
#include "TemporalZonedDateTime.h"
#include "UnlinkedFunctionCodeBlock.h"
#include "UnlinkedModuleProgramCodeBlock.h"
#include "UnlinkedProgramCodeBlock.h"
Expand Down
10 changes: 10 additions & 0 deletions Source/JavaScriptCore/runtime/JSGlobalObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@
#include "TemporalPlainYearMonthPrototype.h"
#include "TemporalTimeZone.h"
#include "TemporalTimeZonePrototype.h"
#include "TemporalZonedDateTime.h"
#include "TemporalZonedDateTimePrototype.h"
#include "TopExceptionScope.h"
#include "VMTrapsInlines.h"
#include "WaiterListManager.h"
Expand Down Expand Up @@ -1860,6 +1862,13 @@ capitalName ## Constructor* lowerName ## Constructor = featureFlag ? capitalName
init.set(TemporalTimeZone::createStructure(init.vm, globalObject, timeZonePrototype));
});

m_zonedDateTimeStructure.initLater(
[] (const Initializer<Structure>& init) {
auto* globalObject = init.owner;
auto* zonedDateTimePrototype = TemporalZonedDateTimePrototype::create(init.vm, globalObject, TemporalZonedDateTimePrototype::createStructure(init.vm, globalObject, globalObject->objectPrototype()));
init.set(TemporalZonedDateTime::createStructure(init.vm, globalObject, zonedDateTimePrototype));
});

TemporalObject* temporal = TemporalObject::create(vm, TemporalObject::createStructure(vm, this));
putDirectWithoutTransition(vm, vm.propertyNames->Temporal, temporal, static_cast<unsigned>(PropertyAttribute::DontEnum));
}
Expand Down Expand Up @@ -3041,6 +3050,7 @@ void JSGlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
thisObject->m_plainTimeStructure.visit(visitor);
thisObject->m_plainYearMonthStructure.visit(visitor);
thisObject->m_timeZoneStructure.visit(visitor);
thisObject->m_zonedDateTimeStructure.visit(visitor);

visitor.append(thisObject->m_nullGetterFunction);
visitor.append(thisObject->m_nullSetterFunction);
Expand Down
2 changes: 2 additions & 0 deletions Source/JavaScriptCore/runtime/JSGlobalObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ class JSGlobalObject : public JSSegmentedVariableObject {
LazyProperty<JSGlobalObject, Structure> m_plainTimeStructure;
LazyProperty<JSGlobalObject, Structure> m_plainYearMonthStructure;
LazyProperty<JSGlobalObject, Structure> m_timeZoneStructure;
LazyProperty<JSGlobalObject, Structure> m_zonedDateTimeStructure;

WriteBarrier<NullGetterFunction> m_nullGetterFunction;
WriteBarrier<NullSetterFunction> m_nullSetterFunction;
Expand Down Expand Up @@ -1048,6 +1049,7 @@ class JSGlobalObject : public JSSegmentedVariableObject {
Structure* plainTimeStructure() { return m_plainTimeStructure.get(this); }
Structure* plainYearMonthStructure() { return m_plainYearMonthStructure.get(this); }
Structure* timeZoneStructure() { return m_timeZoneStructure.get(this); }
Structure* zonedDateTimeStructure() { return m_zonedDateTimeStructure.get(this); }

#if USE(BUN_JSC_ADDITIONS)
Structure* internalFieldTupleStructure() const { return m_internalFieldTupleStructure.get(); }
Expand Down
6 changes: 3 additions & 3 deletions Source/JavaScriptCore/runtime/TemporalInstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include "TemporalDuration.h"
#include "TemporalObject.h"
#include "TemporalTimeZone.h"
#include "TemporalZonedDateTime.h"
#include <wtf/text/MakeString.h>

namespace JSC {
Expand Down Expand Up @@ -150,9 +151,8 @@ TemporalInstant* TemporalInstant::toInstant(JSGlobalObject* globalObject, JSValu
if (itemValue.inherits<TemporalInstant>())
return uncheckedDowncast<TemporalInstant>(itemValue);

// FIXME: when Temporal.ZonedDateTime lands
// if (itemValue.inherits<TemporalZonedDateTime>())
// return TemporalInstant::create(vm, globalObject->instantStructure(), uncheckedDowncast<TemporalZonedDateTime>(itemValue)->epochTime());
if (itemValue.inherits<TemporalZonedDateTime>())
return TemporalInstant::create(vm, globalObject->instantStructure(), uncheckedDowncast<TemporalZonedDateTime>(itemValue)->exactTime());

String string = itemValue.toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, nullptr);
Expand Down
20 changes: 20 additions & 0 deletions Source/JavaScriptCore/runtime/TemporalInstantPrototype.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "IntlDateTimeFormat.h"
#include "JSCInlines.h"
#include "TemporalInstant.h"
#include "TemporalZonedDateTime.h"

namespace JSC {

Expand All @@ -44,6 +45,7 @@ static JSC_DECLARE_HOST_FUNCTION(temporalInstantPrototypeFuncToString);
static JSC_DECLARE_HOST_FUNCTION(temporalInstantPrototypeFuncToJSON);
static JSC_DECLARE_HOST_FUNCTION(temporalInstantPrototypeFuncToLocaleString);
static JSC_DECLARE_HOST_FUNCTION(temporalInstantPrototypeFuncValueOf);
static JSC_DECLARE_HOST_FUNCTION(temporalInstantPrototypeFuncToZonedDateTimeISO);
static JSC_DECLARE_CUSTOM_GETTER(temporalInstantPrototypeGetterEpochMilliseconds);
static JSC_DECLARE_CUSTOM_GETTER(temporalInstantPrototypeGetterEpochNanoseconds);

Expand All @@ -67,6 +69,7 @@ const ClassInfo TemporalInstantPrototype::s_info = { "Temporal.Instant"_s, &Base
toJSON temporalInstantPrototypeFuncToJSON DontEnum|Function 0
toLocaleString temporalInstantPrototypeFuncToLocaleString DontEnum|Function 0
valueOf temporalInstantPrototypeFuncValueOf DontEnum|Function 0
toZonedDateTimeISO temporalInstantPrototypeFuncToZonedDateTimeISO DontEnum|Function 1
epochMilliseconds temporalInstantPrototypeGetterEpochMilliseconds DontEnum|ReadOnly|CustomAccessor
epochNanoseconds temporalInstantPrototypeGetterEpochNanoseconds DontEnum|ReadOnly|CustomAccessor
@end
Expand Down Expand Up @@ -271,6 +274,23 @@ JSC_DEFINE_HOST_FUNCTION(temporalInstantPrototypeFuncValueOf, (JSGlobalObject* g
return throwVMTypeError(globalObject, scope, "Temporal.Instant.prototype.valueOf must not be called. To compare Instant values, use Temporal.Instant.compare"_s);
}

// https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tozoneddatetimeiso
JSC_DEFINE_HOST_FUNCTION(temporalInstantPrototypeFuncToZonedDateTimeISO, (JSGlobalObject* globalObject, CallFrame* callFrame))
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);

auto* instant = dynamicDowncast<TemporalInstant>(callFrame->thisValue());
if (!instant)
return throwVMTypeError(globalObject, scope, "Temporal.Instant.prototype.toZonedDateTimeISO called on value that's not an Instant"_s);

auto timeZone = TemporalZonedDateTime::toTimeZoneIdentifier(globalObject, callFrame->argument(0));
RETURN_IF_EXCEPTION(scope, { });
ASSERT(timeZone);

return JSValue::encode(TemporalZonedDateTime::create(vm, globalObject->zonedDateTimeStructure(), instant->exactTime(), timeZone.value()));
}

JSC_DEFINE_CUSTOM_GETTER(temporalInstantPrototypeGetterEpochMilliseconds, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName))
{
VM& vm = globalObject->vm();
Expand Down
Loading