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. 241
      boa_engine/src/builtins/temporal/duration/mod.rs
  4. 951
      boa_engine/src/builtins/temporal/duration/record.rs
  5. 23
      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::{
builtins::temporal::{
self, create_temporal_date,
self,
date_equations::mathematical_days_in_year,
duration::DurationRecord,
options::{ArithmeticOverflow, TemporalUnit},
plain_date::iso::IsoDateRecord,
},
js_string,
property::PropertyKey,
string::utf16,
Context, JsNativeError, JsResult, JsString, JsValue,
js_string, JsNativeError, JsResult, JsString,
};
use super::BuiltinCalendar;
use super::{BuiltinCalendar, FieldsType};
use icu_calendar::{
iso::Iso,
@ -31,8 +29,7 @@ impl BuiltinCalendar for IsoCalendar {
&self,
fields: &mut temporal::TemporalFields,
overflow: ArithmeticOverflow,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
) -> JsResult<IsoDateRecord> {
// NOTE: we are in ISO by default here.
// a. Perform ? ISOResolveMonth(fields).
// 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()))?;
// 9. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], "iso8601").
Ok(create_temporal_date(
IsoDateRecord::from_date_iso(date),
js_string!("iso8601").into(),
None,
context,
)?
.into())
Ok(IsoDateRecord::from_date_iso(date))
}
/// 12.5.5 `Temporal.Calendar.prototype.yearMonthFromFields ( fields [ , options ] )`
@ -65,8 +56,7 @@ impl BuiltinCalendar for IsoCalendar {
&self,
fields: &mut temporal::TemporalFields,
overflow: ArithmeticOverflow,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
) -> JsResult<IsoDateRecord> {
// 9. If calendar.[[Identifier]] is "iso8601", then
// a. Perform ? ISOResolveMonth(fields).
fields.iso_resolve_month()?;
@ -82,12 +72,7 @@ impl BuiltinCalendar for IsoCalendar {
.map_err(|err| JsNativeError::range().with_message(err.to_string()))?;
// 10. Return ? CreateTemporalYearMonth(result.[[Year]], result.[[Month]], "iso8601", result.[[ReferenceISODay]]).
temporal::create_temporal_year_month(
IsoDateRecord::from_date_iso(result),
js_string!("iso8601").into(),
None,
context,
)
Ok(IsoDateRecord::from_date_iso(result))
}
/// 12.5.6 `Temporal.Calendar.prototype.monthDayFromFields ( fields [ , options ] )`
@ -97,8 +82,7 @@ impl BuiltinCalendar for IsoCalendar {
&self,
fields: &mut temporal::TemporalFields,
overflow: ArithmeticOverflow,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
) -> JsResult<IsoDateRecord> {
// 8. Perform ? ISOResolveMonth(fields).
fields.iso_resolve_month()?;
@ -114,12 +98,7 @@ impl BuiltinCalendar for IsoCalendar {
.map_err(|err| JsNativeError::range().with_message(err.to_string()))?;
// 10. Return ? CreateTemporalMonthDay(result.[[Month]], result.[[Day]], "iso8601", result.[[ReferenceISOYear]]).
temporal::create_temporal_month_day(
IsoDateRecord::from_date_iso(result),
js_string!("iso8601").into(),
None,
context,
)
Ok(IsoDateRecord::from_date_iso(result))
}
/// 12.5.7 `Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] )`
@ -128,10 +107,9 @@ impl BuiltinCalendar for IsoCalendar {
fn date_add(
&self,
_date: &temporal::PlainDate,
_duration: &temporal::duration::DurationRecord,
_duration: &DurationRecord,
_overflow: ArithmeticOverflow,
_context: &mut Context<'_>,
) -> JsResult<JsValue> {
) -> JsResult<IsoDateRecord> {
// TODO: Not stable on `ICU4X`. Implement once completed.
Err(JsNativeError::range()
.with_message("feature not implemented.")
@ -149,8 +127,7 @@ impl BuiltinCalendar for IsoCalendar {
_one: &temporal::PlainDate,
_two: &temporal::PlainDate,
_largest_unit: TemporalUnit,
_: &mut Context<'_>,
) -> JsResult<JsValue> {
) -> JsResult<DurationRecord> {
// TODO: Not stable on `ICU4X`. Implement once completed.
Err(JsNativeError::range()
.with_message("Feature not yet implemented.")
@ -161,19 +138,19 @@ impl BuiltinCalendar for IsoCalendar {
}
/// `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.
Ok(JsValue::undefined())
Ok(None)
}
/// `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.
Ok(JsValue::undefined())
Ok(None)
}
/// 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(
date_like.year(),
date_like.month() as u8,
@ -181,11 +158,11 @@ impl BuiltinCalendar for IsoCalendar {
)
.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.
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(
date_like.year(),
date_like.month() as u8,
@ -193,11 +170,11 @@ impl BuiltinCalendar for IsoCalendar {
)
.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.
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(
date_like.year(),
date_like.month() as u8,
@ -205,11 +182,11 @@ impl BuiltinCalendar for IsoCalendar {
)
.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.
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(
date_like.year(),
date_like.month() as u8,
@ -217,11 +194,11 @@ impl BuiltinCalendar for IsoCalendar {
)
.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.
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(
date_like.year(),
date_like.month() as u8,
@ -229,11 +206,11 @@ impl BuiltinCalendar for IsoCalendar {
)
.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.
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(
date_like.year(),
date_like.month() as u8,
@ -241,11 +218,11 @@ impl BuiltinCalendar for IsoCalendar {
)
.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.
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.
let date = Date::try_new_iso_date(
date_like.year(),
@ -260,11 +237,11 @@ impl BuiltinCalendar for IsoCalendar {
.week_of_year(&week_calculator)
.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.
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.
let date = Date::try_new_iso_date(
date_like.year(),
@ -280,19 +257,19 @@ impl BuiltinCalendar for IsoCalendar {
.map_err(|err| JsNativeError::range().with_message(err.to_string()))?;
match week_of.unit {
RelativeUnit::Previous => Ok((date.year().number - 1).into()),
RelativeUnit::Current => Ok(date.year().number.into()),
RelativeUnit::Next => Ok((date.year().number + 1).into()),
RelativeUnit::Previous => Ok(date.year().number - 1),
RelativeUnit::Current => Ok(date.year().number),
RelativeUnit::Next => Ok(date.year().number + 1),
}
}
/// Returns the `daysInWeek` value for the `Iso` calendar.
fn days_in_week(&self, _: &IsoDateRecord, _: &mut Context<'_>) -> JsResult<JsValue> {
Ok(7.into())
fn days_in_week(&self, _: &IsoDateRecord) -> JsResult<i32> {
Ok(7)
}
/// 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(
date_like.year(),
date_like.month() as u8,
@ -300,11 +277,11 @@ impl BuiltinCalendar for IsoCalendar {
)
.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.
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(
date_like.year(),
date_like.month() as u8,
@ -312,31 +289,31 @@ impl BuiltinCalendar for IsoCalendar {
)
.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.
fn months_in_year(&self, _: &IsoDateRecord, _: &mut Context<'_>) -> JsResult<JsValue> {
Ok(12.into())
fn months_in_year(&self, _: &IsoDateRecord) -> JsResult<i32> {
Ok(12)
}
/// 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.
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.
fn resolve_fields(&self, fields: &mut temporal::TemporalFields, _: &str) -> JsResult<()> {
fn resolve_fields(&self, fields: &mut temporal::TemporalFields, _: FieldsType) -> JsResult<()> {
fields.iso_resolve_month()?;
Ok(())
}
/// 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
// ISO like any other calendar?
// Field descriptors is unused on ISO8601.
@ -344,15 +321,14 @@ impl BuiltinCalendar for IsoCalendar {
}
/// 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();
for key in additional_keys {
let key_string = key.to_string();
result.push(key);
if key_string.as_str() == "month" {
result.push(utf16!("monthCode").into());
} else if key_string.as_str() == "monthCode" {
result.push(utf16!("month").into());
for key in &additional_keys {
result.push(key.clone());
if key.to_std_string_escaped().as_str() == "month" {
result.push(js_string!("monthCode"));
} else if key.to_std_string_escaped().as_str() == "monthCode" {
result.push(js_string!("month"));
}
}
result

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

@ -3,9 +3,11 @@
use self::iso::IsoCalendar;
use super::{
create_temporal_date, create_temporal_duration, create_temporal_month_day,
create_temporal_year_month,
options::{ArithmeticOverflow, TemporalUnit, TemporalUnitGroup},
plain_date::iso::IsoDateRecord,
PlainDate, TemporalFields,
DurationRecord, PlainDate, TemporalFields,
};
use crate::{
builtins::{
@ -16,7 +18,7 @@ use crate::{
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
js_string,
object::{internal_methods::get_prototype_from_constructor, ObjectData},
property::{Attribute, PropertyKey},
property::Attribute,
realm::Realm,
string::{common::StaticJsStrings, utf16},
Context, JsArgs, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue,
@ -30,6 +32,27 @@ pub(crate) mod utils;
#[cfg(test)]
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
// `icu_calendar`.
//
@ -42,111 +65,70 @@ pub(crate) trait BuiltinCalendar {
&self,
fields: &mut TemporalFields,
overflow: ArithmeticOverflow,
context: &mut Context<'_>,
) -> JsResult<JsValue>;
) -> JsResult<IsoDateRecord>;
/// Creates a `Temporal.PlainYearMonth` object from the provided fields.
fn year_month_from_fields(
&self,
fields: &mut TemporalFields,
overflow: ArithmeticOverflow,
context: &mut Context<'_>,
) -> JsResult<JsValue>;
) -> JsResult<IsoDateRecord>;
/// Creates a `Temporal.PlainMonthDay` object from the provided fields.
fn month_day_from_fields(
&self,
fields: &mut TemporalFields,
overflow: ArithmeticOverflow,
context: &mut Context<'_>,
) -> JsResult<JsValue>;
) -> JsResult<IsoDateRecord>;
/// Returns a `Temporal.PlainDate` based off an added date.
fn date_add(
&self,
date: &PlainDate,
duration: &temporal::DurationRecord,
duration: &DurationRecord,
overflow: ArithmeticOverflow,
context: &mut Context<'_>,
) -> JsResult<JsValue>;
) -> JsResult<IsoDateRecord>;
/// Returns a `Temporal.Duration` representing the duration between two dates.
fn date_until(
&self,
one: &PlainDate,
two: &PlainDate,
largest_unit: TemporalUnit,
context: &mut Context<'_>,
) -> JsResult<JsValue>;
) -> JsResult<DurationRecord>;
/// 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`
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`
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`
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`
fn month_code(&self, date_like: &IsoDateRecord, context: &mut Context<'_>)
-> JsResult<JsValue>;
fn month_code(&self, date_like: &IsoDateRecord) -> JsResult<JsString>;
/// 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.
fn day_of_week(
&self,
date_like: &IsoDateRecord,
context: &mut Context<'_>,
) -> JsResult<JsValue>;
fn day_of_week(&self, date_like: &IsoDateRecord) -> JsResult<i32>;
/// Returns a value representing the day of the year for a given calendar.
fn day_of_year(
&self,
date_like: &IsoDateRecord,
context: &mut Context<'_>,
) -> JsResult<JsValue>;
fn day_of_year(&self, date_like: &IsoDateRecord) -> JsResult<i32>;
/// Returns a value representing the week of the year for a given calendar.
fn week_of_year(
&self,
date_like: &IsoDateRecord,
context: &mut Context<'_>,
) -> JsResult<JsValue>;
fn week_of_year(&self, date_like: &IsoDateRecord) -> JsResult<i32>;
/// Returns the year of a given week.
fn year_of_week(
&self,
date_like: &IsoDateRecord,
context: &mut Context<'_>,
) -> JsResult<JsValue>;
fn year_of_week(&self, date_like: &IsoDateRecord) -> JsResult<i32>;
/// Returns the days in a week for a given calendar.
fn days_in_week(
&self,
date_like: &IsoDateRecord,
context: &mut Context<'_>,
) -> JsResult<JsValue>;
fn days_in_week(&self, date_like: &IsoDateRecord) -> JsResult<i32>;
/// Returns the days in a month for a given calendar.
fn days_in_month(
&self,
date_like: &IsoDateRecord,
context: &mut Context<'_>,
) -> JsResult<JsValue>;
fn days_in_month(&self, date_like: &IsoDateRecord) -> JsResult<i32>;
/// Returns the days in a year for a given calendar.
fn days_in_year(
&self,
date_like: &IsoDateRecord,
context: &mut Context<'_>,
) -> JsResult<JsValue>;
fn days_in_year(&self, date_like: &IsoDateRecord) -> JsResult<i32>;
/// Returns the months in a year for a given calendar.
fn months_in_year(
&self,
date_like: &IsoDateRecord,
context: &mut Context<'_>,
) -> JsResult<JsValue>;
fn months_in_year(&self, date_like: &IsoDateRecord) -> JsResult<i32>;
/// Returns whether a value is within a leap year according to the designated calendar.
fn in_leap_year(
&self,
date_like: &IsoDateRecord,
context: &mut Context<'_>,
) -> JsResult<JsValue>;
fn in_leap_year(&self, date_like: &IsoDateRecord) -> JsResult<bool>;
/// 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).
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.
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
fn debug_name(&self) -> &str;
}
@ -334,16 +316,16 @@ impl Calendar {
// 5. Let relevantFieldNames be « "day", "month", "monthCode", "year" ».
let mut relevant_field_names = Vec::from([
"day".to_owned(),
"month".to_owned(),
"monthCode".to_owned(),
"year".to_owned(),
js_string!("day"),
js_string!("month"),
js_string!("monthCode"),
js_string!("year"),
]);
// 6. If calendar.[[Identifier]] is "iso8601", then
let mut fields = if calendar.identifier.as_slice() == ISO {
// 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(
fields_obj,
&mut relevant_field_names,
@ -356,7 +338,7 @@ impl Calendar {
// 7. Else,
} else {
// 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).
temporal::TemporalFields::from_js_object(
fields_obj,
@ -381,7 +363,10 @@ impl Calendar {
// a. Perform ? CalendarResolveFields(calendar.[[Identifier]], fields, date).
// 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
@ -412,15 +397,15 @@ impl Calendar {
let options = get_options_object(args.get_or_undefined(1))?;
let mut relevant_field_names = Vec::from([
"year".to_owned(),
"month".to_owned(),
"monthCode".to_owned(),
js_string!("year"),
js_string!("month"),
js_string!("monthCode"),
]);
// 6. Set fields to ? PrepareTemporalFields(fields, « "month", "monthCode", "year" », « "year" »).
let mut fields = if calendar.identifier.as_slice() == ISO {
// 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(
fields_obj,
&mut relevant_field_names,
@ -434,8 +419,7 @@ impl Calendar {
// a. Let calendarRelevantFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], year-month).
// b. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « », calendarRelevantFieldDescriptors).
let calendar_relevant_fields =
this_calendar.field_descriptors(&["year-month".to_owned()]);
let calendar_relevant_fields = this_calendar.field_descriptors(FieldsType::YearMonth);
temporal::TemporalFields::from_js_object(
fields_obj,
&mut relevant_field_names,
@ -454,7 +438,10 @@ impl Calendar {
let overflow = get_option::<ArithmeticOverflow>(&options, utf16!("overflow"), context)?
.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
@ -490,16 +477,16 @@ impl Calendar {
// 5. Let relevantFieldNames be « "day", "month", "monthCode", "year" ».
let mut relevant_field_names = Vec::from([
"day".to_owned(),
"month".to_owned(),
"monthCode".to_owned(),
"year".to_owned(),
js_string!("day"),
js_string!("month"),
js_string!("monthCode"),
js_string!("year"),
]);
// 6. If calendar.[[Identifier]] is "iso8601", then
let mut fields = if calendar.identifier.as_slice() == ISO {
// 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(
fields_obj,
&mut relevant_field_names,
@ -512,8 +499,7 @@ impl Calendar {
// 7. Else,
} else {
// a. Let calendarRelevantFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], month-day).
let calendar_relevant_fields =
this_calendar.field_descriptors(&["month-day".to_owned()]);
let calendar_relevant_fields = this_calendar.field_descriptors(FieldsType::MonthDay);
// b. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « », calendarRelevantFieldDescriptors).
temporal::TemporalFields::from_js_object(
fields_obj,
@ -530,7 +516,10 @@ impl Calendar {
let overflow = get_option(&options, utf16!("overflow"), context)?
.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
@ -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").
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
@ -616,7 +608,9 @@ impl Calendar {
)?
.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 )`
@ -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 )`
@ -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 )`
@ -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 )`
@ -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 )`
@ -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 )`
@ -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 )`
@ -939,7 +937,9 @@ impl Calendar {
// 3. Let temporalDate be ? ToTemporalDate(temporalDateLike).
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 )`
@ -966,7 +966,9 @@ impl Calendar {
// 3. Let temporalDate be ? ToTemporalDate(temporalDateLike).
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 )`
@ -992,7 +994,9 @@ impl Calendar {
// 3. Let temporalDate be ? ToTemporalDate(temporalDateLike).
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 )`
@ -1018,7 +1022,9 @@ impl Calendar {
// 3. Let temporalDate be ? ToTemporalDate(temporalDateLike).
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 )`
@ -1044,7 +1050,9 @@ impl Calendar {
// 3. Let temporalDate be ? ToTemporalDate(temporalDateLike).
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 )`
@ -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 )`
@ -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 )`
@ -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 )`
@ -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 )`
@ -1290,12 +1306,9 @@ impl Calendar {
// 1. Let completion be ThrowCompletion(a newly created RangeError object).
// 2. Return ? IteratorClose(iteratorRecord, completion).
// v. Append nextValue to the end of the List fieldNames.
let this_name = value.to_std_string_escaped();
match this_name.as_str() {
"year" | "month" | "monthCode" | "day"
if !fields_names.contains(&this_name) =>
{
fields_names.push(this_name);
match value.to_std_string_escaped().as_str() {
"year" | "month" | "monthCode" | "day" if !fields_names.contains(&value) => {
fields_names.push(value);
}
_ => {
let completion = Err(JsNativeError::range()
@ -1319,7 +1332,8 @@ impl Calendar {
if calendar.identifier.as_slice() != ISO {
// a. NOTE: Every built-in calendar preserves all input field names in output.
// 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
for descriptor in extended_fields {
// i. Append desc.[[Property]] to result.
@ -1328,13 +1342,10 @@ impl Calendar {
}
// 9. Return CreateArrayFromList(result).
Ok(Array::create_array_from_list(
fields_names
.iter()
.map(|s| JsString::from(s.clone()).into()),
context,
Ok(
Array::create_array_from_list(fields_names.iter().map(|s| s.clone().into()), context)
.into(),
)
.into())
}
/// 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.
// 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
// a. Let overriddenKeys be ISOFieldKeysToIgnore(additionalKeys).
@ -1395,23 +1410,28 @@ impl Calendar {
// matches that of fields as modified by omitting overridden properties and
// appending non-overlapping properties from additionalFields in iteration order.
// 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
for key in field_keys {
// a. Let propValue be undefined.
// b. If overriddenKeys contains key, then
let prop_value = if overridden_keys.contains(&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,
} else {
// 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).
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())
}
// ---------------------------- AbstractCalendar Methods ----------------------------
// ---------------------------- Native Abstract Calendar Methods ----------------------------
//
// 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
@ -1625,7 +1645,8 @@ fn call_method_on_abstract_calendar(
/// 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)]
pub(crate) fn calendar_fields(
calendar: &JsValue,
@ -1688,14 +1709,13 @@ pub(crate) fn calendar_merge_fields(
#[allow(unused)]
pub(crate) fn calendar_date_add(
calendar: &JsValue,
date: &JsObject,
duration: &JsObject,
options: Option<JsValue>,
date: &PlainDate,
duration: &DurationRecord,
options: &JsValue,
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.
let options = options.unwrap_or(JsValue::undefined());
// 2. If calendar is a String, then
// a. Set calendar to ! CreateTemporalCalendar(calendar).
// 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(
calendar,
&JsString::from("dateAdd"),
&[date.clone().into(), duration.clone().into(), options],
&[
date.as_object(context)?.into(),
duration.as_object(context)?.into(),
options.clone(),
],
context,
)?;
// 5. Perform ? RequireInternalSlot(addedDate, [[InitializedTemporalDate]]).
// 6. Return addedDate.
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()
.with_message("dateAdd returned a value other than a Temoporal.PlainDate")
.into()),
@ -1724,11 +1752,11 @@ pub(crate) fn calendar_date_add(
#[allow(unused)]
pub(crate) fn calendar_date_until(
calendar: &JsValue,
one: &JsObject,
two: &JsObject,
one: &PlainDate,
two: &PlainDate,
options: &JsValue,
context: &mut Context<'_>,
) -> JsResult<super::duration::DurationRecord> {
) -> JsResult<DurationRecord> {
// 1. If calendar is a String, then
// a. Set calendar to ! CreateTemporalCalendar(calendar).
// 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(
calendar,
&JsString::from("dateUntil"),
&[one.clone().into(), two.clone().into(), options.clone()],
&[
one.as_object(context)?.into(),
two.as_object(context)?.into(),
options.clone(),
],
context,
)?;
@ -1920,6 +1952,23 @@ pub(crate) fn calendar_day_of_week(
) -> JsResult<f64> {
// 1. If calendar is a String, then
// 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 »).
// 2. Let result be ? Invoke(calendar, "dayOfWeek", « dateLike »).
let result = call_method_on_abstract_calendar(
@ -1963,11 +2012,11 @@ pub(crate) fn calendar_day_of_year(
) -> JsResult<f64> {
// 1. If calendar is a String, then
// a. Set calendar to ! CreateTemporalCalendar(calendar).
// b. Return ? Call(%Temporal.Calendar.prototype.dayOfYear%, calendar, « dateLike »).
// 2. Let result be ? Invoke(calendar, "dayOfYear", « dateLike »).
// b. Return ? Call(%Temporal.Calendar.prototype.dayOfWeek%, calendar, « dateLike »).
// 2. Let result be ? Invoke(calendar, "dayOfWeek", « dateLike »).
let result = call_method_on_abstract_calendar(
calendar,
&JsString::from("dayOfYear"),
&JsString::from("dayOfWeek"),
&[datelike.clone()],
context,
)?;
@ -1975,21 +2024,21 @@ pub(crate) fn calendar_day_of_year(
// 3. If Type(result) is not Number, throw a TypeError exception.
let Some(number) = result.as_number() else {
return Err(JsNativeError::typ()
.with_message("CalendarDayOfYear result must be a number.")
.with_message("CalendarDayOfWeek result must be a number.")
.into());
};
// 4. If IsIntegralNumber(result) is false, throw a RangeError exception.
if number.is_nan() || number.is_infinite() || number.fract() != 0.0 {
return Err(JsNativeError::range()
.with_message("CalendarDayOfYear was not integral.")
.with_message("CalendarDayOfWeek was not integral.")
.into());
}
// 5. If result < 1𝔽, throw a RangeError exception.
if number < 1.0 {
return Err(JsNativeError::range()
.with_message("dayOfYear must be 1 or greater.")
.with_message("dayOfWeek must be 1 or greater.")
.into());
}
@ -2006,11 +2055,11 @@ pub(crate) fn calendar_week_of_year(
) -> JsResult<f64> {
// 1. If calendar is a String, then
// a. Set calendar to ! CreateTemporalCalendar(calendar).
// b. Return ? Call(%Temporal.Calendar.prototype.weekOfYear%, calendar, « dateLike »).
// 2. Let result be ? Invoke(calendar, "weekOfYear", « dateLike »).
// b. Return ? Call(%Temporal.Calendar.prototype.dayOfYear%, calendar, « dateLike »).
// 2. Let result be ? Invoke(calendar, "dayOfYear", « dateLike »).
let result = call_method_on_abstract_calendar(
calendar,
&JsString::from("weekOfYear"),
&JsString::from("dayOfYear"),
&[datelike.clone()],
context,
)?;
@ -2018,21 +2067,21 @@ pub(crate) fn calendar_week_of_year(
// 3. If Type(result) is not Number, throw a TypeError exception.
let Some(number) = result.as_number() else {
return Err(JsNativeError::typ()
.with_message("CalendarWeekOfYear result must be a number.")
.with_message("CalendarDayOfYear result must be a number.")
.into());
};
// 4. If IsIntegralNumber(result) is false, throw a RangeError exception.
if number.is_nan() || number.is_infinite() || number.fract() != 0.0 {
return Err(JsNativeError::range()
.with_message("CalendarWeekOfYear was not integral.")
.with_message("CalendarDayOfYear was not integral.")
.into());
}
// 5. If result < 1𝔽, throw a RangeError exception.
if number < 1.0 {
return Err(JsNativeError::range()
.with_message("weekOfYear must be 1 or greater.")
.with_message("dayOfYear must be 1 or greater.")
.into());
}
@ -2279,7 +2328,7 @@ pub(crate) fn calendar_in_lear_year(
/// 12.2.24 `CalendarDateFromFields ( calendar, fields [ , options [ , dateFromFields ] ] )`
#[allow(unused)]
pub(crate) fn calendar_date_from_fields(
_calendar: &JsValue,
calendar: &JsValue,
_fields: &JsObject,
options: Option<&JsValue>,
_date_from_fields: Option<&JsObject>,

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

@ -17,10 +17,10 @@ use crate::{
use boa_profiler::Profiler;
use super::{
calendar,
options::{
get_temporal_rounding_increment, get_temporal_unit, TemporalUnit, TemporalUnitGroup,
},
plain_date::{self, PlainDate},
to_integer_if_integral, DateTimeValues,
};
@ -565,6 +565,7 @@ impl Duration {
.into())
}
// TODO: Update needed.
/// 7.3.20 `Temporal.Duration.prototype.round ( roundTo )`
pub(crate) fn round(
this: &JsValue,
@ -613,9 +614,7 @@ impl Duration {
// 6. Let smallestUnitPresent be true.
// 7. Let largestUnitPresent be true.
// 8. NOTE: The following steps read options and perform independent validation in alphabetical order
// (ToRelativeTemporalObject reads "relativeTo", ToTemporalRoundingIncrement reads "roundingIncrement" and ToTemporalRoundingMode reads "roundingMode").
// 8. NOTE: The following steps read options and perform independent validation in alphabetical order (ToRelativeTemporalObject reads "relativeTo", ToTemporalRoundingIncrement reads "roundingIncrement" and ToTemporalRoundingMode reads "roundingMode").
// 9. Let largestUnit be ? GetTemporalUnit(roundTo, "largestUnit", datetime, undefined, « "auto" »).
let largest_unit = get_temporal_unit(
&round_to,
@ -625,17 +624,20 @@ impl Duration {
context,
)?;
// 10. Let relativeTo be ? ToRelativeTemporalObject(roundTo).
let relative_to = super::to_relative_temporal_object(&round_to, context)?;
// 10. Let relativeToRecord be ? ToRelativeTemporalObject(roundTo).
// 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)?;
// 12. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand").
let rounding_mode = get_option(&round_to, utf16!("roundingMode"), context)?
// 14. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand").
let _rounding_mode = get_option(&round_to, utf16!("roundingMode"), context)?
.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(
&round_to,
utf16!("smallestUnit"),
@ -644,8 +646,8 @@ impl Duration {
context,
)?;
// NOTE: execute step 19 earlier before initial values are shadowed.
// 19. If smallestUnitPresent is false and largestUnitPresent is false, then
// NOTE: execute step 21 earlier before initial values are shadowed.
// 21. If smallestUnitPresent is false and largestUnitPresent is false, then
if smallest_unit.is_none() && largest_unit.is_none() {
// a. Throw a RangeError exception.
return Err(JsNativeError::range()
@ -653,7 +655,7 @@ impl Duration {
.into());
}
// 14. If smallestUnit is undefined, then
// 16. If smallestUnit is undefined, then
let smallest_unit = if let Some(unit) = smallest_unit {
unit
} else {
@ -662,14 +664,16 @@ impl Duration {
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]]).
let mut default_largest_unit = duration.inner.default_temporal_largest_unit();
// 17. Let existingLargestUnit be ! DefaultTemporalLargestUnit(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]]).
let existing_largest_unit = duration.inner.default_temporal_largest_unit();
// 16. Set defaultLargestUnit to ! LargerOfTwoTemporalUnits(defaultLargestUnit, smallestUnit).
default_largest_unit = core::cmp::max(default_largest_unit, smallest_unit);
// 18. Set defaultLargestUnit to ! LargerOfTwoTemporalUnits(defaultLargestUnit, smallestUnit).
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 {
// 20. Else if largestUnit is "auto", then
// a. Set largestUnit to defaultLargestUnit.
Some(TemporalUnit::Auto) => default_largest_unit,
Some(u) => u,
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 {
return Err(JsNativeError::range()
.with_message("largestUnit must be larger than smallestUnit")
.into());
}
// 21. Let maximum be ! MaximumTemporalDurationRoundingIncrement(smallestUnit).
// 23. Let maximum be ! MaximumTemporalDurationRoundingIncrement(smallestUnit).
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 {
validate_temporal_rounding_increment(rounding_increment, f64::from(max), false)?;
}
let mut unbalance_duration = DurationRecord::from_date_duration(duration.inner.date());
// 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]]).
// TODO: Complete the rest of the new `Temporal.Duration.prototype.round` impl.
// NOTE: Below is currently incorrect: Handling of zonedRelativeTo and precalculatedPlainDateTime is needed.
Err(JsNativeError::range()
.with_message("not yet implemented.")
.into())
@ -749,7 +717,7 @@ impl Duration {
let o = this.as_object().map(JsObject::borrow).ok_or_else(|| {
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.")
})?;
@ -783,12 +751,14 @@ impl Duration {
};
// 6. NOTE: The following steps read options and perform independent validation in alphabetical order (ToRelativeTemporalObject reads "relativeTo").
// 7. Let relativeTo be ? ToRelativeTemporalObject(totalOf).
// NOTE TO SELF: Should relative_to_temporal_object just return a JsValue and we live with the expect?
let relative_to = super::to_relative_temporal_object(&total_of, context)?;
// 8. Let unit be ? GetTemporalUnit(totalOf, "unit", datetime, required).
let unit = get_temporal_unit(
// 7. Let relativeToRecord be ? ToRelativeTemporalObject(totalOf).
// 8. Let zonedRelativeTo be relativeToRecord.[[ZonedRelativeTo]].
// 9. Let plainRelativeTo be relativeToRecord.[[PlainRelativeTo]].
let (_plain_relative_to, _zoned_relative_to) =
super::to_relative_temporal_object(&total_of, context)?;
// 10. Let unit be ? GetTemporalUnit(totalOf, "unit", datetime, required).
let _unit = get_temporal_unit(
&total_of,
utf16!("unit"),
TemporalUnitGroup::DateTime,
@ -797,106 +767,11 @@ impl Duration {
)?
.ok_or_else(|| JsNativeError::range().with_message("unit cannot be undefined."))?;
let mut unbalance_duration = DurationRecord::from_date_duration(duration.inner.date());
// 9. Let unbalanceResult be ? UnbalanceDurationRelative(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], unit, relativeTo).
unbalance_duration.unbalance_duration_relative(unit, &relative_to, context)?;
// TODO: Implement the rest of the new `Temporal.Duration.prototype.total`
// 10. Let intermediate be undefined.
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.")
.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())
Err(JsNativeError::range()
.with_message("not yet implemented.")
.into())
}
/// 7.3.22 `Temporal.Duration.prototype.toString ( [ options ] )`
@ -1003,24 +878,12 @@ pub(crate) fn create_temporal_duration(
}
/// 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]]).
let obj = earlier.borrow();
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);
let epoch_days_one = earlier.inner.as_epoch_days();
// 2. Let epochDays2 be ISODateToEpochDays(later.[[ISOYear]], later.[[ISOMonth]] - 1, later.[[ISODay]]).
let obj = later.borrow();
let date_two = obj
.as_plain_date()
.expect("earlier must be a PlainDate obj.");
let epoch_days_two = date_two.inner.as_epoch_days();
let epoch_days_two = later.inner.as_epoch_days();
// 3. Return epochDays2 - epochDays1.
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 )`
fn move_relative_date(
calendar: &JsValue,
relative_to: &JsObject,
duration: &JsObject,
relative_to: &PlainDate,
duration: &DurationRecord,
context: &mut Context<'_>,
) -> JsResult<(JsObject, f64)> {
let new_date = calendar::calendar_date_add(calendar, relative_to, duration, None, context)?;
let days = f64::from(days_until(relative_to, &new_date));
Ok((new_date, days))
) -> JsResult<(PlainDate, f64)> {
let new_date = plain_date::add_date(
calendar,
relative_to,
duration,
&JsValue::undefined(),
context,
)?;
let days = days_until(relative_to, &new_date);
Ok((new_date, f64::from(days)))
}

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

File diff suppressed because it is too large Load Diff

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

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

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

@ -606,11 +606,13 @@ fn diff_instant(
nanoseconds.to_f64(),
),
);
let _rem = roundable_duration.round_duration(
let _total = roundable_duration.round_duration(
rounding_increment,
smallest_unit,
rounding_mode,
None,
None,
None,
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(
_options: &JsObject,
_context: &mut Context<'_>,
) -> JsResult<JsValue> {
) -> JsResult<(Option<PlainDate>, Option<ZonedDateTime>)> {
Err(JsNativeError::range()
.with_message("not yet implemented.")
.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.
use crate::{
builtins::temporal::{self, TemporalFields},
builtins::temporal::{self, options::ArithmeticOverflow, DateDuration, TemporalFields},
JsNativeError, JsResult, JsString,
};
@ -59,16 +59,16 @@ impl IsoDateRecord {
year: i32,
month: i32,
day: i32,
overflow: &JsString,
overflow: ArithmeticOverflow,
) -> JsResult<Self> {
match overflow.to_std_string_escaped().as_str() {
"constrain" => {
match overflow {
ArithmeticOverflow::Constrain => {
let m = month.clamp(1, 12);
let days_in_month = temporal::calendar::utils::iso_days_in_month(year, month);
let d = day.clamp(1, days_in_month);
Ok(Self::new(year, m, d))
}
"reject" => {
ArithmeticOverflow::Reject => {
let date = Self::new(year, month, day);
if !date.is_valid() {
return Err(JsNativeError::range()
@ -77,7 +77,6 @@ impl IsoDateRecord {
}
Ok(date)
}
_ => unreachable!(),
}
}
@ -86,7 +85,7 @@ impl IsoDateRecord {
/// Note: fields.month must be resolved prior to using `from_temporal_fields`
pub(crate) fn from_temporal_fields(
fields: &TemporalFields,
overflow: &JsString,
overflow: ArithmeticOverflow,
) -> JsResult<Self> {
Self::from_unregulated(
fields.year().expect("Cannot fail per spec"),
@ -99,7 +98,7 @@ impl IsoDateRecord {
/// Create a Month-Day record from a `TemporalFields` object.
pub(crate) fn month_day_from_temporal_fields(
fields: &TemporalFields,
overflow: &JsString,
overflow: ArithmeticOverflow,
) -> JsResult<Self> {
match fields.year() {
Some(year) => Self::from_unregulated(
@ -202,15 +201,16 @@ impl IsoDateRecord {
/// 3.5.11 `AddISODate ( year, month, day, years, months, weeks, days, overflow )`
pub(crate) fn add_iso_date(
&self,
years: i32,
months: i32,
weeks: i32,
days: i32,
overflow: &JsString,
date_duration: DateDuration,
overflow: ArithmeticOverflow,
) -> JsResult<Self> {
// 1. Assert: year, month, day, years, months, weeks, and days are integers.
// 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).
intermediate.balance_year_month();
@ -225,7 +225,7 @@ impl IsoDateRecord {
// 5. Set days to days + 7 × weeks.
// 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;
// 7. Return BalanceISODate(intermediate.[[Year]], intermediate.[[Month]], d).

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

@ -4,6 +4,7 @@
use crate::{
builtins::{
options::{get_option, get_options_object},
temporal::options::TemporalUnit,
BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject,
},
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
@ -17,7 +18,10 @@ use crate::{
use boa_parser::temporal::{IsoCursor, TemporalDateTimeString};
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;
@ -28,6 +32,15 @@ pub struct PlainDate {
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 {
const NAME: JsString = StaticJsStrings::PLAIN_DATE;
}
@ -378,6 +391,13 @@ impl PlainDate {
// -- `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`
// Implemented on `IsoDateRecord`
@ -429,10 +449,7 @@ pub(crate) fn create_temporal_date(
// 8. Set object.[[Calendar]] to calendar.
let obj = JsObject::from_proto_and_data(
prototype,
ObjectData::plain_date(PlainDate {
inner: iso_date,
calendar,
}),
ObjectData::plain_date(PlainDate::new(iso_date, calendar)),
);
// 9. Return object.
@ -554,14 +571,94 @@ pub(crate) fn to_temporal_date(
// 3.5.5. DifferenceIsoDate
// 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.
// 3.5.7 IsValidIsoDate
// 3.5.8 IsValidIsoDate
// Implemented on IsoDateRecord.
// 3.5.8 BalanceIsoDate
// 3.5.9 BalanceIsoDate
// 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
/// 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