Browse Source

Update tz components for new design (#3543)

pull/3569/head
Kevin 10 months ago committed by GitHub
parent
commit
0cb17cfc61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 76
      core/engine/src/builtins/temporal/time_zone/custom.rs
  2. 30
      core/engine/src/builtins/temporal/time_zone/mod.rs
  3. 28
      core/engine/src/builtins/temporal/zoned_date_time/mod.rs
  4. 6
      core/temporal/src/components/duration.rs
  5. 64
      core/temporal/src/components/tz.rs
  6. 33
      core/temporal/src/components/zoneddatetime.rs

76
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<BigInt> {
let context = ctx
.downcast_mut::<Context>()
.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<Vec<Instant>> {
let _context = ctx
.downcast_mut::<Context>()
.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<String> {
let context = ctx
.downcast_mut::<Context>()
.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())
}
}

30
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)] #![allow(dead_code)]
use crate::{ use crate::{
@ -13,16 +14,29 @@ use crate::{
string::{common::StaticJsStrings, utf16}, string::{common::StaticJsStrings, utf16},
Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, 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_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. /// The `Temporal.TimeZone` object.
#[derive(Debug, Clone, Trace, Finalize, JsData)] #[derive(Debug, Clone, Finalize, JsData)]
// SAFETY: `TimeZone` doesn't contain traceable data.
#[boa_gc(unsafe_empty_trace)]
pub struct TimeZone { pub struct TimeZone {
slot: TimeZoneSlot, slot: TimeZoneSlot<JsCustomTimeZone>,
}
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 { impl BuiltInObject for TimeZone {
@ -143,7 +157,7 @@ impl TimeZone {
.ok_or_else(|| { .ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a Temporal.TimeZone") 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( pub(crate) fn get_offset_nanoseconds_for(
@ -257,7 +271,7 @@ impl TimeZone {
JsNativeError::typ().with_message("this value must be a Temporal.TimeZone") JsNativeError::typ().with_message("this value must be a Temporal.TimeZone")
})?; })?;
// 3. Return timeZone.[[Identifier]]. // 3. Return timeZone.[[Identifier]].
Ok(JsString::from(tz.slot.id(context)).into()) Ok(JsString::from(tz.slot.id(context)?).into())
} }
} }

28
core/engine/src/builtins/temporal/zoned_date_time/mod.rs

@ -7,18 +7,32 @@ use crate::{
string::common::StaticJsStrings, string::common::StaticJsStrings,
Context, JsBigInt, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, 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_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. /// The `Temporal.ZonedDateTime` object.
#[derive(Debug, Clone, Finalize, Trace, JsData)] #[derive(Debug, Clone, Finalize, JsData)]
// SAFETY: ZonedDateTime does not contain any traceable types.
#[boa_gc(unsafe_empty_trace)]
pub struct ZonedDateTime { pub struct ZonedDateTime {
pub(crate) inner: InnerZdt<JsCustomCalendar>, pub(crate) inner: InnerZdt<JsCustomCalendar, JsCustomTimeZone>,
}
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 { impl BuiltInObject for ZonedDateTime {

6
core/temporal/src/components/duration.rs

@ -8,7 +8,7 @@ use crate::{
}; };
use std::{any::Any, str::FromStr}; use std::{any::Any, str::FromStr};
use super::calendar::CalendarProtocol; use super::{calendar::CalendarProtocol, tz::TzProtocol};
// ==== `DateDuration` ==== // ==== `DateDuration` ====
@ -1161,7 +1161,7 @@ impl Duration {
/// seconds, milliseconds, microseconds, nanoseconds, increment, unit, /// seconds, milliseconds, microseconds, nanoseconds, increment, unit,
/// roundingMode [ , plainRelativeTo [, zonedRelativeTo [, precalculatedDateTime]]] )` /// roundingMode [ , plainRelativeTo [, zonedRelativeTo [, precalculatedDateTime]]] )`
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub fn round_duration<C: CalendarProtocol>( pub fn round_duration<C: CalendarProtocol, Z: TzProtocol>(
&self, &self,
unbalance_date_duration: DateDuration, unbalance_date_duration: DateDuration,
increment: f64, increment: f64,
@ -1169,7 +1169,7 @@ impl Duration {
rounding_mode: TemporalRoundingMode, rounding_mode: TemporalRoundingMode,
relative_targets: ( relative_targets: (
Option<&Date<C>>, Option<&Date<C>>,
Option<&ZonedDateTime<C>>, Option<&ZonedDateTime<C, Z>>,
Option<&DateTime<C>>, Option<&DateTime<C>>,
), ),
context: &mut dyn Any, context: &mut dyn Any,

64
core/temporal/src/components/tz.rs

@ -16,29 +16,14 @@ use super::calendar::CalendarProtocol;
pub const TIME_ZONE_PROPERTIES: [&str; 3] = pub const TIME_ZONE_PROPERTIES: [&str; 3] =
["getOffsetNanosecondsFor", "getPossibleInstantsFor", "id"]; ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "id"];
/// A clonable `TzProtocol`
pub trait TzProtocolClone {
/// Clones the current `TimeZoneProtocol`.
fn clone_box(&self) -> Box<dyn TzProtocol>;
}
impl<P> TzProtocolClone for P
where
P: 'static + TzProtocol + Clone,
{
fn clone_box(&self) -> Box<dyn TzProtocol> {
Box::new(self.clone())
}
}
/// The Time Zone Protocol that must be implemented for time zones. /// 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` /// Get the Offset nanoseconds for this `TimeZone`
fn get_offset_nanos_for(&self, context: &mut dyn Any) -> TemporalResult<BigInt>; fn get_offset_nanos_for(&self, context: &mut dyn Any) -> TemporalResult<BigInt>;
/// Get the possible Instant for this `TimeZone` /// Get the possible Instant for this `TimeZone`
fn get_possible_instant_for(&self, context: &mut dyn Any) -> TemporalResult<Vec<Instant>>; // TODO: Implement Instant fn get_possible_instant_for(&self, context: &mut dyn Any) -> TemporalResult<Vec<Instant>>; // TODO: Implement Instant
/// Get the `TimeZone`'s identifier. /// Get the `TimeZone`'s identifier.
fn id(&self, context: &mut dyn Any) -> String; fn id(&self, context: &mut dyn Any) -> TemporalResult<String>;
} }
/// A Temporal `TimeZone`. /// A Temporal `TimeZone`.
@ -50,14 +35,15 @@ pub struct TimeZone {
} }
/// The `TimeZoneSlot` represents a `[[TimeZone]]` internal slot value. /// The `TimeZoneSlot` represents a `[[TimeZone]]` internal slot value.
pub enum TimeZoneSlot { #[derive(Clone)]
pub enum TimeZoneSlot<Z: TzProtocol> {
/// A native `TimeZone` representation. /// A native `TimeZone` representation.
Tz(TimeZone), Tz(TimeZone),
/// A Custom `TimeZone` that implements the `TzProtocol`. /// A Custom `TimeZone` that implements the `TzProtocol`.
Protocol(Box<dyn TzProtocol>), Protocol(Z),
} }
impl core::fmt::Debug for TimeZoneSlot { impl<Z: TzProtocol> core::fmt::Debug for TimeZoneSlot<Z> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::Tz(tz) => write!(f, "{tz:?}"), Self::Tz(tz) => write!(f, "{tz:?}"),
@ -66,16 +52,7 @@ impl core::fmt::Debug for TimeZoneSlot {
} }
} }
impl Clone for TimeZoneSlot { impl<Z: TzProtocol> TimeZoneSlot<Z> {
fn clone(&self) -> Self {
match self {
Self::Tz(tz) => Self::Tz(tz.clone()),
Self::Protocol(p) => Self::Protocol(p.clone_box()),
}
}
}
impl TimeZoneSlot {
pub(crate) fn get_datetime_for<C: CalendarProtocol>( pub(crate) fn get_datetime_for<C: CalendarProtocol>(
&self, &self,
instant: &Instant, instant: &Instant,
@ -87,8 +64,9 @@ impl TimeZoneSlot {
} }
} }
impl TzProtocol for TimeZoneSlot { impl<Z: TzProtocol> TimeZoneSlot<Z> {
fn get_offset_nanos_for(&self, context: &mut dyn Any) -> TemporalResult<BigInt> { /// Get the offset for this current `TimeZoneSlot`.
pub fn get_offset_nanos_for(&self, context: &mut dyn Any) -> TemporalResult<BigInt> {
// 1. Let timeZone be the this value. // 1. Let timeZone be the this value.
// 2. Perform ? RequireInternalSlot(timeZone, [[InitializedTemporalTimeZone]]). // 2. Perform ? RequireInternalSlot(timeZone, [[InitializedTemporalTimeZone]]).
// 3. Set instant to ? ToTemporalInstant(instant). // 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<Vec<Instant>> { /// Get the possible `Instant`s for this `TimeZoneSlot`.
pub fn get_possible_instant_for(&self, _context: &mut dyn Any) -> TemporalResult<Vec<Instant>> {
Err(TemporalError::general("Not yet implemented.")) 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<String> {
match self { 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), Self::Protocol(tz) => tz.id(context),
} }
} }
} }
impl TzProtocol for () {
fn get_offset_nanos_for(&self, _: &mut dyn Any) -> TemporalResult<BigInt> {
unreachable!()
}
fn get_possible_instant_for(&self, _: &mut dyn Any) -> TemporalResult<Vec<Instant>> {
unreachable!()
}
fn id(&self, _: &mut dyn Any) -> TemporalResult<String> {
Ok("() TimeZone".to_owned())
}
}

33
core/temporal/src/components/zoneddatetime.rs

@ -14,24 +14,26 @@ use crate::{
use core::any::Any; use core::any::Any;
use super::tz::TzProtocol;
/// The native Rust implementation of `Temporal.ZonedDateTime`. /// The native Rust implementation of `Temporal.ZonedDateTime`.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ZonedDateTime<C: CalendarProtocol> { pub struct ZonedDateTime<C: CalendarProtocol, Z: TzProtocol> {
instant: Instant, instant: Instant,
calendar: CalendarSlot<C>, calendar: CalendarSlot<C>,
tz: TimeZoneSlot, tz: TimeZoneSlot<Z>,
} }
// ==== Private API ==== // ==== Private API ====
impl<C: CalendarProtocol> ZonedDateTime<C> { impl<C: CalendarProtocol, Z: TzProtocol> ZonedDateTime<C, Z> {
/// Creates a `ZonedDateTime` without validating the input. /// Creates a `ZonedDateTime` without validating the input.
#[inline] #[inline]
#[must_use] #[must_use]
pub(crate) fn new_unchecked( pub(crate) fn new_unchecked(
instant: Instant, instant: Instant,
calendar: CalendarSlot<C>, calendar: CalendarSlot<C>,
tz: TimeZoneSlot, tz: TimeZoneSlot<Z>,
) -> Self { ) -> Self {
Self { Self {
instant, instant,
@ -43,21 +45,32 @@ impl<C: CalendarProtocol> ZonedDateTime<C> {
// ==== Public API ==== // ==== Public API ====
impl<C: CalendarProtocol> ZonedDateTime<C> { impl<C: CalendarProtocol, Z: TzProtocol> ZonedDateTime<C, Z> {
/// Creates a new valid `ZonedDateTime`. /// Creates a new valid `ZonedDateTime`.
#[inline] #[inline]
pub fn new(nanos: BigInt, calendar: CalendarSlot<C>, tz: TimeZoneSlot) -> TemporalResult<Self> { pub fn new(
nanos: BigInt,
calendar: CalendarSlot<C>,
tz: TimeZoneSlot<Z>,
) -> TemporalResult<Self> {
let instant = Instant::new(nanos)?; let instant = Instant::new(nanos)?;
Ok(Self::new_unchecked(instant, calendar, tz)) Ok(Self::new_unchecked(instant, calendar, tz))
} }
/// Returns the `ZonedDateTime`'s Calendar identifier. /// Returns `ZonedDateTime`'s Calendar.
#[inline] #[inline]
#[must_use] #[must_use]
pub fn calendar(&self) -> &CalendarSlot<C> { pub fn calendar(&self) -> &CalendarSlot<C> {
&self.calendar &self.calendar
} }
/// Returns `ZonedDateTime`'s `TimeZone` slot.
#[inline]
#[must_use]
pub fn tz(&self) -> &TimeZoneSlot<Z> {
&self.tz
}
/// Returns the `epochSeconds` value of this `ZonedDateTime`. /// Returns the `epochSeconds` value of this `ZonedDateTime`.
#[must_use] #[must_use]
pub fn epoch_seconds(&self) -> f64 { pub fn epoch_seconds(&self) -> f64 {
@ -85,7 +98,7 @@ impl<C: CalendarProtocol> ZonedDateTime<C> {
// ==== Context based API ==== // ==== Context based API ====
impl<C: CalendarProtocol> ZonedDateTime<C> { impl<C: CalendarProtocol, Z: TzProtocol> ZonedDateTime<C, Z> {
/// Returns the `year` value for this `ZonedDateTime`. /// Returns the `year` value for this `ZonedDateTime`.
#[inline] #[inline]
pub fn contextual_year(&self, context: &mut dyn Any) -> TemporalResult<i32> { pub fn contextual_year(&self, context: &mut dyn Any) -> TemporalResult<i32> {
@ -236,7 +249,7 @@ mod tests {
fn basic_zdt_test() { fn basic_zdt_test() {
let nov_30_2023_utc = BigInt::from(1_701_308_952_000_000_000i64); 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(), nov_30_2023_utc.clone(),
CalendarSlot::from_str("iso8601").unwrap(), CalendarSlot::from_str("iso8601").unwrap(),
TimeZoneSlot::Tz(TimeZone { TimeZoneSlot::Tz(TimeZone {
@ -253,7 +266,7 @@ mod tests {
assert_eq!(zdt.minute().unwrap(), 49); assert_eq!(zdt.minute().unwrap(), 49);
assert_eq!(zdt.second().unwrap(), 12); assert_eq!(zdt.second().unwrap(), 12);
let zdt_minus_five = ZonedDateTime::<()>::new( let zdt_minus_five = ZonedDateTime::<(), ()>::new(
nov_30_2023_utc, nov_30_2023_utc,
CalendarSlot::from_str("iso8601").unwrap(), CalendarSlot::from_str("iso8601").unwrap(),
TimeZoneSlot::Tz(TimeZone { TimeZoneSlot::Tz(TimeZone {

Loading…
Cancel
Save