diff --git a/core/engine/src/builtins/temporal/time_zone/custom.rs b/core/engine/src/builtins/temporal/time_zone/custom.rs new file mode 100644 index 00000000000..a8aaf33aa25 --- /dev/null +++ b/core/engine/src/builtins/temporal/time_zone/custom.rs @@ -0,0 +1,76 @@ +//! A custom `TimeZone` object. +use crate::{property::PropertyKey, string::utf16, Context, JsObject, JsValue}; + +use boa_gc::{Finalize, Trace}; +use boa_temporal::{ + components::{tz::TzProtocol, Instant}, + TemporalError, TemporalResult, +}; +use num_bigint::BigInt; + +#[derive(Debug, Clone, Trace, Finalize)] +pub(crate) struct JsCustomTimeZone { + tz: JsObject, +} + +impl TzProtocol for JsCustomTimeZone { + fn get_offset_nanos_for(&self, ctx: &mut dyn std::any::Any) -> TemporalResult { + let context = ctx + .downcast_mut::() + .expect("Context was not provided for a CustomTz"); + + let method = self + .tz + .get(utf16!("getOffsetNanosFor"), context) + .expect("Method must exist for the custom calendar to be valid."); + + let result = method + .as_callable() + .expect("is method") + .call(&method, &[], context) + .map_err(|e| TemporalError::general(e.to_string()))?; + + // TODO (nekevss): Validate that the below conversion is fine vs. matching to JsValue::BigInt() + let Some(bigint) = result.as_bigint() else { + return Err(TemporalError::r#type() + .with_message("Expected BigInt return from getOffsetNanosFor")); + }; + + Ok(bigint.as_inner().clone()) + } + + fn get_possible_instant_for( + &self, + ctx: &mut dyn std::any::Any, + ) -> TemporalResult> { + let _context = ctx + .downcast_mut::() + .expect("Context was not provided for a CustomTz"); + + // TODO: Implement once Instant has been migrated to `boa_temporal`'s Instant. + Err(TemporalError::range().with_message("Not yet implemented.")) + } + + fn id(&self, ctx: &mut dyn std::any::Any) -> TemporalResult { + let context = ctx + .downcast_mut::() + .expect("Context was not provided for a CustomTz"); + + let ident = self + .tz + .__get__( + &PropertyKey::from(utf16!("id")), + JsValue::undefined(), + &mut context.into(), + ) + .expect("Method must exist for the custom calendar to be valid."); + + let JsValue::String(id) = ident else { + return Err( + TemporalError::r#type().with_message("Invalid custom Time Zone identifier type.") + ); + }; + + Ok(id.to_std_string_escaped()) + } +} diff --git a/core/engine/src/builtins/temporal/time_zone/mod.rs b/core/engine/src/builtins/temporal/time_zone/mod.rs index facf2d7008a..109f59b87f2 100644 --- a/core/engine/src/builtins/temporal/time_zone/mod.rs +++ b/core/engine/src/builtins/temporal/time_zone/mod.rs @@ -1,3 +1,4 @@ +//! Boa's implemetation of the `Temporal.TimeZone` builtin object. #![allow(dead_code)] use crate::{ @@ -13,16 +14,29 @@ use crate::{ string::{common::StaticJsStrings, utf16}, Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; -use boa_gc::{Finalize, Trace}; +use boa_gc::{custom_trace, Finalize, Trace}; use boa_profiler::Profiler; -use boa_temporal::components::tz::{TimeZoneSlot, TzProtocol}; +use boa_temporal::components::tz::TimeZoneSlot; + +mod custom; + +#[doc(inline)] +pub(crate) use custom::JsCustomTimeZone; /// The `Temporal.TimeZone` object. -#[derive(Debug, Clone, Trace, Finalize, JsData)] -// SAFETY: `TimeZone` doesn't contain traceable data. -#[boa_gc(unsafe_empty_trace)] +#[derive(Debug, Clone, Finalize, JsData)] pub struct TimeZone { - slot: TimeZoneSlot, + slot: TimeZoneSlot, +} + +unsafe impl Trace for TimeZone { + custom_trace!(this, mark, { + match &this.slot { + TimeZoneSlot::Protocol(custom) => mark(custom), + // SAFETY: No values that are exposed to gc are in TZ + TimeZoneSlot::Tz(_) => {} + } + }); } impl BuiltInObject for TimeZone { @@ -143,7 +157,7 @@ impl TimeZone { .ok_or_else(|| { JsNativeError::typ().with_message("this value must be a Temporal.TimeZone") })?; - Ok(JsString::from(tz.slot.id(context)).into()) + Ok(JsString::from(tz.slot.id(context)?).into()) } pub(crate) fn get_offset_nanoseconds_for( @@ -257,7 +271,7 @@ impl TimeZone { JsNativeError::typ().with_message("this value must be a Temporal.TimeZone") })?; // 3. Return timeZone.[[Identifier]]. - Ok(JsString::from(tz.slot.id(context)).into()) + Ok(JsString::from(tz.slot.id(context)?).into()) } } 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 eab3c7b1e36..355d806ed67 100644 --- a/core/engine/src/builtins/temporal/zoned_date_time/mod.rs +++ b/core/engine/src/builtins/temporal/zoned_date_time/mod.rs @@ -7,18 +7,32 @@ use crate::{ string::common::StaticJsStrings, Context, JsBigInt, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; -use boa_gc::{Finalize, Trace}; +use boa_gc::{custom_trace, Finalize, Trace}; use boa_profiler::Profiler; -use boa_temporal::components::{Duration as TemporalDuration, ZonedDateTime as InnerZdt}; +use boa_temporal::components::{ + calendar::CalendarSlot, tz::TimeZoneSlot, Duration as TemporalDuration, + ZonedDateTime as InnerZdt, +}; -use super::JsCustomCalendar; +use super::{JsCustomCalendar, JsCustomTimeZone}; /// The `Temporal.ZonedDateTime` object. -#[derive(Debug, Clone, Finalize, Trace, JsData)] -// SAFETY: ZonedDateTime does not contain any traceable types. -#[boa_gc(unsafe_empty_trace)] +#[derive(Debug, Clone, Finalize, JsData)] pub struct ZonedDateTime { - pub(crate) inner: InnerZdt, + pub(crate) inner: InnerZdt, +} + +unsafe impl Trace for ZonedDateTime { + custom_trace!(this, mark, { + match this.inner.calendar() { + CalendarSlot::Protocol(custom) => mark(custom), + CalendarSlot::Builtin(_) => {} + } + match this.inner.tz() { + TimeZoneSlot::Protocol(custom) => mark(custom), + TimeZoneSlot::Tz(_) => {} + } + }); } impl BuiltInObject for ZonedDateTime { diff --git a/core/temporal/src/components/duration.rs b/core/temporal/src/components/duration.rs index e797e5b9116..cc3d93a1f9d 100644 --- a/core/temporal/src/components/duration.rs +++ b/core/temporal/src/components/duration.rs @@ -8,7 +8,7 @@ use crate::{ }; use std::{any::Any, str::FromStr}; -use super::calendar::CalendarProtocol; +use super::{calendar::CalendarProtocol, tz::TzProtocol}; // ==== `DateDuration` ==== @@ -1161,7 +1161,7 @@ impl Duration { /// seconds, milliseconds, microseconds, nanoseconds, increment, unit, /// roundingMode [ , plainRelativeTo [, zonedRelativeTo [, precalculatedDateTime]]] )` #[allow(clippy::type_complexity)] - pub fn round_duration( + pub fn round_duration( &self, unbalance_date_duration: DateDuration, increment: f64, @@ -1169,7 +1169,7 @@ impl Duration { rounding_mode: TemporalRoundingMode, relative_targets: ( Option<&Date>, - Option<&ZonedDateTime>, + Option<&ZonedDateTime>, Option<&DateTime>, ), context: &mut dyn Any, diff --git a/core/temporal/src/components/tz.rs b/core/temporal/src/components/tz.rs index 4025e83493d..6a83f3fedf2 100644 --- a/core/temporal/src/components/tz.rs +++ b/core/temporal/src/components/tz.rs @@ -16,29 +16,14 @@ use super::calendar::CalendarProtocol; pub const TIME_ZONE_PROPERTIES: [&str; 3] = ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "id"]; -/// A clonable `TzProtocol` -pub trait TzProtocolClone { - /// Clones the current `TimeZoneProtocol`. - fn clone_box(&self) -> Box; -} - -impl

TzProtocolClone for P -where - P: 'static + TzProtocol + Clone, -{ - fn clone_box(&self) -> Box { - Box::new(self.clone()) - } -} - /// The Time Zone Protocol that must be implemented for time zones. -pub trait TzProtocol: TzProtocolClone { +pub trait TzProtocol: Clone { /// Get the Offset nanoseconds for this `TimeZone` fn get_offset_nanos_for(&self, context: &mut dyn Any) -> TemporalResult; /// Get the possible Instant for this `TimeZone` fn get_possible_instant_for(&self, context: &mut dyn Any) -> TemporalResult>; // TODO: Implement Instant /// Get the `TimeZone`'s identifier. - fn id(&self, context: &mut dyn Any) -> String; + fn id(&self, context: &mut dyn Any) -> TemporalResult; } /// A Temporal `TimeZone`. @@ -50,14 +35,15 @@ pub struct TimeZone { } /// The `TimeZoneSlot` represents a `[[TimeZone]]` internal slot value. -pub enum TimeZoneSlot { +#[derive(Clone)] +pub enum TimeZoneSlot { /// A native `TimeZone` representation. Tz(TimeZone), /// A Custom `TimeZone` that implements the `TzProtocol`. - Protocol(Box), + Protocol(Z), } -impl core::fmt::Debug for TimeZoneSlot { +impl core::fmt::Debug for TimeZoneSlot { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Tz(tz) => write!(f, "{tz:?}"), @@ -66,16 +52,7 @@ impl core::fmt::Debug for TimeZoneSlot { } } -impl Clone for TimeZoneSlot { - fn clone(&self) -> Self { - match self { - Self::Tz(tz) => Self::Tz(tz.clone()), - Self::Protocol(p) => Self::Protocol(p.clone_box()), - } - } -} - -impl TimeZoneSlot { +impl TimeZoneSlot { pub(crate) fn get_datetime_for( &self, instant: &Instant, @@ -87,8 +64,9 @@ impl TimeZoneSlot { } } -impl TzProtocol for TimeZoneSlot { - fn get_offset_nanos_for(&self, context: &mut dyn Any) -> TemporalResult { +impl TimeZoneSlot { + /// Get the offset for this current `TimeZoneSlot`. + pub fn get_offset_nanos_for(&self, context: &mut dyn Any) -> TemporalResult { // 1. Let timeZone be the this value. // 2. Perform ? RequireInternalSlot(timeZone, [[InitializedTemporalTimeZone]]). // 3. Set instant to ? ToTemporalInstant(instant). @@ -106,14 +84,30 @@ impl TzProtocol for TimeZoneSlot { } } - fn get_possible_instant_for(&self, _context: &mut dyn Any) -> TemporalResult> { + /// Get the possible `Instant`s for this `TimeZoneSlot`. + pub fn get_possible_instant_for(&self, _context: &mut dyn Any) -> TemporalResult> { Err(TemporalError::general("Not yet implemented.")) } - fn id(&self, context: &mut dyn Any) -> String { + /// Returns the current `TimeZoneSlot`'s identifier. + pub fn id(&self, context: &mut dyn Any) -> TemporalResult { match self { - Self::Tz(_) => todo!("implement tz.to_string"), + Self::Tz(_) => Err(TemporalError::range().with_message("Not yet implemented.")), // TODO: Implement Display for Time Zone. Self::Protocol(tz) => tz.id(context), } } } + +impl TzProtocol for () { + fn get_offset_nanos_for(&self, _: &mut dyn Any) -> TemporalResult { + unreachable!() + } + + fn get_possible_instant_for(&self, _: &mut dyn Any) -> TemporalResult> { + unreachable!() + } + + fn id(&self, _: &mut dyn Any) -> TemporalResult { + Ok("() TimeZone".to_owned()) + } +} diff --git a/core/temporal/src/components/zoneddatetime.rs b/core/temporal/src/components/zoneddatetime.rs index b766e098101..7985dfed834 100644 --- a/core/temporal/src/components/zoneddatetime.rs +++ b/core/temporal/src/components/zoneddatetime.rs @@ -14,24 +14,26 @@ use crate::{ use core::any::Any; +use super::tz::TzProtocol; + /// The native Rust implementation of `Temporal.ZonedDateTime`. #[derive(Debug, Clone)] -pub struct ZonedDateTime { +pub struct ZonedDateTime { instant: Instant, calendar: CalendarSlot, - tz: TimeZoneSlot, + tz: TimeZoneSlot, } // ==== Private API ==== -impl ZonedDateTime { +impl ZonedDateTime { /// Creates a `ZonedDateTime` without validating the input. #[inline] #[must_use] pub(crate) fn new_unchecked( instant: Instant, calendar: CalendarSlot, - tz: TimeZoneSlot, + tz: TimeZoneSlot, ) -> Self { Self { instant, @@ -43,21 +45,32 @@ impl ZonedDateTime { // ==== Public API ==== -impl ZonedDateTime { +impl ZonedDateTime { /// Creates a new valid `ZonedDateTime`. #[inline] - pub fn new(nanos: BigInt, calendar: CalendarSlot, tz: TimeZoneSlot) -> TemporalResult { + pub fn new( + nanos: BigInt, + calendar: CalendarSlot, + tz: TimeZoneSlot, + ) -> TemporalResult { let instant = Instant::new(nanos)?; Ok(Self::new_unchecked(instant, calendar, tz)) } - /// Returns the `ZonedDateTime`'s Calendar identifier. + /// Returns `ZonedDateTime`'s Calendar. #[inline] #[must_use] pub fn calendar(&self) -> &CalendarSlot { &self.calendar } + /// Returns `ZonedDateTime`'s `TimeZone` slot. + #[inline] + #[must_use] + pub fn tz(&self) -> &TimeZoneSlot { + &self.tz + } + /// Returns the `epochSeconds` value of this `ZonedDateTime`. #[must_use] pub fn epoch_seconds(&self) -> f64 { @@ -85,7 +98,7 @@ impl ZonedDateTime { // ==== Context based API ==== -impl ZonedDateTime { +impl ZonedDateTime { /// Returns the `year` value for this `ZonedDateTime`. #[inline] pub fn contextual_year(&self, context: &mut dyn Any) -> TemporalResult { @@ -236,7 +249,7 @@ mod tests { fn basic_zdt_test() { let nov_30_2023_utc = BigInt::from(1_701_308_952_000_000_000i64); - let zdt = ZonedDateTime::<()>::new( + let zdt = ZonedDateTime::<(), ()>::new( nov_30_2023_utc.clone(), CalendarSlot::from_str("iso8601").unwrap(), TimeZoneSlot::Tz(TimeZone { @@ -253,7 +266,7 @@ mod tests { assert_eq!(zdt.minute().unwrap(), 49); assert_eq!(zdt.second().unwrap(), 12); - let zdt_minus_five = ZonedDateTime::<()>::new( + let zdt_minus_five = ZonedDateTime::<(), ()>::new( nov_30_2023_utc, CalendarSlot::from_str("iso8601").unwrap(), TimeZoneSlot::Tz(TimeZone {