From 301a312ae41641b938b40bcb9878410575a05119 Mon Sep 17 00:00:00 2001 From: Kevin Ness <46825870+nekevss@users.noreply.github.com> Date: Thu, 5 Dec 2024 08:50:37 -0600 Subject: [PATCH 01/14] Progress on implementing more of zdt --- Cargo.lock | 28 + Cargo.toml | 2 +- .../builtins/temporal/zoned_date_time/mod.rs | 720 ++++++++++++++++-- core/engine/src/context/mod.rs | 13 + 4 files changed, 681 insertions(+), 82 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c3feb302480..a8afc9d9aa0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -851,6 +851,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "comfy-table" version = "7.1.3" @@ -2149,6 +2159,12 @@ dependencies = [ "libc", ] +[[package]] +name = "jiff-tzdb" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91335e575850c5c4c673b9bd467b0e025f164ca59d0564f69d0c2ee0ffad4653" + [[package]] name = "js-sys" version = "0.3.74" @@ -3334,12 +3350,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abc1a6b736bd71cd492b8810334efff905c4a09787a46e9a0ddd54b46a2f4e08" dependencies = [ "bitflags 2.6.0", + "combine", "iana-time-zone", "icu_calendar", "ixdtf", + "jiff-tzdb", "num-traits", "rustc-hash 2.1.0", "tinystr", + "tzif", ] [[package]] @@ -3646,6 +3665,15 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "tzif" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61b8eb18929606c6f3eea7ef096a91dd5c26dbbde2a20a343c4a409b851666fd" +dependencies = [ + "combine", +] + [[package]] name = "unicode-ident" version = "1.0.14" diff --git a/Cargo.toml b/Cargo.toml index 9f8af784096..636d49fa306 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -111,7 +111,7 @@ intrusive-collections = "0.9.7" cfg-if = "1.0.0" either = "1.13.0" sys-locale = "0.3.2" -temporal_rs = "0.0.4" +temporal_rs = { version = "0.0.4", features = ["tzdb"] } web-time = "1.1.0" criterion = "0.5.1" float-cmp = "0.10.0" diff --git a/core/engine/src/builtins/temporal/zoned_date_time/mod.rs b/core/engine/src/builtins/temporal/zoned_date_time/mod.rs index 2b39bc88834..6a4ca84f6b7 100644 --- a/core/engine/src/builtins/temporal/zoned_date_time/mod.rs +++ b/core/engine/src/builtins/temporal/zoned_date_time/mod.rs @@ -1,11 +1,8 @@ #![allow(dead_code, unused_variables)] +use std::str::FromStr; + use crate::{ - builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, - context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, - property::Attribute, - realm::Realm, - string::StaticJsStrings, - Context, JsBigInt, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, + builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, object::internal_methods::get_prototype_from_constructor, property::Attribute, realm::Realm, string::StaticJsStrings, Context, JsArgs, JsBigInt, JsData, JsError, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue }; use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; @@ -15,7 +12,13 @@ use temporal_rs::{Duration as TemporalDuration, ZonedDateTime as InnerZdt}; #[derive(Debug, Clone, Trace, Finalize, JsData)] #[boa_gc(unsafe_empty_trace)] pub struct ZonedDateTime { - pub(crate) inner: InnerZdt, + pub(crate) inner: ZonedDateTimeInner, +} + +impl ZonedDateTime { + pub(crate) fn new(inner: ZonedDateTimeInner) -> Self { + Self { inner } + } } impl BuiltInObject for ZonedDateTime { @@ -26,12 +29,243 @@ impl IntrinsicObject for ZonedDateTime { fn init(realm: &Realm) { let _timer = Profiler::global().start_event(std::any::type_name::(), "init"); + let get_calendar_id = BuiltInBuilder::callable(realm, Self::get_calendar_id) + .name(js_string!("get calendarId")) + .build(); + + let get_year = BuiltInBuilder::callable(realm, Self::get_year) + .name(js_string!("get year")) + .build(); + + let get_month = BuiltInBuilder::callable(realm, Self::get_month) + .name(js_string!("get month")) + .build(); + + let get_month_code = BuiltInBuilder::callable(realm, Self::get_month_code) + .name(js_string!("get monthCode")) + .build(); + + let get_day = BuiltInBuilder::callable(realm, Self::get_day) + .name(js_string!("get day")) + .build(); + + let get_hour = BuiltInBuilder::callable(realm, Self::get_hour) + .name(js_string!("get hour")) + .build(); + + let get_minute = BuiltInBuilder::callable(realm, Self::get_minute) + .name(js_string!("get minute")) + .build(); + + let get_second = BuiltInBuilder::callable(realm, Self::get_second) + .name(js_string!("get second")) + .build(); + + let get_millisecond = BuiltInBuilder::callable(realm, Self::get_millisecond) + .name(js_string!("get millisecond")) + .build(); + + let get_microsecond = BuiltInBuilder::callable(realm, Self::get_microsecond) + .name(js_string!("get microsecond")) + .build(); + + let get_nanosecond = BuiltInBuilder::callable(realm, Self::get_nanosecond) + .name(js_string!("get nanosecond")) + .build(); + + let get_epoch_millisecond = BuiltInBuilder::callable(realm, Self::get_epoch_millisecond) + .name(js_string!("get epochMillisecond")) + .build(); + + let get_epoch_nanosecond = BuiltInBuilder::callable(realm, Self::get_epoch_nanosecond) + .name(js_string!("get epochNanosecond")) + .build(); + + let get_day_of_week = BuiltInBuilder::callable(realm, Self::get_day_of_week) + .name(js_string!("get dayOfWeek")) + .build(); + + let get_day_of_year = BuiltInBuilder::callable(realm, Self::get_day_of_year) + .name(js_string!("get dayOfYear")) + .build(); + + let get_week_of_year = BuiltInBuilder::callable(realm, Self::get_week_of_year) + .name(js_string!("get weekOfYear")) + .build(); + + let get_hours_in_day = BuiltInBuilder::callable(realm, Self::get_hours_in_day) + .name(js_string!("get daysInWeek")) + .build(); + + let get_year_of_week = BuiltInBuilder::callable(realm, Self::get_year_of_week) + .name(js_string!("get yearOfWeek")) + .build(); + + let get_days_in_week = BuiltInBuilder::callable(realm, Self::get_days_in_week) + .name(js_string!("get daysInWeek")) + .build(); + + let get_days_in_month = BuiltInBuilder::callable(realm, Self::get_days_in_month) + .name(js_string!("get daysInMonth")) + .build(); + + let get_days_in_year = BuiltInBuilder::callable(realm, Self::get_days_in_year) + .name(js_string!("get daysInYear")) + .build(); + + let get_months_in_year = BuiltInBuilder::callable(realm, Self::get_months_in_year) + .name(js_string!("get monthsInYear")) + .build(); + + let get_in_leap_year = BuiltInBuilder::callable(realm, Self::get_in_leap_year) + .name(js_string!("get inLeapYear")) + .build(); + + BuiltInBuilder::from_standard_constructor::(realm) .property( JsSymbol::to_string_tag(), StaticJsStrings::ZONED_DT_TAG, Attribute::CONFIGURABLE, ) + .accessor( + js_string!("calendarId"), + Some(get_calendar_id), + None, + Attribute::CONFIGURABLE, + ) + .accessor( + js_string!("year"), + Some(get_year), + None, + Attribute::CONFIGURABLE, + ) + .accessor( + js_string!("month"), + Some(get_month), + None, + Attribute::CONFIGURABLE, + ) + .accessor( + js_string!("monthCode"), + Some(get_month_code), + None, + Attribute::CONFIGURABLE, + ) + .accessor( + js_string!("day"), + Some(get_day), + None, + Attribute::CONFIGURABLE, + ) + .accessor( + js_string!("hour"), + Some(get_hour), + None, + Attribute::CONFIGURABLE, + ) + .accessor( + js_string!("minute"), + Some(get_minute), + None, + Attribute::CONFIGURABLE, + ) + .accessor( + js_string!("second"), + Some(get_second), + None, + Attribute::CONFIGURABLE, + ) + .accessor( + js_string!("millisecond"), + Some(get_millisecond), + None, + Attribute::CONFIGURABLE, + ) + .accessor( + js_string!("microsecond"), + Some(get_microsecond), + None, + Attribute::CONFIGURABLE, + ) + .accessor( + js_string!("nanosecond"), + Some(get_nanosecond), + None, + Attribute::CONFIGURABLE, + ) + .accessor( + js_string!("epochMillisecond"), + Some(get_epoch_millisecond), + None, + Attribute::CONFIGURABLE, + ) + .accessor( + js_string!("epochNanosecond"), + Some(get_epoch_nanosecond), + None, + Attribute::CONFIGURABLE, + ) + .accessor( + js_string!("dayOfWeek"), + Some(get_day_of_week), + None, + Attribute::CONFIGURABLE, + ) + .accessor( + js_string!("dayOfYear"), + Some(get_day_of_year), + None, + Attribute::CONFIGURABLE, + ) + .accessor( + js_string!("weekOfYear"), + Some(get_week_of_year), + None, + Attribute::CONFIGURABLE, + ) + .accessor( + js_string!("yearOfWeek"), + Some(get_year_of_week), + None, + Attribute::CONFIGURABLE, + ) + .accessor( + js_string!("hoursInDay"), + Some(get_hours_in_day), + None, + Attribute::CONFIGURABLE, + ) + .accessor( + js_string!("daysInWeek"), + Some(get_days_in_week), + None, + Attribute::CONFIGURABLE, + ) + .accessor( + js_string!("daysInMonth"), + Some(get_days_in_month), + None, + Attribute::CONFIGURABLE, + ) + .accessor( + js_string!("daysInYear"), + Some(get_days_in_year), + None, + Attribute::CONFIGURABLE, + ) + .accessor( + js_string!("monthsInYear"), + Some(get_months_in_year), + None, + Attribute::CONFIGURABLE, + ) + .accessor( + js_string!("inLeapYear"), + Some(get_in_leap_year), + None, + Attribute::CONFIGURABLE, + ) .build(); } @@ -53,84 +287,408 @@ impl BuiltInConstructor for ZonedDateTime { args: &[JsValue], context: &mut Context, ) -> JsResult { - // TODO: Implement ZonedDateTime. - Err(JsNativeError::error() - .with_message("%ZonedDateTime% not yet implemented.") - .into()) + // 1. If NewTarget is undefined, then + if new_target.is_undefined() { + // a. Throw a TypeError exception. + return Err(JsNativeError::typ().with_message("NewTarget cannot be undefined.").into()) + } + // 2. Set epochNanoseconds to ? ToBigInt(epochNanoseconds). + let epoch_nanos = args.get_or_undefined(0).to_bigint(context)?; + // 3. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception. + // TODO: Better primitive for handling epochNanoseconds is needed in temporal_rs + let Some(nanos) = epoch_nanos.to_f64().to_i128() else { + return Err(JsNativeError::range().with_message("epochNanoseconds exceeded valid range.").into()) + }; + + // 4. If timeZone is not a String, throw a TypeError exception. + let JsValue::String(timezone_str) = args.get_or_undefined(1) else { + return Err(JsNativeError::typ().with_message("timeZone must be a string.").into()) + }; + + // 5. Let timeZoneParse be ? ParseTimeZoneIdentifier(timeZone). + // 6. If timeZoneParse.[[OffsetMinutes]] is empty, then + // a. Let identifierRecord be GetAvailableNamezdtimeZoneIdentifier(timeZoneParse.[[Name]]). + // b. If identifierRecord is empty, throw a RangeError exception. + // c. Set timeZone to identifierRecord.[[Identifier]]. + // 7. Else, + // a. Set timeZone to FormatOffsetTimeZoneIdentifier(timeZoneParse.[[OffsetMinutes]]). + let timezone = TimeZone::try_from_str_with_provider(&timezone_str.to_std_string_escaped(), context.tz_provider())?; + + // 8. If calendar is undefined, set calendar to "iso8601". + // 9. If calendar is not a String, throw a TypeError exception. + // 10. Set calendar to ? CanonicalizeCalendar(calendar). + let calendar = match args.get(2) { + Some(JsValue::String(time_zone)) => Calendar::from_str(&time_zone.to_std_string_escaped())?, + None=> Calendar::default(), + _=> return Err(JsNativeError::typ().with_message("calendar must be a string.").into()) + }; + + let inner = ZonedDateTimeInner::try_new(nanos, calendar, timezone)?; + + // 11. Return ? CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar, NewTarget). + create_temporal_zoneddatetime(inner, Some(new_target), context).map(Into::into) } } -// -- ZonedDateTime Abstract Operations -- +// ==== `ZonedDateTime` accessor property methods ==== + +impl ZonedDateTime { + /// 6.3.3 get `Temporal.PlainDatezdt.prototype.calendarId` + fn get_calendar_id(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + let zdt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a PlainDate object.") + })?; + + Ok(JsString::from(zdt.inner.calendar().identifier()).into()) + } + + /// 6.3.4 get `Temporal.PlainDatezdt.prototype.timeZoneId` + fn get_timezone_id(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + let _zdt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a PlainDate object.") + })?; + + Err(JsNativeError::error().with_message("Not yet implemented.").into()) + } + + /// 6.3.5 get `Temporal.PlainDatezdt.prototype.era` + fn get_era(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + let _zdt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a PlainDate object.") + })?; + + Err(JsNativeError::error().with_message("Not yet implemented.").into()) + } + + /// 6.3.6 get `Temporal.PlainDatezdt.prototype.eraYear` + fn get_era_year(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + let _zdt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a PlainDate object.") + })?; + + Err(JsNativeError::error().with_message("Not yet implemented.").into()) + } + + /// 6.3.7 get `Temporal.PlainDatezdt.prototype.year` + fn get_year(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + let zdt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") + })?; + + Ok(zdt.inner.year_with_provider(context.tz_provider())?.into()) + } -///6.5.5 `AddZonedDateTime ( epochNanoseconds, timeZone, calendar, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds [ , options ] )` -pub(crate) fn add_zoned_date_time( - epoch_nanos: &JsBigInt, - time_zone: &JsObject, - calendar: &JsObject, - duration: TemporalDuration, - options: Option<&JsObject>, -) -> JsResult { - // 1. If options is not present, set options to undefined. - // 2. Assert: Type(options) is Object or Undefined. - // 3. If years = 0, months = 0, weeks = 0, and days = 0, then - // a. Return ? AddInstant(epochNanoseconds, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). - // 4. Let instant be ! CreateTemporalInstant(epochNanoseconds). - // 5. Let temporalDateTime be ? GetPlainDateTimeFor(timeZone, instant, calendar). - // 6. Let datePart be ! CreateTemporalDate(temporalDateTime.[[ISOYear]], temporalDateTime.[[ISOMonth]], temporalDateTime.[[ISODay]], calendar). - // 7. Let dateDuration be ! CreateTemporalDuration(years, months, weeks, days, 0, 0, 0, 0, 0, 0). - // 8. Let addedDate be ? CalendarDateAdd(calendar, datePart, dateDuration, options). - // 9. Let intermediateDateTime be ? CreateTemporalDateTime(addedDate.[[ISOYear]], addedDate.[[ISOMonth]], addedDate.[[ISODay]], temporalDateTime.[[ISOHour]], temporalDateTime.[[ISOMinute]], temporalDateTime.[[ISOSecond]], temporalDateTime.[[ISOMillisecond]], temporalDateTime.[[ISOMicrosecond]], temporalDateTime.[[ISONanosecond]], calendar). - // 10. Let intermediateInstant be ? GetInstantFor(timeZone, intermediateDateTime, "compatible"). - // 11. Return ? AddInstant(intermediateInstant.[[Nanoseconds]], hours, minutes, seconds, milliseconds, microseconds, nanoseconds). - Err(JsNativeError::error() - .with_message("%ZonedDateTime% not yet implemented.") - .into()) + /// 6.3.8 get `Temporal.PlainDatezdt.prototype.month` + fn get_month(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + let zdt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") + })?; + + Ok(zdt.inner.month_with_provider(context.tz_provider())?.into()) + } + + /// 6.3.9 get Temporal.PlainDatezdt.prototype.monthCode + fn get_month_code(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + let zdt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") + })?; + + Ok(JsString::from(zdt.inner.month_code_with_provider(context.tz_provider())?.as_str()).into()) + } + + /// 6.3.10 get `Temporal.PlainDatezdt.prototype.day` + fn get_day(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + let zdt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") + })?; + + Ok(zdt.inner.day_with_provider(context.tz_provider())?.into()) + } + + /// 6.3.11 get `Temporal.PlainDatezdt.prototype.hour` + fn get_hour(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + let zdt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") + })?; + + Ok(zdt.inner.hour_with_provider(context.tz_provider())?.into()) + } + + /// 6.3.12 get `Temporal.PlainDatezdt.prototype.minute` + fn get_minute(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + let zdt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") + })?; + + Ok(zdt.inner.minute_with_provider(context.tz_provider())?.into()) + } + + /// 6.3.13 get `Temporal.PlainDatezdt.prototype.second` + fn get_second(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + let zdt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") + })?; + + Ok(zdt.inner.second_with_provider(context.tz_provider())?.into()) + } + + /// 6.3.14 get `Temporal.PlainDatezdt.prototype.millisecond` + fn get_millisecond(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + let zdt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") + })?; + + Ok(zdt.inner.millisecond_with_provider(context.tz_provider())?.into()) + } + + /// 6.3.15 get `Temporal.PlainDatezdt.prototype.microsecond` + fn get_microsecond(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + let zdt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") + })?; + + Ok(zdt.inner.microsecond_with_provider(context.tz_provider())?.into()) + } + + /// 6.3.16 get `Temporal.PlainDatezdt.prototype.nanosecond` + fn get_nanosecond(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + let zdt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") + })?; + + Ok(zdt.inner.nanosecond_with_provider(context.tz_provider())?.into()) + } + + /// 6.3.17 get `Temporal.PlainDatezdt.prototype.epochMilliseconds` + fn get_epoch_millisecond(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + let zdt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") + })?; + + Ok(zdt.inner.epoch_milliseconds().into()) + } + + /// 6.3.18 get `Temporal.PlainDatezdt.prototype.epochNanosecond` + fn get_epoch_nanosecond(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + let zdt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") + })?; + + Ok(zdt.inner.epoch_nanoseconds().into()) + } + + /// 6.3.19 get `Temporal.PlainDatezdt.prototype.dayOfWeek` + fn get_day_of_week(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + let zdt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") + })?; + + Err(JsNativeError::error().with_message("Not yet implemented.").into()) + // Ok(zdt.inner.day_of_week()?.into()) + } + + /// 6.3.20 get `Temporal.PlainDatezdt.prototype.dayOfYear` + fn get_day_of_year(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + let zdt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") + })?; + + Err(JsNativeError::error().with_message("Not yet implemented.").into()) + // Ok(zdt.inner.day_of_year()?.into()) + } + + /// 6.3.21 get `Temporal.PlainDatezdt.prototype.weekOfYear` + fn get_week_of_year(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + let zdt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") + })?; + + Err(JsNativeError::error().with_message("Not yet implemented.").into()) + // Ok(zdt.inner.week_of_year()?.into_or_undefined()) + } + + /// 6.3.22 get `Temporal.PlainDatezdt.prototype.yearOfWeek` + fn get_year_of_week(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + let zdt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") + })?; + + Err(JsNativeError::error().with_message("Not yet implemented.").into()) + // Ok(zdt.inner.year_of_week()?.into_or_undefined()) + } + + /// 6.3.23 get `Temporal.PlainDatezdt.prototype.hoursInDay` + fn get_hours_in_day(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + let zdt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") + })?; + + Err(JsNativeError::error().with_message("Not yet implemented.").into()) + } + + /// 6.3.24 get `Temporal.PlainDatezdt.prototype.daysInWeek` + fn get_days_in_week(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + let zdt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") + })?; + + Err(JsNativeError::error().with_message("Not yet implemented.").into()) + // Ok(zdt.inner.days_in_week()?.into()) + } + + /// 6.3.25 get `Temporal.PlainDatezdt.prototype.daysInMonth` + fn get_days_in_month( + this: &JsValue, + _: &[JsValue], + context: &mut Context, + ) -> JsResult { + let zdt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") + })?; + + Err(JsNativeError::error().with_message("Not yet implemented.").into()) + // Ok(zdt.inner.days_in_month()?.into()) + } + + /// 6.3.26 get `Temporal.PlainDatezdt.prototype.daysInYear` + fn get_days_in_year(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + let zdt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") + })?; + + Err(JsNativeError::error().with_message("Not yet implemented.").into()) + // Ok(zdt.inner.days_in_year()?.into()) + } + + /// 6.3.27 get `Temporal.PlainDatezdt.prototype.monthsInYear` + fn get_months_in_year( + this: &JsValue, + _: &[JsValue], + context: &mut Context, + ) -> JsResult { + let zdt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") + })?; + + Err(JsNativeError::error().with_message("Not yet implemented.").into()) + // Ok(zdt.inner.months_in_year()?.into()) + } + + /// 6.3.28 get `Temporal.PlainDatezdt.prototype.inLeapYear` + fn get_in_leap_year(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + let zdt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") + })?; + + Err(JsNativeError::error().with_message("Not yet implemented.").into()) + // Ok(zdt.inner.in_leap_year()?.into()) + } } -/// 6.5.7 `NanosecondsToDays ( nanoseconds, relativeTo )` -pub(crate) fn nanoseconds_to_days( - nanoseconds: f64, - relative_to: &JsValue, -) -> JsResult<(i32, i32, i32)> { - // 1. Let dayLengthNs be nsPerDay. - // 2. If nanoseconds = 0, then - // a. Return the Record { [[Days]]: 0, [[Nanoseconds]]: 0, [[DayLength]]: dayLengthNs }. - // 3. If nanoseconds < 0, let sign be -1; else, let sign be 1. - // 4. If Type(relativeTo) is not Object or relativeTo does not have an [[InitializedTemporalZonedDateTime]] internal slot, then - // a. Return the Record { [[Days]]: truncate(nanoseconds / dayLengthNs), [[Nanoseconds]]: (abs(nanoseconds) modulo dayLengthNs) × sign, [[DayLength]]: dayLengthNs }. - // 5. Let startNs be ℝ(relativeTo.[[Nanoseconds]]). - // 6. Let startInstant be ! CreateTemporalInstant(ℤ(startNs)). - // 7. Let startDateTime be ? GetPlainDateTimeFor(relativeTo.[[TimeZone]], startInstant, relativeTo.[[Calendar]]). - // 8. Let endNs be startNs + nanoseconds. - // 9. If ! IsValidEpochNanoseconds(ℤ(endNs)) is false, throw a RangeError exception. - // 10. Let endInstant be ! CreateTemporalInstant(ℤ(endNs)). - // 11. Let endDateTime be ? GetPlainDateTimeFor(relativeTo.[[TimeZone]], endInstant, relativeTo.[[Calendar]]). - // 12. Let dateDifference be ? DifferenceISODateTime(startDateTime.[[ISOYear]], startDateTime.[[ISOMonth]], startDateTime.[[ISODay]], startDateTime.[[ISOHour]], startDateTime.[[ISOMinute]], startDateTime.[[ISOSecond]], startDateTime.[[ISOMillisecond]], startDateTime.[[ISOMicrosecond]], startDateTime.[[ISONanosecond]], endDateTime.[[ISOYear]], endDateTime.[[ISOMonth]], endDateTime.[[ISODay]], endDateTime.[[ISOHour]], endDateTime.[[ISOMinute]], endDateTime.[[ISOSecond]], endDateTime.[[ISOMillisecond]], endDateTime.[[ISOMicrosecond]], endDateTime.[[ISONanosecond]], relativeTo.[[Calendar]], "day", OrdinaryObjectCreate(null)). - // 13. Let days be dateDifference.[[Days]]. - // 14. Let intermediateNs be ℝ(? AddZonedDateTime(ℤ(startNs), relativeTo.[[TimeZone]], relativeTo.[[Calendar]], 0, 0, 0, days, 0, 0, 0, 0, 0, 0)). - // 15. If sign is 1, then - // a. Repeat, while days > 0 and intermediateNs > endNs, - // i. Set days to days - 1. - // ii. Set intermediateNs to ℝ(? AddZonedDateTime(ℤ(startNs), relativeTo.[[TimeZone]], relativeTo.[[Calendar]], 0, 0, 0, days, 0, 0, 0, 0, 0, 0)). - // 16. Set nanoseconds to endNs - intermediateNs. - // 17. Let done be false. - // 18. Repeat, while done is false, - // a. Let oneDayFartherNs be ℝ(? AddZonedDateTime(ℤ(intermediateNs), relativeTo.[[TimeZone]], relativeTo.[[Calendar]], 0, 0, 0, sign, 0, 0, 0, 0, 0, 0)). - // b. Set dayLengthNs to oneDayFartherNs - intermediateNs. - // c. If (nanoseconds - dayLengthNs) × sign ≥ 0, then - // i. Set nanoseconds to nanoseconds - dayLengthNs. - // ii. Set intermediateNs to oneDayFartherNs. - // iii. Set days to days + sign. - // d. Else, - // i. Set done to true. - // 19. If days < 0 and sign = 1, throw a RangeError exception. - // 20. If days > 0 and sign = -1, throw a RangeError exception. - // 21. If nanoseconds < 0, then - // a. Assert: sign is -1. - // 22. If nanoseconds > 0 and sign = -1, throw a RangeError exception. - // 23. Assert: The inequality abs(nanoseconds) < abs(dayLengthNs) holds. - // 24. Return the Record { [[Days]]: days, [[Nanoseconds]]: nanoseconds, [[DayLength]]: abs(dayLengthNs) }. - Err(JsNativeError::error() - .with_message("%ZonedDateTime% not yet implemented.") - .into()) +// -- ZonedDateTime Abstract Operations -- + +pub(crate) fn create_temporal_zoneddatetime( + inner: ZonedDateTimeInner, + new_target: Option<&JsValue>, + context: &mut Context, +) -> JsResult { + // 1. Assert: IsValidEpochNanoseconds(epochNanoseconds) is true. + // 2. If newTarget is not present, set newTarget to %Temporal.ZonedDateTime%. + let new_target = new_target.cloned().unwrap_or( + context + .realm() + .intrinsics() + .constructors() + .zoned_date_time() + .constructor() + .into() + ); + // 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.ZonedDateTime.prototype%", « [[InitializezdtemporalZonedDateTime]], [[EpochNanoseconds]], [[TimeZone]], [[Calendar]] »). + let prototype = + get_prototype_from_constructor(&new_target, StandardConstructors::zoned_date_time, context)?; + // 4. Set object.[[EpochNanoseconds]] to epochNanoseconds. + // 5. Set object.[[TimeZone]] to timeZone. + // 6. Set object.[[Calendar]] to calendar. + let obj = JsObject::from_proto_and_data(prototype, ZonedDateTime::new(inner)); + + // 7. Return object. + Ok(obj) } diff --git a/core/engine/src/context/mod.rs b/core/engine/src/context/mod.rs index 96172ffd500..207b74b9fe1 100644 --- a/core/engine/src/context/mod.rs +++ b/core/engine/src/context/mod.rs @@ -10,6 +10,8 @@ pub use hooks::{DefaultHooks, HostHooks}; #[cfg(feature = "intl")] pub use icu::IcuError; use intrinsics::Intrinsics; +#[cfg(feature="temporal")] +use temporal_rs::tzdb::FsTzdbProvider; use crate::vm::RuntimeLimits; use crate::{ @@ -100,6 +102,9 @@ pub struct Context { can_block: bool, + #[cfg(feature ="temporal")] + tz_provider: FsTzdbProvider, + /// Intl data provider. #[cfg(feature = "intl")] intl_provider: icu::IntlProvider, @@ -860,6 +865,12 @@ impl Context { pub(crate) const fn intl_provider(&self) -> &icu::IntlProvider { &self.intl_provider } + + /// Get the Time Zone Provider + #[cfg(feature = "temporal")] + pub(crate) fn tz_provider(&self) -> &FsTzdbProvider { + &self.tz_provider + } } /// Builder for the [`Context`] type. @@ -1087,6 +1098,8 @@ impl ContextBuilder { interner: self.interner.unwrap_or_default(), vm, strict: false, + #[cfg(feature = "temporal")] + tz_provider: FsTzdbProvider::default(), #[cfg(feature = "intl")] intl_provider: if let Some(icu) = self.icu { icu From 752bfdce2309421ccce16a43bf65afd8792a8866 Mon Sep 17 00:00:00 2001 From: Kevin Ness <46825870+nekevss@users.noreply.github.com> Date: Sat, 7 Dec 2024 00:26:43 -0600 Subject: [PATCH 02/14] Complete build out of ZonedDateTime and Now --- Cargo.lock | 4 +- Cargo.toml | 2 +- .../src/builtins/temporal/calendar/mod.rs | 6 +- .../src/builtins/temporal/instant/mod.rs | 2 +- core/engine/src/builtins/temporal/mod.rs | 4 +- core/engine/src/builtins/temporal/now.rs | 60 +- .../{zoned_date_time => zoneddatetime}/mod.rs | 517 +++++++++++++++--- core/engine/src/context/mod.rs | 4 +- 8 files changed, 470 insertions(+), 129 deletions(-) rename core/engine/src/builtins/temporal/{zoned_date_time => zoneddatetime}/mod.rs (51%) diff --git a/Cargo.lock b/Cargo.lock index a8afc9d9aa0..3eedec8f75d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3346,8 +3346,7 @@ checksum = "42a4d50cdb458045afc8131fd91b64904da29548bcb63c7236e0844936c13078" [[package]] name = "temporal_rs" version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abc1a6b736bd71cd492b8810334efff905c4a09787a46e9a0ddd54b46a2f4e08" +source = "git+https://github.com/boa-dev/temporal.git?rev=1fd02efe568bb5a3d5f9836fef871d91940e36c7#1fd02efe568bb5a3d5f9836fef871d91940e36c7" dependencies = [ "bitflags 2.6.0", "combine", @@ -3359,6 +3358,7 @@ dependencies = [ "rustc-hash 2.1.0", "tinystr", "tzif", + "web-time", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 636d49fa306..7846cfcc24d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -111,7 +111,7 @@ intrusive-collections = "0.9.7" cfg-if = "1.0.0" either = "1.13.0" sys-locale = "0.3.2" -temporal_rs = { version = "0.0.4", features = ["tzdb"] } +temporal_rs = { git = "https://github.com/boa-dev/temporal.git", rev = "1fd02efe568bb5a3d5f9836fef871d91940e36c7", features = ["tzdb", "now"] } web-time = "1.1.0" criterion = "0.5.1" float-cmp = "0.10.0" diff --git a/core/engine/src/builtins/temporal/calendar/mod.rs b/core/engine/src/builtins/temporal/calendar/mod.rs index 04ff76f1310..37c3a2ca368 100644 --- a/core/engine/src/builtins/temporal/calendar/mod.rs +++ b/core/engine/src/builtins/temporal/calendar/mod.rs @@ -22,11 +22,7 @@ pub(crate) fn get_temporal_calendar_slot_value_with_default( |dt| Ok(Some(dt.borrow().data().inner.calendar().clone())), |ym| Ok(Some(ym.borrow().data().inner.calendar().clone())), |md| Ok(Some(md.borrow().data().inner.calendar().clone())), - |zdt| { - Err(JsNativeError::range() - .with_message("Not yet implemented.") - .into()) - }, + |zdt| Ok(Some(zdt.borrow().data().inner.calendar().clone())), )? { return Ok(calendar); } diff --git a/core/engine/src/builtins/temporal/instant/mod.rs b/core/engine/src/builtins/temporal/instant/mod.rs index 6e8d3b22d54..a377dd09763 100644 --- a/core/engine/src/builtins/temporal/instant/mod.rs +++ b/core/engine/src/builtins/temporal/instant/mod.rs @@ -541,7 +541,7 @@ impl Instant { /// 8.5.2 `CreateTemporalInstant ( epochNanoseconds [ , newTarget ] )` #[inline] -fn create_temporal_instant( +pub(crate) fn create_temporal_instant( instant: InnerInstant, new_target: Option, context: &mut Context, diff --git a/core/engine/src/builtins/temporal/mod.rs b/core/engine/src/builtins/temporal/mod.rs index 3dcef45942f..243cf1453b8 100644 --- a/core/engine/src/builtins/temporal/mod.rs +++ b/core/engine/src/builtins/temporal/mod.rs @@ -16,14 +16,14 @@ mod plain_month_day; mod plain_time; mod plain_year_month; mod time_zone; -mod zoned_date_time; +mod zoneddatetime; #[cfg(test)] mod tests; pub use self::{ duration::*, instant::*, now::*, plain_date::*, plain_date_time::*, plain_month_day::*, - plain_time::*, plain_year_month::*, zoned_date_time::*, + plain_time::*, plain_year_month::*, zoneddatetime::*, }; use crate::{ diff --git a/core/engine/src/builtins/temporal/now.rs b/core/engine/src/builtins/temporal/now.rs index d0e9f4caef4..d17fbf07363 100644 --- a/core/engine/src/builtins/temporal/now.rs +++ b/core/engine/src/builtins/temporal/now.rs @@ -11,9 +11,12 @@ use crate::{ Context, JsBigInt, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; use boa_profiler::Profiler; -use temporal_rs::TimeZone; +use temporal_rs::{Now as NowInner, TimeZone}; -use super::{ns_max_instant, ns_min_instant, time_zone::default_time_zone}; +use super::{ + create_temporal_datetime, create_temporal_instant, ns_max_instant, ns_min_instant, + time_zone::default_time_zone, to_temporal_timezone_identifier, +}; /// JavaScript `Temporal.Now` object. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -37,12 +40,9 @@ impl IntrinsicObject for Now { ) .static_method(Self::time_zone_id, js_string!("timeZoneId"), 0) .static_method(Self::instant, js_string!("instant"), 0) - .static_method(Self::plain_date_time, js_string!("plainDateTime"), 2) - .static_method(Self::plain_date_time_iso, js_string!("plainDateTimeISO"), 1) - .static_method(Self::zoned_date_time, js_string!("zonedDateTime"), 2) - .static_method(Self::zoned_date_time_iso, js_string!("zonedDateTimeISO"), 1) - .static_method(Self::plain_date, js_string!("plainDate"), 2) - .static_method(Self::plain_date_iso, js_string!("plainDateISO"), 1) + .static_method(Self::plain_datetime, js_string!("plainDateTimeISO"), 0) + .static_method(Self::zoned_date_time, js_string!("zonedDateTime"), 0) + .static_method(Self::plain_date, js_string!("plainDateISO"), 0) .build(); } @@ -72,24 +72,22 @@ impl Now { } /// `Temporal.Now.instant()` - fn instant(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { - Err(JsNativeError::error() - .with_message("not yet implemented.") - .into()) + fn instant(_: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + create_temporal_instant(NowInner::instant()?, None, context).map(Into::into) } /// `Temporal.Now.plainDateTime()` - fn plain_date_time(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { - Err(JsNativeError::error() - .with_message("not yet implemented.") - .into()) - } - - /// `Temporal.Now.plainDateTimeISO` - fn plain_date_time_iso(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { - Err(JsNativeError::error() - .with_message("not yet implemented.") - .into()) + fn plain_datetime(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + let tz = args + .first() + .map(|v| to_temporal_timezone_identifier(v, context)) + .transpose()?; + create_temporal_datetime( + NowInner::plain_date_time_with_provider(tz, context.tz_provider())?, + None, + context, + ) + .map(Into::into) } /// `Temporal.Now.zonedDateTime` @@ -99,22 +97,8 @@ impl Now { .into()) } - /// `Temporal.Now.zonedDateTimeISO` - fn zoned_date_time_iso(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { - Err(JsNativeError::error() - .with_message("not yet implemented.") - .into()) - } - - /// `Temporal.Now.plainDate()` - fn plain_date(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { - Err(JsNativeError::error() - .with_message("not yet implemented.") - .into()) - } - /// `Temporal.Now.plainDateISO` - fn plain_date_iso(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { + fn plain_date(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { Err(JsNativeError::error() .with_message("not yet implemented.") .into()) diff --git a/core/engine/src/builtins/temporal/zoned_date_time/mod.rs b/core/engine/src/builtins/temporal/zoneddatetime/mod.rs similarity index 51% rename from core/engine/src/builtins/temporal/zoned_date_time/mod.rs rename to core/engine/src/builtins/temporal/zoneddatetime/mod.rs index 6a4ca84f6b7..95b17cd1fa5 100644 --- a/core/engine/src/builtins/temporal/zoned_date_time/mod.rs +++ b/core/engine/src/builtins/temporal/zoneddatetime/mod.rs @@ -1,12 +1,33 @@ -#![allow(dead_code, unused_variables)] use std::str::FromStr; use crate::{ - builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, object::internal_methods::get_prototype_from_constructor, property::Attribute, realm::Realm, string::StaticJsStrings, Context, JsArgs, JsBigInt, JsData, JsError, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue + builtins::{ + options::{get_option, get_options_object}, + BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, + }, + context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, + js_string, + object::internal_methods::get_prototype_from_constructor, + property::Attribute, + realm::Realm, + string::StaticJsStrings, + value::{IntoOrUndefined, PreferredType}, + Context, JsArgs, JsBigInt, JsData, JsError, JsNativeError, JsObject, JsResult, JsString, + JsSymbol, JsValue, }; use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; -use temporal_rs::{Duration as TemporalDuration, ZonedDateTime as InnerZdt}; +use num_traits::ToPrimitive; +use temporal_rs::{ + options::{ArithmeticOverflow, Disambiguation, OffsetDisambiguation}, + partial::PartialZonedDateTime, + Calendar, TimeZone, ZonedDateTime as ZonedDateTimeInner, +}; + +use super::{ + calendar::get_temporal_calendar_slot_value_with_default, to_partial_date_record, + to_partial_time_record, to_temporal_duration, +}; /// The `Temporal.ZonedDateTime` object. #[derive(Debug, Clone, Trace, Finalize, JsData)] @@ -33,6 +54,18 @@ impl IntrinsicObject for ZonedDateTime { .name(js_string!("get calendarId")) .build(); + let get_timezone_id = BuiltInBuilder::callable(realm, Self::get_timezone_id) + .name(js_string!("get timeZoneId")) + .build(); + + let get_era = BuiltInBuilder::callable(realm, Self::get_era) + .name(js_string!("get era")) + .build(); + + let get_era_year = BuiltInBuilder::callable(realm, Self::get_era_year) + .name(js_string!("get eraYear")) + .build(); + let get_year = BuiltInBuilder::callable(realm, Self::get_year) .name(js_string!("get year")) .build(); @@ -121,7 +154,6 @@ impl IntrinsicObject for ZonedDateTime { .name(js_string!("get inLeapYear")) .build(); - BuiltInBuilder::from_standard_constructor::(realm) .property( JsSymbol::to_string_tag(), @@ -134,6 +166,24 @@ impl IntrinsicObject for ZonedDateTime { None, Attribute::CONFIGURABLE, ) + .accessor( + js_string!("timeZoneId"), + Some(get_timezone_id), + None, + Attribute::CONFIGURABLE, + ) + .accessor( + js_string!("era"), + Some(get_era), + None, + Attribute::CONFIGURABLE, + ) + .accessor( + js_string!("eraYear"), + Some(get_era_year), + None, + Attribute::CONFIGURABLE, + ) .accessor( js_string!("year"), Some(get_year), @@ -266,6 +316,9 @@ impl IntrinsicObject for ZonedDateTime { None, Attribute::CONFIGURABLE, ) + .static_method(Self::from, js_string!("from"), 1) + .method(Self::add, js_string!("add"), 1) + .method(Self::subtract, js_string!("subtract"), 1) .build(); } @@ -290,19 +343,25 @@ impl BuiltInConstructor for ZonedDateTime { // 1. If NewTarget is undefined, then if new_target.is_undefined() { // a. Throw a TypeError exception. - return Err(JsNativeError::typ().with_message("NewTarget cannot be undefined.").into()) + return Err(JsNativeError::typ() + .with_message("NewTarget cannot be undefined.") + .into()); } // 2. Set epochNanoseconds to ? ToBigInt(epochNanoseconds). let epoch_nanos = args.get_or_undefined(0).to_bigint(context)?; // 3. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception. // TODO: Better primitive for handling epochNanoseconds is needed in temporal_rs let Some(nanos) = epoch_nanos.to_f64().to_i128() else { - return Err(JsNativeError::range().with_message("epochNanoseconds exceeded valid range.").into()) + return Err(JsNativeError::range() + .with_message("epochNanoseconds exceeded valid range.") + .into()); }; // 4. If timeZone is not a String, throw a TypeError exception. let JsValue::String(timezone_str) = args.get_or_undefined(1) else { - return Err(JsNativeError::typ().with_message("timeZone must be a string.").into()) + return Err(JsNativeError::typ() + .with_message("timeZone must be a string.") + .into()); }; // 5. Let timeZoneParse be ? ParseTimeZoneIdentifier(timeZone). @@ -312,16 +371,28 @@ impl BuiltInConstructor for ZonedDateTime { // c. Set timeZone to identifierRecord.[[Identifier]]. // 7. Else, // a. Set timeZone to FormatOffsetTimeZoneIdentifier(timeZoneParse.[[OffsetMinutes]]). - let timezone = TimeZone::try_from_str_with_provider(&timezone_str.to_std_string_escaped(), context.tz_provider())?; + let timezone = TimeZone::try_from_str_with_provider( + &timezone_str.to_std_string_escaped(), + context.tz_provider(), + )?; // 8. If calendar is undefined, set calendar to "iso8601". // 9. If calendar is not a String, throw a TypeError exception. // 10. Set calendar to ? CanonicalizeCalendar(calendar). - let calendar = match args.get(2) { - Some(JsValue::String(time_zone)) => Calendar::from_str(&time_zone.to_std_string_escaped())?, - None=> Calendar::default(), - _=> return Err(JsNativeError::typ().with_message("calendar must be a string.").into()) - }; + let calendar = args + .get(2) + .map(|v| { + if let JsValue::String(calendar_str) = v { + Calendar::from_str(&calendar_str.to_std_string_escaped()) + .map_err(Into::::into) + } else { + Err(JsNativeError::typ() + .with_message("calendar must be a string.") + .into()) + } + }) + .transpose()? + .unwrap_or_default(); let inner = ZonedDateTimeInner::try_new(nanos, calendar, timezone)?; @@ -333,8 +404,8 @@ impl BuiltInConstructor for ZonedDateTime { // ==== `ZonedDateTime` accessor property methods ==== impl ZonedDateTime { - /// 6.3.3 get `Temporal.PlainDatezdt.prototype.calendarId` - fn get_calendar_id(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + /// 6.3.3 get `Temporal.ZonedDateTime.prototype.calendarId` + fn get_calendar_id(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { let zdt = this .as_object() .and_then(JsObject::downcast_ref::) @@ -345,8 +416,8 @@ impl ZonedDateTime { Ok(JsString::from(zdt.inner.calendar().identifier()).into()) } - /// 6.3.4 get `Temporal.PlainDatezdt.prototype.timeZoneId` - fn get_timezone_id(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + /// 6.3.4 get `Temporal.ZonedDateTime.prototype.timeZoneId` + fn get_timezone_id(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { let _zdt = this .as_object() .and_then(JsObject::downcast_ref::) @@ -354,34 +425,42 @@ impl ZonedDateTime { JsNativeError::typ().with_message("the this object must be a PlainDate object.") })?; - Err(JsNativeError::error().with_message("Not yet implemented.").into()) + Err(JsNativeError::error() + .with_message("Not yet implemented.") + .into()) } - /// 6.3.5 get `Temporal.PlainDatezdt.prototype.era` + /// 6.3.5 get `Temporal.ZonedDateTime.prototype.era` fn get_era(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let _zdt = this + let zdt = this .as_object() .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("the this object must be a PlainDate object.") })?; - Err(JsNativeError::error().with_message("Not yet implemented.").into()) + let era = zdt.inner.era_with_provider(context.tz_provider())?; + Ok(era + .map(|tinystr| JsString::from(tinystr.to_lowercase())) + .into_or_undefined()) } - /// 6.3.6 get `Temporal.PlainDatezdt.prototype.eraYear` + /// 6.3.6 get `Temporal.ZonedDateTime.prototype.eraYear` fn get_era_year(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let _zdt = this + let zdt = this .as_object() .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("the this object must be a PlainDate object.") })?; - Err(JsNativeError::error().with_message("Not yet implemented.").into()) + Ok(zdt + .inner + .era_year_with_provider(context.tz_provider())? + .into_or_undefined()) } - /// 6.3.7 get `Temporal.PlainDatezdt.prototype.year` + /// 6.3.7 get `Temporal.ZonedDateTime.prototype.year` fn get_year(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let zdt = this .as_object() @@ -393,7 +472,7 @@ impl ZonedDateTime { Ok(zdt.inner.year_with_provider(context.tz_provider())?.into()) } - /// 6.3.8 get `Temporal.PlainDatezdt.prototype.month` + /// 6.3.8 get `Temporal.ZonedDateTime.prototype.month` fn get_month(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let zdt = this .as_object() @@ -405,7 +484,7 @@ impl ZonedDateTime { Ok(zdt.inner.month_with_provider(context.tz_provider())?.into()) } - /// 6.3.9 get Temporal.PlainDatezdt.prototype.monthCode + /// 6.3.9 get Temporal.ZonedDateTime.prototype.monthCode fn get_month_code(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let zdt = this .as_object() @@ -414,10 +493,15 @@ impl ZonedDateTime { JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") })?; - Ok(JsString::from(zdt.inner.month_code_with_provider(context.tz_provider())?.as_str()).into()) + Ok(JsString::from( + zdt.inner + .month_code_with_provider(context.tz_provider())? + .as_str(), + ) + .into()) } - /// 6.3.10 get `Temporal.PlainDatezdt.prototype.day` + /// 6.3.10 get `Temporal.ZonedDateTime.prototype.day` fn get_day(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let zdt = this .as_object() @@ -429,7 +513,7 @@ impl ZonedDateTime { Ok(zdt.inner.day_with_provider(context.tz_provider())?.into()) } - /// 6.3.11 get `Temporal.PlainDatezdt.prototype.hour` + /// 6.3.11 get `Temporal.ZonedDateTime.prototype.hour` fn get_hour(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let zdt = this .as_object() @@ -441,7 +525,7 @@ impl ZonedDateTime { Ok(zdt.inner.hour_with_provider(context.tz_provider())?.into()) } - /// 6.3.12 get `Temporal.PlainDatezdt.prototype.minute` + /// 6.3.12 get `Temporal.ZonedDateTime.prototype.minute` fn get_minute(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let zdt = this .as_object() @@ -450,10 +534,13 @@ impl ZonedDateTime { JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") })?; - Ok(zdt.inner.minute_with_provider(context.tz_provider())?.into()) + Ok(zdt + .inner + .minute_with_provider(context.tz_provider())? + .into()) } - /// 6.3.13 get `Temporal.PlainDatezdt.prototype.second` + /// 6.3.13 get `Temporal.ZonedDateTime.prototype.second` fn get_second(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let zdt = this .as_object() @@ -462,10 +549,13 @@ impl ZonedDateTime { JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") })?; - Ok(zdt.inner.second_with_provider(context.tz_provider())?.into()) + Ok(zdt + .inner + .second_with_provider(context.tz_provider())? + .into()) } - /// 6.3.14 get `Temporal.PlainDatezdt.prototype.millisecond` + /// 6.3.14 get `Temporal.ZonedDateTime.prototype.millisecond` fn get_millisecond(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let zdt = this .as_object() @@ -474,10 +564,13 @@ impl ZonedDateTime { JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") })?; - Ok(zdt.inner.millisecond_with_provider(context.tz_provider())?.into()) + Ok(zdt + .inner + .millisecond_with_provider(context.tz_provider())? + .into()) } - /// 6.3.15 get `Temporal.PlainDatezdt.prototype.microsecond` + /// 6.3.15 get `Temporal.ZonedDateTime.prototype.microsecond` fn get_microsecond(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let zdt = this .as_object() @@ -486,10 +579,13 @@ impl ZonedDateTime { JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") })?; - Ok(zdt.inner.microsecond_with_provider(context.tz_provider())?.into()) + Ok(zdt + .inner + .microsecond_with_provider(context.tz_provider())? + .into()) } - /// 6.3.16 get `Temporal.PlainDatezdt.prototype.nanosecond` + /// 6.3.16 get `Temporal.ZonedDateTime.prototype.nanosecond` fn get_nanosecond(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let zdt = this .as_object() @@ -498,11 +594,14 @@ impl ZonedDateTime { JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") })?; - Ok(zdt.inner.nanosecond_with_provider(context.tz_provider())?.into()) + Ok(zdt + .inner + .nanosecond_with_provider(context.tz_provider())? + .into()) } - /// 6.3.17 get `Temporal.PlainDatezdt.prototype.epochMilliseconds` - fn get_epoch_millisecond(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + /// 6.3.17 get `Temporal.ZonedDateTime.prototype.epochMilliseconds` + fn get_epoch_millisecond(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { let zdt = this .as_object() .and_then(JsObject::downcast_ref::) @@ -510,11 +609,12 @@ impl ZonedDateTime { JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") })?; - Ok(zdt.inner.epoch_milliseconds().into()) + // TODO: temporal_rs should update to i64 + Ok((zdt.inner.epoch_milliseconds() as i64).into()) } - /// 6.3.18 get `Temporal.PlainDatezdt.prototype.epochNanosecond` - fn get_epoch_nanosecond(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + /// 6.3.18 get `Temporal.ZonedDateTime.prototype.epochNanosecond` + fn get_epoch_nanosecond(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { let zdt = this .as_object() .and_then(JsObject::downcast_ref::) @@ -522,10 +622,10 @@ impl ZonedDateTime { JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") })?; - Ok(zdt.inner.epoch_nanoseconds().into()) + Ok(JsBigInt::from(zdt.inner.epoch_nanoseconds()).into()) } - /// 6.3.19 get `Temporal.PlainDatezdt.prototype.dayOfWeek` + /// 6.3.19 get `Temporal.ZonedDateTime.prototype.dayOfWeek` fn get_day_of_week(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let zdt = this .as_object() @@ -534,11 +634,13 @@ impl ZonedDateTime { JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") })?; - Err(JsNativeError::error().with_message("Not yet implemented.").into()) - // Ok(zdt.inner.day_of_week()?.into()) + Ok(zdt + .inner + .day_of_week_with_provider(context.tz_provider())? + .into()) } - /// 6.3.20 get `Temporal.PlainDatezdt.prototype.dayOfYear` + /// 6.3.20 get `Temporal.ZonedDateTime.prototype.dayOfYear` fn get_day_of_year(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let zdt = this .as_object() @@ -547,11 +649,13 @@ impl ZonedDateTime { JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") })?; - Err(JsNativeError::error().with_message("Not yet implemented.").into()) - // Ok(zdt.inner.day_of_year()?.into()) + Ok(zdt + .inner + .day_of_year_with_provider(context.tz_provider())? + .into()) } - /// 6.3.21 get `Temporal.PlainDatezdt.prototype.weekOfYear` + /// 6.3.21 get `Temporal.ZonedDateTime.prototype.weekOfYear` fn get_week_of_year(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let zdt = this .as_object() @@ -560,11 +664,13 @@ impl ZonedDateTime { JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") })?; - Err(JsNativeError::error().with_message("Not yet implemented.").into()) - // Ok(zdt.inner.week_of_year()?.into_or_undefined()) + Ok(zdt + .inner + .week_of_year_with_provider(context.tz_provider())? + .into_or_undefined()) } - /// 6.3.22 get `Temporal.PlainDatezdt.prototype.yearOfWeek` + /// 6.3.22 get `Temporal.ZonedDateTime.prototype.yearOfWeek` fn get_year_of_week(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let zdt = this .as_object() @@ -573,11 +679,13 @@ impl ZonedDateTime { JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") })?; - Err(JsNativeError::error().with_message("Not yet implemented.").into()) - // Ok(zdt.inner.year_of_week()?.into_or_undefined()) + Ok(zdt + .inner + .year_of_week_with_provider(context.tz_provider())? + .into_or_undefined()) } - /// 6.3.23 get `Temporal.PlainDatezdt.prototype.hoursInDay` + /// 6.3.23 get `Temporal.ZonedDateTime.prototype.hoursInDay` fn get_hours_in_day(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let zdt = this .as_object() @@ -586,10 +694,13 @@ impl ZonedDateTime { JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") })?; - Err(JsNativeError::error().with_message("Not yet implemented.").into()) + Ok(zdt + .inner + .hours_in_day_with_provider(context.tz_provider())? + .into()) } - /// 6.3.24 get `Temporal.PlainDatezdt.prototype.daysInWeek` + /// 6.3.24 get `Temporal.ZonedDateTime.prototype.daysInWeek` fn get_days_in_week(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let zdt = this .as_object() @@ -598,11 +709,13 @@ impl ZonedDateTime { JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") })?; - Err(JsNativeError::error().with_message("Not yet implemented.").into()) - // Ok(zdt.inner.days_in_week()?.into()) + Ok(zdt + .inner + .days_in_week_with_provider(context.tz_provider())? + .into()) } - /// 6.3.25 get `Temporal.PlainDatezdt.prototype.daysInMonth` + /// 6.3.25 get `Temporal.ZonedDateTime.prototype.daysInMonth` fn get_days_in_month( this: &JsValue, _: &[JsValue], @@ -615,11 +728,13 @@ impl ZonedDateTime { JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") })?; - Err(JsNativeError::error().with_message("Not yet implemented.").into()) - // Ok(zdt.inner.days_in_month()?.into()) + Ok(zdt + .inner + .days_in_month_with_provider(context.tz_provider())? + .into()) } - /// 6.3.26 get `Temporal.PlainDatezdt.prototype.daysInYear` + /// 6.3.26 get `Temporal.ZonedDateTime.prototype.daysInYear` fn get_days_in_year(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let zdt = this .as_object() @@ -628,11 +743,13 @@ impl ZonedDateTime { JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") })?; - Err(JsNativeError::error().with_message("Not yet implemented.").into()) - // Ok(zdt.inner.days_in_year()?.into()) + Ok(zdt + .inner + .days_in_year_with_provider(context.tz_provider())? + .into()) } - /// 6.3.27 get `Temporal.PlainDatezdt.prototype.monthsInYear` + /// 6.3.27 get `Temporal.ZonedDateTime.prototype.monthsInYear` fn get_months_in_year( this: &JsValue, _: &[JsValue], @@ -645,11 +762,13 @@ impl ZonedDateTime { JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") })?; - Err(JsNativeError::error().with_message("Not yet implemented.").into()) - // Ok(zdt.inner.months_in_year()?.into()) + Ok(zdt + .inner + .months_in_year_with_provider(context.tz_provider())? + .into()) } - /// 6.3.28 get `Temporal.PlainDatezdt.prototype.inLeapYear` + /// 6.3.28 get `Temporal.ZonedDateTime.prototype.inLeapYear` fn get_in_leap_year(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let zdt = this .as_object() @@ -658,8 +777,67 @@ impl ZonedDateTime { JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") })?; - Err(JsNativeError::error().with_message("Not yet implemented.").into()) - // Ok(zdt.inner.in_leap_year()?.into()) + Ok(zdt + .inner + .in_leap_year_with_provider(context.tz_provider())? + .into()) + } +} + +// ==== `ZonedDateTime` method implementations ==== + +impl ZonedDateTime { + /// 6.2.2 Temporal.ZonedDateTime.from ( item [ , options ] ) + fn from(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Return ? ToTemporalZonedDateTime(item, options). + let item = args.get_or_undefined(0); + let options = args.get(1); + let inner = to_temporal_zoneddatetime(item, options.cloned(), context)?; + create_temporal_zoneddatetime(inner, None, context).map(Into::into) + } + + fn add(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + let zdt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") + })?; + + let duration = to_temporal_duration(args.get_or_undefined(0), context)?; + + let options = get_options_object(args.get_or_undefined(1))?; + let overflow = get_option::(&options, js_string!("overflow"), context)?; + + create_temporal_zoneddatetime( + zdt.inner + .add_with_provider(&duration, overflow, context.tz_provider())?, + None, + context, + ) + .map(Into::into) + } + + fn subtract(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + let zdt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") + })?; + + let duration = to_temporal_duration(args.get_or_undefined(0), context)?; + + let options = get_options_object(args.get_or_undefined(1))?; + let overflow = get_option::(&options, js_string!("overflow"), context)?; + + create_temporal_zoneddatetime( + zdt.inner + .subtract_with_provider(&duration, overflow, context.tz_provider())?, + None, + context, + ) + .map(Into::into) } } @@ -679,11 +857,14 @@ pub(crate) fn create_temporal_zoneddatetime( .constructors() .zoned_date_time() .constructor() - .into() + .into(), ); // 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.ZonedDateTime.prototype%", « [[InitializezdtemporalZonedDateTime]], [[EpochNanoseconds]], [[TimeZone]], [[Calendar]] »). - let prototype = - get_prototype_from_constructor(&new_target, StandardConstructors::zoned_date_time, context)?; + let prototype = get_prototype_from_constructor( + &new_target, + StandardConstructors::zoned_date_time, + context, + )?; // 4. Set object.[[EpochNanoseconds]] to epochNanoseconds. // 5. Set object.[[TimeZone]] to timeZone. // 6. Set object.[[Calendar]] to calendar. @@ -692,3 +873,183 @@ pub(crate) fn create_temporal_zoneddatetime( // 7. Return object. Ok(obj) } + +pub(crate) fn to_temporal_zoneddatetime( + value: &JsValue, + options: Option, + context: &mut Context, +) -> JsResult { + // 1. If options is not present, set options to undefined. + // 2. Let offsetBehaviour be option. + // 3. Let matchBehaviour be match-exactly. + // 4. If item is an Object, then + match value { + JsValue::Object(object) => { + // a. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then + if let Some(zdt) = object.downcast_ref::() { + // i. NOTE: The following steps, and similar ones below, read options + // and perform independent validation in alphabetical order + // (GetTemporalDisambiguationOption reads "disambiguation", GetTemporalOffsetOption + // reads "offset", and GetTemporalOverflowOption reads "overflow"). + // ii. Let resolvedOptions be ? GetOptionsObject(options). + let options = get_options_object(&options.unwrap_or_default())?; + // iii. Perform ? GetTemporalDisambiguationOption(resolvedOptions). + let _disambiguation = + get_option::(&options, js_string!("disambiguation"), context)? + .unwrap_or(Disambiguation::Compatible); + // iv. Perform ? GetTemporalOffsetOption(resolvedOptions, reject). + let _offset_option = + get_option::(&options, js_string!("offset"), context)? + .unwrap_or(OffsetDisambiguation::Reject); + // v. Perform ? GetTemporalOverflowOption(resolvedOptions). + let _overflow = + get_option::(&options, js_string!("overflow"), context)? + .unwrap_or_default(); + // vi. Return ! CreateTemporalZonedDateTime(item.[[EpochNanoseconds]], item.[[TimeZone]], item.[[Calendar]]). + return Ok(zdt.inner.clone()); + } + // b. Let calendar be ? GetTemporalCalendarIdentifierWithISODefault(item). + let calendar = get_temporal_calendar_slot_value_with_default(object, context)?; + // c. Let fields be ? PrepareCalendarFields(calendar, item, « year, month, month-code, day », « hour, minute, second, millisecond, microsecond, nanosecond, offset, time-zone », « time-zone »). + let date = to_partial_date_record(object, context)?; + let time = to_partial_time_record(object, context)?; + // d. Let timeZone be fields.[[TimeZone]]. + let timezone = object + .get(js_string!("timeZone"), context)? + .map(|v| { + // TODO: to_temporal_timezone_identifier + to_temporal_timezone_identifier(v, context) + }) + .transpose()? + .unwrap_or_default(); + // e. Let offsetString be fields.[[OffsetString]]. + let offset = object + .get(js_string!("offset"), context)? + .map(|v| to_offset_string(v, context)) + .transpose()?; + let partial = PartialZonedDateTime { + date, + time, + offset, + timezone, + }; + // f. If offsetString is unset, then + // i. Set offsetBehaviour to wall. + // g. Let resolvedOptions be ? GetOptionsObject(options). + let options = get_options_object(&options.unwrap_or_default())?; + // h. Let disambiguation be ? GetTemporalDisambiguationOption(resolvedOptions). + let disambiguation = + get_option::(&options, js_string!("disambiguation"), context)?; + // i. Let offsetOption be ? GetTemporalOffsetOption(resolvedOptions, reject). + let offset_option = + get_option::(&options, js_string!("offset"), context)?; + // j. Let overflow be ? GetTemporalOverflowOption(resolvedOptions). + let overflow = + get_option::(&options, js_string!("overflow"), context)?; + // k. Let result be ? InterpretTemporalDateTimeFields(calendar, fields, overflow). + // l. Let isoDate be result.[[ISODate]]. + // m. Let time be result.[[Time]]. + Ok(ZonedDateTimeInner::from_partial_with_provider( + partial, + Some(calendar), + overflow, + disambiguation, + offset_option, + context.tz_provider(), + )?) + } + JsValue::String(zdt_source) => { + // b. Let result be ? ParseISODateTime(item, « TemporalDateTimeString[+Zoned] »). + // c. Let annotation be result.[[TimeZone]].[[TimeZoneAnnotation]]. + // d. Assert: annotation is not empty. + // e. Let timeZone be ? ToTemporalTimeZoneIdentifier(annotation). + // f. Let offsetString be result.[[TimeZone]].[[OffsetString]]. + // g. If result.[[TimeZone]].[[Z]] is true, then + // i. Set offsetBehaviour to exact. + // h. Else if offsetString is empty, then + // i. Set offsetBehaviour to wall. + // i. Let calendar be result.[[Calendar]]. + // j. If calendar is empty, set calendar to "iso8601". + // k. Set calendar to ? CanonicalizeCalendar(calendar). + // l. Set matchBehaviour to match-minutes. + // m. Let resolvedOptions be ? GetOptionsObject(options). + let options = get_options_object(&options.unwrap_or_default())?; + // n. Let disambiguation be ? GetTemporalDisambiguationOption(resolvedOptions). + let disambiguation = + get_option::(&options, js_string!("disambiguation"), context)? + .unwrap_or(Disambiguation::Compatible); + // o. Let offsetOption be ? GetTemporalOffsetOption(resolvedOptions, reject). + let offset_option = + get_option::(&options, js_string!("offset"), context)? + .unwrap_or(OffsetDisambiguation::Reject); + // p. Perform ? GetTemporalOverflowOption(resolvedOptions). + // q. Let isoDate be CreateISODateRecord(result.[[Year]], result.[[Month]], result.[[Day]]). + // r. Let time be result.[[Time]]. + // 6. Let offsetNanoseconds be 0. + // 7. If offsetBehaviour is option, then + // a. Set offsetNanoseconds to ! ParseDateTimeUTCOffset(offsetString). + // 8. Let epochNanoseconds be ? InterpretISODateTimeOffset(isoDate, time, offsetBehaviour, offsetNanoseconds, timeZone, disambiguation, offsetOption, matchBehaviour). + Ok(ZonedDateTimeInner::from_str_with_provider( + &zdt_source.to_std_string_escaped(), + disambiguation, + offset_option, + context.tz_provider(), + )?) + } + // 5. Else, + // a. If item is not a String, throw a TypeError exception. + _ => Err(JsNativeError::typ() + .with_message("Temporal.ZonedDateTime.from only accepts an object or string.") + .into()), + } + // 9. Return ! CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar). +} + +pub(crate) fn to_temporal_timezone_identifier( + value: &JsValue, + context: &mut Context, +) -> JsResult { + // 1. If temporalTimeZoneLike is an Object, then + if let Some(obj) = value.as_object() { + // a. If temporalTimeZoneLike has an [[InitializedTemporalZonedDateTime]] internal slot, then + if let Some(zdt) = obj.downcast_ref::() { + // i. Return temporalTimeZoneLike.[[TimeZone]]. + return Ok(zdt.inner.timezone().clone()); + } + } + + // 2. If temporalTimeZoneLike is not a String, throw a TypeError exception. + let JsValue::String(tz_string) = value else { + return Err(JsNativeError::typ() + .with_message("timeZone must be a string or Temporal.ZonedDateTime") + .into()); + }; + + // 3. Let parseResult be ? ParseTemporalTimeZoneString(temporalTimeZoneLike). + // 4. Let offsetMinutes be parseResult.[[OffsetMinutes]]. + // 5. If offsetMinutes is not empty, return FormatOffsetTimeZoneIdentifier(offsetMinutes). + // 6. Let name be parseResult.[[Name]]. + // 7. Let timeZoneIdentifierRecord be GetAvailableNamedTimeZoneIdentifier(name). + // 8. If timeZoneIdentifierRecord is empty, throw a RangeError exception. + // 9. Return timeZoneIdentifierRecord.[[Identifier]]. + Ok(TimeZone::try_from_str_with_provider( + &tz_string.to_std_string_escaped(), + context.tz_provider(), + )?) +} + +fn to_offset_string(value: &JsValue, context: &mut Context) -> JsResult { + // 1. Let offset be ? ToPrimitive(argument, string). + let offset = value.to_primitive(context, PreferredType::String)?; + // 2. If offset is not a String, throw a TypeError exception. + let JsValue::String(offset_string) = offset else { + return Err(JsNativeError::typ() + .with_message("offset must be a String.") + .into()); + }; + // 3. Perform ? ParseDateTimeUTCOffset(offset). + let result = offset_string.to_std_string_escaped(); + let _u = TimeZone::try_from_str_with_provider(&result, context.tz_provider())?; + // 4. Return offset. + Ok(result) +} diff --git a/core/engine/src/context/mod.rs b/core/engine/src/context/mod.rs index 207b74b9fe1..a5dfd173070 100644 --- a/core/engine/src/context/mod.rs +++ b/core/engine/src/context/mod.rs @@ -10,7 +10,7 @@ pub use hooks::{DefaultHooks, HostHooks}; #[cfg(feature = "intl")] pub use icu::IcuError; use intrinsics::Intrinsics; -#[cfg(feature="temporal")] +#[cfg(feature = "temporal")] use temporal_rs::tzdb::FsTzdbProvider; use crate::vm::RuntimeLimits; @@ -102,7 +102,7 @@ pub struct Context { can_block: bool, - #[cfg(feature ="temporal")] + #[cfg(feature = "temporal")] tz_provider: FsTzdbProvider, /// Intl data provider. From 57036221c33d90432cf6a15a477a8ec2007c8fff Mon Sep 17 00:00:00 2001 From: Kevin Ness <46825870+nekevss@users.noreply.github.com> Date: Sat, 7 Dec 2024 15:54:02 -0600 Subject: [PATCH 03/14] Update temporal to fixed version --- Cargo.lock | 2 +- Cargo.toml | 2 +- .../src/builtins/temporal/instant/mod.rs | 58 ------------------- .../builtins/temporal/zoneddatetime/mod.rs | 3 +- 4 files changed, 3 insertions(+), 62 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3eedec8f75d..ca4c9b3b52c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3346,7 +3346,7 @@ checksum = "42a4d50cdb458045afc8131fd91b64904da29548bcb63c7236e0844936c13078" [[package]] name = "temporal_rs" version = "0.0.4" -source = "git+https://github.com/boa-dev/temporal.git?rev=1fd02efe568bb5a3d5f9836fef871d91940e36c7#1fd02efe568bb5a3d5f9836fef871d91940e36c7" +source = "git+https://github.com/boa-dev/temporal.git?rev=f7fa90083987954f0974ac0167a7f0b4a03f77bf#f7fa90083987954f0974ac0167a7f0b4a03f77bf" dependencies = [ "bitflags 2.6.0", "combine", diff --git a/Cargo.toml b/Cargo.toml index 7846cfcc24d..0c6ec225a19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -111,7 +111,7 @@ intrusive-collections = "0.9.7" cfg-if = "1.0.0" either = "1.13.0" sys-locale = "0.3.2" -temporal_rs = { git = "https://github.com/boa-dev/temporal.git", rev = "1fd02efe568bb5a3d5f9836fef871d91940e36c7", features = ["tzdb", "now"] } +temporal_rs = { git = "https://github.com/boa-dev/temporal.git", rev = "f7fa90083987954f0974ac0167a7f0b4a03f77bf", features = ["tzdb", "now"] } web-time = "1.1.0" criterion = "0.5.1" float-cmp = "0.10.0" diff --git a/core/engine/src/builtins/temporal/instant/mod.rs b/core/engine/src/builtins/temporal/instant/mod.rs index a377dd09763..df9e35af89b 100644 --- a/core/engine/src/builtins/temporal/instant/mod.rs +++ b/core/engine/src/builtins/temporal/instant/mod.rs @@ -46,18 +46,10 @@ impl IntrinsicObject for Instant { fn init(realm: &Realm) { let _timer = Profiler::global().start_event(std::any::type_name::(), "init"); - let get_seconds = BuiltInBuilder::callable(realm, Self::get_epoch_seconds) - .name(js_string!("get epochSeconds")) - .build(); - let get_millis = BuiltInBuilder::callable(realm, Self::get_epoch_milliseconds) .name(js_string!("get epochMilliseconds")) .build(); - let get_micros = BuiltInBuilder::callable(realm, Self::get_epoch_microseconds) - .name(js_string!("get epochMicroseconds")) - .build(); - let get_nanos = BuiltInBuilder::callable(realm, Self::get_epoch_nanoseconds) .name(js_string!("get epochNanoseconds")) .build(); @@ -68,24 +60,12 @@ impl IntrinsicObject for Instant { StaticJsStrings::INSTANT_TAG, Attribute::CONFIGURABLE, ) - .accessor( - js_string!("epochSeconds"), - Some(get_seconds), - None, - Attribute::CONFIGURABLE, - ) .accessor( js_string!("epochMilliseconds"), Some(get_millis), None, Attribute::CONFIGURABLE, ) - .accessor( - js_string!("epochMicroseconds"), - Some(get_micros), - None, - Attribute::CONFIGURABLE, - ) .accessor( js_string!("epochNanoseconds"), Some(get_nanos), @@ -225,24 +205,6 @@ impl Instant { // ==== Instant method implementations ==== impl Instant { - /// 8.3.3 get Temporal.Instant.prototype.epochSeconds - pub(crate) fn get_epoch_seconds( - this: &JsValue, - _: &[JsValue], - _: &mut Context, - ) -> JsResult { - // 1. Let instant be the this value. - // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). - let instant = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be an instant object.") - })?; - // 3. Let ns be instant.[[Nanoseconds]]. - Ok(JsBigInt::from(instant.inner.epoch_seconds()).into()) - } - /// 8.3.4 get Temporal.Instant.prototype.epochMilliseconds pub(crate) fn get_epoch_milliseconds( this: &JsValue, @@ -263,26 +225,6 @@ impl Instant { Ok(JsBigInt::from(instant.inner.epoch_milliseconds()).into()) } - /// 8.3.5 get Temporal.Instant.prototype.epochMicroseconds - pub(crate) fn get_epoch_microseconds( - this: &JsValue, - _: &[JsValue], - _: &mut Context, - ) -> JsResult { - // 1. Let instant be the this value. - // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). - let instant = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be an instant object.") - })?; - // 3. Let ns be instant.[[Nanoseconds]]. - // 4. Let µs be floor(ℝ(ns) / 103). - // 5. Return ℤ(µs). - Ok(JsBigInt::from(instant.inner.epoch_microseconds()).into()) - } - /// 8.3.6 get Temporal.Instant.prototype.epochNanoseconds pub(crate) fn get_epoch_nanoseconds( this: &JsValue, diff --git a/core/engine/src/builtins/temporal/zoneddatetime/mod.rs b/core/engine/src/builtins/temporal/zoneddatetime/mod.rs index 95b17cd1fa5..f4d16cf5a7f 100644 --- a/core/engine/src/builtins/temporal/zoneddatetime/mod.rs +++ b/core/engine/src/builtins/temporal/zoneddatetime/mod.rs @@ -609,8 +609,7 @@ impl ZonedDateTime { JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") })?; - // TODO: temporal_rs should update to i64 - Ok((zdt.inner.epoch_milliseconds() as i64).into()) + Ok((zdt.inner.epoch_milliseconds()).into()) } /// 6.3.18 get `Temporal.ZonedDateTime.prototype.epochNanosecond` From 5082e819b1791a6fb4a847f2371a85048f22622f Mon Sep 17 00:00:00 2001 From: Kevin Ness <46825870+nekevss@users.noreply.github.com> Date: Sat, 7 Dec 2024 18:40:27 -0600 Subject: [PATCH 04/14] zonedDateTime -> zonedDateTimeISO --- core/engine/src/builtins/temporal/now.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/engine/src/builtins/temporal/now.rs b/core/engine/src/builtins/temporal/now.rs index d17fbf07363..f16c9f79411 100644 --- a/core/engine/src/builtins/temporal/now.rs +++ b/core/engine/src/builtins/temporal/now.rs @@ -41,7 +41,7 @@ impl IntrinsicObject for Now { .static_method(Self::time_zone_id, js_string!("timeZoneId"), 0) .static_method(Self::instant, js_string!("instant"), 0) .static_method(Self::plain_datetime, js_string!("plainDateTimeISO"), 0) - .static_method(Self::zoned_date_time, js_string!("zonedDateTime"), 0) + .static_method(Self::zoned_date_time, js_string!("zonedDateTimeISO"), 0) .static_method(Self::plain_date, js_string!("plainDateISO"), 0) .build(); } From 1fdb60c007b51870e53d073786b993933573e486 Mon Sep 17 00:00:00 2001 From: Kevin Ness <46825870+nekevss@users.noreply.github.com> Date: Sat, 7 Dec 2024 21:41:40 -0600 Subject: [PATCH 05/14] Bump test262 commit --- test262_config.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test262_config.toml b/test262_config.toml index a2d9285ffa9..bea6606a31d 100644 --- a/test262_config.toml +++ b/test262_config.toml @@ -1,4 +1,4 @@ -commit = "dde3050bdbfb8f425084077b6293563932d57ebc" +commit = "dad2774b2eab2119cc8390ae65db4a5016de2dfe" [ignored] # Not implemented yet: From 7cca60d8d6a367500277d32ab544406aa6137bef Mon Sep 17 00:00:00 2001 From: Kevin Ness <46825870+nekevss@users.noreply.github.com> Date: Sat, 7 Dec 2024 21:51:26 -0600 Subject: [PATCH 06/14] Add import-defer to ignore list --- test262_config.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test262_config.toml b/test262_config.toml index bea6606a31d..ef5899e3b4c 100644 --- a/test262_config.toml +++ b/test262_config.toml @@ -29,6 +29,9 @@ features = [ # https://github.com/tc39/proposal-import-attributes "import-assertions", + # https://tc39.es/proposal-defer-import-eval + "import-defer", + # https://github.com/tc39/proposal-json-modules "json-modules", From cbe27c3a6990f0f4beaa34913ee064c163d2b615 Mon Sep 17 00:00:00 2001 From: Kevin Ness <46825870+nekevss@users.noreply.github.com> Date: Sat, 7 Dec 2024 22:17:11 -0600 Subject: [PATCH 07/14] Add import-defer to feature edition --- tests/tester/src/edition.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/tester/src/edition.rs b/tests/tester/src/edition.rs index 110169dd9b9..4c17b2c6f12 100644 --- a/tests/tester/src/edition.rs +++ b/tests/tester/src/edition.rs @@ -33,6 +33,10 @@ static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! { // https://github.com/tc39/proposal-import-attributes/ "import-attributes" => SpecEdition::ESNext, + // Import Defer + // https://tc39.es/proposal-defer-import-eval + "import-defer" => SpecEdition::ESNext, + // Import Assertions // https://github.com/tc39/proposal-import-assertions/ "import-assertions" => SpecEdition::ESNext, From 738eab20b7b8ce00d37a73c7ee902d9cd97d2f6d Mon Sep 17 00:00:00 2001 From: Kevin Ness <46825870+nekevss@users.noreply.github.com> Date: Sat, 7 Dec 2024 22:39:10 -0600 Subject: [PATCH 08/14] Add other adjustments for test262 bump --- test262_config.toml | 3 +++ tests/tester/src/edition.rs | 4 ++++ tests/tester/src/read.rs | 2 ++ 3 files changed, 9 insertions(+) diff --git a/test262_config.toml b/test262_config.toml index ef5899e3b4c..f3020ef1272 100644 --- a/test262_config.toml +++ b/test262_config.toml @@ -16,6 +16,9 @@ features = [ ### Pending proposals + # https://github.com/tc39/proposal-is-error + "Error.isError", + # https://github.com/tc39/proposal-intl-locale-info "Intl.Locale-info", diff --git a/tests/tester/src/edition.rs b/tests/tester/src/edition.rs index 4c17b2c6f12..38ca0896665 100644 --- a/tests/tester/src/edition.rs +++ b/tests/tester/src/edition.rs @@ -13,6 +13,10 @@ use crate::read::{MetaData, TestFlag}; static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! { // Proposed language features + // Error.isError + // https://github.com/tc39/proposal-is-error + "Error.isError" => SpecEdition::ESNext, + // Intl.Locale Info // https://github.com/tc39/proposal-intl-locale-info "Intl.Locale-info" => SpecEdition::ESNext, diff --git a/tests/tester/src/read.rs b/tests/tester/src/read.rs index 68a9856cf51..1fac7223c2d 100644 --- a/tests/tester/src/read.rs +++ b/tests/tester/src/read.rs @@ -56,6 +56,7 @@ pub(super) enum ErrorType { ReferenceError, RangeError, TypeError, + EvalError, } impl ErrorType { @@ -66,6 +67,7 @@ impl ErrorType { Self::ReferenceError => "ReferenceError", Self::RangeError => "RangeError", Self::TypeError => "TypeError", + Self::EvalError => "EvalError", } } } From 0b45446786ad297e338ef7a70256dab87bdd15f6 Mon Sep 17 00:00:00 2001 From: Kevin Ness <46825870+nekevss@users.noreply.github.com> Date: Sat, 7 Dec 2024 23:50:37 -0600 Subject: [PATCH 09/14] Remove expects from month-day to address panics --- .../builtins/temporal/plain_month_day/mod.rs | 59 ++++++++++--------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/core/engine/src/builtins/temporal/plain_month_day/mod.rs b/core/engine/src/builtins/temporal/plain_month_day/mod.rs index c2dce5b9d9e..75710c3d410 100644 --- a/core/engine/src/builtins/temporal/plain_month_day/mod.rs +++ b/core/engine/src/builtins/temporal/plain_month_day/mod.rs @@ -25,7 +25,7 @@ use temporal_rs::{ PlainDateTime, PlainMonthDay as InnerMonthDay, TinyAsciiStr, }; -use super::{calendar::to_temporal_calendar_slot_value, DateTimeValues}; +use super::{calendar::to_temporal_calendar_slot_value, to_integer_if_integral, DateTimeValues}; /// The `Temporal.PlainMonthDay` object. #[derive(Debug, Clone, Trace, Finalize, JsData)] @@ -315,40 +315,41 @@ fn to_temporal_month_day( InnerMonthDay::from_str(item_string.to_std_string_escaped().as_str())? } else if item.is_object() { let day = item - .get_v(js_string!("day"), context) - .expect("Day not found") - .to_i32(context) - .expect("Cannot convert day to i32"); + .get_v(js_string!("day"), context)? + .map(|v| to_integer_if_integral(v, context)) + .transpose()?; + let month = item - .get_v(js_string!("month"), context) - .expect("Month not found") - .to_i32(context) - .expect("Cannot convert month to i32"); + .get_v(js_string!("month"), context)? + .map(|v| to_integer_if_integral(v, context)) + .transpose()?; let month_code = item - .get_v(js_string!("monthCode"), context) - .expect("monthCode not found"); - let resolved_month_code = if month_code.is_undefined() { - None - } else { - TinyAsciiStr::<4>::from_str( - &month_code - .to_string(context) - .expect("Cannot convert monthCode to string") - .to_std_string_escaped(), - ) - .map_err(|e| JsError::from(JsNativeError::range().with_message(e.to_string()))) - .ok() - }; - let year = item.get_v(js_string!("year"), context).map_or(1972, |val| { - val.to_i32(context).expect("Cannot convert year to i32") - }); + .get_v(js_string!("monthCode"), context)? + .map(|v| { + let JsValue::String(month_code) = + v.to_primitive(context, crate::value::PreferredType::String)? + else { + return Err(JsNativeError::typ() + .with_message("The monthCode field value must be a string.") + .into()); + }; + TinyAsciiStr::<4>::from_str(&month_code.to_std_string_escaped()) + .map_err(|e| JsError::from(JsNativeError::typ().with_message(e.to_string()))) + }) + .transpose()?; + + let year = item + .get_v(js_string!("year"), context)? + .map(|v| to_integer_if_integral(&v, context)) + .transpose()? + .unwrap_or(1972); let partial_date = &PartialDate { - month: Some(month), - day: Some(day), + month, + day, year: Some(year), - month_code: resolved_month_code, + month_code, ..Default::default() }; From d250da9b65547f553b385f01d66cfb4db3d3faea Mon Sep 17 00:00:00 2001 From: Kevin Ness <46825870+nekevss@users.noreply.github.com> Date: Sat, 7 Dec 2024 23:51:18 -0600 Subject: [PATCH 10/14] Fix panics with Date object --- core/engine/src/builtins/date/mod.rs | 169 +++++++++++++++++++++------ 1 file changed, 134 insertions(+), 35 deletions(-) diff --git a/core/engine/src/builtins/date/mod.rs b/core/engine/src/builtins/date/mod.rs index 9e255893e23..b90d76e6b59 100644 --- a/core/engine/src/builtins/date/mod.rs +++ b/core/engine/src/builtins/date/mod.rs @@ -816,13 +816,19 @@ impl Date { ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). - let mut date_object = this + let date = this .as_object() - .and_then(JsObject::downcast_mut::) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; // 3. Let t be dateObject.[[DateValue]]. - let mut t = date_object.0; + let mut t = date.0; + + // NOTE (nekevss): `downcast_ref` is used and then dropped for a short lived borrow. + // ToNumber() may call userland code which can modify the underlying date + // which will cause a panic. In order to avoid this, we drop the borrow, + // here and only `downcast_mut` when date will be modified. + drop(date); // 4. Let dt be ? ToNumber(date). let dt = args.get_or_undefined(0).to_number(context)?; @@ -851,8 +857,13 @@ impl Date { time_clip(new_date) }; + let mut date_mut = this + .as_object() + .and_then(JsObject::downcast_mut::) + .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; + // 9. Set dateObject.[[DateValue]] to u. - date_object.0 = u; + date_mut.0 = u; // 10. Return u. Ok(JsValue::from(u)) @@ -873,13 +884,19 @@ impl Date { ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). - let mut date_object = this + let date = this .as_object() - .and_then(JsObject::downcast_mut::) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; // 3. Let t be dateObject.[[DateValue]]. - let t = date_object.0; + let t = date.0; + + // NOTE (nekevss): `downcast_ref` is used and then dropped for a short lived borrow. + // ToNumber() may call userland code which can modify the underlying date + // which will cause a panic. In order to avoid this, we drop the borrow, + // here and only `downcast_mut` when date will be modified. + drop(date); let t = if LOCAL { // 5. If t is NaN, set t to +0𝔽; otherwise, set t to LocalTime(t). @@ -925,8 +942,13 @@ impl Date { time_clip(new_date) }; + let mut date_mut = this + .as_object() + .and_then(JsObject::downcast_mut::) + .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; + // 10. Set dateObject.[[DateValue]] to u. - date_object.0 = u; + date_mut.0 = u; // 11. Return u. Ok(JsValue::from(u)) @@ -949,13 +971,19 @@ impl Date { ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). - let mut date_object = this + let date = this .as_object() - .and_then(JsObject::downcast_mut::) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; // 3. Let t be dateObject.[[DateValue]]. - let mut t = date_object.0; + let mut t = date.0; + + // NOTE (nekevss): `downcast_ref` is used and then dropped for a short lived borrow. + // ToNumber() may call userland code which can modify the underlying date + // which will cause a panic. In order to avoid this, we drop the borrow, + // here and only `downcast_mut` when date will be modified. + drop(date); // 4. Let h be ? ToNumber(hour). let h = args.get_or_undefined(0).to_number(context)?; @@ -999,8 +1027,13 @@ impl Date { time_clip(date) }; + let mut date_mut = this + .as_object() + .and_then(JsObject::downcast_mut::) + .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; + // 15. Set dateObject.[[DateValue]] to u. - date_object.0 = u; + date_mut.0 = u; // 16. Return u. Ok(JsValue::from(u)) @@ -1020,13 +1053,19 @@ impl Date { ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). - let mut date_object = this + let date = this .as_object() - .and_then(JsObject::downcast_mut::) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; // 3. Let t be dateObject.[[DateValue]]. - let mut t = date_object.0; + let mut t = date.0; + + // NOTE (nekevss): `downcast_ref` is used and then dropped for a short lived borrow. + // ToNumber() may call userland code which can modify the underlying date + // which will cause a panic. In order to avoid this, we drop the borrow, + // here and only `downcast_mut` when date will be modified. + drop(date); // 4. Set ms to ? ToNumber(ms). let ms = args.get_or_undefined(0).to_number(context)?; @@ -1057,8 +1096,13 @@ impl Date { time_clip(make_date(day(t), time)) }; + let mut date_mut = this + .as_object() + .and_then(JsObject::downcast_mut::) + .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; + // 9. Set dateObject.[[DateValue]] to u. - date_object.0 = u; + date_mut.0 = u; // 10. Return u. Ok(JsValue::from(u)) @@ -1078,13 +1122,19 @@ impl Date { ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). - let mut date_object = this + let date = this .as_object() - .and_then(JsObject::downcast_mut::) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; // 3. Let t be dateObject.[[DateValue]]. - let mut t = date_object.0; + let mut t = date.0; + + // NOTE (nekevss): `downcast_ref` is used and then dropped for a short lived borrow. + // ToNumber() may call userland code which can modify the underlying date + // which will cause a panic. In order to avoid this, we drop the borrow, + // here and only `downcast_mut` when date will be modified. + drop(date); // 4. Let m be ? ToNumber(min). let m = args.get_or_undefined(0).to_number(context)?; @@ -1122,8 +1172,13 @@ impl Date { time_clip(date) }; + let mut date_mut = this + .as_object() + .and_then(JsObject::downcast_mut::) + .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; + // 13. Set dateObject.[[DateValue]] to u. - date_object.0 = u; + date_mut.0 = u; // 14. Return u. Ok(JsValue::from(u)) @@ -1144,13 +1199,19 @@ impl Date { ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). - let mut date_object = this + let date = this .as_object() - .and_then(JsObject::downcast_mut::) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; // 3. Let t be dateObject.[[DateValue]]. - let mut t = date_object.0; + let mut t = date.0; + + // NOTE (nekevss): `downcast_ref` is used and then dropped for a short lived borrow. + // ToNumber() may call userland code which can modify the underlying date + // which will cause a panic. In order to avoid this, we drop the borrow, + // here and only `downcast_mut` when date will be modified. + drop(date); // 4. Let m be ? ToNumber(month). let m = args.get_or_undefined(0).to_number(context)?; @@ -1185,8 +1246,13 @@ impl Date { time_clip(new_date) }; + let mut date_mut = this + .as_object() + .and_then(JsObject::downcast_mut::) + .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; + // 11. Set dateObject.[[DateValue]] to u. - date_object.0 = u; + date_mut.0 = u; // 12. Return u. Ok(JsValue::from(u)) @@ -1206,13 +1272,19 @@ impl Date { ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). - let mut date_object = this + let date = this .as_object() - .and_then(JsObject::downcast_mut::) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; // 3. Let t be dateObject.[[DateValue]]. - let mut t = date_object.0; + let mut t = date.0; + + // NOTE (nekevss): `downcast_ref` is used and then dropped for a short lived borrow. + // ToNumber() may call userland code which can modify the underlying date + // which will cause a panic. In order to avoid this, we drop the borrow, + // here and only `downcast_mut` when date will be modified. + drop(date); // 4. Let s be ? ToNumber(sec). let s = args.get_or_undefined(0).to_number(context)?; @@ -1247,8 +1319,13 @@ impl Date { time_clip(date) }; + let mut date_mut = this + .as_object() + .and_then(JsObject::downcast_mut::) + .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; + // 11. Set dateObject.[[DateValue]] to u. - date_object.0 = u; + date_mut.0 = u; // 12. Return u. Ok(JsValue::from(u)) @@ -1275,13 +1352,19 @@ impl Date { ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). - let mut date_object = this + let date = this .as_object() - .and_then(JsObject::downcast_mut::) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; // 3. Let t be dateObject.[[DateValue]]. - let t = date_object.0; + let t = date.0; + + // NOTE (nekevss): `downcast_ref` is used and then dropped for a short lived borrow. + // ToNumber() may call userland code which can modify the underlying date + // which will cause a panic. In order to avoid this, we drop the borrow, + // here and only `downcast_mut` when date will be modified. + drop(date); // 4. Let y be ? ToNumber(year). let y = args.get_or_undefined(0).to_number(context)?; @@ -1305,8 +1388,13 @@ impl Date { // 9. Let u be TimeClip(UTC(date)). let u = time_clip(utc_t(date, context.host_hooks())); + let mut date_mut = this + .as_object() + .and_then(JsObject::downcast_mut::) + .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; + // 10. Set dateObject.[[DateValue]] to u. - date_object.0 = u; + date_mut.0 = u; // 11. Return u. Ok(JsValue::from(u)) @@ -1329,19 +1417,30 @@ impl Date { ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). - let mut date_object = this + let date = this .as_object() - .and_then(JsObject::downcast_mut::) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; // 3. Let t be ? ToNumber(time). let t = args.get_or_undefined(0).to_number(context)?; + // NOTE (nekevss): `downcast_ref` is used and then dropped for a short lived borrow. + // ToNumber() may call userland code which can modify the underlying date + // which will cause a panic. In order to avoid this, we drop the borrow, + // here and only `downcast_mut` when date will be modified. + drop(date); + // 4. Let v be TimeClip(t). let v = time_clip(t); + let mut date_mut = this + .as_object() + .and_then(JsObject::downcast_mut::) + .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; + // 5. Set dateObject.[[DateValue]] to v. - date_object.0 = v; + date_mut.0 = v; // 6. Return v. Ok(JsValue::from(v)) From 133478eead7a50e66816d17b4bdff75a5dd3709e Mon Sep 17 00:00:00 2001 From: Kevin Ness <46825870+nekevss@users.noreply.github.com> Date: Sat, 7 Dec 2024 23:55:04 -0600 Subject: [PATCH 11/14] Missed one of the clippy lints --- core/engine/src/builtins/temporal/plain_month_day/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/engine/src/builtins/temporal/plain_month_day/mod.rs b/core/engine/src/builtins/temporal/plain_month_day/mod.rs index 75710c3d410..0c50c868948 100644 --- a/core/engine/src/builtins/temporal/plain_month_day/mod.rs +++ b/core/engine/src/builtins/temporal/plain_month_day/mod.rs @@ -341,7 +341,7 @@ fn to_temporal_month_day( let year = item .get_v(js_string!("year"), context)? - .map(|v| to_integer_if_integral(&v, context)) + .map(|v| to_integer_if_integral(v, context)) .transpose()? .unwrap_or(1972); From a3dc6ac6b145b180e7d06d96e100768b4e2a98d4 Mon Sep 17 00:00:00 2001 From: Kevin Ness <46825870+nekevss@users.noreply.github.com> Date: Sun, 8 Dec 2024 01:03:15 -0600 Subject: [PATCH 12/14] Adjustments to address some broken tests + added PlainDate tests broken down from test262 --- core/engine/src/builtins/temporal/error.rs | 5 +- .../src/builtins/temporal/plain_date/mod.rs | 50 ++++++++++++------- .../src/builtins/temporal/plain_date/tests.rs | 46 +++++++++++++++++ .../builtins/temporal/plain_date_time/mod.rs | 2 +- 4 files changed, 82 insertions(+), 21 deletions(-) create mode 100644 core/engine/src/builtins/temporal/plain_date/tests.rs diff --git a/core/engine/src/builtins/temporal/error.rs b/core/engine/src/builtins/temporal/error.rs index 4b7f3e73092..69f983e4bc3 100644 --- a/core/engine/src/builtins/temporal/error.rs +++ b/core/engine/src/builtins/temporal/error.rs @@ -5,10 +5,11 @@ use crate::{JsError, JsNativeError}; impl From for JsNativeError { fn from(value: TemporalError) -> Self { match value.kind() { - ErrorKind::Range => JsNativeError::range().with_message(value.message().to_owned()), + ErrorKind::Range | ErrorKind::Syntax => { + JsNativeError::range().with_message(value.message().to_owned()) + } ErrorKind::Type => JsNativeError::typ().with_message(value.message().to_owned()), ErrorKind::Generic => JsNativeError::error().with_message(value.message().to_owned()), - ErrorKind::Syntax => JsNativeError::syntax().with_message(value.message().to_owned()), ErrorKind::Assert => JsNativeError::error().with_message("internal engine error"), } } diff --git a/core/engine/src/builtins/temporal/plain_date/mod.rs b/core/engine/src/builtins/temporal/plain_date/mod.rs index c3f2b43789b..cc218ea417c 100644 --- a/core/engine/src/builtins/temporal/plain_date/mod.rs +++ b/core/engine/src/builtins/temporal/plain_date/mod.rs @@ -32,6 +32,10 @@ use super::{ to_temporal_duration_record, to_temporal_time, PlainDateTime, ZonedDateTime, }; +#[cfg(feature = "temporal")] +#[cfg(test)] +mod tests; + /// The `Temporal.PlainDate` object. #[derive(Debug, Clone, Trace, Finalize, JsData)] #[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerDate` could contain `Trace` types. @@ -852,35 +856,40 @@ pub(crate) fn to_temporal_date( // 2. Assert: Type(options) is Object or Undefined. // 3. If options is not undefined, set options to ? SnapshotOwnProperties(? GetOptionsObject(options), null). - let options_obj = get_options_object(&options)?; // 4. If Type(item) is Object, then if let Some(object) = item.as_object() { // a. If item has an [[InitializedTemporalDate]] internal slot, then if let Some(date) = object.downcast_ref::() { + let options_obj = get_options_object(&options)?; return Ok(date.inner.clone()); // b. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then - } else if let Some(data) = object.downcast_ref::() { - return Err(JsNativeError::range() - .with_message("ZonedDateTime not yet implemented.") - .into()); + } else if let Some(zdt) = object.downcast_ref::() { + let options_obj = get_options_object(&options)?; // i. Perform ? ToTemporalOverflow(options). + let _overflow = get_option(&options_obj, js_string!("overflow"), context)? + .unwrap_or(ArithmeticOverflow::Constrain); + // ii. Let instant be ! CreateTemporalInstant(item.[[Nanoseconds]]). // iii. Let plainDateTime be ? GetPlainDateTimeFor(item.[[TimeZone]], instant, item.[[Calendar]]). // iv. Return ! CreateTemporalDate(plainDateTime.[[ISOYear]], plainDateTime.[[ISOMonth]], plainDateTime.[[ISODay]], plainDateTime.[[Calendar]]). - - // c. If item has an [[InitializedTemporalDateTime]] internal slot, then - } else if let Some(date_time) = object.downcast_ref::() { + return Err(JsNativeError::error() + .with_message("Not yet implemented.") + .into()); + // c. If item has an [[InitializedTemporalDateTime]] internal slot, then + } else if let Some(dt) = object.downcast_ref::() { + let options_obj = get_options_object(&options)?; // i. Perform ? ToTemporalOverflow(options). - let _o = get_option(&options_obj, js_string!("overflow"), context)? + let _overflow = get_option(&options_obj, js_string!("overflow"), context)? .unwrap_or(ArithmeticOverflow::Constrain); - let date = InnerDate::from(date_time.inner.clone()); + let date = InnerDate::from(dt.inner.clone()); // ii. Return ! CreateTemporalDate(item.[[ISOYear]], item.[[ISOMonth]], item.[[ISODay]], item.[[Calendar]]). return Ok(date); } + let options_obj = get_options_object(&options)?; // d. Let calendar be ? GetTemporalCalendarSlotValueWithISODefault(item). let calendar = get_temporal_calendar_slot_value_with_default(object, context)?; let overflow = @@ -913,19 +922,24 @@ pub(crate) fn to_temporal_date( .into()); }; - // 6. Let result be ? ParseTemporalDateString(item). - // 7. Assert: IsValidISODate(result.[[Year]], result.[[Month]], result.[[Day]]) is true. - // 8. Let calendar be result.[[Calendar]]. - // 9. If calendar is undefined, set calendar to "iso8601". - // 10. If IsBuiltinCalendar(calendar) is false, throw a RangeError exception. - // 11. Set calendar to the ASCII-lowercase of calendar. - // 12. Perform ? ToTemporalOverflow(options). - // 13. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], calendar). + // 4. Let result be ? ParseISODateTime(item, « TemporalDateTimeString[~Zoned] »). let result = date_like_string .to_std_string_escaped() .parse::() .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; + // 5. Let calendar be result.[[Calendar]]. + // 6. If calendar is empty, set calendar to "iso8601". + // 7. Set calendar to ? CanonicalizeCalendar(calendar). + // 8. Let resolvedOptions be ? GetOptionsObject(options). + let resolved_options = get_options_object(&options)?; + // 9. Perform ? GetTemporalOverflowOption(resolvedOptions). + let overflow = + get_option::(&resolved_options, js_string!("overflow"), context)? + .unwrap_or(ArithmeticOverflow::Constrain); + + // 10. Let isoDate be CreateISODateRecord(result.[[Year]], result.[[Month]], result.[[Day]]). + // 11. Return ? CreateTemporalDate(isoDate, calendar). Ok(result) } diff --git a/core/engine/src/builtins/temporal/plain_date/tests.rs b/core/engine/src/builtins/temporal/plain_date/tests.rs new file mode 100644 index 00000000000..d98f5313e89 --- /dev/null +++ b/core/engine/src/builtins/temporal/plain_date/tests.rs @@ -0,0 +1,46 @@ +use crate::{run_test_actions, JsNativeErrorKind, TestAction}; + +#[test] +fn property_bag_null_option_value() { + run_test_actions([TestAction::assert_native_error( + "Temporal.PlainDate.from({ year: 1976, month: 11, day: 18}, null)", + JsNativeErrorKind::Type, + "GetOptionsObject: provided options is not an object", + )]); +} + +#[test] +fn pd_null_option_value() { + run_test_actions([TestAction::assert_native_error( + "Temporal.PlainDate.from(new Temporal.PlainDate(1976, 11, 18), null)", + JsNativeErrorKind::Type, + "GetOptionsObject: provided options is not an object", + )]); +} + +#[test] +fn pdt_null_option_value() { + run_test_actions([TestAction::assert_native_error( + "Temporal.PlainDate.from(new Temporal.PlainDateTime(1976, 11, 18), null)", + JsNativeErrorKind::Type, + "GetOptionsObject: provided options is not an object", + )]); +} + +#[test] +fn zdt_null_option_value() { + run_test_actions([TestAction::assert_native_error( + "Temporal.PlainDate.from(new Temporal.ZonedDateTime(0n, 'UTC'), null)", + JsNativeErrorKind::Type, + "GetOptionsObject: provided options is not an object", + )]); +} + +#[test] +fn string_null_option_value() { + run_test_actions([TestAction::assert_native_error( + "Temporal.PlainDate.from('1976-11-18Z', null)", + JsNativeErrorKind::Range, + "Error: Unexpected character found after parsing was completed.", + )]); +} diff --git a/core/engine/src/builtins/temporal/plain_date_time/mod.rs b/core/engine/src/builtins/temporal/plain_date_time/mod.rs index 18cb14c3e56..309c87de8c9 100644 --- a/core/engine/src/builtins/temporal/plain_date_time/mod.rs +++ b/core/engine/src/builtins/temporal/plain_date_time/mod.rs @@ -976,7 +976,7 @@ pub(crate) fn to_temporal_datetime( // ii. Let instant be ! CreateTemporalInstant(item.[[Nanoseconds]]). // iii. Let timeZoneRec be ? CreateTimeZoneMethodsRecord(item.[[TimeZone]], « get-offset-nanoseconds-for »). // iv. Return ? GetPlainDateTimeFor(timeZoneRec, instant, item.[[Calendar]]). - return Err(JsNativeError::range() + return Err(JsNativeError::error() .with_message("Not yet implemented.") .into()); // c. If item has an [[InitializedTemporalDate]] internal slot, then From 88d547042f24ba023d33a0783e6aa5c7c770bdef Mon Sep 17 00:00:00 2001 From: Kevin Ness <46825870+nekevss@users.noreply.github.com> Date: Sun, 8 Dec 2024 16:01:42 -0600 Subject: [PATCH 13/14] Bump temporal_rs to bug fixed version --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca4c9b3b52c..2535fe84d8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3346,7 +3346,7 @@ checksum = "42a4d50cdb458045afc8131fd91b64904da29548bcb63c7236e0844936c13078" [[package]] name = "temporal_rs" version = "0.0.4" -source = "git+https://github.com/boa-dev/temporal.git?rev=f7fa90083987954f0974ac0167a7f0b4a03f77bf#f7fa90083987954f0974ac0167a7f0b4a03f77bf" +source = "git+https://github.com/boa-dev/temporal.git?rev=016bc31d2ce5484973b71ccdb0faeb33c00a9ae6#016bc31d2ce5484973b71ccdb0faeb33c00a9ae6" dependencies = [ "bitflags 2.6.0", "combine", diff --git a/Cargo.toml b/Cargo.toml index 0c6ec225a19..bb306444396 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -111,7 +111,7 @@ intrusive-collections = "0.9.7" cfg-if = "1.0.0" either = "1.13.0" sys-locale = "0.3.2" -temporal_rs = { git = "https://github.com/boa-dev/temporal.git", rev = "f7fa90083987954f0974ac0167a7f0b4a03f77bf", features = ["tzdb", "now"] } +temporal_rs = { git = "https://github.com/boa-dev/temporal.git", rev = "016bc31d2ce5484973b71ccdb0faeb33c00a9ae6", features = ["tzdb", "now"] } web-time = "1.1.0" criterion = "0.5.1" float-cmp = "0.10.0" From 1265a08c1688ef512ddf18ff9bb8a02a3be03131 Mon Sep 17 00:00:00 2001 From: Kevin Ness <46825870+nekevss@users.noreply.github.com> Date: Sun, 8 Dec 2024 19:28:16 -0600 Subject: [PATCH 14/14] Update get_options_object order of operations --- .../src/builtins/temporal/plain_date/mod.rs | 22 +++++++------ .../builtins/temporal/plain_date_time/mod.rs | 2 +- .../builtins/temporal/plain_month_day/mod.rs | 9 ++++-- .../src/builtins/temporal/plain_time/mod.rs | 32 +++++++++++++------ .../builtins/temporal/plain_year_month/mod.rs | 2 +- 5 files changed, 42 insertions(+), 25 deletions(-) diff --git a/core/engine/src/builtins/temporal/plain_date/mod.rs b/core/engine/src/builtins/temporal/plain_date/mod.rs index cc218ea417c..c8d53f76cfc 100644 --- a/core/engine/src/builtins/temporal/plain_date/mod.rs +++ b/core/engine/src/builtins/temporal/plain_date/mod.rs @@ -661,18 +661,19 @@ impl PlainDate { .with_message("with object was not a PartialTemporalObject.") .into()); }; - let options = get_options_object(args.get_or_undefined(1))?; // SKIP: Steps 4-9 are handled by the with method of temporal_rs's Date - // 4. Let resolvedOptions be ? SnapshotOwnProperties(? GetOptionsObject(options), null). - // 5. Let calendarRec be ? CreateCalendarMethodsRecord(temporalDate.[[Calendar]], « date-from-fields, fields, merge-fields »). - // 6. Let fieldsResult be ? PrepareCalendarFieldsAndFieldNames(calendarRec, temporalDate, « "day", "month", "monthCode", "year" »). - // 7. Let partialDate be ? PrepareTemporalFields(temporalDateLike, fieldsResult.[[FieldNames]], partial). - // 8. Let fields be ? CalendarMergeFields(calendarRec, fieldsResult.[[Fields]], partialDate). - // 9. Set fields to ? PrepareTemporalFields(fields, fieldsResult.[[FieldNames]], «»). - let overflow = get_option::(&options, js_string!("overflow"), context)?; + // 4. Let calendar be temporalDate.[[Calendar]]. + // 5. Let fields be ISODateToFields(calendar, temporalDate.[[ISODate]], date). + // 6. Let partialDate be ? PrepareCalendarFields(calendar, temporalDateLike, « year, month, month-code, day », « », partial). + // 7. Set fields to CalendarMergeFields(calendar, fields, partialDate). + // 8. Let resolvedOptions be ? GetOptionsObject(options). + // 9. Let overflow be ? GetTemporalOverflowOption(resolvedOptions). let partial = to_partial_date_record(partial_object, context)?; + let options = get_options_object(args.get_or_undefined(1))?; + let overflow = get_option::(&options, js_string!("overflow"), context)?; + // 10. Return ? CalendarDateFromFields(calendarRec, fields, resolvedOptions). let resolved_date = date.inner.with(partial, overflow)?; create_temporal_date( @@ -947,13 +948,14 @@ pub(crate) fn to_partial_date_record( partial_object: &JsObject, context: &mut Context, ) -> JsResult { + // TODO: Most likely need to use an iterator to handle. let day = partial_object .get(js_string!("day"), context)? - .map(|v| super::to_integer_if_integral(v, context)) + .map(|v| super::to_positive_integer_with_trunc(v, context)) .transpose()?; let month = partial_object .get(js_string!("month"), context)? - .map(|v| super::to_integer_if_integral(v, context)) + .map(|v| super::to_positive_integer_with_trunc(v, context)) .transpose()?; let month_code = partial_object .get(js_string!("monthCode"), context)? diff --git a/core/engine/src/builtins/temporal/plain_date_time/mod.rs b/core/engine/src/builtins/temporal/plain_date_time/mod.rs index 309c87de8c9..f7409a1b951 100644 --- a/core/engine/src/builtins/temporal/plain_date_time/mod.rs +++ b/core/engine/src/builtins/temporal/plain_date_time/mod.rs @@ -688,13 +688,13 @@ impl PlainDateTime { .with_message("with object was not a PartialTemporalObject.") .into()); }; - let options = get_options_object(args.get_or_undefined(1))?; let date = to_partial_date_record(partial_object, context)?; let time = to_partial_time_record(partial_object, context)?; let partial_dt = PartialDateTime { date, time }; + let options = get_options_object(args.get_or_undefined(1))?; let overflow = get_option::(&options, js_string!("overflow"), context)?; create_temporal_datetime(dt.inner.with(partial_dt, overflow)?, None, context) diff --git a/core/engine/src/builtins/temporal/plain_month_day/mod.rs b/core/engine/src/builtins/temporal/plain_month_day/mod.rs index 0c50c868948..57ef80d2c7f 100644 --- a/core/engine/src/builtins/temporal/plain_month_day/mod.rs +++ b/core/engine/src/builtins/temporal/plain_month_day/mod.rs @@ -25,7 +25,10 @@ use temporal_rs::{ PlainDateTime, PlainMonthDay as InnerMonthDay, TinyAsciiStr, }; -use super::{calendar::to_temporal_calendar_slot_value, to_integer_if_integral, DateTimeValues}; +use super::{ + calendar::to_temporal_calendar_slot_value, to_integer_if_integral, + to_positive_integer_with_trunc, DateTimeValues, +}; /// The `Temporal.PlainMonthDay` object. #[derive(Debug, Clone, Trace, Finalize, JsData)] @@ -316,12 +319,12 @@ fn to_temporal_month_day( } else if item.is_object() { let day = item .get_v(js_string!("day"), context)? - .map(|v| to_integer_if_integral(v, context)) + .map(|v| to_positive_integer_with_trunc(v, context)) .transpose()?; let month = item .get_v(js_string!("month"), context)? - .map(|v| to_integer_if_integral(v, context)) + .map(|v| to_positive_integer_with_trunc(v, context)) .transpose()?; let month_code = item diff --git a/core/engine/src/builtins/temporal/plain_time/mod.rs b/core/engine/src/builtins/temporal/plain_time/mod.rs index ffa97d54531..d453a5831ab 100644 --- a/core/engine/src/builtins/temporal/plain_time/mod.rs +++ b/core/engine/src/builtins/temporal/plain_time/mod.rs @@ -295,11 +295,6 @@ impl PlainTime { let item = args.get_or_undefined(0); // 1. Set options to ? GetOptionsObject(options). // 2. Let overflow be ? GetTemporalOverflowOption(options). - let overflow = get_option::( - &get_options_object(args.get_or_undefined(1))?, - js_string!("overflow"), - context, - )?; // 3. If item is an Object and item has an [[InitializedTemporalTime]] internal slot, then let time = if let Some(time) = item .as_object() @@ -310,7 +305,7 @@ impl PlainTime { // item.[[ISONanosecond]]). time.inner } else { - to_temporal_time(item, overflow, context)? + to_temporal_time(item, args.get(1), context)? }; // 4. Return ? ToTemporalTime(item, overflow). @@ -390,9 +385,12 @@ impl PlainTime { .into()); }; + // Steps 5-16 equate to the below + let partial = to_partial_time_record(partial_object, context)?; + // 17. Let resolvedOptions be ? GetOptionsObject(options). + // 18. Let overflow be ? GetTemporalOverflowOption(resolvedOptions). let options = get_options_object(args.get_or_undefined(1))?; let overflow = get_option::(&options, js_string!("overflow"), context)?; - let partial = to_partial_time_record(partial_object, context)?; create_temporal_time(time.inner.with(partial, overflow)?, None, context).map(Into::into) } @@ -630,17 +628,20 @@ pub(crate) fn create_temporal_time( /// 4.5.3 `ToTemporalTime ( item [ , overflow ] )` pub(crate) fn to_temporal_time( value: &JsValue, - overflow: Option, + options: Option<&JsValue>, context: &mut Context, ) -> JsResult { // 1.If overflow is not present, set overflow to "constrain". - let resolved_overflow = overflow.unwrap_or(ArithmeticOverflow::Constrain); + let options = options.unwrap_or(&JsValue::Undefined); // 2. If item is an Object, then match value { JsValue::Object(object) => { // a. If item has an [[InitializedTemporalTime]] internal slot, then if let Some(time) = object.downcast_ref::() { // i. Return item. + let options = get_options_object(options)?; + let _overflow = + get_option::(&options, js_string!("overflow"), context)?; return Ok(time.inner); // b. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then } else if let Some(_zdt) = object.downcast_ref::() { @@ -650,6 +651,9 @@ pub(crate) fn to_temporal_time( // iv. Return ! CreateTemporalTime(plainDateTime.[[ISOHour]], plainDateTime.[[ISOMinute]], // plainDateTime.[[ISOSecond]], plainDateTime.[[ISOMillisecond]], plainDateTime.[[ISOMicrosecond]], // plainDateTime.[[ISONanosecond]]). + let options = get_options_object(options)?; + let _overflow = + get_option::(&options, js_string!("overflow"), context)?; return Err(JsNativeError::range() .with_message("Not yet implemented.") .into()); @@ -658,6 +662,9 @@ pub(crate) fn to_temporal_time( // i. Return ! CreateTemporalTime(item.[[ISOHour]], item.[[ISOMinute]], // item.[[ISOSecond]], item.[[ISOMillisecond]], item.[[ISOMicrosecond]], // item.[[ISONanosecond]]). + let options = get_options_object(options)?; + let _overflow = + get_option::(&options, js_string!("overflow"), context)?; return Ok(PlainTimeInner::from(dt.inner.clone())); } // d. Let result be ? ToTemporalTimeRecord(item). @@ -666,6 +673,11 @@ pub(crate) fn to_temporal_time( // result.[[Nanosecond]], overflow). let partial = to_partial_time_record(object, context)?; + let options = get_options_object(options)?; + let overflow = + get_option::(&options, js_string!("overflow"), context)? + .unwrap_or(ArithmeticOverflow::Constrain); + PlainTimeInner::new_with_overflow( partial.hour.unwrap_or(0), partial.minute.unwrap_or(0), @@ -673,7 +685,7 @@ pub(crate) fn to_temporal_time( partial.millisecond.unwrap_or(0), partial.microsecond.unwrap_or(0), partial.nanosecond.unwrap_or(0), - resolved_overflow, + overflow, ) .map_err(Into::into) } diff --git a/core/engine/src/builtins/temporal/plain_year_month/mod.rs b/core/engine/src/builtins/temporal/plain_year_month/mod.rs index 673d570f384..d4dcdfd7f3a 100644 --- a/core/engine/src/builtins/temporal/plain_year_month/mod.rs +++ b/core/engine/src/builtins/temporal/plain_year_month/mod.rs @@ -348,7 +348,7 @@ impl PlainYearMonth { impl PlainYearMonth { fn with(_this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { - Err(JsNativeError::typ() + Err(JsNativeError::error() .with_message("not yet implemented.") .into()) }