Browse Source

add some temporal methods (#3856)

* lazy builtins

* Bump temporal_rs to latest commit

* Fix build

* Begining of plainYearMonth methods

* plainYearMonth: get_in_leap_year, get_months_in_year

* plain_year_month: implement add/subtract

* Addition of MonthDay methods

* toString implementations

* some changes to migrate to FiniteF64

* - bump the temporal version
- Move with implementation to to_temporal_month_day
- review comments

* switch to using as_inner() instead of from
Also use let _ to appease new clippy rules

* get the calendarID from the object

* update temporal ref

* Revert "lazy builtins"

This reverts commit a4e602d129.

* changes from review comments

---------

Co-authored-by: jedel1043 <jedel0124@gmail.com>
pull/3950/head
Jason Williams 3 months ago committed by GitHub
parent
commit
5ee0dc1ceb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      Cargo.lock
  2. 2
      Cargo.toml
  3. 210
      core/engine/src/builtins/temporal/duration/mod.rs
  4. 3
      core/engine/src/builtins/temporal/options.rs
  5. 199
      core/engine/src/builtins/temporal/plain_month_day/mod.rs
  6. 299
      core/engine/src/builtins/temporal/plain_year_month/mod.rs

2
Cargo.lock generated

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

2
Cargo.toml

@ -116,7 +116,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 = "8ef18fedc401761875b815a692443bf54ce9bd96" }
temporal_rs = { git = "https://github.com/boa-dev/temporal.git", rev = "af94bbc31d409a2bfdce473e667e08e16c677149" }
web-time = "1.1.0"
criterion = "0.5.1"
float-cmp = "0.9.0"

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

@ -17,8 +17,9 @@ use boa_gc::{Finalize, Trace};
use boa_macros::js_str;
use boa_profiler::Profiler;
use temporal_rs::{
components::Duration as InnerDuration,
components::{duration::PartialDuration, Duration as InnerDuration},
options::{RelativeTo, RoundingIncrement, RoundingOptions, TemporalRoundingMode, TemporalUnit},
primitive::FiniteF64,
};
use super::{
@ -218,61 +219,61 @@ impl BuiltInConstructor for Duration {
}
// 2. If years is undefined, let y be 0; else let y be ? ToIntegerIfIntegral(years).
let years = f64::from(
let years = FiniteF64::from(
args.first()
.map_or(Ok(0), |y| to_integer_if_integral(y, context))?,
);
// 3. If months is undefined, let mo be 0; else let mo be ? ToIntegerIfIntegral(months).
let months = f64::from(
let months = FiniteF64::from(
args.get(1)
.map_or(Ok(0), |mo| to_integer_if_integral(mo, context))?,
);
// 4. If weeks is undefined, let w be 0; else let w be ? ToIntegerIfIntegral(weeks).
let weeks = f64::from(
let weeks = FiniteF64::from(
args.get(2)
.map_or(Ok(0), |wk| to_integer_if_integral(wk, context))?,
);
// 5. If days is undefined, let d be 0; else let d be ? ToIntegerIfIntegral(days).
let days = f64::from(
let days = FiniteF64::from(
args.get(3)
.map_or(Ok(0), |d| to_integer_if_integral(d, context))?,
);
// 6. If hours is undefined, let h be 0; else let h be ? ToIntegerIfIntegral(hours).
let hours = f64::from(
let hours = FiniteF64::from(
args.get(4)
.map_or(Ok(0), |h| to_integer_if_integral(h, context))?,
);
// 7. If minutes is undefined, let m be 0; else let m be ? ToIntegerIfIntegral(minutes).
let minutes = f64::from(
let minutes = FiniteF64::from(
args.get(5)
.map_or(Ok(0), |m| to_integer_if_integral(m, context))?,
);
// 8. If seconds is undefined, let s be 0; else let s be ? ToIntegerIfIntegral(seconds).
let seconds = f64::from(
let seconds = FiniteF64::from(
args.get(6)
.map_or(Ok(0), |s| to_integer_if_integral(s, context))?,
);
// 9. If milliseconds is undefined, let ms be 0; else let ms be ? ToIntegerIfIntegral(milliseconds).
let milliseconds = f64::from(
let milliseconds = FiniteF64::from(
args.get(7)
.map_or(Ok(0), |ms| to_integer_if_integral(ms, context))?,
);
// 10. If microseconds is undefined, let mis be 0; else let mis be ? ToIntegerIfIntegral(microseconds).
let microseconds = f64::from(
let microseconds = FiniteF64::from(
args.get(8)
.map_or(Ok(0), |mis| to_integer_if_integral(mis, context))?,
);
// 11. If nanoseconds is undefined, let ns be 0; else let ns be ? ToIntegerIfIntegral(nanoseconds).
let nanoseconds = f64::from(
let nanoseconds = FiniteF64::from(
args.get(9)
.map_or(Ok(0), |ns| to_integer_if_integral(ns, context))?,
);
@ -310,16 +311,16 @@ impl Duration {
let inner = &duration.inner;
match field {
DateTimeValues::Year => Ok(JsValue::Rational(inner.years())),
DateTimeValues::Month => Ok(JsValue::Rational(inner.months())),
DateTimeValues::Week => Ok(JsValue::Rational(inner.weeks())),
DateTimeValues::Day => Ok(JsValue::Rational(inner.days())),
DateTimeValues::Hour => Ok(JsValue::Rational(inner.hours())),
DateTimeValues::Minute => Ok(JsValue::Rational(inner.minutes())),
DateTimeValues::Second => Ok(JsValue::Rational(inner.seconds())),
DateTimeValues::Millisecond => Ok(JsValue::Rational(inner.milliseconds())),
DateTimeValues::Microsecond => Ok(JsValue::Rational(inner.microseconds())),
DateTimeValues::Nanosecond => Ok(JsValue::Rational(inner.nanoseconds())),
DateTimeValues::Year => Ok(JsValue::Rational(inner.years().as_inner())),
DateTimeValues::Month => Ok(JsValue::Rational(inner.months().as_inner())),
DateTimeValues::Week => Ok(JsValue::Rational(inner.weeks().as_inner())),
DateTimeValues::Day => Ok(JsValue::Rational(inner.days().as_inner())),
DateTimeValues::Hour => Ok(JsValue::Rational(inner.hours().as_inner())),
DateTimeValues::Minute => Ok(JsValue::Rational(inner.minutes().as_inner())),
DateTimeValues::Second => Ok(JsValue::Rational(inner.seconds().as_inner())),
DateTimeValues::Millisecond => Ok(JsValue::Rational(inner.milliseconds().as_inner())),
DateTimeValues::Microsecond => Ok(JsValue::Rational(inner.microseconds().as_inner())),
DateTimeValues::Nanosecond => Ok(JsValue::Rational(inner.nanoseconds().as_inner())),
DateTimeValues::MonthCode => unreachable!(
"Any other DateTimeValue fields on Duration would be an implementation error."
),
@ -458,101 +459,79 @@ impl Duration {
// a. Let years be temporalDurationLike.[[Years]].
// 5. Else,
// a. Let years be duration.[[Years]].
let years = if temporal_duration_like.years().is_nan() {
duration.inner.years()
} else {
temporal_duration_like.years()
};
let years = temporal_duration_like
.years
.unwrap_or(duration.inner.years());
// 6. If temporalDurationLike.[[Months]] is not undefined, then
// a. Let months be temporalDurationLike.[[Months]].
// 7. Else,
// a. Let months be duration.[[Months]].
let months = if temporal_duration_like.months().is_nan() {
duration.inner.months()
} else {
temporal_duration_like.months()
};
let months = temporal_duration_like
.months
.unwrap_or(duration.inner.months());
// 8. If temporalDurationLike.[[Weeks]] is not undefined, then
// a. Let weeks be temporalDurationLike.[[Weeks]].
// 9. Else,
// a. Let weeks be duration.[[Weeks]].
let weeks = if temporal_duration_like.weeks().is_nan() {
duration.inner.weeks()
} else {
temporal_duration_like.weeks()
};
let weeks = temporal_duration_like
.weeks
.unwrap_or(duration.inner.weeks());
// 10. If temporalDurationLike.[[Days]] is not undefined, then
// a. Let days be temporalDurationLike.[[Days]].
// 11. Else,
// a. Let days be duration.[[Days]].
let days = if temporal_duration_like.days().is_nan() {
duration.inner.days()
} else {
temporal_duration_like.days()
};
let days = temporal_duration_like.days.unwrap_or(duration.inner.days());
// 12. If temporalDurationLike.[[Hours]] is not undefined, then
// a. Let hours be temporalDurationLike.[[Hours]].
// 13. Else,
// a. Let hours be duration.[[Hours]].
let hours = if temporal_duration_like.hours().is_nan() {
duration.inner.hours()
} else {
temporal_duration_like.hours()
};
let hours = temporal_duration_like
.hours
.unwrap_or(duration.inner.hours());
// 14. If temporalDurationLike.[[Minutes]] is not undefined, then
// a. Let minutes be temporalDurationLike.[[Minutes]].
// 15. Else,
// a. Let minutes be duration.[[Minutes]].
let minutes = if temporal_duration_like.minutes().is_nan() {
duration.inner.minutes()
} else {
temporal_duration_like.minutes()
};
let minutes = temporal_duration_like
.minutes
.unwrap_or(duration.inner.minutes());
// 16. If temporalDurationLike.[[Seconds]] is not undefined, then
// a. Let seconds be temporalDurationLike.[[Seconds]].
// 17. Else,
// a. Let seconds be duration.[[Seconds]].
let seconds = if temporal_duration_like.seconds().is_nan() {
duration.inner.seconds()
} else {
temporal_duration_like.seconds()
};
let seconds = temporal_duration_like
.seconds
.unwrap_or(duration.inner.seconds());
// 18. If temporalDurationLike.[[Milliseconds]] is not undefined, then
// a. Let milliseconds be temporalDurationLike.[[Milliseconds]].
// 19. Else,
// a. Let milliseconds be duration.[[Milliseconds]].
let milliseconds = if temporal_duration_like.milliseconds().is_nan() {
duration.inner.milliseconds()
} else {
temporal_duration_like.milliseconds()
};
let milliseconds = temporal_duration_like
.milliseconds
.unwrap_or(duration.inner.milliseconds());
// 20. If temporalDurationLike.[[Microseconds]] is not undefined, then
// a. Let microseconds be temporalDurationLike.[[Microseconds]].
// 21. Else,
// a. Let microseconds be duration.[[Microseconds]].
let microseconds = if temporal_duration_like.microseconds().is_nan() {
duration.inner.microseconds()
} else {
temporal_duration_like.microseconds()
};
let microseconds = temporal_duration_like
.microseconds
.unwrap_or(duration.inner.microseconds());
// 22. If temporalDurationLike.[[Nanoseconds]] is not undefined, then
// a. Let nanoseconds be temporalDurationLike.[[Nanoseconds]].
// 23. Else,
// a. Let nanoseconds be duration.[[Nanoseconds]].
let nanoseconds = if temporal_duration_like.nanoseconds().is_nan() {
duration.inner.nanoseconds()
} else {
temporal_duration_like.nanoseconds()
};
let nanoseconds = temporal_duration_like
.nanoseconds
.unwrap_or(duration.inner.nanoseconds());
// 24. Return ? CreateTemporalDuration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds).
let new_duration = InnerDuration::new(
@ -827,7 +806,27 @@ impl Duration {
// -- Duration Abstract Operations --
/// 7.5.9 `ToTemporalDurationRecord ( temporalDurationLike )`
/// 7.5.12 `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.13 `ToTemporalDurationRecord ( temporalDurationLike )`
pub(crate) fn to_temporal_duration_record(
temporal_duration_like: &JsValue,
context: &mut Context,
@ -859,6 +858,7 @@ pub(crate) fn to_temporal_duration_record(
let partial = to_temporal_partial_duration(temporal_duration_like, context)?;
// 5. If partial.[[Years]] is not undefined, set result.[[Years]] to partial.[[Years]].
// 6. If partial.[[Months]] is not undefined, set result.[[Months]] to partial.[[Months]].
// 7. If partial.[[Weeks]] is not undefined, set result.[[Weeks]] to partial.[[Weeks]].
// 8. If partial.[[Days]] is not undefined, set result.[[Days]] to partial.[[Days]].
@ -871,7 +871,7 @@ pub(crate) fn to_temporal_duration_record(
// 15. If ! IsValidDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]) is false, then
// a. Throw a RangeError exception.
// 16. Return result.
InnerDuration::from_partial(&partial).map_err(Into::into)
InnerDuration::from_partial_duration(partial).map_err(Into::into)
}
/// 7.5.14 `CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds [ , newTarget ] )`
@ -919,7 +919,7 @@ pub(crate) fn create_temporal_duration(
pub(crate) fn to_temporal_partial_duration(
duration_like: &JsValue,
context: &mut Context,
) -> JsResult<InnerDuration> {
) -> JsResult<PartialDuration> {
// 1. If Type(temporalDurationLike) is not Object, then
let JsValue::Object(unknown_object) = duration_like else {
// a. Throw a TypeError exception.
@ -929,77 +929,106 @@ pub(crate) fn to_temporal_partial_duration(
};
// 2. Let result be a new partial Duration Record with each field set to undefined.
let mut result = InnerDuration::partial();
let mut result = PartialDuration::default();
// 3. NOTE: The following steps read properties and perform independent validation in alphabetical order.
// 4. Let days be ? Get(temporalDurationLike, "days").
let days = unknown_object.get(js_str!("days"), context)?;
if !days.is_undefined() {
// 5. If days is not undefined, set result.[[Days]] to ? ToIntegerIfIntegral(days).
result.set_days(f64::from(to_integer_if_integral(&days, context)?));
let _ = result
.days
.insert(FiniteF64::from(to_integer_if_integral(&days, context)?));
}
// 6. Let hours be ? Get(temporalDurationLike, "hours").
let hours = unknown_object.get(js_str!("hours"), context)?;
// 7. If hours is not undefined, set result.[[Hours]] to ? ToIntegerIfIntegral(hours).
if !hours.is_undefined() {
result.set_hours(f64::from(to_integer_if_integral(&hours, context)?));
let _ = result
.hours
.insert(FiniteF64::from(to_integer_if_integral(&hours, context)?));
}
// 8. Let microseconds be ? Get(temporalDurationLike, "microseconds").
let microseconds = unknown_object.get(js_str!("microseconds"), context)?;
// 9. If microseconds is not undefined, set result.[[Microseconds]] to ? ToIntegerIfIntegral(microseconds).
if !microseconds.is_undefined() {
result.set_microseconds(f64::from(to_integer_if_integral(&microseconds, context)?));
let _ = result
.microseconds
.insert(FiniteF64::from(to_integer_if_integral(
&microseconds,
context,
)?));
}
// 10. Let milliseconds be ? Get(temporalDurationLike, "milliseconds").
let milliseconds = unknown_object.get(js_str!("milliseconds"), context)?;
// 11. If milliseconds is not undefined, set result.[[Milliseconds]] to ? ToIntegerIfIntegral(milliseconds).
if !milliseconds.is_undefined() {
result.set_milliseconds(f64::from(to_integer_if_integral(&milliseconds, context)?));
let _ = result
.milliseconds
.insert(FiniteF64::from(to_integer_if_integral(
&milliseconds,
context,
)?));
}
// 12. Let minutes be ? Get(temporalDurationLike, "minutes").
let minutes = unknown_object.get(js_str!("minutes"), context)?;
// 13. If minutes is not undefined, set result.[[Minutes]] to ? ToIntegerIfIntegral(minutes).
if !minutes.is_undefined() {
result.set_minutes(f64::from(to_integer_if_integral(&minutes, context)?));
let _ = result
.minutes
.insert(FiniteF64::from(to_integer_if_integral(&minutes, context)?));
}
// 14. Let months be ? Get(temporalDurationLike, "months").
let months = unknown_object.get(js_str!("months"), context)?;
// 15. If months is not undefined, set result.[[Months]] to ? ToIntegerIfIntegral(months).
if !months.is_undefined() {
result.set_months(f64::from(to_integer_if_integral(&months, context)?));
let _ = result
.months
.insert(FiniteF64::from(to_integer_if_integral(&months, context)?));
}
// 16. Let nanoseconds be ? Get(temporalDurationLike, "nanoseconds").
let nanoseconds = unknown_object.get(js_str!("nanoseconds"), context)?;
// 17. If nanoseconds is not undefined, set result.[[Nanoseconds]] to ? ToIntegerIfIntegral(nanoseconds).
if !nanoseconds.is_undefined() {
result.set_nanoseconds(f64::from(to_integer_if_integral(&nanoseconds, context)?));
let _ = result
.nanoseconds
.insert(FiniteF64::from(to_integer_if_integral(
&nanoseconds,
context,
)?));
}
// 18. Let seconds be ? Get(temporalDurationLike, "seconds").
let seconds = unknown_object.get(js_str!("seconds"), context)?;
// 19. If seconds is not undefined, set result.[[Seconds]] to ? ToIntegerIfIntegral(seconds).
if !seconds.is_undefined() {
result.set_seconds(f64::from(to_integer_if_integral(&seconds, context)?));
let _ = result
.seconds
.insert(FiniteF64::from(to_integer_if_integral(&seconds, context)?));
}
// 20. Let weeks be ? Get(temporalDurationLike, "weeks").
let weeks = unknown_object.get(js_str!("weeks"), context)?;
// 21. If weeks is not undefined, set result.[[Weeks]] to ? ToIntegerIfIntegral(weeks).
if !weeks.is_undefined() {
result.set_weeks(f64::from(to_integer_if_integral(&weeks, context)?));
let _ = result
.weeks
.insert(FiniteF64::from(to_integer_if_integral(&weeks, context)?));
}
// 22. Let years be ? Get(temporalDurationLike, "years").
let years = unknown_object.get(js_str!("years"), context)?;
// 23. If years is not undefined, set result.[[Years]] to ? ToIntegerIfIntegral(years).
if !years.is_undefined() {
result.set_years(f64::from(to_integer_if_integral(&years, context)?));
let _ = result
.years
.insert(FiniteF64::from(to_integer_if_integral(&years, context)?));
}
// TODO: Implement this functionality better in `temporal_rs`.
@ -1007,16 +1036,7 @@ pub(crate) fn to_temporal_partial_duration(
// is undefined, and hours is undefined, and minutes is undefined, and seconds is
// undefined, and milliseconds is undefined, and microseconds is undefined, and
// nanoseconds is undefined, throw a TypeError exception.
if result.years().is_nan()
&& result.months().is_nan()
&& result.weeks().is_nan()
&& result.days().is_nan()
&& result.minutes().is_nan()
&& result.seconds().is_nan()
&& result.milliseconds().is_nan()
&& result.microseconds().is_nan()
&& result.nanoseconds().is_nan()
{
if result.is_empty() {
return Err(JsNativeError::typ()
.with_message("PartialDurationRecord must have a defined field.")
.into());

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

@ -15,7 +15,7 @@ use crate::{
};
use boa_macros::js_str;
use temporal_rs::options::{
ArithmeticOverflow, DifferenceSettings, DurationOverflow, InstantDisambiguation,
ArithmeticOverflow, CalendarName, DifferenceSettings, DurationOverflow, InstantDisambiguation,
OffsetDisambiguation, RoundingIncrement, TemporalRoundingMode, TemporalUnit,
};
@ -116,6 +116,7 @@ impl ParsableOptionType for DurationOverflow {}
impl ParsableOptionType for InstantDisambiguation {}
impl ParsableOptionType for OffsetDisambiguation {}
impl ParsableOptionType for TemporalRoundingMode {}
impl ParsableOptionType for CalendarName {}
impl OptionType for RoundingIncrement {
fn from_value(value: JsValue, context: &mut Context) -> JsResult<Self> {

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

@ -1,15 +1,22 @@
//! Boa's implementation of the ECMAScript `Temporal.PlainMonthDay` builtin object.
#![allow(dead_code, unused_variables)]
use std::str::FromStr;
use crate::{
builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject},
builtins::{
options::{get_option, get_options_object},
BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject,
},
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
js_string,
object::internal_methods::get_prototype_from_constructor,
property::Attribute,
realm::Realm,
string::StaticJsStrings,
Context, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue,
Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_macros::js_str;
use boa_profiler::Profiler;
use temporal_rs::{
@ -18,8 +25,11 @@ use temporal_rs::{
DateTime, MonthDay as InnerMonthDay,
},
iso::IsoDateSlots,
options::{ArithmeticOverflow, CalendarName},
};
use super::{calendar::to_temporal_calendar_slot_value, DateTimeValues};
/// The `Temporal.PlainMonthDay` object.
#[derive(Debug, Clone, Trace, Finalize, JsData)]
#[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerMonthDay` could contain `Trace` types.
@ -33,6 +43,81 @@ impl PlainMonthDay {
}
}
// ==== `Temporal.PlainMonthDay` static Methods ====
impl PlainMonthDay {
// 10.2.2 Temporal.PlainMonthDay.from ( item [ , options ] )
fn from(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let options = get_options_object(args.get_or_undefined(1))?;
let item = args.get_or_undefined(0);
to_temporal_month_day(item, &options, context)
}
}
// === `PlainMonthDay` Accessor Implementations ===== /
impl PlainMonthDay {
fn get_internal_field(this: &JsValue, field: &DateTimeValues) -> JsResult<JsValue> {
let month_day = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a PlainMonthDay object.")
})?;
let inner = &month_day.inner;
match field {
DateTimeValues::Day => Ok(inner.iso_day().into()),
DateTimeValues::MonthCode => Ok(js_string!(inner.month_code()?.to_string()).into()),
_ => unreachable!(),
}
}
fn get_day(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
Self::get_internal_field(this, &DateTimeValues::Day)
}
fn get_year(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
Self::get_internal_field(this, &DateTimeValues::Year)
}
fn get_month_code(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
Self::get_internal_field(this, &DateTimeValues::MonthCode)
}
fn get_calendar_id(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let month_day = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a PlainMonthDay object.")
})?;
let inner = &month_day.inner;
Ok(js_string!(inner.calendar().identifier()).into())
}
}
// ==== `Temporal.PlainMonthDay` Methods ====
impl PlainMonthDay {
// 10.3.7 Temporal.PlainMonthDay.prototype.toString ( [ options ] )
fn to_string(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let monthDay be the this value.
// 2. Perform ? RequireInternalSlot(monthDay, [[InitializedTemporalMonthDay]]).
let month_day = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a PlainMonthDay object.")
})?;
let inner = &month_day.inner;
// 3. Set options to ? NormalizeOptionsObject(options).
let options = get_options_object(args.get_or_undefined(0))?;
// 4. Let showCalendar be ? ToShowCalendarOption(options).
// Get calendarName from the options object
let show_calendar = get_option::<CalendarName>(&options, js_str!("calendarName"), context)?
.unwrap_or(CalendarName::Auto);
Ok(month_day_to_string(inner, show_calendar))
}
}
impl IsoDateSlots for JsObject<PlainMonthDay> {
fn iso_date(&self) -> temporal_rs::iso::IsoDate {
self.borrow().data().inner.iso_date()
@ -52,6 +137,17 @@ impl BuiltInObject for PlainMonthDay {
impl IntrinsicObject for PlainMonthDay {
fn init(realm: &Realm) {
let _timer = Profiler::global().start_event(std::any::type_name::<Self>(), "init");
let get_day = BuiltInBuilder::callable(realm, Self::get_day)
.name(js_string!("get day"))
.build();
let get_month_code = BuiltInBuilder::callable(realm, Self::get_month_code)
.name(js_string!("get monthCode"))
.build();
let get_calendar_id = BuiltInBuilder::callable(realm, Self::get_calendar_id)
.name(js_string!("get calendarId"))
.build();
BuiltInBuilder::from_standard_constructor::<Self>(realm)
.property(
@ -59,6 +155,26 @@ impl IntrinsicObject for PlainMonthDay {
StaticJsStrings::PLAIN_MD_TAG,
Attribute::CONFIGURABLE,
)
.accessor(
js_string!("day"),
Some(get_day),
None,
Attribute::CONFIGURABLE,
)
.accessor(
js_string!("monthCode"),
Some(get_month_code),
None,
Attribute::CONFIGURABLE,
)
.accessor(
js_string!("calendarId"),
Some(get_calendar_id),
None,
Attribute::CONFIGURABLE,
)
.method(Self::to_string, js_string!("toString"), 1)
.static_method(Self::from, js_string!("from"), 2)
.build();
}
@ -86,6 +202,40 @@ impl BuiltInConstructor for PlainMonthDay {
// ==== `PlainMonthDay` Abstract Operations ====
fn month_day_to_string(inner: &InnerMonthDay, show_calendar: CalendarName) -> JsValue {
// Let month be monthDay.[[ISOMonth]] formatted as a two-digit decimal number, padded to the left with a zero if necessary
let month = inner.iso_month().to_string();
// 2. Let day be ! FormatDayOfMonth(monthDay.[[ISODay]]).
let day = inner.iso_day().to_string();
// 3. Let result be the string-concatenation of month and the code unit 0x002D (HYPHEN-MINUS).
let mut result = format!("{month:0>2}-{day:0>2}");
let calendar_id = inner.calendar().identifier();
// 5. Let calendar be monthDay.[[Calendar]].
// 6. If showCalendar is "auto", then
// a. Set showCalendar to "always".
// 7. If showCalendar is "always", then
// a. Let calendarString be ! FormatCalendarAnnotation(calendar).
// b. Set result to the string-concatenation of result, the code unit 0x0040 (COMMERCIAL AT), and calendarString.
if (matches!(
show_calendar,
CalendarName::Critical | CalendarName::Always | CalendarName::Auto
)) && !(matches!(show_calendar, CalendarName::Auto) && calendar_id == "iso8601")
{
let flag = if matches!(show_calendar, CalendarName::Critical) {
"!"
} else {
""
};
result.push_str(&format!("[{flag}c={calendar_id}]",));
}
// 8. Return result.
js_string!(result).into()
}
pub(crate) fn create_temporal_month_day(
inner: InnerMonthDay,
new_target: Option<&JsValue>,
@ -93,9 +243,9 @@ pub(crate) fn create_temporal_month_day(
) -> 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::validate(&inner) {
if !DateTime::validate(&inner) {
return Err(JsNativeError::range()
.with_message("PlainMonthDay is not a valid ISO date time.")
.with_message("PlainMonthDay does not hold a valid ISO date time.")
.into());
}
@ -128,3 +278,44 @@ pub(crate) fn create_temporal_month_day(
// 9. Return object.
Ok(obj.into())
}
fn to_temporal_month_day(
item: &JsValue,
options: &JsObject,
context: &mut Context,
) -> JsResult<JsValue> {
let overflow = get_option::<ArithmeticOverflow>(options, js_str!("overflow"), context)?
.unwrap_or(ArithmeticOverflow::Constrain);
// get the calendar property (string) from the item object
let calender_id = item.get_v(js_str!("calendar"), context)?;
let calendar = to_temporal_calendar_slot_value(&calender_id)?;
let inner = if let Some(item_obj) = item
.as_object()
.and_then(JsObject::downcast_ref::<PlainMonthDay>)
{
item_obj.inner.clone()
} else if let Some(item_string) = item.as_string() {
InnerMonthDay::from_str(item_string.to_std_string_escaped().as_str())?
} else if item.is_object() {
InnerMonthDay::new(
item.get_v(js_str!("month"), context)
.expect("Month not found")
.to_i32(context)
.expect("Cannot convert month to i32"),
item.get_v(js_str!("day"), context)
.expect("Day not found")
.to_i32(context)
.expect("Cannot convert day to i32"),
calendar,
overflow,
)?
} else {
return Err(JsNativeError::typ()
.with_message("item must be an object or a string")
.into());
};
create_temporal_month_day(inner, None, context)
}

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

@ -1,7 +1,12 @@
//! Boa's implementation of the `Temporal.PlainYearMonth` builtin object.
use std::str::FromStr;
use crate::{
builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject},
builtins::{
options::{get_option, get_options_object},
BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject,
},
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
js_string,
object::internal_methods::get_prototype_from_constructor,
@ -11,20 +16,19 @@ use crate::{
Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_macros::js_str;
use boa_profiler::Profiler;
use temporal_rs::{
iso::IsoDateSlots,
{
components::{
calendar::{Calendar as InnerCalendar, GetTemporalCalendar},
YearMonth as InnerYearMonth,
},
options::ArithmeticOverflow,
components::{
calendar::{Calendar as InnerCalendar, GetTemporalCalendar},
Duration, YearMonth as InnerYearMonth,
},
iso::IsoDateSlots,
options::{ArithmeticOverflow, CalendarName},
};
use super::calendar;
use super::{calendar::to_temporal_calendar_slot_value, to_temporal_duration, DateTimeValues};
/// The `Temporal.PlainYearMonth` object.
#[derive(Debug, Clone, Trace, Finalize, JsData)]
@ -145,12 +149,14 @@ impl IntrinsicObject for PlainYearMonth {
None,
Attribute::CONFIGURABLE,
)
.static_method(Self::from, js_string!("from"), 2)
.method(Self::with, js_string!("with"), 2)
.method(Self::add, js_string!("add"), 2)
.method(Self::subtract, js_string!("subtract"), 2)
.method(Self::until, js_string!("until"), 2)
.method(Self::since, js_string!("since"), 2)
.method(Self::equals, js_string!("equals"), 1)
.method(Self::to_string, js_string!("toString"), 1)
.build();
}
@ -193,7 +199,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 = calendar::to_temporal_calendar_slot_value(args.get_or_undefined(2))?;
let 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)?;
@ -202,55 +208,151 @@ impl BuiltInConstructor for PlainYearMonth {
}
}
// ==== `PlainYearMonth` Accessor Implementations ====
// ==== `Temporal.PlainYearMonth` static Methods ====
impl PlainYearMonth {
fn get_calendar_id(_this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Err(JsNativeError::error()
.with_message("not yet implemented.")
.into())
// 9.2.2 `Temporal.PlainYearMonth.from ( item [ , options ] )`
fn from(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let item = args.get_or_undefined(0);
// 1. If Type(item) is Object or Type(item) is String and item is not null, then
let inner = if item.is_object() {
// 9.2.2.2
if let Some(data) = item
.as_object()
.and_then(JsObject::downcast_ref::<PlainYearMonth>)
{
// Perform ? [GetTemporalOverflowOption](https://tc39.es/proposal-temporal/#sec-temporal-gettemporaloverflowoption)(options).
let options = get_options_object(args.get_or_undefined(1))?;
let _ = get_option::<ArithmeticOverflow>(&options, js_str!("overflow"), context)?;
data.inner.clone()
} else {
let options = get_options_object(args.get_or_undefined(1))?;
let overflow = get_option(&options, js_str!("overflow"), context)?
.unwrap_or(ArithmeticOverflow::Constrain);
// a. Let calendar be ? ToTemporalCalendar(item).
let calendar = to_temporal_calendar_slot_value(args.get_or_undefined(1))?;
InnerYearMonth::new(
super::to_integer_with_truncation(
&item.get_v(js_str!("year"), context)?,
context,
)?,
super::to_integer_with_truncation(
&item.get_v(js_str!("month"), context)?,
context,
)?,
super::to_integer_with_truncation(
&item.get_v(js_str!("day"), context)?,
context,
)
.ok(),
calendar,
overflow,
)?
}
} else if let Some(item_as_string) = item.as_string() {
InnerYearMonth::from_str(item_as_string.to_std_string_escaped().as_str())?
} else {
return Err(JsNativeError::typ()
.with_message("item must be an object, string, or null.")
.into());
};
// b. Return ? ToTemporalYearMonth(item, calendar).
create_temporal_year_month(inner, None, context)
}
}
fn get_year(_this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Err(JsNativeError::error()
.with_message("not yet implemented.")
.into())
// ==== `PlainYearMonth` Accessor Implementations ====/
impl PlainYearMonth {
fn get_internal_field(this: &JsValue, field: &DateTimeValues) -> JsResult<JsValue> {
let year_month = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
})?;
let inner = &year_month.inner;
match field {
DateTimeValues::Year => Ok(inner.iso_year().into()),
DateTimeValues::Month => Ok(inner.iso_month().into()),
DateTimeValues::MonthCode => {
Ok(JsString::from(InnerYearMonth::month_code(inner)?.as_str()).into())
}
_ => unreachable!(),
}
}
fn get_month(_this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Err(JsNativeError::error()
.with_message("not yet implemented.")
.into())
fn get_calendar_id(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
let obj = this
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
let Ok(year_month) = obj.clone().downcast::<Self>() else {
return Err(JsNativeError::typ()
.with_message("the this object must be a PlainYearMonth object.")
.into());
};
Ok(js_string!(year_month.get_calendar().identifier()).into())
}
fn get_month_code(_this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Err(JsNativeError::error()
.with_message("not yet implemented.")
.into())
fn get_year(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Self::get_internal_field(this, &DateTimeValues::Year)
}
fn get_days_in_year(_this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Err(JsNativeError::error()
.with_message("not yet implemented.")
.into())
fn get_month(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Self::get_internal_field(this, &DateTimeValues::Month)
}
fn get_days_in_month(_this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Err(JsNativeError::error()
.with_message("not yet implemented.")
.into())
fn get_month_code(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Self::get_internal_field(this, &DateTimeValues::MonthCode)
}
fn get_months_in_year(_this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Err(JsNativeError::error()
.with_message("not yet implemented.")
.into())
fn get_days_in_year(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
let year_month = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
})?;
let inner = &year_month.inner;
Ok(inner.get_days_in_year()?.into())
}
fn get_in_leap_year(_this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Err(JsNativeError::error()
.with_message("not yet implemented.")
.into())
fn get_days_in_month(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
let year_month = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
})?;
let inner = &year_month.inner;
Ok(inner.get_days_in_month()?.into())
}
fn get_months_in_year(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
let year_month = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
})?;
let inner = &year_month.inner;
Ok(inner.get_months_in_year()?.into())
}
fn get_in_leap_year(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
let year_month = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
})?;
Ok(year_month.inner.in_leap_year().into())
}
}
@ -263,16 +365,18 @@ impl PlainYearMonth {
.into())
}
fn add(_this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Err(JsNativeError::typ()
.with_message("not yet implemented.")
.into())
fn add(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let duration_like = args.get_or_undefined(0);
let options = get_options_object(args.get_or_undefined(1))?;
add_or_subtract_duration(true, this, duration_like, &options, context)
}
fn subtract(_this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Err(JsNativeError::typ()
.with_message("not yet implemented.")
.into())
fn subtract(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let duration_like = args.get_or_undefined(0);
let options = get_options_object(args.get_or_undefined(1))?;
add_or_subtract_duration(false, this, duration_like, &options, context)
}
fn until(_this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
@ -292,6 +396,27 @@ impl PlainYearMonth {
.with_message("not yet implemented.")
.into())
}
fn to_string(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let YearMonth be the this value.
// 2. Perform ? RequireInternalSlot(yearMonth, [[InitializedTemporalYearMonth]]).
let year_month = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
})?;
let inner = &year_month.inner;
// 3. Set options to ? NormalizeOptionsObject(options).
let options = get_options_object(args.get_or_undefined(0))?;
// 4. Let showCalendar be ? ToShowCalendarOption(options).
// Get calendarName from the options object
let show_calendar = get_option::<CalendarName>(&options, js_str!("calendarName"), context)?
.unwrap_or(CalendarName::Auto);
Ok(year_month_to_string(inner, show_calendar))
}
}
// ==== Abstract Operations ====
@ -299,7 +424,7 @@ impl PlainYearMonth {
// 9.5.2 `RegulateISOYearMonth ( year, month, overflow )`
// Implemented on `TemporalFields`.
// 9.5.5 `CreateTemporalYearMonth ( isoYear, isoMonth, calendar, referenceISODay [ , newTarget ] )`
// 9.5.6 `CreateTemporalYearMonth ( isoYear, isoMonth, calendar, referenceISODay [ , newTarget ] )`
pub(crate) fn create_temporal_year_month(
ym: InnerYearMonth,
new_target: Option<&JsValue>,
@ -338,3 +463,73 @@ pub(crate) fn create_temporal_year_month(
// 9. Return object.
Ok(obj.into())
}
// 9.5.9 AddDurationToOrSubtractDurationFromPlainYearMonth ( operation, yearMonth, temporalDurationLike, options )
fn add_or_subtract_duration(
is_addition: bool,
this: &JsValue,
duration_like: &JsValue,
options: &JsObject,
context: &mut Context,
) -> JsResult<JsValue> {
let duration: Duration = if duration_like.is_object() {
to_temporal_duration(duration_like, context)?
} else if let Some(duration_string) = duration_like.as_string() {
Duration::from_str(duration_string.to_std_string_escaped().as_str())?
} else {
return Err(JsNativeError::typ()
.with_message("cannot handler string durations yet.")
.into());
};
let overflow =
get_option(options, js_str!("overflow"), context)?.unwrap_or(ArithmeticOverflow::Constrain);
let year_month = this
.as_object()
.and_then(JsObject::downcast_ref::<PlainYearMonth>)
.ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
})?;
let inner = &year_month.inner;
let year_month_result = if is_addition {
inner.add_duration(&duration, overflow)?
} else {
inner.subtract_duration(&duration, overflow)?
};
create_temporal_year_month(year_month_result, None, context)
}
fn year_month_to_string(inner: &InnerYearMonth, show_calendar: CalendarName) -> JsValue {
// Let year be PadISOYear(yearMonth.[[ISOYear]]).
let year = inner.padded_iso_year_string();
// Let month be ToZeroPaddedDecimalString(yearMonth.[[ISOMonth]], 2).
let month = inner.iso_month().to_string();
// Let result be the string-concatenation of year, the code unit 0x002D (HYPHEN-MINUS), and month.
let mut result = format!("{year}-{month:0>2}");
// 5. If showCalendar is one of "always" or "critical", or if calendarIdentifier is not "iso8601", then
// a. Let day be ToZeroPaddedDecimalString(yearMonth.[[ISODay]], 2).
// b. Set result to the string-concatenation of result, the code unit 0x002D (HYPHEN-MINUS), and day.
// 6. Let calendarString be FormatCalendarAnnotation(calendarIdentifier, showCalendar).
// 7. Set result to the string-concatenation of result and calendarString.
if matches!(
show_calendar,
CalendarName::Critical | CalendarName::Always | CalendarName::Auto
) && !(matches!(show_calendar, CalendarName::Auto) && inner.calendar_id() == "iso8601")
{
let calendar = inner.calendar_id();
let calendar_string = calendar.to_string();
let flag = if matches!(show_calendar, CalendarName::Critical) {
"!"
} else {
""
};
result.push_str(&format!("[{flag}c={calendar_string}]",));
}
// 8. Return result.
js_string!(result).into()
}

Loading…
Cancel
Save