Browse Source

Duration methods and some cleanup (#3443)

pull/3453/head
Kevin 1 year ago committed by GitHub
parent
commit
34d5c519ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 134
      boa_engine/src/builtins/temporal/calendar/iso.rs
  2. 353
      boa_engine/src/builtins/temporal/calendar/mod.rs
  3. 239
      boa_engine/src/builtins/temporal/duration/mod.rs
  4. 877
      boa_engine/src/builtins/temporal/duration/record.rs
  5. 21
      boa_engine/src/builtins/temporal/fields.rs
  6. 4
      boa_engine/src/builtins/temporal/instant/mod.rs
  7. 2
      boa_engine/src/builtins/temporal/mod.rs
  8. 30
      boa_engine/src/builtins/temporal/plain_date/iso.rs
  9. 115
      boa_engine/src/builtins/temporal/plain_date/mod.rs

134
boa_engine/src/builtins/temporal/calendar/iso.rs

@ -2,18 +2,16 @@
use crate::{ use crate::{
builtins::temporal::{ builtins::temporal::{
self, create_temporal_date, self,
date_equations::mathematical_days_in_year, date_equations::mathematical_days_in_year,
duration::DurationRecord,
options::{ArithmeticOverflow, TemporalUnit}, options::{ArithmeticOverflow, TemporalUnit},
plain_date::iso::IsoDateRecord, plain_date::iso::IsoDateRecord,
}, },
js_string, js_string, JsNativeError, JsResult, JsString,
property::PropertyKey,
string::utf16,
Context, JsNativeError, JsResult, JsString, JsValue,
}; };
use super::BuiltinCalendar; use super::{BuiltinCalendar, FieldsType};
use icu_calendar::{ use icu_calendar::{
iso::Iso, iso::Iso,
@ -31,8 +29,7 @@ impl BuiltinCalendar for IsoCalendar {
&self, &self,
fields: &mut temporal::TemporalFields, fields: &mut temporal::TemporalFields,
overflow: ArithmeticOverflow, overflow: ArithmeticOverflow,
context: &mut Context<'_>, ) -> JsResult<IsoDateRecord> {
) -> JsResult<JsValue> {
// NOTE: we are in ISO by default here. // NOTE: we are in ISO by default here.
// a. Perform ? ISOResolveMonth(fields). // a. Perform ? ISOResolveMonth(fields).
// b. Let result be ? ISODateFromFields(fields, overflow). // b. Let result be ? ISODateFromFields(fields, overflow).
@ -49,13 +46,7 @@ impl BuiltinCalendar for IsoCalendar {
.map_err(|err| JsNativeError::range().with_message(err.to_string()))?; .map_err(|err| JsNativeError::range().with_message(err.to_string()))?;
// 9. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], "iso8601"). // 9. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], "iso8601").
Ok(create_temporal_date( Ok(IsoDateRecord::from_date_iso(date))
IsoDateRecord::from_date_iso(date),
js_string!("iso8601").into(),
None,
context,
)?
.into())
} }
/// 12.5.5 `Temporal.Calendar.prototype.yearMonthFromFields ( fields [ , options ] )` /// 12.5.5 `Temporal.Calendar.prototype.yearMonthFromFields ( fields [ , options ] )`
@ -65,8 +56,7 @@ impl BuiltinCalendar for IsoCalendar {
&self, &self,
fields: &mut temporal::TemporalFields, fields: &mut temporal::TemporalFields,
overflow: ArithmeticOverflow, overflow: ArithmeticOverflow,
context: &mut Context<'_>, ) -> JsResult<IsoDateRecord> {
) -> JsResult<JsValue> {
// 9. If calendar.[[Identifier]] is "iso8601", then // 9. If calendar.[[Identifier]] is "iso8601", then
// a. Perform ? ISOResolveMonth(fields). // a. Perform ? ISOResolveMonth(fields).
fields.iso_resolve_month()?; fields.iso_resolve_month()?;
@ -82,12 +72,7 @@ impl BuiltinCalendar for IsoCalendar {
.map_err(|err| JsNativeError::range().with_message(err.to_string()))?; .map_err(|err| JsNativeError::range().with_message(err.to_string()))?;
// 10. Return ? CreateTemporalYearMonth(result.[[Year]], result.[[Month]], "iso8601", result.[[ReferenceISODay]]). // 10. Return ? CreateTemporalYearMonth(result.[[Year]], result.[[Month]], "iso8601", result.[[ReferenceISODay]]).
temporal::create_temporal_year_month( Ok(IsoDateRecord::from_date_iso(result))
IsoDateRecord::from_date_iso(result),
js_string!("iso8601").into(),
None,
context,
)
} }
/// 12.5.6 `Temporal.Calendar.prototype.monthDayFromFields ( fields [ , options ] )` /// 12.5.6 `Temporal.Calendar.prototype.monthDayFromFields ( fields [ , options ] )`
@ -97,8 +82,7 @@ impl BuiltinCalendar for IsoCalendar {
&self, &self,
fields: &mut temporal::TemporalFields, fields: &mut temporal::TemporalFields,
overflow: ArithmeticOverflow, overflow: ArithmeticOverflow,
context: &mut Context<'_>, ) -> JsResult<IsoDateRecord> {
) -> JsResult<JsValue> {
// 8. Perform ? ISOResolveMonth(fields). // 8. Perform ? ISOResolveMonth(fields).
fields.iso_resolve_month()?; fields.iso_resolve_month()?;
@ -114,12 +98,7 @@ impl BuiltinCalendar for IsoCalendar {
.map_err(|err| JsNativeError::range().with_message(err.to_string()))?; .map_err(|err| JsNativeError::range().with_message(err.to_string()))?;
// 10. Return ? CreateTemporalMonthDay(result.[[Month]], result.[[Day]], "iso8601", result.[[ReferenceISOYear]]). // 10. Return ? CreateTemporalMonthDay(result.[[Month]], result.[[Day]], "iso8601", result.[[ReferenceISOYear]]).
temporal::create_temporal_month_day( Ok(IsoDateRecord::from_date_iso(result))
IsoDateRecord::from_date_iso(result),
js_string!("iso8601").into(),
None,
context,
)
} }
/// 12.5.7 `Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] )` /// 12.5.7 `Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] )`
@ -128,10 +107,9 @@ impl BuiltinCalendar for IsoCalendar {
fn date_add( fn date_add(
&self, &self,
_date: &temporal::PlainDate, _date: &temporal::PlainDate,
_duration: &temporal::duration::DurationRecord, _duration: &DurationRecord,
_overflow: ArithmeticOverflow, _overflow: ArithmeticOverflow,
_context: &mut Context<'_>, ) -> JsResult<IsoDateRecord> {
) -> JsResult<JsValue> {
// TODO: Not stable on `ICU4X`. Implement once completed. // TODO: Not stable on `ICU4X`. Implement once completed.
Err(JsNativeError::range() Err(JsNativeError::range()
.with_message("feature not implemented.") .with_message("feature not implemented.")
@ -149,8 +127,7 @@ impl BuiltinCalendar for IsoCalendar {
_one: &temporal::PlainDate, _one: &temporal::PlainDate,
_two: &temporal::PlainDate, _two: &temporal::PlainDate,
_largest_unit: TemporalUnit, _largest_unit: TemporalUnit,
_: &mut Context<'_>, ) -> JsResult<DurationRecord> {
) -> JsResult<JsValue> {
// TODO: Not stable on `ICU4X`. Implement once completed. // TODO: Not stable on `ICU4X`. Implement once completed.
Err(JsNativeError::range() Err(JsNativeError::range()
.with_message("Feature not yet implemented.") .with_message("Feature not yet implemented.")
@ -161,19 +138,19 @@ impl BuiltinCalendar for IsoCalendar {
} }
/// `Temporal.Calendar.prototype.era( dateLike )` for iso8601 calendar. /// `Temporal.Calendar.prototype.era( dateLike )` for iso8601 calendar.
fn era(&self, _: &IsoDateRecord, _: &mut Context<'_>) -> JsResult<JsValue> { fn era(&self, _: &IsoDateRecord) -> JsResult<Option<JsString>> {
// Returns undefined on iso8601. // Returns undefined on iso8601.
Ok(JsValue::undefined()) Ok(None)
} }
/// `Temporal.Calendar.prototype.eraYear( dateLike )` for iso8601 calendar. /// `Temporal.Calendar.prototype.eraYear( dateLike )` for iso8601 calendar.
fn era_year(&self, _: &IsoDateRecord, _: &mut Context<'_>) -> JsResult<JsValue> { fn era_year(&self, _: &IsoDateRecord) -> JsResult<Option<i32>> {
// Returns undefined on iso8601. // Returns undefined on iso8601.
Ok(JsValue::undefined()) Ok(None)
} }
/// Returns the `year` for the `Iso` calendar. /// Returns the `year` for the `Iso` calendar.
fn year(&self, date_like: &IsoDateRecord, _: &mut Context<'_>) -> JsResult<JsValue> { fn year(&self, date_like: &IsoDateRecord) -> JsResult<i32> {
let date = Date::try_new_iso_date( let date = Date::try_new_iso_date(
date_like.year(), date_like.year(),
date_like.month() as u8, date_like.month() as u8,
@ -181,11 +158,11 @@ impl BuiltinCalendar for IsoCalendar {
) )
.map_err(|err| JsNativeError::range().with_message(err.to_string()))?; .map_err(|err| JsNativeError::range().with_message(err.to_string()))?;
Ok(date.year().number.into()) Ok(date.year().number)
} }
/// Returns the `month` for the `Iso` calendar. /// Returns the `month` for the `Iso` calendar.
fn month(&self, date_like: &IsoDateRecord, _: &mut Context<'_>) -> JsResult<JsValue> { fn month(&self, date_like: &IsoDateRecord) -> JsResult<i32> {
let date = Date::try_new_iso_date( let date = Date::try_new_iso_date(
date_like.year(), date_like.year(),
date_like.month() as u8, date_like.month() as u8,
@ -193,11 +170,11 @@ impl BuiltinCalendar for IsoCalendar {
) )
.map_err(|err| JsNativeError::range().with_message(err.to_string()))?; .map_err(|err| JsNativeError::range().with_message(err.to_string()))?;
Ok(date.month().ordinal.into()) Ok(date.month().ordinal as i32)
} }
/// Returns the `monthCode` for the `Iso` calendar. /// Returns the `monthCode` for the `Iso` calendar.
fn month_code(&self, date_like: &IsoDateRecord, _: &mut Context<'_>) -> JsResult<JsValue> { fn month_code(&self, date_like: &IsoDateRecord) -> JsResult<JsString> {
let date = Date::try_new_iso_date( let date = Date::try_new_iso_date(
date_like.year(), date_like.year(),
date_like.month() as u8, date_like.month() as u8,
@ -205,11 +182,11 @@ impl BuiltinCalendar for IsoCalendar {
) )
.map_err(|err| JsNativeError::range().with_message(err.to_string()))?; .map_err(|err| JsNativeError::range().with_message(err.to_string()))?;
Ok(JsString::from(date.month().code.to_string()).into()) Ok(JsString::from(date.month().code.to_string()))
} }
/// Returns the `day` for the `Iso` calendar. /// Returns the `day` for the `Iso` calendar.
fn day(&self, date_like: &IsoDateRecord, _: &mut Context<'_>) -> JsResult<JsValue> { fn day(&self, date_like: &IsoDateRecord) -> JsResult<i32> {
let date = Date::try_new_iso_date( let date = Date::try_new_iso_date(
date_like.year(), date_like.year(),
date_like.month() as u8, date_like.month() as u8,
@ -217,11 +194,11 @@ impl BuiltinCalendar for IsoCalendar {
) )
.map_err(|err| JsNativeError::range().with_message(err.to_string()))?; .map_err(|err| JsNativeError::range().with_message(err.to_string()))?;
Ok(date.day_of_month().0.into()) Ok(date.day_of_month().0 as i32)
} }
/// Returns the `dayOfWeek` for the `Iso` calendar. /// Returns the `dayOfWeek` for the `Iso` calendar.
fn day_of_week(&self, date_like: &IsoDateRecord, _: &mut Context<'_>) -> JsResult<JsValue> { fn day_of_week(&self, date_like: &IsoDateRecord) -> JsResult<i32> {
let date = Date::try_new_iso_date( let date = Date::try_new_iso_date(
date_like.year(), date_like.year(),
date_like.month() as u8, date_like.month() as u8,
@ -229,11 +206,11 @@ impl BuiltinCalendar for IsoCalendar {
) )
.map_err(|err| JsNativeError::range().with_message(err.to_string()))?; .map_err(|err| JsNativeError::range().with_message(err.to_string()))?;
Ok((date.day_of_week() as u8).into()) Ok(date.day_of_week() as i32)
} }
/// Returns the `dayOfYear` for the `Iso` calendar. /// Returns the `dayOfYear` for the `Iso` calendar.
fn day_of_year(&self, date_like: &IsoDateRecord, _: &mut Context<'_>) -> JsResult<JsValue> { fn day_of_year(&self, date_like: &IsoDateRecord) -> JsResult<i32> {
let date = Date::try_new_iso_date( let date = Date::try_new_iso_date(
date_like.year(), date_like.year(),
date_like.month() as u8, date_like.month() as u8,
@ -241,11 +218,11 @@ impl BuiltinCalendar for IsoCalendar {
) )
.map_err(|err| JsNativeError::range().with_message(err.to_string()))?; .map_err(|err| JsNativeError::range().with_message(err.to_string()))?;
Ok(i32::from(date.day_of_year_info().day_of_year).into()) Ok(i32::from(date.day_of_year_info().day_of_year))
} }
/// Returns the `weekOfYear` for the `Iso` calendar. /// Returns the `weekOfYear` for the `Iso` calendar.
fn week_of_year(&self, date_like: &IsoDateRecord, _: &mut Context<'_>) -> JsResult<JsValue> { fn week_of_year(&self, date_like: &IsoDateRecord) -> JsResult<i32> {
// TODO: Determine `ICU4X` equivalent. // TODO: Determine `ICU4X` equivalent.
let date = Date::try_new_iso_date( let date = Date::try_new_iso_date(
date_like.year(), date_like.year(),
@ -260,11 +237,11 @@ impl BuiltinCalendar for IsoCalendar {
.week_of_year(&week_calculator) .week_of_year(&week_calculator)
.map_err(|err| JsNativeError::range().with_message(err.to_string()))?; .map_err(|err| JsNativeError::range().with_message(err.to_string()))?;
Ok(week_of.week.into()) Ok(i32::from(week_of.week))
} }
/// Returns the `yearOfWeek` for the `Iso` calendar. /// Returns the `yearOfWeek` for the `Iso` calendar.
fn year_of_week(&self, date_like: &IsoDateRecord, _: &mut Context<'_>) -> JsResult<JsValue> { fn year_of_week(&self, date_like: &IsoDateRecord) -> JsResult<i32> {
// TODO: Determine `ICU4X` equivalent. // TODO: Determine `ICU4X` equivalent.
let date = Date::try_new_iso_date( let date = Date::try_new_iso_date(
date_like.year(), date_like.year(),
@ -280,19 +257,19 @@ impl BuiltinCalendar for IsoCalendar {
.map_err(|err| JsNativeError::range().with_message(err.to_string()))?; .map_err(|err| JsNativeError::range().with_message(err.to_string()))?;
match week_of.unit { match week_of.unit {
RelativeUnit::Previous => Ok((date.year().number - 1).into()), RelativeUnit::Previous => Ok(date.year().number - 1),
RelativeUnit::Current => Ok(date.year().number.into()), RelativeUnit::Current => Ok(date.year().number),
RelativeUnit::Next => Ok((date.year().number + 1).into()), RelativeUnit::Next => Ok(date.year().number + 1),
} }
} }
/// Returns the `daysInWeek` value for the `Iso` calendar. /// Returns the `daysInWeek` value for the `Iso` calendar.
fn days_in_week(&self, _: &IsoDateRecord, _: &mut Context<'_>) -> JsResult<JsValue> { fn days_in_week(&self, _: &IsoDateRecord) -> JsResult<i32> {
Ok(7.into()) Ok(7)
} }
/// Returns the `daysInMonth` value for the `Iso` calendar. /// Returns the `daysInMonth` value for the `Iso` calendar.
fn days_in_month(&self, date_like: &IsoDateRecord, _: &mut Context<'_>) -> JsResult<JsValue> { fn days_in_month(&self, date_like: &IsoDateRecord) -> JsResult<i32> {
let date = Date::try_new_iso_date( let date = Date::try_new_iso_date(
date_like.year(), date_like.year(),
date_like.month() as u8, date_like.month() as u8,
@ -300,11 +277,11 @@ impl BuiltinCalendar for IsoCalendar {
) )
.map_err(|err| JsNativeError::range().with_message(err.to_string()))?; .map_err(|err| JsNativeError::range().with_message(err.to_string()))?;
Ok(date.days_in_month().into()) Ok(i32::from(date.days_in_month()))
} }
/// Returns the `daysInYear` value for the `Iso` calendar. /// Returns the `daysInYear` value for the `Iso` calendar.
fn days_in_year(&self, date_like: &IsoDateRecord, _: &mut Context<'_>) -> JsResult<JsValue> { fn days_in_year(&self, date_like: &IsoDateRecord) -> JsResult<i32> {
let date = Date::try_new_iso_date( let date = Date::try_new_iso_date(
date_like.year(), date_like.year(),
date_like.month() as u8, date_like.month() as u8,
@ -312,31 +289,31 @@ impl BuiltinCalendar for IsoCalendar {
) )
.map_err(|err| JsNativeError::range().with_message(err.to_string()))?; .map_err(|err| JsNativeError::range().with_message(err.to_string()))?;
Ok(date.days_in_year().into()) Ok(i32::from(date.days_in_year()))
} }
/// Return the amount of months in an ISO Calendar. /// Return the amount of months in an ISO Calendar.
fn months_in_year(&self, _: &IsoDateRecord, _: &mut Context<'_>) -> JsResult<JsValue> { fn months_in_year(&self, _: &IsoDateRecord) -> JsResult<i32> {
Ok(12.into()) Ok(12)
} }
/// Returns whether provided date is in a leap year according to this calendar. /// Returns whether provided date is in a leap year according to this calendar.
fn in_leap_year(&self, date_like: &IsoDateRecord, _: &mut Context<'_>) -> JsResult<JsValue> { fn in_leap_year(&self, date_like: &IsoDateRecord) -> JsResult<bool> {
// `ICU4X`'s `CalendarArithmetic` is currently private. // `ICU4X`'s `CalendarArithmetic` is currently private.
if mathematical_days_in_year(date_like.year()) == 366 { if mathematical_days_in_year(date_like.year()) == 366 {
return Ok(true.into()); return Ok(true);
} }
Ok(false.into()) Ok(false)
} }
// Resolve the fields for the iso calendar. // Resolve the fields for the iso calendar.
fn resolve_fields(&self, fields: &mut temporal::TemporalFields, _: &str) -> JsResult<()> { fn resolve_fields(&self, fields: &mut temporal::TemporalFields, _: FieldsType) -> JsResult<()> {
fields.iso_resolve_month()?; fields.iso_resolve_month()?;
Ok(()) Ok(())
} }
/// Returns the ISO field descriptors, which is not called for the iso8601 calendar. /// Returns the ISO field descriptors, which is not called for the iso8601 calendar.
fn field_descriptors(&self, _: &[String]) -> Vec<(String, bool)> { fn field_descriptors(&self, _: FieldsType) -> Vec<(JsString, bool)> {
// NOTE(potential improvement): look into implementing field descriptors and call // NOTE(potential improvement): look into implementing field descriptors and call
// ISO like any other calendar? // ISO like any other calendar?
// Field descriptors is unused on ISO8601. // Field descriptors is unused on ISO8601.
@ -344,15 +321,14 @@ impl BuiltinCalendar for IsoCalendar {
} }
/// Returns the `CalendarFieldKeysToIgnore` implementation for ISO. /// Returns the `CalendarFieldKeysToIgnore` implementation for ISO.
fn field_keys_to_ignore(&self, additional_keys: Vec<PropertyKey>) -> Vec<PropertyKey> { fn field_keys_to_ignore(&self, additional_keys: Vec<JsString>) -> Vec<JsString> {
let mut result = Vec::new(); let mut result = Vec::new();
for key in additional_keys { for key in &additional_keys {
let key_string = key.to_string(); result.push(key.clone());
result.push(key); if key.to_std_string_escaped().as_str() == "month" {
if key_string.as_str() == "month" { result.push(js_string!("monthCode"));
result.push(utf16!("monthCode").into()); } else if key.to_std_string_escaped().as_str() == "monthCode" {
} else if key_string.as_str() == "monthCode" { result.push(js_string!("month"));
result.push(utf16!("month").into());
} }
} }
result result

353
boa_engine/src/builtins/temporal/calendar/mod.rs

@ -3,9 +3,11 @@
use self::iso::IsoCalendar; use self::iso::IsoCalendar;
use super::{ use super::{
create_temporal_date, create_temporal_duration, create_temporal_month_day,
create_temporal_year_month,
options::{ArithmeticOverflow, TemporalUnit, TemporalUnitGroup}, options::{ArithmeticOverflow, TemporalUnit, TemporalUnitGroup},
plain_date::iso::IsoDateRecord, plain_date::iso::IsoDateRecord,
PlainDate, TemporalFields, DurationRecord, PlainDate, TemporalFields,
}; };
use crate::{ use crate::{
builtins::{ builtins::{
@ -16,7 +18,7 @@ use crate::{
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
js_string, js_string,
object::{internal_methods::get_prototype_from_constructor, ObjectData}, object::{internal_methods::get_prototype_from_constructor, ObjectData},
property::{Attribute, PropertyKey}, property::Attribute,
realm::Realm, realm::Realm,
string::{common::StaticJsStrings, utf16}, string::{common::StaticJsStrings, utf16},
Context, JsArgs, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, Context, JsArgs, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue,
@ -30,6 +32,27 @@ pub(crate) mod utils;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
pub(crate) enum FieldsType {
Date,
YearMonth,
MonthDay,
}
impl From<&[JsString]> for FieldsType {
fn from(value: &[JsString]) -> Self {
let year_present = value.contains(&js_string!("year"));
let day_present = value.contains(&js_string!("day"));
if year_present && day_present {
FieldsType::Date
} else if year_present {
FieldsType::YearMonth
} else {
FieldsType::MonthDay
}
}
}
// TODO: Determine how many methods actually need the context on them while using // TODO: Determine how many methods actually need the context on them while using
// `icu_calendar`. // `icu_calendar`.
// //
@ -42,111 +65,70 @@ pub(crate) trait BuiltinCalendar {
&self, &self,
fields: &mut TemporalFields, fields: &mut TemporalFields,
overflow: ArithmeticOverflow, overflow: ArithmeticOverflow,
context: &mut Context<'_>, ) -> JsResult<IsoDateRecord>;
) -> JsResult<JsValue>;
/// Creates a `Temporal.PlainYearMonth` object from the provided fields. /// Creates a `Temporal.PlainYearMonth` object from the provided fields.
fn year_month_from_fields( fn year_month_from_fields(
&self, &self,
fields: &mut TemporalFields, fields: &mut TemporalFields,
overflow: ArithmeticOverflow, overflow: ArithmeticOverflow,
context: &mut Context<'_>, ) -> JsResult<IsoDateRecord>;
) -> JsResult<JsValue>;
/// Creates a `Temporal.PlainMonthDay` object from the provided fields. /// Creates a `Temporal.PlainMonthDay` object from the provided fields.
fn month_day_from_fields( fn month_day_from_fields(
&self, &self,
fields: &mut TemporalFields, fields: &mut TemporalFields,
overflow: ArithmeticOverflow, overflow: ArithmeticOverflow,
context: &mut Context<'_>, ) -> JsResult<IsoDateRecord>;
) -> JsResult<JsValue>;
/// Returns a `Temporal.PlainDate` based off an added date. /// Returns a `Temporal.PlainDate` based off an added date.
fn date_add( fn date_add(
&self, &self,
date: &PlainDate, date: &PlainDate,
duration: &temporal::DurationRecord, duration: &DurationRecord,
overflow: ArithmeticOverflow, overflow: ArithmeticOverflow,
context: &mut Context<'_>, ) -> JsResult<IsoDateRecord>;
) -> JsResult<JsValue>;
/// Returns a `Temporal.Duration` representing the duration between two dates. /// Returns a `Temporal.Duration` representing the duration between two dates.
fn date_until( fn date_until(
&self, &self,
one: &PlainDate, one: &PlainDate,
two: &PlainDate, two: &PlainDate,
largest_unit: TemporalUnit, largest_unit: TemporalUnit,
context: &mut Context<'_>, ) -> JsResult<DurationRecord>;
) -> JsResult<JsValue>;
/// Returns the era for a given `temporaldatelike`. /// Returns the era for a given `temporaldatelike`.
fn era(&self, date_like: &IsoDateRecord, context: &mut Context<'_>) -> JsResult<JsValue>; fn era(&self, date_like: &IsoDateRecord) -> JsResult<Option<JsString>>;
/// Returns the era year for a given `temporaldatelike` /// Returns the era year for a given `temporaldatelike`
fn era_year(&self, date_like: &IsoDateRecord, context: &mut Context<'_>) -> JsResult<JsValue>; fn era_year(&self, date_like: &IsoDateRecord) -> JsResult<Option<i32>>;
/// Returns the `year` for a given `temporaldatelike` /// Returns the `year` for a given `temporaldatelike`
fn year(&self, date_like: &IsoDateRecord, context: &mut Context<'_>) -> JsResult<JsValue>; fn year(&self, date_like: &IsoDateRecord) -> JsResult<i32>;
/// Returns the `month` for a given `temporaldatelike` /// Returns the `month` for a given `temporaldatelike`
fn month(&self, date_like: &IsoDateRecord, context: &mut Context<'_>) -> JsResult<JsValue>; fn month(&self, date_like: &IsoDateRecord) -> JsResult<i32>;
// Note: Best practice would probably be to switch to a MonthCode enum after extraction.
/// Returns the `monthCode` for a given `temporaldatelike` /// Returns the `monthCode` for a given `temporaldatelike`
fn month_code(&self, date_like: &IsoDateRecord, context: &mut Context<'_>) fn month_code(&self, date_like: &IsoDateRecord) -> JsResult<JsString>;
-> JsResult<JsValue>;
/// Returns the `day` for a given `temporaldatelike` /// Returns the `day` for a given `temporaldatelike`
fn day(&self, date_like: &IsoDateRecord, context: &mut Context<'_>) -> JsResult<JsValue>; fn day(&self, date_like: &IsoDateRecord) -> JsResult<i32>;
/// Returns a value representing the day of the week for a date. /// Returns a value representing the day of the week for a date.
fn day_of_week( fn day_of_week(&self, date_like: &IsoDateRecord) -> JsResult<i32>;
&self,
date_like: &IsoDateRecord,
context: &mut Context<'_>,
) -> JsResult<JsValue>;
/// Returns a value representing the day of the year for a given calendar. /// Returns a value representing the day of the year for a given calendar.
fn day_of_year( fn day_of_year(&self, date_like: &IsoDateRecord) -> JsResult<i32>;
&self,
date_like: &IsoDateRecord,
context: &mut Context<'_>,
) -> JsResult<JsValue>;
/// Returns a value representing the week of the year for a given calendar. /// Returns a value representing the week of the year for a given calendar.
fn week_of_year( fn week_of_year(&self, date_like: &IsoDateRecord) -> JsResult<i32>;
&self,
date_like: &IsoDateRecord,
context: &mut Context<'_>,
) -> JsResult<JsValue>;
/// Returns the year of a given week. /// Returns the year of a given week.
fn year_of_week( fn year_of_week(&self, date_like: &IsoDateRecord) -> JsResult<i32>;
&self,
date_like: &IsoDateRecord,
context: &mut Context<'_>,
) -> JsResult<JsValue>;
/// Returns the days in a week for a given calendar. /// Returns the days in a week for a given calendar.
fn days_in_week( fn days_in_week(&self, date_like: &IsoDateRecord) -> JsResult<i32>;
&self,
date_like: &IsoDateRecord,
context: &mut Context<'_>,
) -> JsResult<JsValue>;
/// Returns the days in a month for a given calendar. /// Returns the days in a month for a given calendar.
fn days_in_month( fn days_in_month(&self, date_like: &IsoDateRecord) -> JsResult<i32>;
&self,
date_like: &IsoDateRecord,
context: &mut Context<'_>,
) -> JsResult<JsValue>;
/// Returns the days in a year for a given calendar. /// Returns the days in a year for a given calendar.
fn days_in_year( fn days_in_year(&self, date_like: &IsoDateRecord) -> JsResult<i32>;
&self,
date_like: &IsoDateRecord,
context: &mut Context<'_>,
) -> JsResult<JsValue>;
/// Returns the months in a year for a given calendar. /// Returns the months in a year for a given calendar.
fn months_in_year( fn months_in_year(&self, date_like: &IsoDateRecord) -> JsResult<i32>;
&self,
date_like: &IsoDateRecord,
context: &mut Context<'_>,
) -> JsResult<JsValue>;
/// Returns whether a value is within a leap year according to the designated calendar. /// Returns whether a value is within a leap year according to the designated calendar.
fn in_leap_year( fn in_leap_year(&self, date_like: &IsoDateRecord) -> JsResult<bool>;
&self,
date_like: &IsoDateRecord,
context: &mut Context<'_>,
) -> JsResult<JsValue>;
/// Resolve the `TemporalFields` for the implemented Calendar /// Resolve the `TemporalFields` for the implemented Calendar
fn resolve_fields(&self, fields: &mut TemporalFields, r#type: &str) -> JsResult<()>; fn resolve_fields(&self, fields: &mut TemporalFields, r#type: FieldsType) -> JsResult<()>;
/// Return this calendar's a fieldName and whether it is required depending on type (date, day-month). /// Return this calendar's a fieldName and whether it is required depending on type (date, day-month).
fn field_descriptors(&self, r#type: &[String]) -> Vec<(String, bool)>; fn field_descriptors(&self, r#type: FieldsType) -> Vec<(JsString, bool)>;
/// Return the fields to ignore for this Calendar based on provided keys. /// Return the fields to ignore for this Calendar based on provided keys.
fn field_keys_to_ignore(&self, additional_keys: Vec<PropertyKey>) -> Vec<PropertyKey>; fn field_keys_to_ignore(&self, additional_keys: Vec<JsString>) -> Vec<JsString>;
/// Debug name /// Debug name
fn debug_name(&self) -> &str; fn debug_name(&self) -> &str;
} }
@ -334,16 +316,16 @@ impl Calendar {
// 5. Let relevantFieldNames be « "day", "month", "monthCode", "year" ». // 5. Let relevantFieldNames be « "day", "month", "monthCode", "year" ».
let mut relevant_field_names = Vec::from([ let mut relevant_field_names = Vec::from([
"day".to_owned(), js_string!("day"),
"month".to_owned(), js_string!("month"),
"monthCode".to_owned(), js_string!("monthCode"),
"year".to_owned(), js_string!("year"),
]); ]);
// 6. If calendar.[[Identifier]] is "iso8601", then // 6. If calendar.[[Identifier]] is "iso8601", then
let mut fields = if calendar.identifier.as_slice() == ISO { let mut fields = if calendar.identifier.as_slice() == ISO {
// a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "year", "day" »). // a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "year", "day" »).
let mut required_fields = Vec::from(["year".to_owned(), "day".to_owned()]); let mut required_fields = Vec::from([js_string!("year"), js_string!("day")]);
temporal::TemporalFields::from_js_object( temporal::TemporalFields::from_js_object(
fields_obj, fields_obj,
&mut relevant_field_names, &mut relevant_field_names,
@ -356,7 +338,7 @@ impl Calendar {
// 7. Else, // 7. Else,
} else { } else {
// a. Let calendarRelevantFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], date). // a. Let calendarRelevantFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], date).
let calendar_relevant_fields = this_calendar.field_descriptors(&["date".to_owned()]); let calendar_relevant_fields = this_calendar.field_descriptors(FieldsType::Date);
// b. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « », calendarRelevantFieldDescriptors). // b. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « », calendarRelevantFieldDescriptors).
temporal::TemporalFields::from_js_object( temporal::TemporalFields::from_js_object(
fields_obj, fields_obj,
@ -381,7 +363,10 @@ impl Calendar {
// a. Perform ? CalendarResolveFields(calendar.[[Identifier]], fields, date). // a. Perform ? CalendarResolveFields(calendar.[[Identifier]], fields, date).
// b. Let result be ? CalendarDateToISO(calendar.[[Identifier]], fields, overflow). // b. Let result be ? CalendarDateToISO(calendar.[[Identifier]], fields, overflow).
this_calendar.date_from_fields(&mut fields, overflow, context) let result = this_calendar.date_from_fields(&mut fields, overflow)?;
create_temporal_date(result, calendar.identifier.clone().into(), None, context)
.map(Into::into)
} }
/// 15.8.2.2 `Temporal.Calendar.prototype.yearMonthFromFields ( fields [ , options ] )` - Supercedes 12.5.5 /// 15.8.2.2 `Temporal.Calendar.prototype.yearMonthFromFields ( fields [ , options ] )` - Supercedes 12.5.5
@ -412,15 +397,15 @@ impl Calendar {
let options = get_options_object(args.get_or_undefined(1))?; let options = get_options_object(args.get_or_undefined(1))?;
let mut relevant_field_names = Vec::from([ let mut relevant_field_names = Vec::from([
"year".to_owned(), js_string!("year"),
"month".to_owned(), js_string!("month"),
"monthCode".to_owned(), js_string!("monthCode"),
]); ]);
// 6. Set fields to ? PrepareTemporalFields(fields, « "month", "monthCode", "year" », « "year" »). // 6. Set fields to ? PrepareTemporalFields(fields, « "month", "monthCode", "year" », « "year" »).
let mut fields = if calendar.identifier.as_slice() == ISO { let mut fields = if calendar.identifier.as_slice() == ISO {
// a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "year" »). // a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "year" »).
let mut required_fields = Vec::from(["year".to_owned()]); let mut required_fields = Vec::from([js_string!("year")]);
temporal::TemporalFields::from_js_object( temporal::TemporalFields::from_js_object(
fields_obj, fields_obj,
&mut relevant_field_names, &mut relevant_field_names,
@ -434,8 +419,7 @@ impl Calendar {
// a. Let calendarRelevantFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], year-month). // a. Let calendarRelevantFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], year-month).
// b. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « », calendarRelevantFieldDescriptors). // b. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « », calendarRelevantFieldDescriptors).
let calendar_relevant_fields = let calendar_relevant_fields = this_calendar.field_descriptors(FieldsType::YearMonth);
this_calendar.field_descriptors(&["year-month".to_owned()]);
temporal::TemporalFields::from_js_object( temporal::TemporalFields::from_js_object(
fields_obj, fields_obj,
&mut relevant_field_names, &mut relevant_field_names,
@ -454,7 +438,10 @@ impl Calendar {
let overflow = get_option::<ArithmeticOverflow>(&options, utf16!("overflow"), context)? let overflow = get_option::<ArithmeticOverflow>(&options, utf16!("overflow"), context)?
.unwrap_or(ArithmeticOverflow::Constrain); .unwrap_or(ArithmeticOverflow::Constrain);
this_calendar.year_month_from_fields(&mut fields, overflow, context) let result = this_calendar.year_month_from_fields(&mut fields, overflow)?;
create_temporal_year_month(result, calendar.identifier.clone().into(), None, context)
.map(Into::into)
} }
/// 15.8.2.3 `Temporal.Calendar.prototype.monthDayFromFields ( fields [ , options ] )` - Supercedes 12.5.6 /// 15.8.2.3 `Temporal.Calendar.prototype.monthDayFromFields ( fields [ , options ] )` - Supercedes 12.5.6
@ -490,16 +477,16 @@ impl Calendar {
// 5. Let relevantFieldNames be « "day", "month", "monthCode", "year" ». // 5. Let relevantFieldNames be « "day", "month", "monthCode", "year" ».
let mut relevant_field_names = Vec::from([ let mut relevant_field_names = Vec::from([
"day".to_owned(), js_string!("day"),
"month".to_owned(), js_string!("month"),
"monthCode".to_owned(), js_string!("monthCode"),
"year".to_owned(), js_string!("year"),
]); ]);
// 6. If calendar.[[Identifier]] is "iso8601", then // 6. If calendar.[[Identifier]] is "iso8601", then
let mut fields = if calendar.identifier.as_slice() == ISO { let mut fields = if calendar.identifier.as_slice() == ISO {
// a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "day" »). // a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "day" »).
let mut required_fields = Vec::from(["day".to_owned()]); let mut required_fields = Vec::from([js_string!("day")]);
temporal::TemporalFields::from_js_object( temporal::TemporalFields::from_js_object(
fields_obj, fields_obj,
&mut relevant_field_names, &mut relevant_field_names,
@ -512,8 +499,7 @@ impl Calendar {
// 7. Else, // 7. Else,
} else { } else {
// a. Let calendarRelevantFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], month-day). // a. Let calendarRelevantFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], month-day).
let calendar_relevant_fields = let calendar_relevant_fields = this_calendar.field_descriptors(FieldsType::MonthDay);
this_calendar.field_descriptors(&["month-day".to_owned()]);
// b. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « », calendarRelevantFieldDescriptors). // b. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « », calendarRelevantFieldDescriptors).
temporal::TemporalFields::from_js_object( temporal::TemporalFields::from_js_object(
fields_obj, fields_obj,
@ -530,7 +516,10 @@ impl Calendar {
let overflow = get_option(&options, utf16!("overflow"), context)? let overflow = get_option(&options, utf16!("overflow"), context)?
.unwrap_or(ArithmeticOverflow::Constrain); .unwrap_or(ArithmeticOverflow::Constrain);
this_calendar.month_day_from_fields(&mut fields, overflow, context) let result = this_calendar.month_day_from_fields(&mut fields, overflow)?;
create_temporal_month_day(result, calendar.identifier.clone().into(), None, context)
.map(Into::into)
} }
/// 15.8.2.4 `Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] )` - supercedes 12.5.7 /// 15.8.2.4 `Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] )` - supercedes 12.5.7
@ -571,7 +560,10 @@ impl Calendar {
// 8. Let balanceResult be ? BalanceTimeDuration(duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], "day"). // 8. Let balanceResult be ? BalanceTimeDuration(duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], "day").
duration.balance_time_duration(TemporalUnit::Day, None)?; duration.balance_time_duration(TemporalUnit::Day, None)?;
this_calendar.date_add(&date, &duration, overflow, context) let result = this_calendar.date_add(&date, &duration, overflow)?;
create_temporal_date(result, calendar.identifier.clone().into(), None, context)
.map(Into::into)
} }
///15.8.2.5 `Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] )` - Supercedes 12.5.8 ///15.8.2.5 `Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] )` - Supercedes 12.5.8
@ -616,7 +608,9 @@ impl Calendar {
)? )?
.unwrap_or(TemporalUnit::Day); .unwrap_or(TemporalUnit::Day);
this_calendar.date_until(&one, &two, largest_unit, context) let result = this_calendar.date_until(&one, &two, largest_unit)?;
create_temporal_duration(result, None, context).map(Into::into)
} }
/// 15.8.2.6 `Temporal.Calendar.prototype.era ( temporalDateLike )` /// 15.8.2.6 `Temporal.Calendar.prototype.era ( temporalDateLike )`
@ -662,7 +656,9 @@ impl Calendar {
} }
}; };
this_calendar.era(&date_info, context) this_calendar
.era(&date_info)
.map(|r| r.map_or(JsValue::undefined(), Into::into))
} }
/// 15.8.2.7 `Temporal.Calendar.prototype.eraYear ( temporalDateLike )` /// 15.8.2.7 `Temporal.Calendar.prototype.eraYear ( temporalDateLike )`
@ -708,7 +704,9 @@ impl Calendar {
} }
}; };
this_calendar.era_year(&date_info, context) this_calendar
.era_year(&date_info)
.map(|r| r.map_or(JsValue::undefined(), JsValue::from))
} }
/// 15.8.2.8 `Temporal.Calendar.prototype.year ( temporalDateLike )` /// 15.8.2.8 `Temporal.Calendar.prototype.year ( temporalDateLike )`
@ -754,7 +752,7 @@ impl Calendar {
} }
}; };
this_calendar.year(&date_record, context) this_calendar.year(&date_record).map(Into::into)
} }
/// 15.8.2.9 `Temporal.Calendar.prototype.month ( temporalDateLike )` /// 15.8.2.9 `Temporal.Calendar.prototype.month ( temporalDateLike )`
@ -809,7 +807,7 @@ impl Calendar {
} }
}; };
this_calendar.month(&date_record, context) this_calendar.month(&date_record).map(Into::into)
} }
/// 15.8.2.10 `Temporal.Calendar.prototype.monthCode ( temporalDateLike )` /// 15.8.2.10 `Temporal.Calendar.prototype.monthCode ( temporalDateLike )`
@ -865,7 +863,7 @@ impl Calendar {
} }
}; };
this_calendar.month_code(&date_record, context) this_calendar.month_code(&date_record).map(Into::into)
} }
/// 15.8.2.11 `Temporal.Calendar.prototype.day ( temporalDateLike )` /// 15.8.2.11 `Temporal.Calendar.prototype.day ( temporalDateLike )`
@ -911,7 +909,7 @@ impl Calendar {
} }
}; };
this_calendar.day(&date_record, context) this_calendar.day(&date_record).map(Into::into)
} }
/// 15.8.2.12 `Temporal.Calendar.prototype.dayOfWeek ( dateOrDateTime )` /// 15.8.2.12 `Temporal.Calendar.prototype.dayOfWeek ( dateOrDateTime )`
@ -939,7 +937,9 @@ impl Calendar {
// 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike).
let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?;
this_calendar.day_of_week(&date.inner, context) let result = this_calendar.day_of_week(&date.inner);
result.map(Into::into)
} }
/// 15.8.2.13 `Temporal.Calendar.prototype.dayOfYear ( temporalDateLike )` /// 15.8.2.13 `Temporal.Calendar.prototype.dayOfYear ( temporalDateLike )`
@ -966,7 +966,9 @@ impl Calendar {
// 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike).
let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?;
this_calendar.day_of_year(&date.inner, context) let result = this_calendar.day_of_year(&date.inner);
result.map(Into::into)
} }
/// 15.8.2.14 `Temporal.Calendar.prototype.weekOfYear ( temporalDateLike )` /// 15.8.2.14 `Temporal.Calendar.prototype.weekOfYear ( temporalDateLike )`
@ -992,7 +994,9 @@ impl Calendar {
// 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike).
let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?;
this_calendar.week_of_year(&date.inner, context) let result = this_calendar.week_of_year(&date.inner);
result.map(Into::into)
} }
/// 15.8.2.15 `Temporal.Calendar.prototype.yearOfWeek ( temporalDateLike )` /// 15.8.2.15 `Temporal.Calendar.prototype.yearOfWeek ( temporalDateLike )`
@ -1018,7 +1022,9 @@ impl Calendar {
// 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike).
let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?;
this_calendar.year_of_week(&date.inner, context) let result = this_calendar.year_of_week(&date.inner);
result.map(Into::into)
} }
/// 15.8.2.16 `Temporal.Calendar.prototype.daysInWeek ( temporalDateLike )` /// 15.8.2.16 `Temporal.Calendar.prototype.daysInWeek ( temporalDateLike )`
@ -1044,7 +1050,9 @@ impl Calendar {
// 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike).
let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?;
this_calendar.days_in_week(&date.inner, context) let result = this_calendar.days_in_week(&date.inner);
result.map(Into::into)
} }
/// 15.8.2.17 `Temporal.Calendar.prototype.daysInMonth ( temporalDateLike )` /// 15.8.2.17 `Temporal.Calendar.prototype.daysInMonth ( temporalDateLike )`
@ -1094,7 +1102,9 @@ impl Calendar {
} }
}; };
this_calendar.days_in_month(&date_record, context) let result = this_calendar.days_in_month(&date_record);
result.map(Into::into)
} }
/// 15.8.2.18 `Temporal.Calendar.prototype.daysInYear ( temporalDateLike )` /// 15.8.2.18 `Temporal.Calendar.prototype.daysInYear ( temporalDateLike )`
@ -1144,7 +1154,9 @@ impl Calendar {
} }
}; };
this_calendar.days_in_year(&date_record, context) let result = this_calendar.days_in_year(&date_record);
result.map(Into::into)
} }
/// 15.8.2.19 `Temporal.Calendar.prototype.monthsInYear ( temporalDateLike )` /// 15.8.2.19 `Temporal.Calendar.prototype.monthsInYear ( temporalDateLike )`
@ -1194,7 +1206,9 @@ impl Calendar {
} }
}; };
this_calendar.months_in_year(&date_record, context) let result = this_calendar.months_in_year(&date_record);
result.map(Into::into)
} }
/// 15.8.2.20 `Temporal.Calendar.prototype.inLeapYear ( temporalDateLike )` /// 15.8.2.20 `Temporal.Calendar.prototype.inLeapYear ( temporalDateLike )`
@ -1244,7 +1258,9 @@ impl Calendar {
} }
}; };
this_calendar.in_leap_year(&date_record, context) let result = this_calendar.in_leap_year(&date_record);
result.map(Into::into)
} }
/// 15.8.2.21 `Temporal.Calendar.prototype.fields ( fields )` /// 15.8.2.21 `Temporal.Calendar.prototype.fields ( fields )`
@ -1290,12 +1306,9 @@ impl Calendar {
// 1. Let completion be ThrowCompletion(a newly created RangeError object). // 1. Let completion be ThrowCompletion(a newly created RangeError object).
// 2. Return ? IteratorClose(iteratorRecord, completion). // 2. Return ? IteratorClose(iteratorRecord, completion).
// v. Append nextValue to the end of the List fieldNames. // v. Append nextValue to the end of the List fieldNames.
let this_name = value.to_std_string_escaped(); match value.to_std_string_escaped().as_str() {
match this_name.as_str() { "year" | "month" | "monthCode" | "day" if !fields_names.contains(&value) => {
"year" | "month" | "monthCode" | "day" fields_names.push(value);
if !fields_names.contains(&this_name) =>
{
fields_names.push(this_name);
} }
_ => { _ => {
let completion = Err(JsNativeError::range() let completion = Err(JsNativeError::range()
@ -1319,7 +1332,8 @@ impl Calendar {
if calendar.identifier.as_slice() != ISO { if calendar.identifier.as_slice() != ISO {
// a. NOTE: Every built-in calendar preserves all input field names in output. // a. NOTE: Every built-in calendar preserves all input field names in output.
// b. Let extraFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], fieldNames). // b. Let extraFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], fieldNames).
let extended_fields = this_calendar.field_descriptors(&fields_names); let extended_fields =
this_calendar.field_descriptors(FieldsType::from(&fields_names[..]));
// c. For each Calendar Field Descriptor Record desc of extraFieldDescriptors, do // c. For each Calendar Field Descriptor Record desc of extraFieldDescriptors, do
for descriptor in extended_fields { for descriptor in extended_fields {
// i. Append desc.[[Property]] to result. // i. Append desc.[[Property]] to result.
@ -1328,13 +1342,10 @@ impl Calendar {
} }
// 9. Return CreateArrayFromList(result). // 9. Return CreateArrayFromList(result).
Ok(Array::create_array_from_list( Ok(
fields_names Array::create_array_from_list(fields_names.iter().map(|s| s.clone().into()), context)
.iter() .into(),
.map(|s| JsString::from(s.clone()).into()),
context,
) )
.into())
} }
/// 15.8.2.22 `Temporal.Calendar.prototype.mergeFields ( fields, additionalFields )` /// 15.8.2.22 `Temporal.Calendar.prototype.mergeFields ( fields, additionalFields )`
@ -1380,7 +1391,11 @@ impl Calendar {
// 5. NOTE: Every property of fieldsCopy and additionalFieldsCopy is an enumerable data property with non-undefined value, but some property keys may be Symbols. // 5. NOTE: Every property of fieldsCopy and additionalFieldsCopy is an enumerable data property with non-undefined value, but some property keys may be Symbols.
// 6. Let additionalKeys be ! additionalFieldsCopy.[[OwnPropertyKeys]](). // 6. Let additionalKeys be ! additionalFieldsCopy.[[OwnPropertyKeys]]().
let add_keys = additional_fields_copy.__own_property_keys__(context)?; let add_keys = additional_fields_copy
.__own_property_keys__(context)?
.iter()
.map(|k| JsString::from(k.to_string()))
.collect::<Vec<_>>();
// 7. If calendar.[[Identifier]] is "iso8601", then // 7. If calendar.[[Identifier]] is "iso8601", then
// a. Let overriddenKeys be ISOFieldKeysToIgnore(additionalKeys). // a. Let overriddenKeys be ISOFieldKeysToIgnore(additionalKeys).
@ -1395,23 +1410,28 @@ impl Calendar {
// matches that of fields as modified by omitting overridden properties and // matches that of fields as modified by omitting overridden properties and
// appending non-overlapping properties from additionalFields in iteration order. // appending non-overlapping properties from additionalFields in iteration order.
// 11. Let fieldsKeys be ! fieldsCopy.[[OwnPropertyKeys]](). // 11. Let fieldsKeys be ! fieldsCopy.[[OwnPropertyKeys]]().
let field_keys = fields_copy.__own_property_keys__(context)?; let field_keys = fields_copy
.__own_property_keys__(context)?
.iter()
.map(|k| JsString::from(k.to_string()))
.collect::<Vec<_>>();
// 12. For each element key of fieldsKeys, do // 12. For each element key of fieldsKeys, do
for key in field_keys { for key in field_keys {
// a. Let propValue be undefined. // a. Let propValue be undefined.
// b. If overriddenKeys contains key, then // b. If overriddenKeys contains key, then
let prop_value = if overridden_keys.contains(&key) { let prop_value = if overridden_keys.contains(&key) {
// i. Set propValue to ! Get(additionalFieldsCopy, key). // i. Set propValue to ! Get(additionalFieldsCopy, key).
additional_fields_copy.get(key.clone(), context)? additional_fields_copy.get(key.as_slice(), context)?
// c. Else, // c. Else,
} else { } else {
// i. Set propValue to ! Get(fieldsCopy, key). // i. Set propValue to ! Get(fieldsCopy, key).
fields_copy.get(key.clone(), context)? fields_copy.get(key.as_slice(), context)?
}; };
// d. If propValue is not undefined, perform ! CreateDataPropertyOrThrow(merged, key, propValue). // d. If propValue is not undefined, perform ! CreateDataPropertyOrThrow(merged, key, propValue).
if !prop_value.is_undefined() { if !prop_value.is_undefined() {
merged.create_data_property_or_throw(key, prop_value, context)?; merged.create_data_property_or_throw(key.as_slice(), prop_value, context)?;
} }
} }
@ -1590,7 +1610,7 @@ fn to_temporal_calendar_slot_value(
Ok(js_string!(ISO).into()) Ok(js_string!(ISO).into())
} }
// ---------------------------- AbstractCalendar Methods ---------------------------- // ---------------------------- Native Abstract Calendar Methods ----------------------------
// //
// The above refers to the functions in the Abstract Operations section of the Calendar // The above refers to the functions in the Abstract Operations section of the Calendar
// spec takes either a calendar identifier or `Temporal.Calendar` and calls the a // spec takes either a calendar identifier or `Temporal.Calendar` and calls the a
@ -1625,7 +1645,8 @@ fn call_method_on_abstract_calendar(
/// 12.2.2 `CalendarFields ( calendar, fieldNames )` /// 12.2.2 `CalendarFields ( calendar, fieldNames )`
/// ///
/// Returns either a normal completion containing a List of Strings, or a throw completion. /// `CalendarFields` takes the input fields and adds the `extraFieldDescriptors` for
/// that specific calendar.
#[allow(unused)] #[allow(unused)]
pub(crate) fn calendar_fields( pub(crate) fn calendar_fields(
calendar: &JsValue, calendar: &JsValue,
@ -1688,14 +1709,13 @@ pub(crate) fn calendar_merge_fields(
#[allow(unused)] #[allow(unused)]
pub(crate) fn calendar_date_add( pub(crate) fn calendar_date_add(
calendar: &JsValue, calendar: &JsValue,
date: &JsObject, date: &PlainDate,
duration: &JsObject, duration: &DurationRecord,
options: Option<JsValue>, options: &JsValue,
context: &mut Context<'_>, context: &mut Context<'_>,
) -> JsResult<JsObject> { ) -> JsResult<PlainDate> {
// NOTE: The specification never calls CalendarDateAdd without an options argument provided.
// 1. If options is not present, set options to undefined. // 1. If options is not present, set options to undefined.
let options = options.unwrap_or(JsValue::undefined());
// 2. If calendar is a String, then // 2. If calendar is a String, then
// a. Set calendar to ! CreateTemporalCalendar(calendar). // a. Set calendar to ! CreateTemporalCalendar(calendar).
// b. Return ? Call(%Temporal.Calendar.prototype.dateAdd%, calendar, « date, duration, options »). // b. Return ? Call(%Temporal.Calendar.prototype.dateAdd%, calendar, « date, duration, options »).
@ -1704,14 +1724,22 @@ pub(crate) fn calendar_date_add(
let added_date = call_method_on_abstract_calendar( let added_date = call_method_on_abstract_calendar(
calendar, calendar,
&JsString::from("dateAdd"), &JsString::from("dateAdd"),
&[date.clone().into(), duration.clone().into(), options], &[
date.as_object(context)?.into(),
duration.as_object(context)?.into(),
options.clone(),
],
context, context,
)?; )?;
// 5. Perform ? RequireInternalSlot(addedDate, [[InitializedTemporalDate]]). // 5. Perform ? RequireInternalSlot(addedDate, [[InitializedTemporalDate]]).
// 6. Return addedDate. // 6. Return addedDate.
match added_date { match added_date {
JsValue::Object(o) if o.is_plain_date() => Ok(o), JsValue::Object(o) if o.is_plain_date() => {
let obj = o.borrow();
let result = obj.as_plain_date().expect("must be a plain date");
Ok(result.clone())
}
_ => Err(JsNativeError::typ() _ => Err(JsNativeError::typ()
.with_message("dateAdd returned a value other than a Temoporal.PlainDate") .with_message("dateAdd returned a value other than a Temoporal.PlainDate")
.into()), .into()),
@ -1724,11 +1752,11 @@ pub(crate) fn calendar_date_add(
#[allow(unused)] #[allow(unused)]
pub(crate) fn calendar_date_until( pub(crate) fn calendar_date_until(
calendar: &JsValue, calendar: &JsValue,
one: &JsObject, one: &PlainDate,
two: &JsObject, two: &PlainDate,
options: &JsValue, options: &JsValue,
context: &mut Context<'_>, context: &mut Context<'_>,
) -> JsResult<super::duration::DurationRecord> { ) -> JsResult<DurationRecord> {
// 1. If calendar is a String, then // 1. If calendar is a String, then
// a. Set calendar to ! CreateTemporalCalendar(calendar). // a. Set calendar to ! CreateTemporalCalendar(calendar).
// b. Return ? Call(%Temporal.Calendar.prototype.dateUntil%, calendar, « one, two, options »). // b. Return ? Call(%Temporal.Calendar.prototype.dateUntil%, calendar, « one, two, options »).
@ -1737,7 +1765,11 @@ pub(crate) fn calendar_date_until(
let duration = call_method_on_abstract_calendar( let duration = call_method_on_abstract_calendar(
calendar, calendar,
&JsString::from("dateUntil"), &JsString::from("dateUntil"),
&[one.clone().into(), two.clone().into(), options.clone()], &[
one.as_object(context)?.into(),
two.as_object(context)?.into(),
options.clone(),
],
context, context,
)?; )?;
@ -1920,6 +1952,23 @@ pub(crate) fn calendar_day_of_week(
) -> JsResult<f64> { ) -> JsResult<f64> {
// 1. If calendar is a String, then // 1. If calendar is a String, then
// a. Set calendar to ! CreateTemporalCalendar(calendar). // a. Set calendar to ! CreateTemporalCalendar(calendar).
let identifier = match calendar {
JsValue::String(s) => s.clone(),
JsValue::Object(o) if o.is_calendar() => {
let obj = o.borrow();
let calendar = obj.as_calendar().expect("value must be a calendar");
calendar.identifier.clone()
}
_ => unreachable!(
"A calendar slot value not being a calendar obj or string is an implementation error."
),
};
let calendars = available_calendars();
let this = calendars.get(identifier.as_slice()).ok_or_else(|| {
JsNativeError::range().with_message("calendar value was not an implemented calendar")
})?;
// b. Return ? Call(%Temporal.Calendar.prototype.dayOfWeek%, calendar, « dateLike »). // b. Return ? Call(%Temporal.Calendar.prototype.dayOfWeek%, calendar, « dateLike »).
// 2. Let result be ? Invoke(calendar, "dayOfWeek", « dateLike »). // 2. Let result be ? Invoke(calendar, "dayOfWeek", « dateLike »).
let result = call_method_on_abstract_calendar( let result = call_method_on_abstract_calendar(
@ -1963,11 +2012,11 @@ pub(crate) fn calendar_day_of_year(
) -> JsResult<f64> { ) -> JsResult<f64> {
// 1. If calendar is a String, then // 1. If calendar is a String, then
// a. Set calendar to ! CreateTemporalCalendar(calendar). // a. Set calendar to ! CreateTemporalCalendar(calendar).
// b. Return ? Call(%Temporal.Calendar.prototype.dayOfYear%, calendar, « dateLike »). // b. Return ? Call(%Temporal.Calendar.prototype.dayOfWeek%, calendar, « dateLike »).
// 2. Let result be ? Invoke(calendar, "dayOfYear", « dateLike »). // 2. Let result be ? Invoke(calendar, "dayOfWeek", « dateLike »).
let result = call_method_on_abstract_calendar( let result = call_method_on_abstract_calendar(
calendar, calendar,
&JsString::from("dayOfYear"), &JsString::from("dayOfWeek"),
&[datelike.clone()], &[datelike.clone()],
context, context,
)?; )?;
@ -1975,21 +2024,21 @@ pub(crate) fn calendar_day_of_year(
// 3. If Type(result) is not Number, throw a TypeError exception. // 3. If Type(result) is not Number, throw a TypeError exception.
let Some(number) = result.as_number() else { let Some(number) = result.as_number() else {
return Err(JsNativeError::typ() return Err(JsNativeError::typ()
.with_message("CalendarDayOfYear result must be a number.") .with_message("CalendarDayOfWeek result must be a number.")
.into()); .into());
}; };
// 4. If IsIntegralNumber(result) is false, throw a RangeError exception. // 4. If IsIntegralNumber(result) is false, throw a RangeError exception.
if number.is_nan() || number.is_infinite() || number.fract() != 0.0 { if number.is_nan() || number.is_infinite() || number.fract() != 0.0 {
return Err(JsNativeError::range() return Err(JsNativeError::range()
.with_message("CalendarDayOfYear was not integral.") .with_message("CalendarDayOfWeek was not integral.")
.into()); .into());
} }
// 5. If result < 1𝔽, throw a RangeError exception. // 5. If result < 1𝔽, throw a RangeError exception.
if number < 1.0 { if number < 1.0 {
return Err(JsNativeError::range() return Err(JsNativeError::range()
.with_message("dayOfYear must be 1 or greater.") .with_message("dayOfWeek must be 1 or greater.")
.into()); .into());
} }
@ -2006,11 +2055,11 @@ pub(crate) fn calendar_week_of_year(
) -> JsResult<f64> { ) -> JsResult<f64> {
// 1. If calendar is a String, then // 1. If calendar is a String, then
// a. Set calendar to ! CreateTemporalCalendar(calendar). // a. Set calendar to ! CreateTemporalCalendar(calendar).
// b. Return ? Call(%Temporal.Calendar.prototype.weekOfYear%, calendar, « dateLike »). // b. Return ? Call(%Temporal.Calendar.prototype.dayOfYear%, calendar, « dateLike »).
// 2. Let result be ? Invoke(calendar, "weekOfYear", « dateLike »). // 2. Let result be ? Invoke(calendar, "dayOfYear", « dateLike »).
let result = call_method_on_abstract_calendar( let result = call_method_on_abstract_calendar(
calendar, calendar,
&JsString::from("weekOfYear"), &JsString::from("dayOfYear"),
&[datelike.clone()], &[datelike.clone()],
context, context,
)?; )?;
@ -2018,21 +2067,21 @@ pub(crate) fn calendar_week_of_year(
// 3. If Type(result) is not Number, throw a TypeError exception. // 3. If Type(result) is not Number, throw a TypeError exception.
let Some(number) = result.as_number() else { let Some(number) = result.as_number() else {
return Err(JsNativeError::typ() return Err(JsNativeError::typ()
.with_message("CalendarWeekOfYear result must be a number.") .with_message("CalendarDayOfYear result must be a number.")
.into()); .into());
}; };
// 4. If IsIntegralNumber(result) is false, throw a RangeError exception. // 4. If IsIntegralNumber(result) is false, throw a RangeError exception.
if number.is_nan() || number.is_infinite() || number.fract() != 0.0 { if number.is_nan() || number.is_infinite() || number.fract() != 0.0 {
return Err(JsNativeError::range() return Err(JsNativeError::range()
.with_message("CalendarWeekOfYear was not integral.") .with_message("CalendarDayOfYear was not integral.")
.into()); .into());
} }
// 5. If result < 1𝔽, throw a RangeError exception. // 5. If result < 1𝔽, throw a RangeError exception.
if number < 1.0 { if number < 1.0 {
return Err(JsNativeError::range() return Err(JsNativeError::range()
.with_message("weekOfYear must be 1 or greater.") .with_message("dayOfYear must be 1 or greater.")
.into()); .into());
} }
@ -2279,7 +2328,7 @@ pub(crate) fn calendar_in_lear_year(
/// 12.2.24 `CalendarDateFromFields ( calendar, fields [ , options [ , dateFromFields ] ] )` /// 12.2.24 `CalendarDateFromFields ( calendar, fields [ , options [ , dateFromFields ] ] )`
#[allow(unused)] #[allow(unused)]
pub(crate) fn calendar_date_from_fields( pub(crate) fn calendar_date_from_fields(
_calendar: &JsValue, calendar: &JsValue,
_fields: &JsObject, _fields: &JsObject,
options: Option<&JsValue>, options: Option<&JsValue>,
_date_from_fields: Option<&JsObject>, _date_from_fields: Option<&JsObject>,

239
boa_engine/src/builtins/temporal/duration/mod.rs

@ -17,10 +17,10 @@ use crate::{
use boa_profiler::Profiler; use boa_profiler::Profiler;
use super::{ use super::{
calendar,
options::{ options::{
get_temporal_rounding_increment, get_temporal_unit, TemporalUnit, TemporalUnitGroup, get_temporal_rounding_increment, get_temporal_unit, TemporalUnit, TemporalUnitGroup,
}, },
plain_date::{self, PlainDate},
to_integer_if_integral, DateTimeValues, to_integer_if_integral, DateTimeValues,
}; };
@ -565,6 +565,7 @@ impl Duration {
.into()) .into())
} }
// TODO: Update needed.
/// 7.3.20 `Temporal.Duration.prototype.round ( roundTo )` /// 7.3.20 `Temporal.Duration.prototype.round ( roundTo )`
pub(crate) fn round( pub(crate) fn round(
this: &JsValue, this: &JsValue,
@ -613,9 +614,7 @@ impl Duration {
// 6. Let smallestUnitPresent be true. // 6. Let smallestUnitPresent be true.
// 7. Let largestUnitPresent be true. // 7. Let largestUnitPresent be true.
// 8. NOTE: The following steps read options and perform independent validation in alphabetical order // 8. NOTE: The following steps read options and perform independent validation in alphabetical order (ToRelativeTemporalObject reads "relativeTo", ToTemporalRoundingIncrement reads "roundingIncrement" and ToTemporalRoundingMode reads "roundingMode").
// (ToRelativeTemporalObject reads "relativeTo", ToTemporalRoundingIncrement reads "roundingIncrement" and ToTemporalRoundingMode reads "roundingMode").
// 9. Let largestUnit be ? GetTemporalUnit(roundTo, "largestUnit", datetime, undefined, « "auto" »). // 9. Let largestUnit be ? GetTemporalUnit(roundTo, "largestUnit", datetime, undefined, « "auto" »).
let largest_unit = get_temporal_unit( let largest_unit = get_temporal_unit(
&round_to, &round_to,
@ -625,17 +624,20 @@ impl Duration {
context, context,
)?; )?;
// 10. Let relativeTo be ? ToRelativeTemporalObject(roundTo). // 10. Let relativeToRecord be ? ToRelativeTemporalObject(roundTo).
let relative_to = super::to_relative_temporal_object(&round_to, context)?; // 11. Let zonedRelativeTo be relativeToRecord.[[ZonedRelativeTo]].
// 12. Let plainRelativeTo be relativeToRecord.[[PlainRelativeTo]].
let (_plain_relative_to, _zoned_relative_to) =
super::to_relative_temporal_object(&round_to, context)?;
// 11. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo). // 13. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo).
let rounding_increment = get_temporal_rounding_increment(&round_to, context)?; let rounding_increment = get_temporal_rounding_increment(&round_to, context)?;
// 12. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand"). // 14. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand").
let rounding_mode = get_option(&round_to, utf16!("roundingMode"), context)? let _rounding_mode = get_option(&round_to, utf16!("roundingMode"), context)?
.unwrap_or(RoundingMode::HalfExpand); .unwrap_or(RoundingMode::HalfExpand);
// 13. Let smallestUnit be ? GetTemporalUnit(roundTo, "smallestUnit", datetime, undefined). // 15. Let smallestUnit be ? GetTemporalUnit(roundTo, "smallestUnit", datetime, undefined).
let smallest_unit = get_temporal_unit( let smallest_unit = get_temporal_unit(
&round_to, &round_to,
utf16!("smallestUnit"), utf16!("smallestUnit"),
@ -644,8 +646,8 @@ impl Duration {
context, context,
)?; )?;
// NOTE: execute step 19 earlier before initial values are shadowed. // NOTE: execute step 21 earlier before initial values are shadowed.
// 19. If smallestUnitPresent is false and largestUnitPresent is false, then // 21. If smallestUnitPresent is false and largestUnitPresent is false, then
if smallest_unit.is_none() && largest_unit.is_none() { if smallest_unit.is_none() && largest_unit.is_none() {
// a. Throw a RangeError exception. // a. Throw a RangeError exception.
return Err(JsNativeError::range() return Err(JsNativeError::range()
@ -653,7 +655,7 @@ impl Duration {
.into()); .into());
} }
// 14. If smallestUnit is undefined, then // 16. If smallestUnit is undefined, then
let smallest_unit = if let Some(unit) = smallest_unit { let smallest_unit = if let Some(unit) = smallest_unit {
unit unit
} else { } else {
@ -662,14 +664,16 @@ impl Duration {
TemporalUnit::Nanosecond TemporalUnit::Nanosecond
}; };
// 15. Let defaultLargestUnit be ! DefaultTemporalLargestUnit(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]]). // 17. Let existingLargestUnit be ! DefaultTemporalLargestUnit(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]]).
let mut default_largest_unit = duration.inner.default_temporal_largest_unit(); let existing_largest_unit = duration.inner.default_temporal_largest_unit();
// 16. Set defaultLargestUnit to ! LargerOfTwoTemporalUnits(defaultLargestUnit, smallestUnit). // 18. Set defaultLargestUnit to ! LargerOfTwoTemporalUnits(defaultLargestUnit, smallestUnit).
default_largest_unit = core::cmp::max(default_largest_unit, smallest_unit); let default_largest_unit = core::cmp::max(existing_largest_unit, smallest_unit);
// 17. If largestUnit is undefined, then // 19. If largestUnit is undefined, then
let largest_unit = match largest_unit { let largest_unit = match largest_unit {
// 20. Else if largestUnit is "auto", then
// a. Set largestUnit to defaultLargestUnit.
Some(TemporalUnit::Auto) => default_largest_unit, Some(TemporalUnit::Auto) => default_largest_unit,
Some(u) => u, Some(u) => u,
None => { None => {
@ -679,60 +683,24 @@ impl Duration {
} }
}; };
// 20. If LargerOfTwoTemporalUnits(largestUnit, smallestUnit) is not largestUnit, throw a RangeError exception. // 22. If LargerOfTwoTemporalUnits(largestUnit, smallestUnit) is not largestUnit, throw a RangeError exception.
if core::cmp::max(largest_unit, smallest_unit) != largest_unit { if core::cmp::max(largest_unit, smallest_unit) != largest_unit {
return Err(JsNativeError::range() return Err(JsNativeError::range()
.with_message("largestUnit must be larger than smallestUnit") .with_message("largestUnit must be larger than smallestUnit")
.into()); .into());
} }
// 21. Let maximum be ! MaximumTemporalDurationRoundingIncrement(smallestUnit). // 23. Let maximum be ! MaximumTemporalDurationRoundingIncrement(smallestUnit).
let maximum = smallest_unit.to_maximum_rounding_increment(); let maximum = smallest_unit.to_maximum_rounding_increment();
// 22. If maximum is not undefined, perform ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false). // 24. If maximum is not undefined, perform ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false).
if let Some(max) = maximum { if let Some(max) = maximum {
validate_temporal_rounding_increment(rounding_increment, f64::from(max), false)?; validate_temporal_rounding_increment(rounding_increment, f64::from(max), false)?;
} }
let mut unbalance_duration = DurationRecord::from_date_duration(duration.inner.date()); // TODO: Complete the rest of the new `Temporal.Duration.prototype.round` impl.
// 23. Let unbalanceResult be ? UnbalanceDateDurationRelative(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], largestUnit, relativeTo).
unbalance_duration.unbalance_duration_relative(largest_unit, &relative_to, context)?;
let mut roundable_duration =
DurationRecord::new(unbalance_duration.date(), duration.inner.time());
// 24. Let roundResult be (? RoundDuration(unbalanceResult.[[Years]], unbalanceResult.[[Months]], unbalanceResult.[[Weeks]],
// unbalanceResult.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]],
// duration.[[Microseconds]], duration.[[Nanoseconds]], roundingIncrement, smallestUnit, roundingMode, relativeTo)).[[DurationRecord]].
let _rem = roundable_duration.round_duration(
rounding_increment,
smallest_unit,
rounding_mode,
Some(&relative_to),
context,
)?;
// 25. Let roundResult be roundRecord.[[DurationRecord]].
// 26. If relativeTo is not undefined and relativeTo has an [[InitializedTemporalZonedDateTime]] internal slot, then
match relative_to {
JsValue::Object(o) if o.is_zoned_date_time() => {
// TODO: AdjustRoundedDurationDays requires 6.5.5 AddZonedDateTime.
// a. Set roundResult to ? AdjustRoundedDurationDays(roundResult.[[Years]], roundResult.[[Months]], roundResult.[[Weeks]], roundResult.[[Days]], roundResult.[[Hours]], roundResult.[[Minutes]], roundResult.[[Seconds]], roundResult.[[Milliseconds]], roundResult.[[Microseconds]], roundResult.[[Nanoseconds]], roundingIncrement, smallestUnit, roundingMode, relativeTo).
// b. Let balanceResult be ? BalanceTimeDurationRelative(roundResult.[[Days]], roundResult.[[Hours]], roundResult.[[Minutes]], roundResult.[[Seconds]], roundResult.[[Milliseconds]], roundResult.[[Microseconds]], roundResult.[[Nanoseconds]], largestUnit, relativeTo).
return Err(JsNativeError::range()
.with_message("not yet implemented.")
.into());
}
// 27. Else,
_ => {
// a. Let balanceResult be ? BalanceTimeDuration(roundResult.[[Days]], roundResult.[[Hours]], roundResult.[[Minutes]], roundResult.[[Seconds]], roundResult.[[Milliseconds]], roundResult.[[Microseconds]], roundResult.[[Nanoseconds]], largestUnit).
roundable_duration.balance_time_duration(largest_unit, None)?;
}
}
// 28. Let result be ? BalanceDateDurationRelative(roundResult.[[Years]], roundResult.[[Months]], roundResult.[[Weeks]], balanceResult.[[Days]], largestUnit, relativeTo).
// 29. Return ! CreateTemporalDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], balanceResult.[[Hours]], balanceResult.[[Minutes]], balanceResult.[[Seconds]], balanceResult.[[Milliseconds]], balanceResult.[[Microseconds]], balanceResult.[[Nanoseconds]]).
// NOTE: Below is currently incorrect: Handling of zonedRelativeTo and precalculatedPlainDateTime is needed.
Err(JsNativeError::range() Err(JsNativeError::range()
.with_message("not yet implemented.") .with_message("not yet implemented.")
.into()) .into())
@ -749,7 +717,7 @@ impl Duration {
let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { let o = this.as_object().map(JsObject::borrow).ok_or_else(|| {
JsNativeError::typ().with_message("this value of Duration must be an object.") JsNativeError::typ().with_message("this value of Duration must be an object.")
})?; })?;
let duration = o.as_duration().ok_or_else(|| { let _duration = o.as_duration().ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a Duration object.") JsNativeError::typ().with_message("the this object must be a Duration object.")
})?; })?;
@ -783,12 +751,14 @@ impl Duration {
}; };
// 6. NOTE: The following steps read options and perform independent validation in alphabetical order (ToRelativeTemporalObject reads "relativeTo"). // 6. NOTE: The following steps read options and perform independent validation in alphabetical order (ToRelativeTemporalObject reads "relativeTo").
// 7. Let relativeTo be ? ToRelativeTemporalObject(totalOf). // 7. Let relativeToRecord be ? ToRelativeTemporalObject(totalOf).
// NOTE TO SELF: Should relative_to_temporal_object just return a JsValue and we live with the expect? // 8. Let zonedRelativeTo be relativeToRecord.[[ZonedRelativeTo]].
let relative_to = super::to_relative_temporal_object(&total_of, context)?; // 9. Let plainRelativeTo be relativeToRecord.[[PlainRelativeTo]].
let (_plain_relative_to, _zoned_relative_to) =
// 8. Let unit be ? GetTemporalUnit(totalOf, "unit", datetime, required). super::to_relative_temporal_object(&total_of, context)?;
let unit = get_temporal_unit(
// 10. Let unit be ? GetTemporalUnit(totalOf, "unit", datetime, required).
let _unit = get_temporal_unit(
&total_of, &total_of,
utf16!("unit"), utf16!("unit"),
TemporalUnitGroup::DateTime, TemporalUnitGroup::DateTime,
@ -797,106 +767,11 @@ impl Duration {
)? )?
.ok_or_else(|| JsNativeError::range().with_message("unit cannot be undefined."))?; .ok_or_else(|| JsNativeError::range().with_message("unit cannot be undefined."))?;
let mut unbalance_duration = DurationRecord::from_date_duration(duration.inner.date()); // TODO: Implement the rest of the new `Temporal.Duration.prototype.total`
// 9. Let unbalanceResult be ? UnbalanceDurationRelative(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], unit, relativeTo).
unbalance_duration.unbalance_duration_relative(unit, &relative_to, context)?;
// 10. Let intermediate be undefined. Err(JsNativeError::range()
let mut _intermediate = JsValue::undefined();
// 11. If Type(relativeTo) is Object and relativeTo has an [[InitializedTemporalZonedDateTime]] internal slot, then
if relative_to.is_object()
&& relative_to
.as_object()
.expect("relative_to must be an object")
.is_zoned_date_time()
{
// a. Set intermediate to ? MoveRelativeZonedDateTime(relativeTo, unbalanceResult.[[Years]], unbalanceResult.[[Months]], unbalanceResult.[[Weeks]], 0).
return Err(JsNativeError::error()
.with_message("not yet implemented.") .with_message("not yet implemented.")
.into()); .into())
}
let mut balance_duration = DurationRecord::new(
DateDuration::new(0.0, 0.0, 0.0, unbalance_duration.days()),
duration.inner.time(),
);
// 12. Let balanceResult be ? BalancePossiblyInfiniteDuration(unbalanceResult.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], unit, intermediate).
balance_duration.balance_possibly_infinite_duration(unit, Some(&relative_to))?;
// 13. If balanceResult is positive overflow, return +∞𝔽.
if balance_duration.is_positive_overflow() {
return Ok(f64::INFINITY.into());
};
// 14. If balanceResult is negative overflow, return -∞𝔽.
if balance_duration.is_negative_overflow() {
return Ok(f64::NEG_INFINITY.into());
}
// TODO: determine whether and how to assert 15.
// 15. Assert: balanceResult is a Time Duration Record.
// 16. Let roundRecord be ? RoundDuration(unbalanceResult.[[Years]], unbalanceResult.[[Months]], unbalanceResult.[[Weeks]], balanceResult.[[Days]],
// balanceResult.[[Hours]], balanceResult.[[Minutes]], balanceResult.[[Seconds]], balanceResult.[[Milliseconds]], balanceResult.[[Microseconds]],
// balanceResult.[[Nanoseconds]], 1, unit, "trunc", relativeTo).
// 17. Let roundResult be roundRecord.[[DurationRecord]].
let mut round_record = DurationRecord::new(
DateDuration::new(
unbalance_duration.years(),
unbalance_duration.months(),
unbalance_duration.weeks(),
balance_duration.days(),
),
balance_duration.time(),
);
let remainder = round_record.round_duration(
1_f64,
unit,
RoundingMode::Trunc,
Some(&relative_to),
context,
)?;
let whole = match unit {
// 18. If unit is "year", then
// a. Let whole be roundResult.[[Years]].
TemporalUnit::Year => round_record.years(),
// 19. Else if unit is "month", then
// a. Let whole be roundResult.[[Months]].
TemporalUnit::Month => round_record.months(),
// 20. Else if unit is "week", then
// a. Let whole be roundResult.[[Weeks]].
TemporalUnit::Week => round_record.weeks(),
// 21. Else if unit is "day", then
// a. Let whole be roundResult.[[Days]].
TemporalUnit::Day => round_record.days(),
// 22. Else if unit is "hour", then
// a. Let whole be roundResult.[[Hours]].
TemporalUnit::Hour => round_record.hours(),
// 23. Else if unit is "minute", then
// a. Let whole be roundResult.[[Minutes]].
TemporalUnit::Minute => round_record.minutes(),
// 24. Else if unit is "second", then
// a. Let whole be roundResult.[[Seconds]].
TemporalUnit::Second => round_record.seconds(),
// 25. Else if unit is "millisecond", then
// a. Let whole be roundResult.[[Milliseconds]].
TemporalUnit::Millisecond => round_record.milliseconds(),
// 26. Else if unit is "microsecond", then
// a. Let whole be roundResult.[[Microseconds]].
TemporalUnit::Microsecond => round_record.microseconds(),
// 27. Else,
// b. Let whole be roundResult.[[Nanoseconds]].
TemporalUnit::Nanosecond => round_record.nanoseconds(),
// a. Assert: unit is "nanosecond".
TemporalUnit::Auto=> unreachable!("Unit must be a valid temporal unit. Any other value would be an implementation error."),
};
// 28. Return 𝔽(whole + roundRecord.[[Remainder]]).
Ok((whole + remainder).into())
} }
/// 7.3.22 `Temporal.Duration.prototype.toString ( [ options ] )` /// 7.3.22 `Temporal.Duration.prototype.toString ( [ options ] )`
@ -1003,24 +878,12 @@ pub(crate) fn create_temporal_duration(
} }
/// 7.5.23 `DaysUntil ( earlier, later )` /// 7.5.23 `DaysUntil ( earlier, later )`
fn days_until(earlier: &JsObject, later: &JsObject) -> i32 { pub(crate) fn days_until(earlier: &PlainDate, later: &PlainDate) -> i32 {
// 1. Let epochDays1 be ISODateToEpochDays(earlier.[[ISOYear]], earlier.[[ISOMonth]] - 1, earlier.[[ISODay]]). // 1. Let epochDays1 be ISODateToEpochDays(earlier.[[ISOYear]], earlier.[[ISOMonth]] - 1, earlier.[[ISODay]]).
let obj = earlier.borrow(); let epoch_days_one = earlier.inner.as_epoch_days();
let date_one = obj
.as_plain_date()
.expect("earlier must be a PlainDate obj.");
let epoch_days_one = date_one.inner.as_epoch_days();
drop(obj);
// 2. Let epochDays2 be ISODateToEpochDays(later.[[ISOYear]], later.[[ISOMonth]] - 1, later.[[ISODay]]). // 2. Let epochDays2 be ISODateToEpochDays(later.[[ISOYear]], later.[[ISOMonth]] - 1, later.[[ISODay]]).
let obj = later.borrow(); let epoch_days_two = later.inner.as_epoch_days();
let date_two = obj
.as_plain_date()
.expect("earlier must be a PlainDate obj.");
let epoch_days_two = date_two.inner.as_epoch_days();
// 3. Return epochDays2 - epochDays1. // 3. Return epochDays2 - epochDays1.
epoch_days_two - epoch_days_one epoch_days_two - epoch_days_one
@ -1029,11 +892,17 @@ fn days_until(earlier: &JsObject, later: &JsObject) -> i32 {
/// Abstract Operation 7.5.24 `MoveRelativeDate ( calendar, relativeTo, duration, dateAdd )` /// Abstract Operation 7.5.24 `MoveRelativeDate ( calendar, relativeTo, duration, dateAdd )`
fn move_relative_date( fn move_relative_date(
calendar: &JsValue, calendar: &JsValue,
relative_to: &JsObject, relative_to: &PlainDate,
duration: &JsObject, duration: &DurationRecord,
context: &mut Context<'_>, context: &mut Context<'_>,
) -> JsResult<(JsObject, f64)> { ) -> JsResult<(PlainDate, f64)> {
let new_date = calendar::calendar_date_add(calendar, relative_to, duration, None, context)?; let new_date = plain_date::add_date(
let days = f64::from(days_until(relative_to, &new_date)); calendar,
Ok((new_date, days)) relative_to,
duration,
&JsValue::undefined(),
context,
)?;
let days = days_until(relative_to, &new_date);
Ok((new_date, f64::from(days)))
} }

877
boa_engine/src/builtins/temporal/duration/record.rs

File diff suppressed because it is too large Load Diff

21
boa_engine/src/builtins/temporal/fields.rs

@ -40,7 +40,7 @@ bitflags! {
/// ## Table 17: Temporal field requirements /// ## Table 17: Temporal field requirements
/// ///
/// | Property | Conversion | Default | /// | Property | Conversion | Default |
/// | -------------|---------------------------------|------------| /// | -------------|-----------------------------------|------------|
/// | "year" | `ToIntegerWithTruncation` | undefined | /// | "year" | `ToIntegerWithTruncation` | undefined |
/// | "month" | `ToPositiveIntegerWithTruncation` | undefined | /// | "month" | `ToPositiveIntegerWithTruncation` | undefined |
/// | "monthCode" | `ToPrimitiveAndRequireString` | undefined | /// | "monthCode" | `ToPrimitiveAndRequireString` | undefined |
@ -54,7 +54,7 @@ bitflags! {
/// | "offset" | `ToPrimitiveAndRequireString` | undefined | /// | "offset" | `ToPrimitiveAndRequireString` | undefined |
/// | "era" | `ToPrimitiveAndRequireString` | undefined | /// | "era" | `ToPrimitiveAndRequireString` | undefined |
/// | "eraYear" | `ToIntegerWithTruncation` | undefined | /// | "eraYear" | `ToIntegerWithTruncation` | undefined |
/// | "timeZone" | | undefined | /// | "timeZone" | `None` | undefined |
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct TemporalFields { pub(crate) struct TemporalFields {
bit_map: FieldMap, bit_map: FieldMap,
@ -114,11 +114,11 @@ impl TemporalFields {
#[inline] #[inline]
fn set_field_value( fn set_field_value(
&mut self, &mut self,
field: &str, field: &JsString,
value: &JsValue, value: &JsValue,
context: &mut Context<'_>, context: &mut Context<'_>,
) -> JsResult<()> { ) -> JsResult<()> {
match field { match field.to_std_string_escaped().as_str() {
"year" => self.set_year(value, context)?, "year" => self.set_year(value, context)?,
"month" => self.set_month(value, context)?, "month" => self.set_month(value, context)?,
"monthCode" => self.set_month_code(value, context)?, "monthCode" => self.set_month_code(value, context)?,
@ -281,9 +281,9 @@ impl TemporalFields {
/// This is the equivalant to Abstract Operation 13.46 `PrepareTemporalFields` /// This is the equivalant to Abstract Operation 13.46 `PrepareTemporalFields`
pub(crate) fn from_js_object( pub(crate) fn from_js_object(
fields: &JsObject, fields: &JsObject,
field_names: &mut Vec<String>, field_names: &mut Vec<JsString>,
required_fields: &mut Vec<String>, // None when Partial required_fields: &mut Vec<JsString>, // None when Partial
extended_fields: Option<Vec<(String, bool)>>, extended_fields: Option<Vec<(JsString, bool)>>,
partial: bool, partial: bool,
dup_behaviour: Option<JsString>, dup_behaviour: Option<JsString>,
context: &mut Context<'_>, context: &mut Context<'_>,
@ -319,7 +319,9 @@ impl TemporalFields {
// 7. For each property name property of sortedFieldNames, do // 7. For each property name property of sortedFieldNames, do
for field in &*field_names { for field in &*field_names {
// a. If property is one of "constructor" or "__proto__", then // a. If property is one of "constructor" or "__proto__", then
if field.as_str() == "constructor" || field.as_str() == "__proto__" { if field.to_std_string_escaped().as_str() == "constructor"
|| field.to_std_string_escaped().as_str() == "__proto__"
{
// i. Throw a RangeError exception. // i. Throw a RangeError exception.
return Err(JsNativeError::range() return Err(JsNativeError::range()
.with_message("constructor or proto is out of field range.") .with_message("constructor or proto is out of field range.")
@ -331,8 +333,7 @@ impl TemporalFields {
// b. If property is not equal to previousProperty, then // b. If property is not equal to previousProperty, then
if new_value { if new_value {
// i. Let value be ? Get(fields, property). // i. Let value be ? Get(fields, property).
let value = let value = fields.get(PropertyKey::from(field.clone()), context)?;
fields.get(PropertyKey::from(JsString::from(field.clone())), context)?;
// ii. If value is not undefined, then // ii. If value is not undefined, then
if !value.is_undefined() { if !value.is_undefined() {
// 1. Set any to true. // 1. Set any to true.

4
boa_engine/src/builtins/temporal/instant/mod.rs

@ -606,11 +606,13 @@ fn diff_instant(
nanoseconds.to_f64(), nanoseconds.to_f64(),
), ),
); );
let _rem = roundable_duration.round_duration( let _total = roundable_duration.round_duration(
rounding_increment, rounding_increment,
smallest_unit, smallest_unit,
rounding_mode, rounding_mode,
None, None,
None,
None,
context, context,
)?; )?;

2
boa_engine/src/builtins/temporal/mod.rs

@ -294,7 +294,7 @@ pub(crate) fn validate_temporal_rounding_increment(
pub(crate) fn to_relative_temporal_object( pub(crate) fn to_relative_temporal_object(
_options: &JsObject, _options: &JsObject,
_context: &mut Context<'_>, _context: &mut Context<'_>,
) -> JsResult<JsValue> { ) -> JsResult<(Option<PlainDate>, Option<ZonedDateTime>)> {
Err(JsNativeError::range() Err(JsNativeError::range()
.with_message("not yet implemented.") .with_message("not yet implemented.")
.into()) .into())

30
boa_engine/src/builtins/temporal/plain_date/iso.rs

@ -1,7 +1,7 @@
//! An `IsoDateRecord` that represents the `[[ISOYear]]`, `[[ISOMonth]]`, and `[[ISODay]]` internal slots. //! An `IsoDateRecord` that represents the `[[ISOYear]]`, `[[ISOMonth]]`, and `[[ISODay]]` internal slots.
use crate::{ use crate::{
builtins::temporal::{self, TemporalFields}, builtins::temporal::{self, options::ArithmeticOverflow, DateDuration, TemporalFields},
JsNativeError, JsResult, JsString, JsNativeError, JsResult, JsString,
}; };
@ -59,16 +59,16 @@ impl IsoDateRecord {
year: i32, year: i32,
month: i32, month: i32,
day: i32, day: i32,
overflow: &JsString, overflow: ArithmeticOverflow,
) -> JsResult<Self> { ) -> JsResult<Self> {
match overflow.to_std_string_escaped().as_str() { match overflow {
"constrain" => { ArithmeticOverflow::Constrain => {
let m = month.clamp(1, 12); let m = month.clamp(1, 12);
let days_in_month = temporal::calendar::utils::iso_days_in_month(year, month); let days_in_month = temporal::calendar::utils::iso_days_in_month(year, month);
let d = day.clamp(1, days_in_month); let d = day.clamp(1, days_in_month);
Ok(Self::new(year, m, d)) Ok(Self::new(year, m, d))
} }
"reject" => { ArithmeticOverflow::Reject => {
let date = Self::new(year, month, day); let date = Self::new(year, month, day);
if !date.is_valid() { if !date.is_valid() {
return Err(JsNativeError::range() return Err(JsNativeError::range()
@ -77,7 +77,6 @@ impl IsoDateRecord {
} }
Ok(date) Ok(date)
} }
_ => unreachable!(),
} }
} }
@ -86,7 +85,7 @@ impl IsoDateRecord {
/// Note: fields.month must be resolved prior to using `from_temporal_fields` /// Note: fields.month must be resolved prior to using `from_temporal_fields`
pub(crate) fn from_temporal_fields( pub(crate) fn from_temporal_fields(
fields: &TemporalFields, fields: &TemporalFields,
overflow: &JsString, overflow: ArithmeticOverflow,
) -> JsResult<Self> { ) -> JsResult<Self> {
Self::from_unregulated( Self::from_unregulated(
fields.year().expect("Cannot fail per spec"), fields.year().expect("Cannot fail per spec"),
@ -99,7 +98,7 @@ impl IsoDateRecord {
/// Create a Month-Day record from a `TemporalFields` object. /// Create a Month-Day record from a `TemporalFields` object.
pub(crate) fn month_day_from_temporal_fields( pub(crate) fn month_day_from_temporal_fields(
fields: &TemporalFields, fields: &TemporalFields,
overflow: &JsString, overflow: ArithmeticOverflow,
) -> JsResult<Self> { ) -> JsResult<Self> {
match fields.year() { match fields.year() {
Some(year) => Self::from_unregulated( Some(year) => Self::from_unregulated(
@ -202,15 +201,16 @@ impl IsoDateRecord {
/// 3.5.11 `AddISODate ( year, month, day, years, months, weeks, days, overflow )` /// 3.5.11 `AddISODate ( year, month, day, years, months, weeks, days, overflow )`
pub(crate) fn add_iso_date( pub(crate) fn add_iso_date(
&self, &self,
years: i32, date_duration: DateDuration,
months: i32, overflow: ArithmeticOverflow,
weeks: i32,
days: i32,
overflow: &JsString,
) -> JsResult<Self> { ) -> JsResult<Self> {
// 1. Assert: year, month, day, years, months, weeks, and days are integers. // 1. Assert: year, month, day, years, months, weeks, and days are integers.
// 2. Assert: overflow is either "constrain" or "reject". // 2. Assert: overflow is either "constrain" or "reject".
let mut intermediate = Self::new(self.year + years, self.month + months, 0); let mut intermediate = Self::new(
self.year + date_duration.years() as i32,
self.month + date_duration.months() as i32,
0,
);
// 3. Let intermediate be ! BalanceISOYearMonth(year + years, month + months). // 3. Let intermediate be ! BalanceISOYearMonth(year + years, month + months).
intermediate.balance_year_month(); intermediate.balance_year_month();
@ -225,7 +225,7 @@ impl IsoDateRecord {
// 5. Set days to days + 7 × weeks. // 5. Set days to days + 7 × weeks.
// 6. Let d be intermediate.[[Day]] + days. // 6. Let d be intermediate.[[Day]] + days.
let additional_days = days + (weeks * 7); let additional_days = date_duration.days() as i32 + (date_duration.weeks() as i32 * 7);
new_date.day += additional_days; new_date.day += additional_days;
// 7. Return BalanceISODate(intermediate.[[Year]], intermediate.[[Month]], d). // 7. Return BalanceISODate(intermediate.[[Year]], intermediate.[[Month]], d).

115
boa_engine/src/builtins/temporal/plain_date/mod.rs

@ -4,6 +4,7 @@
use crate::{ use crate::{
builtins::{ builtins::{
options::{get_option, get_options_object}, options::{get_option, get_options_object},
temporal::options::TemporalUnit,
BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject,
}, },
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
@ -17,7 +18,10 @@ use crate::{
use boa_parser::temporal::{IsoCursor, TemporalDateTimeString}; use boa_parser::temporal::{IsoCursor, TemporalDateTimeString};
use boa_profiler::Profiler; use boa_profiler::Profiler;
use super::{options::ArithmeticOverflow, plain_date::iso::IsoDateRecord, plain_date_time}; use super::{
calendar, duration::DurationRecord, options::ArithmeticOverflow,
plain_date::iso::IsoDateRecord, plain_date_time, DateDuration, TimeDuration,
};
pub(crate) mod iso; pub(crate) mod iso;
@ -28,6 +32,15 @@ pub struct PlainDate {
pub(crate) calendar: JsValue, // Calendar can probably be stored as a JsObject. pub(crate) calendar: JsValue, // Calendar can probably be stored as a JsObject.
} }
impl PlainDate {
pub(crate) fn new(record: IsoDateRecord, calendar: JsValue) -> Self {
Self {
inner: record,
calendar,
}
}
}
impl BuiltInObject for PlainDate { impl BuiltInObject for PlainDate {
const NAME: JsString = StaticJsStrings::PLAIN_DATE; const NAME: JsString = StaticJsStrings::PLAIN_DATE;
} }
@ -378,6 +391,13 @@ impl PlainDate {
// -- `PlainDate` Abstract Operations -- // -- `PlainDate` Abstract Operations --
impl PlainDate {
/// Utitily function for translating a `Temporal.PlainDate` into a `JsObject`.
pub(crate) fn as_object(&self, context: &mut Context<'_>) -> JsResult<JsObject> {
create_temporal_date(self.inner, self.calendar.clone(), None, context)
}
}
// 3.5.2 `CreateIsoDateRecord` // 3.5.2 `CreateIsoDateRecord`
// Implemented on `IsoDateRecord` // Implemented on `IsoDateRecord`
@ -429,10 +449,7 @@ pub(crate) fn create_temporal_date(
// 8. Set object.[[Calendar]] to calendar. // 8. Set object.[[Calendar]] to calendar.
let obj = JsObject::from_proto_and_data( let obj = JsObject::from_proto_and_data(
prototype, prototype,
ObjectData::plain_date(PlainDate { ObjectData::plain_date(PlainDate::new(iso_date, calendar)),
inner: iso_date,
calendar,
}),
); );
// 9. Return object. // 9. Return object.
@ -554,14 +571,94 @@ pub(crate) fn to_temporal_date(
// 3.5.5. DifferenceIsoDate // 3.5.5. DifferenceIsoDate
// Implemented on IsoDateRecord. // Implemented on IsoDateRecord.
// 3.5.6 RegulateIsoDate /// 3.5.6 `DifferenceDate ( calendar, one, two, options )`
pub(crate) fn difference_date(
calendar: &JsValue,
one: &PlainDate,
two: &PlainDate,
largest_unit: TemporalUnit,
context: &mut Context<'_>,
) -> JsResult<DurationRecord> {
// 1. Assert: one.[[Calendar]] and two.[[Calendar]] have been determined to be equivalent as with CalendarEquals.
// 2. Assert: options is an ordinary Object.
// 3. Assert: options.[[Prototype]] is null.
// 4. Assert: options has a "largestUnit" data property.
// 5. If one.[[ISOYear]] = two.[[ISOYear]] and one.[[ISOMonth]] = two.[[ISOMonth]] and one.[[ISODay]] = two.[[ISODay]], then
if one.inner.year() == two.inner.year()
&& one.inner.month() == two.inner.month()
&& one.inner.day() == two.inner.day()
{
// a. Return ! CreateTemporalDuration(0, 0, 0, 0, 0, 0, 0, 0, 0, 0).
return Ok(DurationRecord::default());
}
// 6. If ! Get(options, "largestUnit") is "day", then
if largest_unit == TemporalUnit::Day {
// a. Let days be DaysUntil(one, two).
let days = super::duration::days_until(one, two);
// b. Return ! CreateTemporalDuration(0, 0, 0, days, 0, 0, 0, 0, 0, 0).
return Ok(DurationRecord::new(
DateDuration::new(0.0, 0.0, 0.0, f64::from(days)),
TimeDuration::default(),
));
}
// Create the options object prior to sending it to the calendars.
let options_obj = JsObject::with_null_proto();
options_obj.create_data_property_or_throw(
utf16!("largestUnit"),
JsString::from(largest_unit.to_string()),
context,
)?;
// 7. Return ? CalendarDateUntil(calendar, one, two, options).
calendar::calendar_date_until(calendar, one, two, &options_obj.into(), context)
}
// 3.5.7 RegulateIsoDate
// Implemented on IsoDateRecord. // Implemented on IsoDateRecord.
// 3.5.7 IsValidIsoDate // 3.5.8 IsValidIsoDate
// Implemented on IsoDateRecord. // Implemented on IsoDateRecord.
// 3.5.8 BalanceIsoDate // 3.5.9 BalanceIsoDate
// Implemented on IsoDateRecord. // Implemented on IsoDateRecord.
// 3.5.11 AddISODate ( year, month, day, years, months, weeks, days, overflow ) // 3.5.12 AddISODate ( year, month, day, years, months, weeks, days, overflow )
// Implemented on IsoDateRecord // Implemented on IsoDateRecord
/// 3.5.13 `AddDate ( calendar, plainDate, duration [ , options [ , dateAdd ]] )`
pub(crate) fn add_date(
calendar: &JsValue,
plain_date: &PlainDate,
duration: &DurationRecord,
options: &JsValue,
context: &mut Context<'_>,
) -> JsResult<PlainDate> {
// 1. If options is not present, set options to undefined.
// 2. If duration.[[Years]] ≠ 0, or duration.[[Months]] ≠ 0, or duration.[[Weeks]] ≠ 0, then
if duration.years() != 0.0 || duration.months() != 0.0 || duration.weeks() != 0.0 {
// a. If dateAdd is not present, then
// i. Set dateAdd to unused.
// ii. If calendar is an Object, set dateAdd to ? GetMethod(calendar, "dateAdd").
// b. Return ? CalendarDateAdd(calendar, plainDate, duration, options, dateAdd).
return calendar::calendar_date_add(calendar, plain_date, duration, options, context);
}
// 3. Let overflow be ? ToTemporalOverflow(options).
let options_obj = get_options_object(options)?;
let overflow = get_option(&options_obj, utf16!("overflow"), context)?
.unwrap_or(ArithmeticOverflow::Constrain);
let mut intermediate = *duration;
// 4. Let days be ? BalanceTimeDuration(duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], "day").[[Days]].
intermediate.balance_time_duration(TemporalUnit::Day, None)?;
// 5. Let result be ? AddISODate(plainDate.[[ISOYear]], plainDate.[[ISOMonth]], plainDate.[[ISODay]], 0, 0, 0, days, overflow).
let result = plain_date
.inner
.add_iso_date(intermediate.date(), overflow)?;
// 6. Return ! CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], calendar).
Ok(PlainDate::new(result, plain_date.calendar.clone()))
}

Loading…
Cancel
Save