Browse Source

Remove `Temporal.Calendar` and `Temporal.TimeZone` (#3890)

* Remove `Temporal.Calendar` and `Temporal.TimeZone`

* bump test262

* add new test262 features

* fix bug
pull/3895/head
José Julián Espina 4 months ago committed by GitHub
parent
commit
8e76836c8c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      Cargo.lock
  2. 4
      Cargo.toml
  3. 2
      core/engine/src/builtins/mod.rs
  4. 1074
      core/engine/src/builtins/temporal/calendar/mod.rs
  5. 860
      core/engine/src/builtins/temporal/calendar/object.rs
  6. 61
      core/engine/src/builtins/temporal/calendar/tests.rs
  7. 21
      core/engine/src/builtins/temporal/duration/mod.rs
  8. 250
      core/engine/src/builtins/temporal/fields.rs
  9. 41
      core/engine/src/builtins/temporal/mod.rs
  10. 22
      core/engine/src/builtins/temporal/now.rs
  11. 3
      core/engine/src/builtins/temporal/options.rs
  12. 91
      core/engine/src/builtins/temporal/plain_date/mod.rs
  13. 74
      core/engine/src/builtins/temporal/plain_date_time/mod.rs
  14. 3
      core/engine/src/builtins/temporal/plain_date_time/tests.rs
  15. 14
      core/engine/src/builtins/temporal/plain_month_day/mod.rs
  16. 18
      core/engine/src/builtins/temporal/plain_year_month/mod.rs
  17. 63
      core/engine/src/builtins/temporal/time_zone/custom.rs
  18. 307
      core/engine/src/builtins/temporal/time_zone/mod.rs
  19. 27
      core/engine/src/builtins/temporal/zoned_date_time/mod.rs
  20. 5
      core/gc/src/cell.rs
  21. 2
      test262_config.toml
  22. 10
      tests/tester/src/edition.rs

2
Cargo.lock generated

@ -3201,7 +3201,7 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "temporal_rs"
version = "0.0.2"
source = "git+https://github.com/boa-dev/temporal.git?rev=ec2f2d00294ee641285ec1570df6683ecd0d1a8e#ec2f2d00294ee641285ec1570df6683ecd0d1a8e"
source = "git+https://github.com/boa-dev/temporal.git?rev=c658ac7db4701822cc179d04b56bb9c8fb7e954c#c658ac7db4701822cc179d04b56bb9c8fb7e954c"
dependencies = [
"bitflags 2.6.0",
"icu_calendar",

4
Cargo.toml

@ -29,7 +29,7 @@ exclude = [
[workspace.package]
edition = "2021"
version = "0.18.0"
rust-version = "1.74.0"
rust-version = "1.79.0"
authors = ["boa-dev"]
repository = "https://github.com/boa-dev/boa"
license = "Unlicense OR MIT"
@ -115,7 +115,7 @@ intrusive-collections = "0.9.6"
cfg-if = "1.0.0"
either = "1.13.0"
sys-locale = "0.3.1"
temporal_rs = { git = "https://github.com/boa-dev/temporal.git", rev = "ec2f2d00294ee641285ec1570df6683ecd0d1a8e" }
temporal_rs = { git = "https://github.com/boa-dev/temporal.git", rev = "c658ac7db4701822cc179d04b56bb9c8fb7e954c" }
web-time = "1.1.0"
criterion = "0.5.1"
float-cmp = "0.9.0"

2
core/engine/src/builtins/mod.rs

@ -284,7 +284,6 @@ impl Realm {
#[cfg(feature = "temporal")]
{
temporal::TimeZone::init(self);
temporal::Temporal::init(self);
temporal::Now::init(self);
temporal::Instant::init(self);
@ -295,7 +294,6 @@ impl Realm {
temporal::PlainMonthDay::init(self);
temporal::PlainYearMonth::init(self);
temporal::ZonedDateTime::init(self);
temporal::Calendar::init(self);
}
}
}

1074
core/engine/src/builtins/temporal/calendar/mod.rs

File diff suppressed because it is too large Load Diff

860
core/engine/src/builtins/temporal/calendar/object.rs

@ -1,860 +0,0 @@
//! Boa's implementation of a user-defined Anonymous Calendar.
use crate::{
builtins::{
iterable::IteratorHint,
temporal::{
fields::object_to_temporal_fields, plain_date, plain_date_time, plain_month_day,
plain_year_month,
},
Array,
},
property::PropertyKey,
Context, JsObject, JsString, JsValue,
};
use boa_macros::js_str;
use num_traits::ToPrimitive;
use plain_date::PlainDate;
use plain_date_time::PlainDateTime;
use plain_month_day::PlainMonthDay;
use plain_year_month::PlainYearMonth;
use temporal_rs::{
components::{
calendar::{CalendarDateLike, CalendarProtocol},
Date, Duration, MonthDay, YearMonth,
},
options::ArithmeticOverflow,
TemporalError, TemporalFields, TemporalResult, TinyAsciiStr,
};
impl CalendarProtocol for JsObject {
type Date = JsObject<PlainDate>;
type DateTime = JsObject<PlainDateTime>;
type YearMonth = JsObject<PlainYearMonth>;
type MonthDay = JsObject<PlainMonthDay>;
type Context = Context;
fn date_from_fields(
&self,
fields: &mut TemporalFields,
overflow: ArithmeticOverflow,
context: &mut Context,
) -> TemporalResult<Date<Self>> {
let method = self
.get(js_str!("dateFromFields"), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let fields = JsObject::from_temporal_fields(fields, context)
.map_err(|e| TemporalError::general(e.to_string()))?;
let overflow_obj = JsObject::with_null_proto();
overflow_obj
.create_data_property_or_throw(
js_str!("overflow"),
JsString::from(overflow.to_string()),
context,
)
.map_err(|e| TemporalError::general(e.to_string()))?;
let value = method
.as_callable()
.ok_or_else(|| {
TemporalError::general("dateFromFields must be implemented as a callable method.")
})?
.call(
&self.clone().into(),
&[fields.into(), overflow_obj.into()],
context,
)
.map_err(|e| TemporalError::general(e.to_string()))?;
let obj = value.as_object().map(JsObject::borrow).ok_or_else(|| {
TemporalError::r#type()
.with_message("datefromFields must return a valid PlainDate object.")
})?;
let pd = obj.downcast_ref::<PlainDate>().ok_or_else(|| {
TemporalError::r#type().with_message("Object returned was not a PlainDate")
})?;
Ok(pd.inner.clone())
}
fn year_month_from_fields(
&self,
fields: &mut TemporalFields,
overflow: ArithmeticOverflow,
context: &mut Context,
) -> TemporalResult<YearMonth<JsObject>> {
let method = self
.get(js_str!("yearMonthFromFields"), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let fields = JsObject::from_temporal_fields(fields, context)
.map_err(|e| TemporalError::general(e.to_string()))?;
let overflow_obj = JsObject::with_null_proto();
overflow_obj
.create_data_property_or_throw(
js_str!("overflow"),
JsString::from(overflow.to_string()),
context,
)
.map_err(|e| TemporalError::general(e.to_string()))?;
let value = method
.as_callable()
.ok_or_else(|| {
TemporalError::general(
"yearMonthFromFields must be implemented as a callable method.",
)
})?
.call(
&self.clone().into(),
&[fields.into(), overflow_obj.into()],
context,
)
.map_err(|e| TemporalError::general(e.to_string()))?;
let obj = value.as_object().map(JsObject::borrow).ok_or_else(|| {
TemporalError::r#type()
.with_message("yearMonthFromFields must return a valid PlainYearMonth object.")
})?;
let ym = obj.downcast_ref::<PlainYearMonth>().ok_or_else(|| {
TemporalError::r#type().with_message("Object returned was not a PlainDate")
})?;
Ok(ym.inner.clone())
}
fn month_day_from_fields(
&self,
fields: &mut TemporalFields,
overflow: ArithmeticOverflow,
context: &mut Context,
) -> TemporalResult<MonthDay<JsObject>> {
let method = self
.get(js_str!("yearMonthFromFields"), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let fields = JsObject::from_temporal_fields(fields, context)
.map_err(|e| TemporalError::general(e.to_string()))?;
let overflow_obj = JsObject::with_null_proto();
overflow_obj
.create_data_property_or_throw(
js_str!("overflow"),
JsString::from(overflow.to_string()),
context,
)
.map_err(|e| TemporalError::general(e.to_string()))?;
let value = method
.as_callable()
.ok_or_else(|| {
TemporalError::general(
"yearMonthFromFields must be implemented as a callable method.",
)
})?
.call(
&self.clone().into(),
&[fields.into(), overflow_obj.into()],
context,
)
.map_err(|e| TemporalError::general(e.to_string()))?;
let obj = value.as_object().map(JsObject::borrow).ok_or_else(|| {
TemporalError::r#type()
.with_message("yearMonthFromFields must return a valid PlainYearMonth object.")
})?;
let md = obj.downcast_ref::<PlainMonthDay>().ok_or_else(|| {
TemporalError::r#type().with_message("Object returned was not a PlainDate")
})?;
Ok(md.inner.clone())
}
fn date_add(
&self,
_date: &Date<JsObject>,
_duration: &Duration,
_overflow: ArithmeticOverflow,
_context: &mut Context,
) -> TemporalResult<Date<JsObject>> {
// TODO
Err(TemporalError::general("Not yet implemented."))
}
fn date_until(
&self,
_one: &Date<JsObject>,
_two: &Date<JsObject>,
_largest_unit: temporal_rs::options::TemporalUnit,
_context: &mut Context,
) -> TemporalResult<Duration> {
// TODO
Err(TemporalError::general("Not yet implemented."))
}
fn era(
&self,
_: &CalendarDateLike<JsObject>,
_: &mut Context,
) -> TemporalResult<Option<TinyAsciiStr<16>>> {
// Return undefined as custom calendars do not implement -> Currently.
Ok(None)
}
fn era_year(
&self,
_: &CalendarDateLike<JsObject>,
_: &mut Context,
) -> TemporalResult<Option<i32>> {
// Return undefined as custom calendars do not implement -> Currently.
Ok(None)
}
fn year(
&self,
date_like: &CalendarDateLike<JsObject>,
context: &mut Context,
) -> TemporalResult<i32> {
let date_like = date_like_to_object(date_like, context)?;
let method = self
.get(PropertyKey::from(js_str!("year")), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let val = method
.as_callable()
.expect("is method")
.call(&self.clone().into(), &[date_like], context)
.map_err(|err| TemporalError::general(err.to_string()))?;
// Validate the return value.
// 3. If Type(result) is not Number, throw a TypeError exception.
// 4. If IsIntegralNumber(result) is false, throw a RangeError exception.
// 5. If result < 1𝔽, throw a RangeError exception.
// 6. Return ℝ(result).
let Some(number) = val.as_number() else {
return Err(TemporalError::r#type().with_message("year must return a number."));
};
if !number.is_finite() || number.fract() != 0.0 {
return Err(TemporalError::r#type().with_message("year return must be integral."));
}
if number < 1f64 {
return Err(TemporalError::r#type().with_message("year return must be larger than 1."));
}
let result = number
.to_i32()
.ok_or_else(|| TemporalError::range().with_message("year exceeded a valid range."))?;
Ok(result)
}
fn month(
&self,
date_like: &CalendarDateLike<JsObject>,
context: &mut Context,
) -> TemporalResult<u8> {
let date_like = date_like_to_object(date_like, context)?;
let method = self
.get(PropertyKey::from(js_str!("month")), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let val = method
.as_callable()
.expect("is method")
.call(&self.clone().into(), &[date_like], context)
.map_err(|err| TemporalError::general(err.to_string()))?;
// Validate the return value.
// 3. If Type(result) is not Number, throw a TypeError exception.
// 4. If IsIntegralNumber(result) is false, throw a RangeError exception.
// 5. If result < 1𝔽, throw a RangeError exception.
// 6. Return ℝ(result).
let Some(number) = val.as_number() else {
return Err(TemporalError::r#type().with_message("month must return a number."));
};
if !number.is_finite() || number.fract() != 0.0 {
return Err(TemporalError::r#type().with_message("month return must be integral."));
}
if number < 1f64 {
return Err(TemporalError::r#type().with_message("month return must be larger than 1."));
}
let result = number
.to_u8()
.ok_or_else(|| TemporalError::range().with_message("month exceeded a valid range."))?;
Ok(result)
}
fn month_code(
&self,
date_like: &CalendarDateLike<JsObject>,
context: &mut Context,
) -> TemporalResult<TinyAsciiStr<4>> {
let date_like = date_like_to_object(date_like, context)?;
let method = self
.get(PropertyKey::from(js_str!("monthCode")), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let val = method
.as_callable()
.expect("is method")
.call(&self.clone().into(), &[date_like], context)
.map_err(|err| TemporalError::general(err.to_string()))?;
let JsValue::String(result) = val else {
return Err(TemporalError::r#type().with_message("monthCode return must be a String."));
};
let result = TinyAsciiStr::<4>::from_str(&result.to_std_string_escaped())
.map_err(|_| TemporalError::general("Unexpected monthCode value."))?;
Ok(result)
}
fn day(
&self,
date_like: &CalendarDateLike<JsObject>,
context: &mut Context,
) -> TemporalResult<u8> {
let date_like = date_like_to_object(date_like, context)?;
let method = self
.get(PropertyKey::from(js_str!("day")), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let val = method
.as_callable()
.expect("is method")
.call(&self.clone().into(), &[date_like], context)
.map_err(|err| TemporalError::general(err.to_string()))?;
// Validate the return value.
// 3. If Type(result) is not Number, throw a TypeError exception.
// 4. If IsIntegralNumber(result) is false, throw a RangeError exception.
// 5. If result < 1𝔽, throw a RangeError exception.
// 6. Return ℝ(result).
let Some(number) = val.as_number() else {
return Err(TemporalError::r#type().with_message("day must return a number."));
};
if !number.is_finite() || number.fract() != 0.0 {
return Err(TemporalError::r#type().with_message("day return must be integral."));
}
if number < 1f64 {
return Err(TemporalError::r#type().with_message("day return must be larger than 1."));
}
let result = number
.to_u8()
.ok_or_else(|| TemporalError::range().with_message("day exceeded a valid range."))?;
Ok(result)
}
fn day_of_week(
&self,
date_like: &CalendarDateLike<JsObject>,
context: &mut Context,
) -> TemporalResult<u16> {
let date_like = date_like_to_object(date_like, context)?;
let method = self
.get(PropertyKey::from(js_str!("dayOfWeek")), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let val = method
.as_callable()
.expect("is method")
.call(&self.clone().into(), &[date_like], context)
.map_err(|err| TemporalError::general(err.to_string()))?;
// Validate the return value.
// 3. If Type(result) is not Number, throw a TypeError exception.
// 4. If IsIntegralNumber(result) is false, throw a RangeError exception.
// 5. If result < 1𝔽, throw a RangeError exception.
// 6. Return ℝ(result).
let Some(number) = val.as_number() else {
return Err(TemporalError::r#type().with_message("DayOfWeek must return a number."));
};
if !number.is_finite() || number.fract() != 0.0 {
return Err(TemporalError::r#type().with_message("DayOfWeek return must be integral."));
}
if number < 1f64 {
return Err(
TemporalError::r#type().with_message("DayOfWeek return must be larger than 1.")
);
}
let result = number.to_u16().ok_or_else(|| {
TemporalError::range().with_message("DayOfWeek exceeded valid range.")
})?;
Ok(result)
}
fn day_of_year(
&self,
date_like: &CalendarDateLike<JsObject>,
context: &mut Context,
) -> TemporalResult<u16> {
let date_like = date_like_to_object(date_like, context)?;
let method = self
.get(PropertyKey::from(js_str!("dayOfYear")), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let val = method
.as_callable()
.expect("is method")
.call(&self.clone().into(), &[date_like], context)
.map_err(|err| TemporalError::general(err.to_string()))?;
// Validate the return value.
// 3. If Type(result) is not Number, throw a TypeError exception.
// 4. If IsIntegralNumber(result) is false, throw a RangeError exception.
// 5. If result < 1𝔽, throw a RangeError exception.
// 6. Return ℝ(result).
let Some(number) = val.as_number() else {
return Err(TemporalError::r#type().with_message("dayOfYear must return a number."));
};
if !number.is_finite() || number.fract() != 0.0 {
return Err(TemporalError::r#type().with_message("dayOfYear return must be integral."));
}
if number < 1f64 {
return Err(
TemporalError::r#type().with_message("dayOfYear return must be larger than 1.")
);
}
let result = number.to_u16().ok_or_else(|| {
TemporalError::range().with_message("dayOfYear exceeded valid range.")
})?;
Ok(result)
}
fn week_of_year(
&self,
date_like: &CalendarDateLike<JsObject>,
context: &mut Context,
) -> TemporalResult<u16> {
let date_like = date_like_to_object(date_like, context)?;
let method = self
.get(PropertyKey::from(js_str!("weekOfYear")), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let val = method
.as_callable()
.expect("is method")
.call(&self.clone().into(), &[date_like], context)
.map_err(|err| TemporalError::general(err.to_string()))?;
// Validate the return value.
// 3. If Type(result) is not Number, throw a TypeError exception.
// 4. If IsIntegralNumber(result) is false, throw a RangeError exception.
// 5. If result < 1𝔽, throw a RangeError exception.
// 6. Return ℝ(result).
let Some(number) = val.as_number() else {
return Err(TemporalError::r#type().with_message("weekOfYear must return a number."));
};
if !number.is_finite() || number.fract() != 0.0 {
return Err(TemporalError::r#type().with_message("weekOfYear return must be integral."));
}
if number < 1f64 {
return Err(
TemporalError::r#type().with_message("weekOfYear return must be larger than 1.")
);
}
let result = number.to_u16().ok_or_else(|| {
TemporalError::range().with_message("weekOfYear exceeded valid range.")
})?;
Ok(result)
}
fn year_of_week(
&self,
date_like: &CalendarDateLike<JsObject>,
context: &mut Context,
) -> TemporalResult<i32> {
let date_like = date_like_to_object(date_like, context)?;
let method = self
.get(PropertyKey::from(js_str!("yearOfWeek")), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let val = method
.as_callable()
.expect("is method")
.call(&self.clone().into(), &[date_like], context)
.map_err(|err| TemporalError::general(err.to_string()))?;
// Validate the return value.
// 3. If Type(result) is not Number, throw a TypeError exception.
// 4. If IsIntegralNumber(result) is false, throw a RangeError exception.
// 5. Return ℝ(result).
let Some(number) = val.as_number() else {
return Err(TemporalError::r#type().with_message("yearOfWeek must return a number."));
};
if !number.is_finite() || number.fract() != 0.0 {
return Err(TemporalError::r#type().with_message("yearOfWeek return must be integral."));
}
let result = number.to_i32().ok_or_else(|| {
TemporalError::range().with_message("yearOfWeek exceeded valid range.")
})?;
Ok(result)
}
fn days_in_week(
&self,
date_like: &CalendarDateLike<JsObject>,
context: &mut Context,
) -> TemporalResult<u16> {
let date_like = date_like_to_object(date_like, context)?;
let method = self
.get(PropertyKey::from(js_str!("daysInWeek")), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let val = method
.as_callable()
.expect("is method")
.call(&self.clone().into(), &[date_like], context)
.map_err(|err| TemporalError::general(err.to_string()))?;
// Validate the return value.
// 3. If Type(result) is not Number, throw a TypeError exception.
// 4. If IsIntegralNumber(result) is false, throw a RangeError exception.
// 5. If result < 1𝔽, throw a RangeError exception.
// 6. Return ℝ(result).
let Some(number) = val.as_number() else {
return Err(TemporalError::r#type().with_message("daysInWeek must return a number."));
};
if !number.is_finite() || number.fract() != 0.0 {
return Err(TemporalError::r#type().with_message("daysInWeek return must be integral."));
}
if number < 1f64 {
return Err(
TemporalError::r#type().with_message("daysInWeek return must be larger than 1.")
);
}
let result = number.to_u16().ok_or_else(|| {
TemporalError::range().with_message("daysInWeek exceeded valid range.")
})?;
Ok(result)
}
fn days_in_month(
&self,
date_like: &CalendarDateLike<JsObject>,
context: &mut Context,
) -> TemporalResult<u16> {
let date_like = date_like_to_object(date_like, context)?;
let method = self
.get(PropertyKey::from(js_str!("daysInMonth")), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let val = method
.as_callable()
.expect("is method")
.call(&self.clone().into(), &[date_like], context)
.map_err(|err| TemporalError::general(err.to_string()))?;
// Validate the return value.
// 3. If Type(result) is not Number, throw a TypeError exception.
// 4. If IsIntegralNumber(result) is false, throw a RangeError exception.
// 5. If result < 1𝔽, throw a RangeError exception.
// 6. Return ℝ(result).
let Some(number) = val.as_number() else {
return Err(TemporalError::r#type().with_message("daysInMonth must return a number."));
};
if !number.is_finite() || number.fract() != 0.0 {
return Err(
TemporalError::r#type().with_message("daysInMonth return must be integral.")
);
}
if number < 1f64 {
return Err(
TemporalError::r#type().with_message("daysInMonth return must be larger than 1.")
);
}
let result = number.to_u16().ok_or_else(|| {
TemporalError::range().with_message("daysInMonth exceeded valid range.")
})?;
Ok(result)
}
fn days_in_year(
&self,
date_like: &CalendarDateLike<JsObject>,
context: &mut Context,
) -> TemporalResult<u16> {
let date_like = date_like_to_object(date_like, context)?;
let method = self
.get(PropertyKey::from(js_str!("daysInYear")), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let val = method
.as_callable()
.expect("is method")
.call(&self.clone().into(), &[date_like], context)
.map_err(|err| TemporalError::general(err.to_string()))?;
// Validate the return value.
// 3. If Type(result) is not Number, throw a TypeError exception.
// 4. If IsIntegralNumber(result) is false, throw a RangeError exception.
// 5. If result < 1𝔽, throw a RangeError exception.
// 6. Return ℝ(result).
let Some(number) = val.as_number() else {
return Err(TemporalError::r#type().with_message("daysInYear must return a number."));
};
if !number.is_finite() || number.fract() != 0.0 {
return Err(TemporalError::r#type().with_message("daysInYear return must be integral."));
}
if number < 1f64 {
return Err(
TemporalError::r#type().with_message("daysInYear return must be larger than 1.")
);
}
let result = number.to_u16().ok_or_else(|| {
TemporalError::range().with_message("monthsInYear exceeded valid range.")
})?;
Ok(result)
}
fn months_in_year(
&self,
date_like: &CalendarDateLike<JsObject>,
context: &mut Context,
) -> TemporalResult<u16> {
let date_like = date_like_to_object(date_like, context)?;
let method = self
.get(PropertyKey::from(js_str!("monthsInYear")), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let val = method
.as_callable()
.expect("is method")
.call(&self.clone().into(), &[date_like], context)
.map_err(|err| TemporalError::general(err.to_string()))?;
// Validate the return value.
// 3. If Type(result) is not Number, throw a TypeError exception.
// 4. If IsIntegralNumber(result) is false, throw a RangeError exception.
// 5. If result < 1𝔽, throw a RangeError exception.
// 6. Return ℝ(result).
let Some(number) = val.as_number() else {
return Err(TemporalError::r#type().with_message("monthsInYear must return a number."));
};
if !number.is_finite() || number.fract() != 0.0 {
return Err(
TemporalError::r#type().with_message("monthsInYear return must be integral.")
);
}
if number < 1f64 {
return Err(
TemporalError::r#type().with_message("monthsInYear return must be larger than 1.")
);
}
let result = number.to_u16().ok_or_else(|| {
TemporalError::range().with_message("monthsInYear exceeded valid range.")
})?;
Ok(result)
}
fn in_leap_year(
&self,
date_like: &CalendarDateLike<JsObject>,
context: &mut Context,
) -> TemporalResult<bool> {
let date_like = date_like_to_object(date_like, context)?;
let method = self
.get(PropertyKey::from(js_str!("inLeapYear")), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let val = method
.as_callable()
.expect("is method")
.call(&self.clone().into(), &[date_like], context)
.map_err(|err| TemporalError::general(err.to_string()))?;
let JsValue::Boolean(result) = val else {
return Err(
TemporalError::r#type().with_message("inLeapYear must return a valid boolean.")
);
};
Ok(result)
}
fn fields(&self, fields: Vec<String>, context: &mut Context) -> TemporalResult<Vec<String>> {
let fields_js = Array::create_array_from_list(
fields.iter().map(|s| JsString::from(s.clone()).into()),
context,
);
let method = self
.get(PropertyKey::from(js_str!("fields")), context)
.expect("method must exist on an object that implements the CalendarProtocol.");
let result = method
.as_callable()
.expect("is method")
.call(&self.clone().into(), &[fields_js.into()], context)
.map_err(|e| TemporalError::general(e.to_string()))?;
// validate result and map to a `Vec<String>`
let mut iterator = result
.get_iterator(context, Some(IteratorHint::Sync), None)
.map_err(|e| TemporalError::general(e.to_string()))?;
let mut result = Vec::default();
while iterator
.step(context)
.map_err(|e| TemporalError::general(e.to_string()))?
{
let next_value = iterator
.value(context)
.map_err(|e| TemporalError::general(e.to_string()))?;
let JsValue::String(s) = next_value else {
return Err(TemporalError::r#type()
.with_message("Invalid return type in fields method implementation."));
};
result.push(s.to_std_string_escaped());
}
Ok(result)
}
fn merge_fields(
&self,
fields: &TemporalFields,
additional_fields: &TemporalFields,
context: &mut Context,
) -> TemporalResult<TemporalFields> {
let fields = JsObject::from_temporal_fields(fields, context)
.map_err(|e| TemporalError::general(e.to_string()))?;
let add_fields = JsObject::from_temporal_fields(additional_fields, context)
.map_err(|e| TemporalError::general(e.to_string()))?;
let method = self
.get(PropertyKey::from(js_str!("mergeFields")), context)
.expect("method must exist on an object that implements the CalendarProtocol.");
let value = method
.as_callable()
.expect("is method")
.call(
&self.clone().into(),
&[fields.into(), add_fields.into()],
context,
)
.map_err(|e| TemporalError::general(e.to_string()))?;
let JsValue::Object(o) = value else {
return Err(
TemporalError::r#type().with_message("mergeFields did not return an object.")
);
};
object_to_temporal_fields(&o, context).map_err(|e| TemporalError::general(e.to_string()))
}
fn identifier(&self, context: &mut Context) -> TemporalResult<String> {
let identifier = self
.__get__(
&PropertyKey::from(js_str!("id")),
self.clone().into(),
&mut context.into(),
)
.expect("method must exist on a object that implements the CalendarProtocol.");
let JsValue::String(s) = identifier else {
return Err(TemporalError::range().with_message("Identifier was not a string"));
};
Ok(s.to_std_string_escaped())
}
}
/// Utility function for converting `Temporal`'s `CalendarDateLike` to it's `Boa` specific `JsObject`.
pub(crate) fn date_like_to_object(
date_like: &CalendarDateLike<JsObject>,
context: &mut Context,
) -> TemporalResult<JsValue> {
match date_like {
CalendarDateLike::Date(d) => plain_date::create_temporal_date(d.clone(), None, context)
.map_err(|e| TemporalError::general(e.to_string()))
.map(Into::into),
CalendarDateLike::DateTime(dt) => {
plain_date_time::create_temporal_datetime(dt.clone(), None, context)
.map_err(|e| TemporalError::general(e.to_string()))
.map(Into::into)
}
CalendarDateLike::CustomMonthDay(md) => Ok(md.clone().upcast().into()),
CalendarDateLike::CustomYearMonth(ym) => Ok(ym.clone().upcast().into()),
CalendarDateLike::CustomDate(pd) => Ok(pd.clone().upcast().into()),
CalendarDateLike::CustomDateTime(pdt) => Ok(pdt.clone().upcast().into()),
}
}

61
core/engine/src/builtins/temporal/calendar/tests.rs

@ -1,61 +0,0 @@
use crate::{js_string, run_test_actions, TestAction};
#[test]
fn calendar_constructor() {
// TODO: Add other BuiltinCalendars
run_test_actions([TestAction::assert_eq(
"new Temporal.Calendar('iso8601').id",
js_string!("iso8601"),
)]);
}
#[test]
fn calendar_methods() {
run_test_actions([
TestAction::run("let iso = new Temporal.Calendar('iso8601');"),
TestAction::assert_eq("iso.inLeapYear('2020-11-20')", true),
TestAction::assert_eq("iso.daysInYear('2020-11-20')", 366),
TestAction::assert_eq("iso.daysInYear('2021-11-20')", 365),
TestAction::assert_eq("iso.monthsInYear('2021-11-20')", 12),
TestAction::assert_eq("iso.daysInWeek('2021-11-20')", 7),
]);
}
#[test]
fn run_custom_calendar() {
run_test_actions([
TestAction::run(
r#"const custom = {
dateAdd() {},
dateFromFields() {},
dateUntil() {},
day() {},
dayOfWeek() {},
dayOfYear() {},
daysInMonth() { return 14 },
daysInWeek() {return 6},
daysInYear() {return 360},
fields() {},
id: "custom-calendar",
inLeapYear() {},
mergeFields() {},
month() {},
monthCode() {},
monthDayFromFields() {},
monthsInYear() {},
weekOfYear() {},
year() {},
yearMonthFromFields() {},
yearOfWeek() {},
};
let cal = Temporal.Calendar.from(custom);
let date = "1972-05-01";
"#,
),
TestAction::assert_eq("cal.id", js_string!("custom-calendar")),
TestAction::assert_eq("cal.daysInMonth(date)", 14),
TestAction::assert_eq("cal.daysInWeek(date)", 6),
TestAction::assert_eq("cal.daysInYear(date)", 360),
]);
}

21
core/engine/src/builtins/temporal/duration/mod.rs

@ -691,7 +691,6 @@ impl Duration {
date: plain_relative_to.as_ref(),
zdt: zoned_relative_to.as_ref(),
},
context,
)?;
create_temporal_duration(rounded_duration, None, context).map(Into::into)
}
@ -781,26 +780,6 @@ impl Duration {
// -- Duration Abstract Operations --
/// 7.5.8 `ToTemporalDuration ( item )`
pub(crate) fn to_temporal_duration(
item: &JsValue,
context: &mut Context,
) -> JsResult<InnerDuration> {
// 1a. If Type(item) is Object
// 1b. and item has an [[InitializedTemporalDuration]] internal slot, then
if let Some(duration) = item
.as_object()
.and_then(JsObject::downcast_ref::<Duration>)
{
return Ok(duration.inner);
}
// 2. Let result be ? ToTemporalDurationRecord(item).
let result = to_temporal_duration_record(item, context)?;
// 3. Return ! CreateTemporalDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]).
Ok(result)
}
/// 7.5.9 `ToTemporalDurationRecord ( temporalDurationLike )`
pub(crate) fn to_temporal_duration_record(
temporal_duration_like: &JsValue,

250
core/engine/src/builtins/temporal/fields.rs

@ -1,250 +0,0 @@
//! A Rust native implementation of the `fields` object used in `Temporal`.
use std::str::FromStr;
use crate::{
js_string, object::internal_methods::InternalMethodContext, property::PropertyKey,
value::PreferredType, Context, JsNativeError, JsObject, JsResult, JsString, JsValue,
};
use rustc_hash::FxHashSet;
use temporal_rs::fields::{FieldConversion, FieldValue, TemporalFields};
use super::{to_integer_with_truncation, to_positive_integer_with_trunc};
// TODO: Move extended and required fields into the temporal library?
/// `PrepareTemporalFeilds`
pub(crate) fn prepare_temporal_fields(
fields: &JsObject,
field_names: &mut Vec<JsString>,
required_fields: &mut Vec<JsString>,
extended_fields: Option<Vec<(String, bool)>>,
partial: bool,
dup_behaviour: Option<JsString>,
context: &mut Context,
) -> JsResult<TemporalFields> {
// 1. If duplicateBehaviour is not present, set duplicateBehaviour to throw.
let dup_option = dup_behaviour.unwrap_or_else(|| js_string!("throw"));
// 2. Let result be OrdinaryObjectCreate(null).
let mut result = TemporalFields::default();
// 3. Let any be false.
let mut any = false;
// 4. If extraFieldDescriptors is present, then
if let Some(extra_fields) = extended_fields {
for (field_name, required) in extra_fields {
// a. For each Calendar Field Descriptor Record desc of extraFieldDescriptors, do
// i. Assert: fieldNames does not contain desc.[[Property]].
// ii. Append desc.[[Property]] to fieldNames.
field_names.push(JsString::from(field_name.clone()));
// iii. If desc.[[Required]] is true and requiredFields is a List, then
if required && !partial {
// 1. Append desc.[[Property]] to requiredFields.
required_fields.push(JsString::from(field_name));
}
}
}
// 5. Let sortedFieldNames be SortStringListByCodeUnit(fieldNames).
// 6. Let previousProperty be undefined.
let mut dups_map = FxHashSet::default();
// 7. For each property name property of sortedFieldNames, do
for field in &*field_names {
// a. If property is one of "constructor" or "__proto__", then
if field.to_std_string_escaped().as_str() == "constructor"
|| field.to_std_string_escaped().as_str() == "__proto__"
{
// i. Throw a RangeError exception.
return Err(JsNativeError::range()
.with_message("constructor or proto is out of field range.")
.into());
}
let new_value = dups_map.insert(field);
// b. If property is not equal to previousProperty, then
if new_value {
// i. Let value be ? Get(fields, property).
let value = fields.get(PropertyKey::from(field.clone()), context)?;
// ii. If value is not undefined, then
if !value.is_undefined() {
// 1. Set any to true.
any = true;
// 2. If property is in the Property column of Table 17 and there is a Conversion value in the same row, then
// a. Let Conversion be the Conversion value of the same row.
// TODO: Conversion from TemporalError -> JsError
let conversion = FieldConversion::from_str(field.to_std_string_escaped().as_str())
.map_err(|_| JsNativeError::range().with_message("wrong field value"))?;
// b. If Conversion is ToIntegerWithTruncation, then
let converted_value = match conversion {
FieldConversion::ToIntegerWithTruncation => {
// i. Set value to ? ToIntegerWithTruncation(value).
let v = to_integer_with_truncation(&value, context)?;
// ii. Set value to 𝔽(value).
FieldValue::Integer(v)
}
// c. Else if Conversion is ToPositiveIntegerWithTruncation, then
FieldConversion::ToPositiveIntegerWithTruncation => {
// i. Set value to ? ToPositiveIntegerWithTruncation(value).
let v = to_positive_integer_with_trunc(&value, context)?;
// ii. Set value to 𝔽(value).
FieldValue::Integer(v)
}
// d. Else,
// i. Assert: Conversion is ToPrimitiveAndRequireString.
FieldConversion::ToPrimativeAndRequireString => {
// ii. NOTE: Non-primitive values are supported here for consistency with other fields, but such values must coerce to Strings.
// iii. Set value to ? ToPrimitive(value, string).
let primitive = value.to_primitive(context, PreferredType::String)?;
// iv. If value is not a String, throw a TypeError exception.
FieldValue::String(primitive.to_string(context)?.to_std_string_escaped())
}
FieldConversion::None => {
unreachable!("todo need to implement conversion handling for tz.")
}
};
// 3. Perform ! CreateDataPropertyOrThrow(result, property, value).
result
.set_field_value(&field.to_std_string_escaped(), &converted_value)
.expect("FieldConversion enforces the appropriate type");
// iii. Else if requiredFields is a List, then
} else if !partial {
// 1. If requiredFields contains property, then
if required_fields.contains(field) {
// a. Throw a TypeError exception.
return Err(JsNativeError::typ()
.with_message("A required TemporalField was not provided.")
.into());
}
// NOTE: flag that the value is active and the default should be used.
// 2. If property is in the Property column of Table 17, then
// a. Set value to the corresponding Default value of the same row.
// 3. Perform ! CreateDataPropertyOrThrow(result, property, value).
result.require_field(&field.to_std_string_escaped());
}
// c. Else if duplicateBehaviour is throw, then
} else if dup_option.to_std_string_escaped() == "throw" {
// i. Throw a RangeError exception.
return Err(JsNativeError::range()
.with_message("Cannot have a duplicate field")
.into());
}
// d. Set previousProperty to property.
}
// 8. If requiredFields is partial and any is false, then
if partial && !any {
// a. Throw a TypeError exception.
return Err(JsNativeError::range()
.with_message("requiredFields cannot be partial when any is false")
.into());
}
// 9. Return result.
Ok(result)
}
// NOTE(nekevss): The below serves as a replacement for `Snapshot` on `Calendar.prototype.mergeFields`.
//
// Some potential issues here: `Calendar.prototype.mergeFields` appears to allow extra fields that
// are not part of a `TemporalFields` record; however, the specification only calls `mergeFields` on an
// object returned by `PrepareTemporalFields`, so the translation should be fine sound.
//
// The restriction/trade-off would occur if someone wanted to include non-normative calendar fields (i.e. something
// not accounted for in the specification) in a Custom Calendar or use `Calendar.prototype.mergeFields` in
// general as a way to merge two objects.
pub(crate) fn object_to_temporal_fields(
source: &JsObject,
context: &mut Context,
) -> JsResult<TemporalFields> {
// Adapted from `CopyDataProperties` with ExcludedKeys -> << >> && ExcludedValues -> << Undefined >>
const VALID_FIELDS: [&str; 14] = [
"year",
"month",
"monthCode",
"day",
"hour",
"minute",
"second",
"millisecond",
"microsecond",
"nanosecond",
"offset",
"timeZone",
"era",
"eraYear",
];
let mut copy = TemporalFields::default();
let keys = source.__own_property_keys__(context)?;
for key in &keys {
let desc = source.__get_own_property__(key, &mut InternalMethodContext::new(context))?;
match desc {
// Enforce that the `PropertyKey` is a valid field here.
Some(desc)
if desc.expect_enumerable() && VALID_FIELDS.contains(&key.to_string().as_str()) =>
{
let value = source.get(key.clone(), context)?;
// Below is repurposed from `PrepareTemporalFields`.
if !value.is_undefined() {
let conversion = FieldConversion::from_str(&key.to_string())?;
let converted_value = match conversion {
FieldConversion::ToIntegerWithTruncation => {
let v = to_integer_with_truncation(&value, context)?;
FieldValue::Integer(v)
}
FieldConversion::ToPositiveIntegerWithTruncation => {
let v = to_positive_integer_with_trunc(&value, context)?;
FieldValue::Integer(v)
}
FieldConversion::ToPrimativeAndRequireString => {
let primitive = value.to_primitive(context, PreferredType::String)?;
FieldValue::String(
primitive.to_string(context)?.to_std_string_escaped(),
)
}
FieldConversion::None => {
unreachable!("todo need to implement conversion handling for tz.")
}
};
// TODO: Test the below further and potentially expand handling.
copy.set_field_value(&key.to_string(), &converted_value)
.expect("FieldConversion enforces the appropriate type");
}
}
_ => {}
};
}
Ok(copy)
}
impl JsObject {
pub(crate) fn from_temporal_fields(
fields: &TemporalFields,
context: &mut Context,
) -> JsResult<Self> {
let obj = JsObject::with_null_proto();
for (key, value) in fields.active_kvs() {
let js_value = match value {
FieldValue::Undefined => JsValue::undefined(),
FieldValue::Integer(x) => JsValue::Integer(x),
FieldValue::String(s) => JsValue::String(s.into()),
};
obj.create_data_property_or_throw(JsString::from(key), js_value, context)?;
}
Ok(obj)
}
}

41
core/engine/src/builtins/temporal/mod.rs

@ -7,7 +7,6 @@
mod calendar;
mod duration;
mod error;
mod fields;
mod instant;
mod now;
mod options;
@ -23,8 +22,8 @@ mod zoned_date_time;
mod tests;
pub use self::{
calendar::*, duration::*, instant::*, now::*, plain_date::*, plain_date_time::*,
plain_month_day::*, plain_time::*, plain_year_month::*, time_zone::*, zoned_date_time::*,
duration::*, instant::*, now::*, plain_date::*, plain_date_time::*, plain_month_day::*,
plain_time::*, plain_year_month::*, zoned_date_time::*,
};
use crate::{
@ -242,10 +241,7 @@ pub(crate) fn _iterator_to_list_of_types(
// 13.17 `ValidateTemporalRoundingIncrement ( increment, dividend, inclusive )`
// Moved to temporal_rs
type RelativeTemporalObjectResult = JsResult<(
Option<TemporalDate<JsObject>>,
Option<TemporalZonedDateTime<JsObject, JsCustomTimeZone>>,
)>;
type RelativeTemporalObjectResult = JsResult<(Option<TemporalDate>, Option<TemporalZonedDateTime>)>;
/// 13.21 `ToRelativeTemporalObject ( options )`
pub(crate) fn to_relative_temporal_object(
@ -285,6 +281,7 @@ pub(crate) fn to_relative_temporal_object(
/// 13.43 `ToPositiveIntegerWithTruncation ( argument )`
#[inline]
#[allow(unused)]
pub(crate) fn to_positive_integer_with_trunc(
value: &JsValue,
context: &mut Context,
@ -345,3 +342,33 @@ pub(crate) fn to_integer_if_integral(arg: &JsValue, context: &mut Context) -> Js
// Note: Deviates from Proposal spec -> proto appears to be always null across the specification.
// 14.7 `SnapshotOwnProperties ( source, proto [ , excludedKeys [ , excludedValues ] ] )`
// Migrated or repurposed to `temporal_rs`/`fields.rs`
fn extract_from_temporal_type<DF, DTF, YMF, MDF, ZDTF, Ret>(
object: &JsObject,
date_f: DF,
datetime_f: DTF,
year_month_f: YMF,
month_day_f: MDF,
zoned_datetime_f: ZDTF,
) -> JsResult<Option<Ret>>
where
DF: FnOnce(JsObject<PlainDate>) -> JsResult<Option<Ret>>,
DTF: FnOnce(JsObject<PlainDateTime>) -> JsResult<Option<Ret>>,
YMF: FnOnce(JsObject<PlainYearMonth>) -> JsResult<Option<Ret>>,
MDF: FnOnce(JsObject<PlainMonthDay>) -> JsResult<Option<Ret>>,
ZDTF: FnOnce(JsObject<ZonedDateTime>) -> JsResult<Option<Ret>>,
{
if let Ok(date) = object.clone().downcast::<PlainDate>() {
return date_f(date);
} else if let Ok(dt) = object.clone().downcast::<PlainDateTime>() {
return datetime_f(dt);
} else if let Ok(ym) = object.clone().downcast::<PlainYearMonth>() {
return year_month_f(ym);
} else if let Ok(md) = object.clone().downcast::<PlainMonthDay>() {
return month_day_f(md);
} else if let Ok(dt) = object.clone().downcast::<ZonedDateTime>() {
return zoned_datetime_f(dt);
}
Ok(None)
}

22
core/engine/src/builtins/temporal/now.rs

@ -1,10 +1,7 @@
//! Boa's implementation of `Temporal.Now` ECMAScript Builtin object.
use crate::{
builtins::{
temporal::{create_temporal_time_zone, default_time_zone},
BuiltInBuilder, BuiltInObject, IntrinsicObject,
},
builtins::{BuiltInBuilder, BuiltInObject, IntrinsicObject},
context::intrinsics::Intrinsics,
js_string,
property::Attribute,
@ -14,8 +11,9 @@ use crate::{
Context, JsBigInt, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue,
};
use boa_profiler::Profiler;
use temporal_rs::components::tz::TimeZone;
use super::{ns_max_instant, ns_min_instant};
use super::{ns_max_instant, ns_min_instant, time_zone::default_time_zone};
/// JavaScript `Temporal.Now` object.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
@ -61,13 +59,16 @@ impl Now {
/// `Temporal.Now.timeZoneId ( )`
///
/// More information:
/// - [ECMAScript specififcation][spec]
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.now.timezone
#[allow(clippy::unnecessary_wraps)]
fn time_zone_id(_: &JsValue, _args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Return ! SystemTimeZone().
system_time_zone(context)
system_time_zone(context)?
.id()
.map(|s| JsValue::from(js_string!(s.as_str())))
.map_err(Into::into)
}
/// `Temporal.Now.instant()`
@ -176,9 +177,12 @@ fn system_zoned_date_time() {
///
/// [spec]: https://tc39.es/proposal-temporal/#sec-temporal-systemtimezone
#[allow(unused)]
fn system_time_zone(context: &mut Context) -> JsResult<JsValue> {
fn system_time_zone(context: &mut Context) -> JsResult<TimeZone> {
// 1. Let identifier be ! DefaultTimeZone().
let identifier = default_time_zone(context);
// 2. Return ! CreateTemporalTimeZone(identifier).
create_temporal_time_zone(identifier, None, context)
Err(JsNativeError::error()
.with_message("not yet implemented.")
.into())
}

3
core/engine/src/builtins/temporal/options.rs

@ -47,8 +47,9 @@ pub(crate) fn get_temporal_unit(
}
#[derive(Debug, Clone, Copy)]
#[allow(unused)]
pub(crate) enum TemporalUnitGroup {
Date,
Date, // Need to assert if this is neede anymore with the removal of `Temporal.Calendar`
Time,
DateTime,
}

91
core/engine/src/builtins/temporal/plain_date/mod.rs

@ -21,24 +21,24 @@ use boa_macros::js_str;
use boa_profiler::Profiler;
use temporal_rs::{
components::{
calendar::{CalendarSlot, GetCalendarSlot},
calendar::{Calendar, GetTemporalCalendar},
Date as InnerDate, DateTime,
},
iso::IsoDateSlots,
options::ArithmeticOverflow,
};
use super::{calendar, create_temporal_calendar, PlainDateTime, ZonedDateTime};
use super::{calendar, PlainDateTime, ZonedDateTime};
/// The `Temporal.PlainDate` object.
#[derive(Debug, Clone, Trace, Finalize, JsData)]
#[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerDate` could contain `Trace` types.
pub struct PlainDate {
pub(crate) inner: InnerDate<JsObject>,
pub(crate) inner: InnerDate,
}
impl PlainDate {
pub(crate) fn new(inner: InnerDate<JsObject>) -> Self {
pub(crate) fn new(inner: InnerDate) -> Self {
Self { inner }
}
}
@ -49,8 +49,8 @@ impl IsoDateSlots for JsObject<PlainDate> {
}
}
impl GetCalendarSlot<JsObject> for JsObject<PlainDate> {
fn get_calendar(&self) -> CalendarSlot<JsObject> {
impl GetTemporalCalendar for JsObject<PlainDate> {
fn get_calendar(&self) -> Calendar {
self.borrow().data().inner.get_calendar()
}
}
@ -212,7 +212,6 @@ impl IntrinsicObject for PlainDate {
.method(Self::to_plain_year_month, js_string!("toPlainYearMonth"), 0)
.method(Self::to_plain_month_day, js_string!("toPlainMonthDay"), 0)
.method(Self::get_iso_fields, js_string!("getISOFields"), 0)
.method(Self::get_calendar, js_string!("getCalendar"), 0)
.method(Self::add, js_string!("add"), 2)
.method(Self::subtract, js_string!("subtract"), 2)
.method(Self::with, js_string!("with"), 2)
@ -248,8 +247,7 @@ impl BuiltInConstructor for PlainDate {
let iso_year = super::to_integer_with_truncation(args.get_or_undefined(0), context)?;
let iso_month = super::to_integer_with_truncation(args.get_or_undefined(1), context)?;
let iso_day = super::to_integer_with_truncation(args.get_or_undefined(2), context)?;
let calendar_slot =
calendar::to_temporal_calendar_slot_value(args.get_or_undefined(3), context)?;
let calendar_slot = calendar::to_temporal_calendar_slot_value(args.get_or_undefined(3))?;
let date = InnerDate::new(
iso_year,
@ -275,7 +273,7 @@ impl PlainDate {
JsNativeError::typ().with_message("the this object must be a PlainDate object.")
})?;
Ok(JsString::from(date.inner.calendar().identifier(context)?).into())
Ok(JsString::from(date.inner.calendar().identifier()?).into())
}
/// 3.3.4 get `Temporal.PlainDate.prototype.year`
@ -284,13 +282,13 @@ impl PlainDate {
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
let Ok(date) = obj.clone().downcast::<Self>() else {
let Some(date) = obj.downcast_ref::<Self>() else {
return Err(JsNativeError::typ()
.with_message("the this object must be a PlainDate object.")
.into());
};
Ok(InnerDate::<JsObject>::contextual_year(&date, context)?.into())
Ok(date.inner.year()?.into())
}
/// 3.3.5 get `Temporal.PlainDate.prototype.month`
@ -299,13 +297,13 @@ impl PlainDate {
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
let Ok(date) = obj.clone().downcast::<Self>() else {
let Some(date) = obj.downcast_ref::<Self>() else {
return Err(JsNativeError::typ()
.with_message("the this object must be a PlainDate object.")
.into());
};
Ok(InnerDate::<JsObject>::contextual_month(&date, context)?.into())
Ok(date.inner.month()?.into())
}
/// 3.3.6 get Temporal.PlainDate.prototype.monthCode
@ -314,16 +312,13 @@ impl PlainDate {
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
let Ok(date) = obj.clone().downcast::<Self>() else {
let Some(date) = obj.downcast_ref::<Self>() else {
return Err(JsNativeError::typ()
.with_message("the this object must be a PlainDate object.")
.into());
};
Ok(
JsString::from(InnerDate::<JsObject>::contextual_month_code(&date, context)?.as_str())
.into(),
)
Ok(JsString::from(date.inner.month_code()?.as_str()).into())
}
/// 3.3.7 get `Temporal.PlainDate.prototype.day`
@ -332,13 +327,13 @@ impl PlainDate {
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
let Ok(date) = obj.clone().downcast::<Self>() else {
let Some(date) = obj.downcast_ref::<Self>() else {
return Err(JsNativeError::typ()
.with_message("the this object must be a PlainDate object.")
.into());
};
Ok(InnerDate::<JsObject>::contextual_day(&date, context)?.into())
Ok(date.inner.day()?.into())
}
/// 3.3.8 get `Temporal.PlainDate.prototype.dayOfWeek`
@ -347,13 +342,13 @@ impl PlainDate {
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
let Ok(date) = obj.clone().downcast::<Self>() else {
let Some(date) = obj.downcast_ref::<Self>() else {
return Err(JsNativeError::typ()
.with_message("the this object must be a PlainDate object.")
.into());
};
Ok(InnerDate::<JsObject>::contextual_day_of_week(&date, context)?.into())
Ok(date.inner.day_of_week()?.into())
}
/// 3.3.9 get `Temporal.PlainDate.prototype.dayOfYear`
@ -362,13 +357,13 @@ impl PlainDate {
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
let Ok(date) = obj.clone().downcast::<Self>() else {
let Some(date) = obj.downcast_ref::<Self>() else {
return Err(JsNativeError::typ()
.with_message("the this object must be a PlainDate object.")
.into());
};
Ok(InnerDate::<JsObject>::contextual_day_of_year(&date, context)?.into())
Ok(date.inner.day_of_year()?.into())
}
/// 3.3.10 get `Temporal.PlainDate.prototype.weekOfYear`
@ -377,13 +372,13 @@ impl PlainDate {
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
let Ok(date) = obj.clone().downcast::<Self>() else {
let Some(date) = obj.downcast_ref::<Self>() else {
return Err(JsNativeError::typ()
.with_message("the this object must be a PlainDate object.")
.into());
};
Ok(InnerDate::<JsObject>::contextual_week_of_year(&date, context)?.into())
Ok(date.inner.week_of_year()?.into())
}
/// 3.3.11 get `Temporal.PlainDate.prototype.yearOfWeek`
@ -392,13 +387,13 @@ impl PlainDate {
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
let Ok(date) = obj.clone().downcast::<Self>() else {
let Some(date) = obj.downcast_ref::<Self>() else {
return Err(JsNativeError::typ()
.with_message("the this object must be a PlainDate object.")
.into());
};
Ok(InnerDate::<JsObject>::contextual_year_of_week(&date, context)?.into())
Ok(date.inner.year_of_week()?.into())
}
/// 3.3.12 get `Temporal.PlainDate.prototype.daysInWeek`
@ -407,13 +402,13 @@ impl PlainDate {
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
let Ok(date) = obj.clone().downcast::<Self>() else {
let Some(date) = obj.downcast_ref::<Self>() else {
return Err(JsNativeError::typ()
.with_message("the this object must be a PlainDate object.")
.into());
};
Ok(InnerDate::<JsObject>::contextual_days_in_week(&date, context)?.into())
Ok(date.inner.days_in_week()?.into())
}
/// 3.3.13 get `Temporal.PlainDate.prototype.daysInMonth`
@ -426,13 +421,13 @@ impl PlainDate {
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
let Ok(date) = obj.clone().downcast::<Self>() else {
let Some(date) = obj.downcast_ref::<Self>() else {
return Err(JsNativeError::typ()
.with_message("the this object must be a PlainDate object.")
.into());
};
Ok(InnerDate::<JsObject>::contextual_days_in_month(&date, context)?.into())
Ok(date.inner.days_in_month()?.into())
}
/// 3.3.14 get `Temporal.PlainDate.prototype.daysInYear`
@ -441,13 +436,13 @@ impl PlainDate {
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
let Ok(date) = obj.clone().downcast::<Self>() else {
let Some(date) = obj.downcast_ref::<Self>() else {
return Err(JsNativeError::typ()
.with_message("the this object must be a PlainDate object.")
.into());
};
Ok(InnerDate::<JsObject>::contextual_days_in_year(&date, context)?.into())
Ok(date.inner.days_in_year()?.into())
}
/// 3.3.15 get `Temporal.PlainDate.prototype.monthsInYear`
@ -460,13 +455,13 @@ impl PlainDate {
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
let Ok(date) = obj.clone().downcast::<Self>() else {
let Some(date) = obj.downcast_ref::<Self>() else {
return Err(JsNativeError::typ()
.with_message("the this object must be a PlainDate object.")
.into());
};
Ok(InnerDate::<JsObject>::contextual_months_in_year(&date, context)?.into())
Ok(date.inner.months_in_year()?.into())
}
/// 3.3.16 get `Temporal.PlainDate.prototype.inLeapYear`
@ -475,13 +470,13 @@ impl PlainDate {
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
let Ok(date) = obj.clone().downcast::<Self>() else {
let Some(date) = obj.downcast_ref::<Self>() else {
return Err(JsNativeError::typ()
.with_message("the this object must be a PlainDate object.")
.into());
};
Ok(InnerDate::<JsObject>::contextual_in_leap_year(&date, context)?.into())
Ok(date.inner.in_leap_year()?.into())
}
}
@ -506,18 +501,6 @@ impl PlainDate {
.into())
}
/// 3.3.20 `Temporal.PlainDate.prototype.getCalendar ( )`
fn get_calendar(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let date = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainDate object.")
})?;
create_temporal_calendar(date.inner.calendar().clone(), None, context)
}
fn add(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Err(JsNativeError::error()
.with_message("not yet implemented.")
@ -575,7 +558,7 @@ impl PlainDate {
/// 3.5.3 `CreateTemporalDate ( isoYear, isoMonth, isoDay, calendar [ , newTarget ] )`
pub(crate) fn create_temporal_date(
inner: InnerDate<JsObject>,
inner: InnerDate,
new_target: Option<&JsValue>,
context: &mut Context,
) -> JsResult<JsObject> {
@ -583,7 +566,7 @@ pub(crate) fn create_temporal_date(
// 1. If IsValidISODate(isoYear, isoMonth, isoDay) is false, throw a RangeError exception.
// 2. If ISODateTimeWithinLimits(isoYear, isoMonth, isoDay, 12, 0, 0, 0, 0, 0) is false, throw a RangeError exception.
if !DateTime::<JsObject>::validate(&inner) {
if !DateTime::validate(&inner) {
return Err(JsNativeError::range()
.with_message("Date is not within ISO date time limits.")
.into());
@ -684,7 +667,7 @@ pub(crate) fn to_temporal_date(
// 13. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], calendar).
let result = date_like_string
.to_std_string_escaped()
.parse::<InnerDate<JsObject>>()
.parse::<InnerDate>()
.map_err(|err| JsNativeError::range().with_message(err.to_string()))?;
Ok(PlainDate::new(result))

74
core/engine/src/builtins/temporal/plain_date_time/mod.rs

@ -23,7 +23,7 @@ mod tests;
use temporal_rs::{
components::{
calendar::{CalendarSlot, GetCalendarSlot},
calendar::{Calendar, GetTemporalCalendar},
DateTime as InnerDateTime,
},
iso::{IsoDate, IsoDateSlots},
@ -33,15 +33,15 @@ use temporal_rs::{
#[derive(Debug, Clone, Trace, Finalize, JsData)]
#[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerDateTime` could contain `Trace` types.
pub struct PlainDateTime {
pub(crate) inner: InnerDateTime<JsObject>,
pub(crate) inner: InnerDateTime,
}
impl PlainDateTime {
fn new(inner: InnerDateTime<JsObject>) -> Self {
fn new(inner: InnerDateTime) -> Self {
Self { inner }
}
pub(crate) fn inner(&self) -> &InnerDateTime<JsObject> {
pub(crate) fn inner(&self) -> &InnerDateTime {
&self.inner
}
}
@ -52,8 +52,8 @@ impl IsoDateSlots for JsObject<PlainDateTime> {
}
}
impl GetCalendarSlot<JsObject> for JsObject<PlainDateTime> {
fn get_calendar(&self) -> CalendarSlot<JsObject> {
impl GetTemporalCalendar for JsObject<PlainDateTime> {
fn get_calendar(&self) -> Calendar {
self.borrow().data().inner.get_calendar()
}
}
@ -325,8 +325,7 @@ impl BuiltInConstructor for PlainDateTime {
.get(8)
.map_or(Ok(0), |v| to_integer_with_truncation(v, context))?;
// 11. Let calendar be ? ToTemporalCalendarSlotValue(calendarLike, "iso8601").
let calendar_slot =
calendar::to_temporal_calendar_slot_value(args.get_or_undefined(9), context)?;
let calendar_slot = calendar::to_temporal_calendar_slot_value(args.get_or_undefined(9))?;
let dt = InnerDateTime::new(
iso_year,
@ -358,7 +357,7 @@ impl PlainDateTime {
JsNativeError::typ().with_message("the this object must be a PlainDateTime object.")
})?;
Ok(JsString::from(date.inner.calendar().identifier(context)?).into())
Ok(JsString::from(date.inner.calendar().identifier()?).into())
}
/// 5.3.4 get `Temporal.PlainDateTime.prototype.year`
@ -367,13 +366,13 @@ impl PlainDateTime {
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
let Ok(date) = obj.clone().downcast::<Self>() else {
let Some(date) = obj.downcast_ref::<Self>() else {
return Err(JsNativeError::typ()
.with_message("the this object must be a PlainDateTime object.")
.into());
};
Ok(InnerDateTime::<JsObject>::contextual_year(&date, context)?.into())
Ok(date.inner.year()?.into())
}
/// 5.3.5 get `Temporal.PlainDateTime.prototype.month`
@ -382,13 +381,13 @@ impl PlainDateTime {
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
let Ok(date) = obj.clone().downcast::<Self>() else {
let Some(date) = obj.downcast_ref::<Self>() else {
return Err(JsNativeError::typ()
.with_message("the this object must be a PlainDateTime object.")
.into());
};
Ok(InnerDateTime::<JsObject>::contextual_month(&date, context)?.into())
Ok(date.inner.month()?.into())
}
/// 5.3.6 get Temporal.PlainDateTime.prototype.monthCode
@ -397,16 +396,13 @@ impl PlainDateTime {
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
let Ok(date) = obj.clone().downcast::<Self>() else {
let Some(date) = obj.downcast_ref::<Self>() else {
return Err(JsNativeError::typ()
.with_message("the this object must be a PlainDateTime object.")
.into());
};
Ok(JsString::from(
InnerDateTime::<JsObject>::contextual_month_code(&date, context)?.as_str(),
)
.into())
Ok(JsString::from(date.inner.month_code()?.as_str()).into())
}
/// 5.3.7 get `Temporal.PlainDateTime.prototype.day`
@ -415,13 +411,13 @@ impl PlainDateTime {
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
let Ok(date) = obj.clone().downcast::<Self>() else {
let Some(date) = obj.downcast_ref::<Self>() else {
return Err(JsNativeError::typ()
.with_message("the this object must be a PlainDateTime object.")
.into());
};
Ok(InnerDateTime::<JsObject>::contextual_day(&date, context)?.into())
Ok(date.inner.day()?.into())
}
/// 5.3.8 get `Temporal.PlainDateTime.prototype.hour`
@ -520,13 +516,13 @@ impl PlainDateTime {
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
let Ok(date) = obj.clone().downcast::<Self>() else {
let Some(date) = obj.downcast_ref::<Self>() else {
return Err(JsNativeError::typ()
.with_message("the this object must be a PlainDateTime object.")
.into());
};
Ok(InnerDateTime::<JsObject>::contextual_day_of_week(&date, context)?.into())
Ok(date.inner.day_of_week()?.into())
}
/// 5.3.15 get `Temporal.PlainDateTime.prototype.dayOfYear`
@ -535,13 +531,13 @@ impl PlainDateTime {
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
let Ok(date) = obj.clone().downcast::<Self>() else {
let Some(date) = obj.downcast_ref::<Self>() else {
return Err(JsNativeError::typ()
.with_message("the this object must be a PlainDateTime object.")
.into());
};
Ok(InnerDateTime::<JsObject>::contextual_day_of_year(&date, context)?.into())
Ok(date.inner.day_of_year()?.into())
}
/// 5.3.16 get `Temporal.PlainDateTime.prototype.weekOfYear`
@ -550,13 +546,13 @@ impl PlainDateTime {
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
let Ok(date) = obj.clone().downcast::<Self>() else {
let Some(date) = obj.downcast_ref::<Self>() else {
return Err(JsNativeError::typ()
.with_message("the this object must be a PlainDateTime object.")
.into());
};
Ok(InnerDateTime::<JsObject>::contextual_week_of_year(&date, context)?.into())
Ok(date.inner.week_of_year()?.into())
}
/// 5.3.17 get `Temporal.PlainDateTime.prototype.yearOfWeek`
@ -565,13 +561,13 @@ impl PlainDateTime {
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
let Ok(date) = obj.clone().downcast::<Self>() else {
let Some(date) = obj.downcast_ref::<Self>() else {
return Err(JsNativeError::typ()
.with_message("the this object must be a PlainDateTime object.")
.into());
};
Ok(InnerDateTime::<JsObject>::contextual_year_of_week(&date, context)?.into())
Ok(date.inner.year_of_week()?.into())
}
/// 5.3.18 get `Temporal.PlainDateTime.prototype.daysInWeek`
@ -580,13 +576,13 @@ impl PlainDateTime {
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
let Ok(date) = obj.clone().downcast::<Self>() else {
let Some(date) = obj.downcast_ref::<Self>() else {
return Err(JsNativeError::typ()
.with_message("the this object must be a PlainDateTime object.")
.into());
};
Ok(InnerDateTime::<JsObject>::contextual_days_in_week(&date, context)?.into())
Ok(date.inner.days_in_week()?.into())
}
/// 5.3.19 get `Temporal.PlainDateTime.prototype.daysInMonth`
@ -599,13 +595,13 @@ impl PlainDateTime {
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
let Ok(date) = obj.clone().downcast::<Self>() else {
let Some(date) = obj.downcast_ref::<Self>() else {
return Err(JsNativeError::typ()
.with_message("the this object must be a PlainDateTime object.")
.into());
};
Ok(InnerDateTime::<JsObject>::contextual_days_in_month(&date, context)?.into())
Ok(date.inner.days_in_month()?.into())
}
/// 5.3.20 get `Temporal.PlainDateTime.prototype.daysInYear`
@ -614,13 +610,13 @@ impl PlainDateTime {
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
let Ok(date) = obj.clone().downcast::<Self>() else {
let Some(date) = obj.downcast_ref::<Self>() else {
return Err(JsNativeError::typ()
.with_message("the this object must be a PlainDateTime object.")
.into());
};
Ok(InnerDateTime::<JsObject>::contextual_days_in_year(&date, context)?.into())
Ok(date.inner.days_in_year()?.into())
}
/// 5.3.21 get `Temporal.PlainDateTime.prototype.monthsInYear`
@ -633,13 +629,13 @@ impl PlainDateTime {
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
let Ok(date) = obj.clone().downcast::<Self>() else {
let Some(date) = obj.downcast_ref::<Self>() else {
return Err(JsNativeError::typ()
.with_message("the this object must be a PlainDateTime object.")
.into());
};
Ok(InnerDateTime::<JsObject>::contextual_months_in_year(&date, context)?.into())
Ok(date.inner.months_in_year()?.into())
}
/// 5.3.22 get `Temporal.PlainDateTime.prototype.inLeapYear`
@ -648,20 +644,20 @@ impl PlainDateTime {
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
let Ok(date) = obj.clone().downcast::<Self>() else {
let Some(date) = obj.downcast_ref::<Self>() else {
return Err(JsNativeError::typ()
.with_message("the this object must be a PlainDateTime object.")
.into());
};
Ok(InnerDateTime::<JsObject>::contextual_in_leap_year(&date, context)?.into())
Ok(date.inner.in_leap_year()?.into())
}
}
// ==== `PlainDateTime` Abstract Operations` ====
pub(crate) fn create_temporal_datetime(
inner: InnerDateTime<JsObject>,
inner: InnerDateTime,
new_target: Option<&JsValue>,
context: &mut Context,
) -> JsResult<JsObject> {

3
core/engine/src/builtins/temporal/plain_date_time/tests.rs

@ -3,8 +3,7 @@ use crate::{run_test_actions, TestAction};
#[test]
fn pdt_year_of_week_basic() {
run_test_actions([
TestAction::run("let calendar = Temporal.Calendar.from('iso8601')"),
TestAction::run("let pdt = new Temporal.PlainDateTime(1976, 11, 18, 15, 23, 30, 123, 456, 789, calendar)"),
TestAction::run("let pdt = new Temporal.PlainDateTime(1976, 11, 18, 15, 23, 30, 123, 456, 789, 'iso8601')"),
TestAction::assert_eq("pdt.yearOfWeek", 1976),
]);
}

14
core/engine/src/builtins/temporal/plain_month_day/mod.rs

@ -14,7 +14,7 @@ use boa_profiler::Profiler;
use temporal_rs::{
components::{
calendar::{CalendarSlot, GetCalendarSlot},
calendar::{Calendar, GetTemporalCalendar},
DateTime, MonthDay as InnerMonthDay,
},
iso::IsoDateSlots,
@ -24,11 +24,11 @@ use temporal_rs::{
#[derive(Debug, Clone, Trace, Finalize, JsData)]
#[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerMonthDay` could contain `Trace` types.
pub struct PlainMonthDay {
pub(crate) inner: InnerMonthDay<JsObject>,
pub(crate) inner: InnerMonthDay,
}
impl PlainMonthDay {
fn new(inner: InnerMonthDay<JsObject>) -> Self {
fn new(inner: InnerMonthDay) -> Self {
Self { inner }
}
}
@ -39,8 +39,8 @@ impl IsoDateSlots for JsObject<PlainMonthDay> {
}
}
impl GetCalendarSlot<JsObject> for JsObject<PlainMonthDay> {
fn get_calendar(&self) -> CalendarSlot<JsObject> {
impl GetTemporalCalendar for JsObject<PlainMonthDay> {
fn get_calendar(&self) -> Calendar {
self.borrow().data().inner.get_calendar()
}
}
@ -87,13 +87,13 @@ impl BuiltInConstructor for PlainMonthDay {
// ==== `PlainMonthDay` Abstract Operations ====
pub(crate) fn create_temporal_month_day(
inner: InnerMonthDay<JsObject>,
inner: InnerMonthDay,
new_target: Option<&JsValue>,
context: &mut Context,
) -> JsResult<JsValue> {
// 1. If IsValidISODate(referenceISOYear, isoMonth, isoDay) is false, throw a RangeError exception.
// 2. If ISODateTimeWithinLimits(referenceISOYear, isoMonth, isoDay, 12, 0, 0, 0, 0, 0) is false, throw a RangeError exception.
if DateTime::<JsObject>::validate(&inner) {
if DateTime::validate(&inner) {
return Err(JsNativeError::range()
.with_message("PlainMonthDay is not a valid ISO date time.")
.into());

18
core/engine/src/builtins/temporal/plain_year_month/mod.rs

@ -13,28 +13,28 @@ use crate::{
use boa_gc::{Finalize, Trace};
use boa_profiler::Profiler;
use super::calendar::to_temporal_calendar_slot_value;
use temporal_rs::{
iso::IsoDateSlots,
{
components::{
calendar::{CalendarSlot, GetCalendarSlot},
calendar::{Calendar as InnerCalendar, GetTemporalCalendar},
YearMonth as InnerYearMonth,
},
options::ArithmeticOverflow,
},
};
use super::calendar;
/// The `Temporal.PlainYearMonth` object.
#[derive(Debug, Clone, Trace, Finalize, JsData)]
#[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerYearMonth` could contain `Trace` types.
pub struct PlainYearMonth {
pub(crate) inner: InnerYearMonth<JsObject>,
pub(crate) inner: InnerYearMonth,
}
impl PlainYearMonth {
pub(crate) fn new(inner: InnerYearMonth<JsObject>) -> Self {
pub(crate) fn new(inner: InnerYearMonth) -> Self {
Self { inner }
}
}
@ -45,8 +45,8 @@ impl IsoDateSlots for JsObject<PlainYearMonth> {
}
}
impl GetCalendarSlot<JsObject> for JsObject<PlainYearMonth> {
fn get_calendar(&self) -> CalendarSlot<JsObject> {
impl GetTemporalCalendar for JsObject<PlainYearMonth> {
fn get_calendar(&self) -> InnerCalendar {
self.borrow().data().inner.get_calendar()
}
}
@ -193,7 +193,7 @@ impl BuiltInConstructor for PlainYearMonth {
// 4. Let m be ? ToIntegerWithTruncation(isoMonth).
let m = super::to_integer_with_truncation(args.get_or_undefined(1), context)?;
// 5. Let calendar be ? ToTemporalCalendarSlotValue(calendarLike, "iso8601").
let calendar = to_temporal_calendar_slot_value(args.get_or_undefined(2), context)?;
let calendar = calendar::to_temporal_calendar_slot_value(args.get_or_undefined(2))?;
// 7. Return ? CreateTemporalYearMonth(y, m, calendar, ref, NewTarget).
let inner = InnerYearMonth::new(y, m, ref_day, calendar, ArithmeticOverflow::Reject)?;
@ -301,7 +301,7 @@ impl PlainYearMonth {
// 9.5.5 `CreateTemporalYearMonth ( isoYear, isoMonth, calendar, referenceISODay [ , newTarget ] )`
pub(crate) fn create_temporal_year_month(
ym: InnerYearMonth<JsObject>,
ym: InnerYearMonth,
new_target: Option<&JsValue>,
context: &mut Context,
) -> JsResult<JsValue> {

63
core/engine/src/builtins/temporal/time_zone/custom.rs

@ -1,63 +0,0 @@
//! A custom `TimeZone` object.
use crate::{property::PropertyKey, Context, JsObject, JsValue};
use boa_gc::{Finalize, Trace};
use boa_macros::js_str;
use num_bigint::BigInt;
use temporal_rs::{
components::{tz::TzProtocol, Instant},
TemporalError, TemporalResult,
};
#[derive(Debug, Clone, Trace, Finalize)]
pub(crate) struct JsCustomTimeZone {
tz: JsObject,
}
impl TzProtocol for JsCustomTimeZone {
type Context = Context;
fn get_offset_nanos_for(&self, context: &mut Context) -> TemporalResult<BigInt> {
let method = self
.tz
.get(js_str!("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, _context: &mut Context) -> TemporalResult<Vec<Instant>> {
// TODO: Implement once Instant has been migrated to `boa_temporal`'s Instant.
Err(TemporalError::range().with_message("Not yet implemented."))
}
fn id(&self, context: &mut Context) -> TemporalResult<String> {
let ident = self
.tz
.__get__(
&PropertyKey::from(js_str!("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())
}
}

307
core/engine/src/builtins/temporal/time_zone/mod.rs

@ -1,279 +1,7 @@
//! Boa's implemetation of the `Temporal.TimeZone` builtin object.
#![allow(dead_code)]
use crate::{
builtins::{
temporal::to_zero_padded_decimal_string, BuiltInBuilder, BuiltInConstructor, BuiltInObject,
IntrinsicObject,
},
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
js_string,
object::{internal_methods::get_prototype_from_constructor, CONSTRUCTOR},
property::Attribute,
realm::Realm,
string::StaticJsStrings,
Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue,
};
use boa_gc::{custom_trace, Finalize, Trace};
use boa_profiler::Profiler;
use temporal_rs::components::tz::TimeZoneSlot;
mod custom;
#[doc(inline)]
pub(crate) use custom::JsCustomTimeZone;
/// The `Temporal.TimeZone` object.
#[derive(Debug, Clone, Finalize, JsData)]
pub struct TimeZone {
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 {
const NAME: JsString = StaticJsStrings::TIMEZONE;
}
impl IntrinsicObject for TimeZone {
fn init(realm: &Realm) {
let _timer = Profiler::global().start_event(std::any::type_name::<Self>(), "init");
let get_id = BuiltInBuilder::callable(realm, Self::get_id)
.name(js_string!("get Id"))
.build();
BuiltInBuilder::from_standard_constructor::<Self>(realm)
.method(
Self::get_offset_nanoseconds_for,
js_string!("getOffsetNanosecondsFor"),
1,
)
.method(
Self::get_offset_string_for,
js_string!("getOffsetStringFor"),
1,
)
.method(
Self::get_plain_date_time_for,
js_string!("getPlainDateTimeFor"),
2,
)
.method(Self::get_instant_for, js_string!("getInstantFor"), 2)
.method(
Self::get_possible_instants_for,
js_string!("getPossibleInstantFor"),
1,
)
.method(
Self::get_next_transition,
js_string!("getNextTransition"),
1,
)
.method(
Self::get_previous_transition,
js_string!("getPreviousTransition"),
1,
)
.method(Self::to_string, js_string!("toString"), 0)
.method(Self::to_string, js_string!("toJSON"), 0)
.property(
JsSymbol::to_string_tag(),
Self::NAME,
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.static_property(
CONSTRUCTOR,
realm.intrinsics().constructors().time_zone().prototype(),
Attribute::default(),
)
.accessor(js_string!("id"), Some(get_id), None, Attribute::default())
.build();
}
fn get(intrinsics: &Intrinsics) -> JsObject {
Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor()
}
}
impl BuiltInConstructor for TimeZone {
const LENGTH: usize = 1;
const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
StandardConstructors::time_zone;
fn constructor(
new_target: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. If NewTarget is undefined, then
// 1a. Throw a TypeError exception.
if new_target.is_undefined() {
return Err(JsNativeError::typ()
.with_message("newTarget cannot be undefined for Temporal.TimeZone constructor")
.into());
};
// 2. Set identifier to ? ToString(identifier).
let identifier = args.get_or_undefined(0);
if identifier.is_undefined() {
return Err(JsNativeError::range()
.with_message("Temporal.TimeZone must be called with a valid initializer")
.into());
}
// 3. If IsTimeZoneOffsetString(identifier) is false, then
// a. If IsAvailableTimeZoneName(identifier) is false, then
// i. Throw a RangeError exception.
// b. Set identifier to ! CanonicalizeTimeZoneName(identifier).
// 4. Return ? CreateTemporalTimeZone(identifier, NewTarget).
create_temporal_time_zone(
identifier.to_string(context)?.to_std_string_escaped(),
Some(new_target.clone()),
context,
)
}
}
impl TimeZone {
// NOTE: id, toJSON, toString currently share the exact same implementation -> Consolidate into one function and define multiple accesors?
pub(crate) fn get_id(
this: &JsValue,
_: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
let tz = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a Temporal.TimeZone")
})?;
Ok(JsString::from(tz.slot.id(context)?).into())
}
pub(crate) fn get_offset_nanoseconds_for(
this: &JsValue,
args: &[JsValue],
_: &mut Context,
) -> JsResult<JsValue> {
// 1. Let timeZone be the this value.
// 2. Perform ? RequireInternalSlot(timeZone, [[InitializedTemporalTimeZone]]).
let _tz = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a Temporal.TimeZone")
})?;
// 3. Set instant to ? ToTemporalInstant(instant).
let _i = args.get_or_undefined(0);
// 4. If timeZone.[[OffsetNanoseconds]] is not undefined, return 𝔽(timeZone.[[OffsetNanoseconds]]).
// 5. Return 𝔽(GetNamedTimeZoneOffsetNanoseconds(timeZone.[[Identifier]], instant.[[Nanoseconds]])).
Err(JsNativeError::error()
.with_message("not yet implemented.")
.into())
}
pub(crate) fn get_offset_string_for(
this: &JsValue,
args: &[JsValue],
_: &mut Context,
) -> JsResult<JsValue> {
// 1. Let timeZone be the this value.
// 2. Perform ? RequireInternalSlot(timeZone, [[InitializedTemporalTimeZone]]).
let _tz = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a Temporal.TimeZone")
})?;
// 3. Set instant to ? ToTemporalInstant(instant).
let _i = args.get_or_undefined(0);
// TODO: to_temporal_instant is abstract operation for Temporal.Instant objects.
// let instant = to_temporal_instant(i)?;
Err(JsNativeError::error()
.with_message("not yet implemented.")
.into())
// 4. Return ? GetOffsetStringFor(timeZone, instant).
}
pub(crate) fn get_plain_date_time_for(
_: &JsValue,
_: &[JsValue],
_: &mut Context,
) -> JsResult<JsValue> {
Err(JsNativeError::error()
.with_message("not yet implemented.")
.into())
}
pub(crate) fn get_instant_for(
_: &JsValue,
_: &[JsValue],
_: &mut Context,
) -> JsResult<JsValue> {
Err(JsNativeError::error()
.with_message("not yet implemented.")
.into())
}
pub(crate) fn get_possible_instants_for(
_: &JsValue,
_: &[JsValue],
_: &mut Context,
) -> JsResult<JsValue> {
Err(JsNativeError::error()
.with_message("not yet implemented.")
.into())
}
pub(crate) fn get_next_transition(
_: &JsValue,
_: &[JsValue],
_: &mut Context,
) -> JsResult<JsValue> {
Err(JsNativeError::error()
.with_message("not yet implemented.")
.into())
}
pub(crate) fn get_previous_transition(
_: &JsValue,
_: &[JsValue],
_: &mut Context,
) -> JsResult<JsValue> {
Err(JsNativeError::error()
.with_message("not yet implemented.")
.into())
}
pub(crate) fn to_string(
this: &JsValue,
_: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let timeZone be the this value.
// 2. Perform ? RequireInternalSlot(timeZone, [[InitializedTemporalTimeZone]]).
let tz = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a Temporal.TimeZone")
})?;
// 3. Return timeZone.[[Identifier]].
Ok(JsString::from(tz.slot.id(context)?).into())
}
}
use crate::{builtins::temporal::to_zero_padded_decimal_string, Context};
// -- TimeZone Abstract Operations --
@ -303,39 +31,6 @@ pub(super) fn default_time_zone(context: &mut Context) -> String {
// TO-DO: full, system-aware implementation (and intl feature)
}
/// Abstract operation `CreateTemporalTimeZone ( identifier [ , newTarget ] )`
///
/// More information:
/// - [ECMAScript specififcation][spec]
///
/// [spec]: https://tc39.es/proposal-temporal/#sec-temporal-createtemporaltimezone
#[allow(clippy::needless_pass_by_value, unused)]
pub(super) fn create_temporal_time_zone(
identifier: String,
new_target: Option<JsValue>,
context: &mut Context,
) -> JsResult<JsValue> {
// 1. If newTarget is not present, set newTarget to %Temporal.TimeZone%.
let new_target = new_target.unwrap_or_else(|| {
context
.realm()
.intrinsics()
.constructors()
.time_zone()
.prototype()
.into()
});
// 2. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.TimeZone.prototype%", « [[InitializedTemporalTimeZone]], [[Identifier]], [[OffsetNanoseconds]] »).
let prototype =
get_prototype_from_constructor(&new_target, StandardConstructors::time_zone, context)?;
// TODO: Migrate ISO8601 parsing to `boa_temporal`
Err(JsNativeError::error()
.with_message("not yet implemented.")
.into())
}
/// Abstract operation `FormatTimeZoneOffsetString ( offsetNanoseconds )`
fn format_time_zone_offset_string(offset_nanoseconds: i64) -> String {
// 1. Assert: offsetNanoseconds is an integer.

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

@ -7,32 +7,15 @@ use crate::{
string::StaticJsStrings,
Context, JsBigInt, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue,
};
use boa_gc::{custom_trace, Finalize, Trace};
use boa_gc::{Finalize, Trace};
use boa_profiler::Profiler;
use temporal_rs::components::{
calendar::CalendarSlot, tz::TimeZoneSlot, Duration as TemporalDuration,
ZonedDateTime as InnerZdt,
};
use super::JsCustomTimeZone;
use temporal_rs::components::{Duration as TemporalDuration, ZonedDateTime as InnerZdt};
/// The `Temporal.ZonedDateTime` object.
#[derive(Debug, Clone, Finalize, JsData)]
#[derive(Debug, Clone, Trace, Finalize, JsData)]
#[boa_gc(unsafe_empty_trace)]
pub struct ZonedDateTime {
pub(crate) inner: InnerZdt<JsObject, 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(_) => {}
}
});
pub(crate) inner: InnerZdt,
}
impl BuiltInObject for ZonedDateTime {

5
core/gc/src/cell.rs

@ -10,6 +10,7 @@ use std::{
fmt::{self, Debug, Display},
hash::Hash,
ops::{Deref, DerefMut},
ptr,
};
/// `BorrowFlag` represent the internal state of a `GcCell` and
@ -405,7 +406,7 @@ impl<'a, T: ?Sized, U: ?Sized> GcRefMut<'a, T, U> {
{
#[allow(trivial_casts)]
// SAFETY: This is safe as `GcCellRefMut` is already borrowed, so the value is rooted.
let value = unsafe { &mut *(orig.value as *mut U) };
let value = unsafe { &mut *ptr::from_mut::<U>(orig.value) };
let ret = GcRefMut {
gc_cell: orig.gc_cell,
@ -434,7 +435,7 @@ impl<'a, T: ?Sized, U: ?Sized> GcRefMut<'a, T, U> {
{
#[allow(trivial_casts)]
// SAFETY: This is safe as `GcCellRefMut` is already borrowed, so the value is rooted.
let value = unsafe { &mut *(orig.value as *mut U) };
let value = unsafe { &mut *ptr::from_mut::<U>(orig.value) };
let ret = GcRefMut {
gc_cell: orig.gc_cell,

2
test262_config.toml

@ -1,4 +1,4 @@
commit = "c47b716e8d6bea0c4510d449fd22b7ed5f8b0151"
commit = "3a7a72aef5009eb22117231d40f9a5a66a9a595a"
[ignored]
# Not implemented yet:

10
tests/tester/src/edition.rs

@ -97,6 +97,16 @@ static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! {
// https://github.com/tc39/proposal-float16array
"Float16Array" => SpecEdition::ESNext,
// Math.sumPrecise
// https://github.com/tc39/proposal-math-sum
"Math.sumPrecise" => SpecEdition::ESNext,
// Source Phase Imports
// https://github.com/tc39/proposal-source-phase-imports
"source-phase-imports" => SpecEdition::ESNext,
// test262 special specifier
"source-phase-imports-module-source" => SpecEdition::ESNext,
// Part of the next ES15 edition
"Atomics.waitAsync" => SpecEdition::ESNext,
"regexp-v-flag" => SpecEdition::ESNext,

Loading…
Cancel
Save