mirror of https://github.com/boa-dev/boa.git
Browse Source
* Remove `Temporal.Calendar` and `Temporal.TimeZone` * bump test262 * add new test262 features * fix bugpull/3895/head
José Julián Espina
5 months ago
committed by
GitHub
22 changed files with 169 additions and 2785 deletions
File diff suppressed because it is too large
Load Diff
@ -1,860 +0,0 @@ |
|||||||
//! Boa's implementation of a user-defined Anonymous Calendar.
|
|
||||||
|
|
||||||
use crate::{ |
|
||||||
builtins::{ |
|
||||||
iterable::IteratorHint, |
|
||||||
temporal::{ |
|
||||||
fields::object_to_temporal_fields, plain_date, plain_date_time, plain_month_day, |
|
||||||
plain_year_month, |
|
||||||
}, |
|
||||||
Array, |
|
||||||
}, |
|
||||||
property::PropertyKey, |
|
||||||
Context, JsObject, JsString, JsValue, |
|
||||||
}; |
|
||||||
|
|
||||||
use boa_macros::js_str; |
|
||||||
use num_traits::ToPrimitive; |
|
||||||
use plain_date::PlainDate; |
|
||||||
use plain_date_time::PlainDateTime; |
|
||||||
use plain_month_day::PlainMonthDay; |
|
||||||
use plain_year_month::PlainYearMonth; |
|
||||||
use temporal_rs::{ |
|
||||||
components::{ |
|
||||||
calendar::{CalendarDateLike, CalendarProtocol}, |
|
||||||
Date, Duration, MonthDay, YearMonth, |
|
||||||
}, |
|
||||||
options::ArithmeticOverflow, |
|
||||||
TemporalError, TemporalFields, TemporalResult, TinyAsciiStr, |
|
||||||
}; |
|
||||||
|
|
||||||
impl CalendarProtocol for JsObject { |
|
||||||
type Date = JsObject<PlainDate>; |
|
||||||
type DateTime = JsObject<PlainDateTime>; |
|
||||||
type YearMonth = JsObject<PlainYearMonth>; |
|
||||||
type MonthDay = JsObject<PlainMonthDay>; |
|
||||||
type Context = Context; |
|
||||||
fn date_from_fields( |
|
||||||
&self, |
|
||||||
fields: &mut TemporalFields, |
|
||||||
overflow: ArithmeticOverflow, |
|
||||||
context: &mut Context, |
|
||||||
) -> TemporalResult<Date<Self>> { |
|
||||||
let method = self |
|
||||||
.get(js_str!("dateFromFields"), context) |
|
||||||
.expect("method must exist on a object that implements the CalendarProtocol."); |
|
||||||
|
|
||||||
let fields = JsObject::from_temporal_fields(fields, context) |
|
||||||
.map_err(|e| TemporalError::general(e.to_string()))?; |
|
||||||
|
|
||||||
let overflow_obj = JsObject::with_null_proto(); |
|
||||||
|
|
||||||
overflow_obj |
|
||||||
.create_data_property_or_throw( |
|
||||||
js_str!("overflow"), |
|
||||||
JsString::from(overflow.to_string()), |
|
||||||
context, |
|
||||||
) |
|
||||||
.map_err(|e| TemporalError::general(e.to_string()))?; |
|
||||||
|
|
||||||
let value = method |
|
||||||
.as_callable() |
|
||||||
.ok_or_else(|| { |
|
||||||
TemporalError::general("dateFromFields must be implemented as a callable method.") |
|
||||||
})? |
|
||||||
.call( |
|
||||||
&self.clone().into(), |
|
||||||
&[fields.into(), overflow_obj.into()], |
|
||||||
context, |
|
||||||
) |
|
||||||
.map_err(|e| TemporalError::general(e.to_string()))?; |
|
||||||
|
|
||||||
let obj = value.as_object().map(JsObject::borrow).ok_or_else(|| { |
|
||||||
TemporalError::r#type() |
|
||||||
.with_message("datefromFields must return a valid PlainDate object.") |
|
||||||
})?; |
|
||||||
|
|
||||||
let pd = obj.downcast_ref::<PlainDate>().ok_or_else(|| { |
|
||||||
TemporalError::r#type().with_message("Object returned was not a PlainDate") |
|
||||||
})?; |
|
||||||
|
|
||||||
Ok(pd.inner.clone()) |
|
||||||
} |
|
||||||
|
|
||||||
fn year_month_from_fields( |
|
||||||
&self, |
|
||||||
fields: &mut TemporalFields, |
|
||||||
overflow: ArithmeticOverflow, |
|
||||||
context: &mut Context, |
|
||||||
) -> TemporalResult<YearMonth<JsObject>> { |
|
||||||
let method = self |
|
||||||
.get(js_str!("yearMonthFromFields"), context) |
|
||||||
.expect("method must exist on a object that implements the CalendarProtocol."); |
|
||||||
|
|
||||||
let fields = JsObject::from_temporal_fields(fields, context) |
|
||||||
.map_err(|e| TemporalError::general(e.to_string()))?; |
|
||||||
|
|
||||||
let overflow_obj = JsObject::with_null_proto(); |
|
||||||
|
|
||||||
overflow_obj |
|
||||||
.create_data_property_or_throw( |
|
||||||
js_str!("overflow"), |
|
||||||
JsString::from(overflow.to_string()), |
|
||||||
context, |
|
||||||
) |
|
||||||
.map_err(|e| TemporalError::general(e.to_string()))?; |
|
||||||
|
|
||||||
let value = method |
|
||||||
.as_callable() |
|
||||||
.ok_or_else(|| { |
|
||||||
TemporalError::general( |
|
||||||
"yearMonthFromFields must be implemented as a callable method.", |
|
||||||
) |
|
||||||
})? |
|
||||||
.call( |
|
||||||
&self.clone().into(), |
|
||||||
&[fields.into(), overflow_obj.into()], |
|
||||||
context, |
|
||||||
) |
|
||||||
.map_err(|e| TemporalError::general(e.to_string()))?; |
|
||||||
|
|
||||||
let obj = value.as_object().map(JsObject::borrow).ok_or_else(|| { |
|
||||||
TemporalError::r#type() |
|
||||||
.with_message("yearMonthFromFields must return a valid PlainYearMonth object.") |
|
||||||
})?; |
|
||||||
|
|
||||||
let ym = obj.downcast_ref::<PlainYearMonth>().ok_or_else(|| { |
|
||||||
TemporalError::r#type().with_message("Object returned was not a PlainDate") |
|
||||||
})?; |
|
||||||
|
|
||||||
Ok(ym.inner.clone()) |
|
||||||
} |
|
||||||
|
|
||||||
fn month_day_from_fields( |
|
||||||
&self, |
|
||||||
fields: &mut TemporalFields, |
|
||||||
overflow: ArithmeticOverflow, |
|
||||||
context: &mut Context, |
|
||||||
) -> TemporalResult<MonthDay<JsObject>> { |
|
||||||
let method = self |
|
||||||
.get(js_str!("yearMonthFromFields"), context) |
|
||||||
.expect("method must exist on a object that implements the CalendarProtocol."); |
|
||||||
|
|
||||||
let fields = JsObject::from_temporal_fields(fields, context) |
|
||||||
.map_err(|e| TemporalError::general(e.to_string()))?; |
|
||||||
|
|
||||||
let overflow_obj = JsObject::with_null_proto(); |
|
||||||
|
|
||||||
overflow_obj |
|
||||||
.create_data_property_or_throw( |
|
||||||
js_str!("overflow"), |
|
||||||
JsString::from(overflow.to_string()), |
|
||||||
context, |
|
||||||
) |
|
||||||
.map_err(|e| TemporalError::general(e.to_string()))?; |
|
||||||
|
|
||||||
let value = method |
|
||||||
.as_callable() |
|
||||||
.ok_or_else(|| { |
|
||||||
TemporalError::general( |
|
||||||
"yearMonthFromFields must be implemented as a callable method.", |
|
||||||
) |
|
||||||
})? |
|
||||||
.call( |
|
||||||
&self.clone().into(), |
|
||||||
&[fields.into(), overflow_obj.into()], |
|
||||||
context, |
|
||||||
) |
|
||||||
.map_err(|e| TemporalError::general(e.to_string()))?; |
|
||||||
|
|
||||||
let obj = value.as_object().map(JsObject::borrow).ok_or_else(|| { |
|
||||||
TemporalError::r#type() |
|
||||||
.with_message("yearMonthFromFields must return a valid PlainYearMonth object.") |
|
||||||
})?; |
|
||||||
|
|
||||||
let md = obj.downcast_ref::<PlainMonthDay>().ok_or_else(|| { |
|
||||||
TemporalError::r#type().with_message("Object returned was not a PlainDate") |
|
||||||
})?; |
|
||||||
|
|
||||||
Ok(md.inner.clone()) |
|
||||||
} |
|
||||||
|
|
||||||
fn date_add( |
|
||||||
&self, |
|
||||||
_date: &Date<JsObject>, |
|
||||||
_duration: &Duration, |
|
||||||
_overflow: ArithmeticOverflow, |
|
||||||
_context: &mut Context, |
|
||||||
) -> TemporalResult<Date<JsObject>> { |
|
||||||
// TODO
|
|
||||||
Err(TemporalError::general("Not yet implemented.")) |
|
||||||
} |
|
||||||
|
|
||||||
fn date_until( |
|
||||||
&self, |
|
||||||
_one: &Date<JsObject>, |
|
||||||
_two: &Date<JsObject>, |
|
||||||
_largest_unit: temporal_rs::options::TemporalUnit, |
|
||||||
_context: &mut Context, |
|
||||||
) -> TemporalResult<Duration> { |
|
||||||
// TODO
|
|
||||||
Err(TemporalError::general("Not yet implemented.")) |
|
||||||
} |
|
||||||
|
|
||||||
fn era( |
|
||||||
&self, |
|
||||||
_: &CalendarDateLike<JsObject>, |
|
||||||
_: &mut Context, |
|
||||||
) -> TemporalResult<Option<TinyAsciiStr<16>>> { |
|
||||||
// Return undefined as custom calendars do not implement -> Currently.
|
|
||||||
Ok(None) |
|
||||||
} |
|
||||||
|
|
||||||
fn era_year( |
|
||||||
&self, |
|
||||||
_: &CalendarDateLike<JsObject>, |
|
||||||
_: &mut Context, |
|
||||||
) -> TemporalResult<Option<i32>> { |
|
||||||
// Return undefined as custom calendars do not implement -> Currently.
|
|
||||||
Ok(None) |
|
||||||
} |
|
||||||
|
|
||||||
fn year( |
|
||||||
&self, |
|
||||||
date_like: &CalendarDateLike<JsObject>, |
|
||||||
context: &mut Context, |
|
||||||
) -> TemporalResult<i32> { |
|
||||||
let date_like = date_like_to_object(date_like, context)?; |
|
||||||
|
|
||||||
let method = self |
|
||||||
.get(PropertyKey::from(js_str!("year")), context) |
|
||||||
.expect("method must exist on a object that implements the CalendarProtocol."); |
|
||||||
|
|
||||||
let val = method |
|
||||||
.as_callable() |
|
||||||
.expect("is method") |
|
||||||
.call(&self.clone().into(), &[date_like], context) |
|
||||||
.map_err(|err| TemporalError::general(err.to_string()))?; |
|
||||||
|
|
||||||
// Validate the return value.
|
|
||||||
// 3. If Type(result) is not Number, throw a TypeError exception.
|
|
||||||
// 4. If IsIntegralNumber(result) is false, throw a RangeError exception.
|
|
||||||
// 5. If result < 1𝔽, throw a RangeError exception.
|
|
||||||
// 6. Return ℝ(result).
|
|
||||||
|
|
||||||
let Some(number) = val.as_number() else { |
|
||||||
return Err(TemporalError::r#type().with_message("year must return a number.")); |
|
||||||
}; |
|
||||||
|
|
||||||
if !number.is_finite() || number.fract() != 0.0 { |
|
||||||
return Err(TemporalError::r#type().with_message("year return must be integral.")); |
|
||||||
} |
|
||||||
|
|
||||||
if number < 1f64 { |
|
||||||
return Err(TemporalError::r#type().with_message("year return must be larger than 1.")); |
|
||||||
} |
|
||||||
|
|
||||||
let result = number |
|
||||||
.to_i32() |
|
||||||
.ok_or_else(|| TemporalError::range().with_message("year exceeded a valid range."))?; |
|
||||||
|
|
||||||
Ok(result) |
|
||||||
} |
|
||||||
|
|
||||||
fn month( |
|
||||||
&self, |
|
||||||
date_like: &CalendarDateLike<JsObject>, |
|
||||||
context: &mut Context, |
|
||||||
) -> TemporalResult<u8> { |
|
||||||
let date_like = date_like_to_object(date_like, context)?; |
|
||||||
|
|
||||||
let method = self |
|
||||||
.get(PropertyKey::from(js_str!("month")), context) |
|
||||||
.expect("method must exist on a object that implements the CalendarProtocol."); |
|
||||||
|
|
||||||
let val = method |
|
||||||
.as_callable() |
|
||||||
.expect("is method") |
|
||||||
.call(&self.clone().into(), &[date_like], context) |
|
||||||
.map_err(|err| TemporalError::general(err.to_string()))?; |
|
||||||
|
|
||||||
// Validate the return value.
|
|
||||||
// 3. If Type(result) is not Number, throw a TypeError exception.
|
|
||||||
// 4. If IsIntegralNumber(result) is false, throw a RangeError exception.
|
|
||||||
// 5. If result < 1𝔽, throw a RangeError exception.
|
|
||||||
// 6. Return ℝ(result).
|
|
||||||
|
|
||||||
let Some(number) = val.as_number() else { |
|
||||||
return Err(TemporalError::r#type().with_message("month must return a number.")); |
|
||||||
}; |
|
||||||
|
|
||||||
if !number.is_finite() || number.fract() != 0.0 { |
|
||||||
return Err(TemporalError::r#type().with_message("month return must be integral.")); |
|
||||||
} |
|
||||||
|
|
||||||
if number < 1f64 { |
|
||||||
return Err(TemporalError::r#type().with_message("month return must be larger than 1.")); |
|
||||||
} |
|
||||||
|
|
||||||
let result = number |
|
||||||
.to_u8() |
|
||||||
.ok_or_else(|| TemporalError::range().with_message("month exceeded a valid range."))?; |
|
||||||
|
|
||||||
Ok(result) |
|
||||||
} |
|
||||||
|
|
||||||
fn month_code( |
|
||||||
&self, |
|
||||||
date_like: &CalendarDateLike<JsObject>, |
|
||||||
context: &mut Context, |
|
||||||
) -> TemporalResult<TinyAsciiStr<4>> { |
|
||||||
let date_like = date_like_to_object(date_like, context)?; |
|
||||||
|
|
||||||
let method = self |
|
||||||
.get(PropertyKey::from(js_str!("monthCode")), context) |
|
||||||
.expect("method must exist on a object that implements the CalendarProtocol."); |
|
||||||
|
|
||||||
let val = method |
|
||||||
.as_callable() |
|
||||||
.expect("is method") |
|
||||||
.call(&self.clone().into(), &[date_like], context) |
|
||||||
.map_err(|err| TemporalError::general(err.to_string()))?; |
|
||||||
|
|
||||||
let JsValue::String(result) = val else { |
|
||||||
return Err(TemporalError::r#type().with_message("monthCode return must be a String.")); |
|
||||||
}; |
|
||||||
|
|
||||||
let result = TinyAsciiStr::<4>::from_str(&result.to_std_string_escaped()) |
|
||||||
.map_err(|_| TemporalError::general("Unexpected monthCode value."))?; |
|
||||||
|
|
||||||
Ok(result) |
|
||||||
} |
|
||||||
|
|
||||||
fn day( |
|
||||||
&self, |
|
||||||
date_like: &CalendarDateLike<JsObject>, |
|
||||||
context: &mut Context, |
|
||||||
) -> TemporalResult<u8> { |
|
||||||
let date_like = date_like_to_object(date_like, context)?; |
|
||||||
|
|
||||||
let method = self |
|
||||||
.get(PropertyKey::from(js_str!("day")), context) |
|
||||||
.expect("method must exist on a object that implements the CalendarProtocol."); |
|
||||||
|
|
||||||
let val = method |
|
||||||
.as_callable() |
|
||||||
.expect("is method") |
|
||||||
.call(&self.clone().into(), &[date_like], context) |
|
||||||
.map_err(|err| TemporalError::general(err.to_string()))?; |
|
||||||
|
|
||||||
// Validate the return value.
|
|
||||||
// 3. If Type(result) is not Number, throw a TypeError exception.
|
|
||||||
// 4. If IsIntegralNumber(result) is false, throw a RangeError exception.
|
|
||||||
// 5. If result < 1𝔽, throw a RangeError exception.
|
|
||||||
// 6. Return ℝ(result).
|
|
||||||
|
|
||||||
let Some(number) = val.as_number() else { |
|
||||||
return Err(TemporalError::r#type().with_message("day must return a number.")); |
|
||||||
}; |
|
||||||
|
|
||||||
if !number.is_finite() || number.fract() != 0.0 { |
|
||||||
return Err(TemporalError::r#type().with_message("day return must be integral.")); |
|
||||||
} |
|
||||||
|
|
||||||
if number < 1f64 { |
|
||||||
return Err(TemporalError::r#type().with_message("day return must be larger than 1.")); |
|
||||||
} |
|
||||||
|
|
||||||
let result = number |
|
||||||
.to_u8() |
|
||||||
.ok_or_else(|| TemporalError::range().with_message("day exceeded a valid range."))?; |
|
||||||
|
|
||||||
Ok(result) |
|
||||||
} |
|
||||||
|
|
||||||
fn day_of_week( |
|
||||||
&self, |
|
||||||
date_like: &CalendarDateLike<JsObject>, |
|
||||||
context: &mut Context, |
|
||||||
) -> TemporalResult<u16> { |
|
||||||
let date_like = date_like_to_object(date_like, context)?; |
|
||||||
|
|
||||||
let method = self |
|
||||||
.get(PropertyKey::from(js_str!("dayOfWeek")), context) |
|
||||||
.expect("method must exist on a object that implements the CalendarProtocol."); |
|
||||||
|
|
||||||
let val = method |
|
||||||
.as_callable() |
|
||||||
.expect("is method") |
|
||||||
.call(&self.clone().into(), &[date_like], context) |
|
||||||
.map_err(|err| TemporalError::general(err.to_string()))?; |
|
||||||
|
|
||||||
// Validate the return value.
|
|
||||||
// 3. If Type(result) is not Number, throw a TypeError exception.
|
|
||||||
// 4. If IsIntegralNumber(result) is false, throw a RangeError exception.
|
|
||||||
// 5. If result < 1𝔽, throw a RangeError exception.
|
|
||||||
// 6. Return ℝ(result).
|
|
||||||
|
|
||||||
let Some(number) = val.as_number() else { |
|
||||||
return Err(TemporalError::r#type().with_message("DayOfWeek must return a number.")); |
|
||||||
}; |
|
||||||
|
|
||||||
if !number.is_finite() || number.fract() != 0.0 { |
|
||||||
return Err(TemporalError::r#type().with_message("DayOfWeek return must be integral.")); |
|
||||||
} |
|
||||||
|
|
||||||
if number < 1f64 { |
|
||||||
return Err( |
|
||||||
TemporalError::r#type().with_message("DayOfWeek return must be larger than 1.") |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
let result = number.to_u16().ok_or_else(|| { |
|
||||||
TemporalError::range().with_message("DayOfWeek exceeded valid range.") |
|
||||||
})?; |
|
||||||
|
|
||||||
Ok(result) |
|
||||||
} |
|
||||||
|
|
||||||
fn day_of_year( |
|
||||||
&self, |
|
||||||
date_like: &CalendarDateLike<JsObject>, |
|
||||||
context: &mut Context, |
|
||||||
) -> TemporalResult<u16> { |
|
||||||
let date_like = date_like_to_object(date_like, context)?; |
|
||||||
|
|
||||||
let method = self |
|
||||||
.get(PropertyKey::from(js_str!("dayOfYear")), context) |
|
||||||
.expect("method must exist on a object that implements the CalendarProtocol."); |
|
||||||
|
|
||||||
let val = method |
|
||||||
.as_callable() |
|
||||||
.expect("is method") |
|
||||||
.call(&self.clone().into(), &[date_like], context) |
|
||||||
.map_err(|err| TemporalError::general(err.to_string()))?; |
|
||||||
|
|
||||||
// Validate the return value.
|
|
||||||
// 3. If Type(result) is not Number, throw a TypeError exception.
|
|
||||||
// 4. If IsIntegralNumber(result) is false, throw a RangeError exception.
|
|
||||||
// 5. If result < 1𝔽, throw a RangeError exception.
|
|
||||||
// 6. Return ℝ(result).
|
|
||||||
|
|
||||||
let Some(number) = val.as_number() else { |
|
||||||
return Err(TemporalError::r#type().with_message("dayOfYear must return a number.")); |
|
||||||
}; |
|
||||||
|
|
||||||
if !number.is_finite() || number.fract() != 0.0 { |
|
||||||
return Err(TemporalError::r#type().with_message("dayOfYear return must be integral.")); |
|
||||||
} |
|
||||||
|
|
||||||
if number < 1f64 { |
|
||||||
return Err( |
|
||||||
TemporalError::r#type().with_message("dayOfYear return must be larger than 1.") |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
let result = number.to_u16().ok_or_else(|| { |
|
||||||
TemporalError::range().with_message("dayOfYear exceeded valid range.") |
|
||||||
})?; |
|
||||||
|
|
||||||
Ok(result) |
|
||||||
} |
|
||||||
|
|
||||||
fn week_of_year( |
|
||||||
&self, |
|
||||||
date_like: &CalendarDateLike<JsObject>, |
|
||||||
context: &mut Context, |
|
||||||
) -> TemporalResult<u16> { |
|
||||||
let date_like = date_like_to_object(date_like, context)?; |
|
||||||
|
|
||||||
let method = self |
|
||||||
.get(PropertyKey::from(js_str!("weekOfYear")), context) |
|
||||||
.expect("method must exist on a object that implements the CalendarProtocol."); |
|
||||||
|
|
||||||
let val = method |
|
||||||
.as_callable() |
|
||||||
.expect("is method") |
|
||||||
.call(&self.clone().into(), &[date_like], context) |
|
||||||
.map_err(|err| TemporalError::general(err.to_string()))?; |
|
||||||
|
|
||||||
// Validate the return value.
|
|
||||||
// 3. If Type(result) is not Number, throw a TypeError exception.
|
|
||||||
// 4. If IsIntegralNumber(result) is false, throw a RangeError exception.
|
|
||||||
// 5. If result < 1𝔽, throw a RangeError exception.
|
|
||||||
// 6. Return ℝ(result).
|
|
||||||
|
|
||||||
let Some(number) = val.as_number() else { |
|
||||||
return Err(TemporalError::r#type().with_message("weekOfYear must return a number.")); |
|
||||||
}; |
|
||||||
|
|
||||||
if !number.is_finite() || number.fract() != 0.0 { |
|
||||||
return Err(TemporalError::r#type().with_message("weekOfYear return must be integral.")); |
|
||||||
} |
|
||||||
|
|
||||||
if number < 1f64 { |
|
||||||
return Err( |
|
||||||
TemporalError::r#type().with_message("weekOfYear return must be larger than 1.") |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
let result = number.to_u16().ok_or_else(|| { |
|
||||||
TemporalError::range().with_message("weekOfYear exceeded valid range.") |
|
||||||
})?; |
|
||||||
|
|
||||||
Ok(result) |
|
||||||
} |
|
||||||
|
|
||||||
fn year_of_week( |
|
||||||
&self, |
|
||||||
date_like: &CalendarDateLike<JsObject>, |
|
||||||
context: &mut Context, |
|
||||||
) -> TemporalResult<i32> { |
|
||||||
let date_like = date_like_to_object(date_like, context)?; |
|
||||||
|
|
||||||
let method = self |
|
||||||
.get(PropertyKey::from(js_str!("yearOfWeek")), context) |
|
||||||
.expect("method must exist on a object that implements the CalendarProtocol."); |
|
||||||
|
|
||||||
let val = method |
|
||||||
.as_callable() |
|
||||||
.expect("is method") |
|
||||||
.call(&self.clone().into(), &[date_like], context) |
|
||||||
.map_err(|err| TemporalError::general(err.to_string()))?; |
|
||||||
|
|
||||||
// Validate the return value.
|
|
||||||
// 3. If Type(result) is not Number, throw a TypeError exception.
|
|
||||||
// 4. If IsIntegralNumber(result) is false, throw a RangeError exception.
|
|
||||||
// 5. Return ℝ(result).
|
|
||||||
|
|
||||||
let Some(number) = val.as_number() else { |
|
||||||
return Err(TemporalError::r#type().with_message("yearOfWeek must return a number.")); |
|
||||||
}; |
|
||||||
|
|
||||||
if !number.is_finite() || number.fract() != 0.0 { |
|
||||||
return Err(TemporalError::r#type().with_message("yearOfWeek return must be integral.")); |
|
||||||
} |
|
||||||
|
|
||||||
let result = number.to_i32().ok_or_else(|| { |
|
||||||
TemporalError::range().with_message("yearOfWeek exceeded valid range.") |
|
||||||
})?; |
|
||||||
|
|
||||||
Ok(result) |
|
||||||
} |
|
||||||
|
|
||||||
fn days_in_week( |
|
||||||
&self, |
|
||||||
date_like: &CalendarDateLike<JsObject>, |
|
||||||
context: &mut Context, |
|
||||||
) -> TemporalResult<u16> { |
|
||||||
let date_like = date_like_to_object(date_like, context)?; |
|
||||||
|
|
||||||
let method = self |
|
||||||
.get(PropertyKey::from(js_str!("daysInWeek")), context) |
|
||||||
.expect("method must exist on a object that implements the CalendarProtocol."); |
|
||||||
|
|
||||||
let val = method |
|
||||||
.as_callable() |
|
||||||
.expect("is method") |
|
||||||
.call(&self.clone().into(), &[date_like], context) |
|
||||||
.map_err(|err| TemporalError::general(err.to_string()))?; |
|
||||||
|
|
||||||
// Validate the return value.
|
|
||||||
// 3. If Type(result) is not Number, throw a TypeError exception.
|
|
||||||
// 4. If IsIntegralNumber(result) is false, throw a RangeError exception.
|
|
||||||
// 5. If result < 1𝔽, throw a RangeError exception.
|
|
||||||
// 6. Return ℝ(result).
|
|
||||||
|
|
||||||
let Some(number) = val.as_number() else { |
|
||||||
return Err(TemporalError::r#type().with_message("daysInWeek must return a number.")); |
|
||||||
}; |
|
||||||
|
|
||||||
if !number.is_finite() || number.fract() != 0.0 { |
|
||||||
return Err(TemporalError::r#type().with_message("daysInWeek return must be integral.")); |
|
||||||
} |
|
||||||
|
|
||||||
if number < 1f64 { |
|
||||||
return Err( |
|
||||||
TemporalError::r#type().with_message("daysInWeek return must be larger than 1.") |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
let result = number.to_u16().ok_or_else(|| { |
|
||||||
TemporalError::range().with_message("daysInWeek exceeded valid range.") |
|
||||||
})?; |
|
||||||
|
|
||||||
Ok(result) |
|
||||||
} |
|
||||||
|
|
||||||
fn days_in_month( |
|
||||||
&self, |
|
||||||
date_like: &CalendarDateLike<JsObject>, |
|
||||||
context: &mut Context, |
|
||||||
) -> TemporalResult<u16> { |
|
||||||
let date_like = date_like_to_object(date_like, context)?; |
|
||||||
|
|
||||||
let method = self |
|
||||||
.get(PropertyKey::from(js_str!("daysInMonth")), context) |
|
||||||
.expect("method must exist on a object that implements the CalendarProtocol."); |
|
||||||
let val = method |
|
||||||
.as_callable() |
|
||||||
.expect("is method") |
|
||||||
.call(&self.clone().into(), &[date_like], context) |
|
||||||
.map_err(|err| TemporalError::general(err.to_string()))?; |
|
||||||
|
|
||||||
// Validate the return value.
|
|
||||||
// 3. If Type(result) is not Number, throw a TypeError exception.
|
|
||||||
// 4. If IsIntegralNumber(result) is false, throw a RangeError exception.
|
|
||||||
// 5. If result < 1𝔽, throw a RangeError exception.
|
|
||||||
// 6. Return ℝ(result).
|
|
||||||
|
|
||||||
let Some(number) = val.as_number() else { |
|
||||||
return Err(TemporalError::r#type().with_message("daysInMonth must return a number.")); |
|
||||||
}; |
|
||||||
|
|
||||||
if !number.is_finite() || number.fract() != 0.0 { |
|
||||||
return Err( |
|
||||||
TemporalError::r#type().with_message("daysInMonth return must be integral.") |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
if number < 1f64 { |
|
||||||
return Err( |
|
||||||
TemporalError::r#type().with_message("daysInMonth return must be larger than 1.") |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
let result = number.to_u16().ok_or_else(|| { |
|
||||||
TemporalError::range().with_message("daysInMonth exceeded valid range.") |
|
||||||
})?; |
|
||||||
|
|
||||||
Ok(result) |
|
||||||
} |
|
||||||
|
|
||||||
fn days_in_year( |
|
||||||
&self, |
|
||||||
date_like: &CalendarDateLike<JsObject>, |
|
||||||
context: &mut Context, |
|
||||||
) -> TemporalResult<u16> { |
|
||||||
let date_like = date_like_to_object(date_like, context)?; |
|
||||||
|
|
||||||
let method = self |
|
||||||
.get(PropertyKey::from(js_str!("daysInYear")), context) |
|
||||||
.expect("method must exist on a object that implements the CalendarProtocol."); |
|
||||||
|
|
||||||
let val = method |
|
||||||
.as_callable() |
|
||||||
.expect("is method") |
|
||||||
.call(&self.clone().into(), &[date_like], context) |
|
||||||
.map_err(|err| TemporalError::general(err.to_string()))?; |
|
||||||
|
|
||||||
// Validate the return value.
|
|
||||||
// 3. If Type(result) is not Number, throw a TypeError exception.
|
|
||||||
// 4. If IsIntegralNumber(result) is false, throw a RangeError exception.
|
|
||||||
// 5. If result < 1𝔽, throw a RangeError exception.
|
|
||||||
// 6. Return ℝ(result).
|
|
||||||
|
|
||||||
let Some(number) = val.as_number() else { |
|
||||||
return Err(TemporalError::r#type().with_message("daysInYear must return a number.")); |
|
||||||
}; |
|
||||||
|
|
||||||
if !number.is_finite() || number.fract() != 0.0 { |
|
||||||
return Err(TemporalError::r#type().with_message("daysInYear return must be integral.")); |
|
||||||
} |
|
||||||
|
|
||||||
if number < 1f64 { |
|
||||||
return Err( |
|
||||||
TemporalError::r#type().with_message("daysInYear return must be larger than 1.") |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
let result = number.to_u16().ok_or_else(|| { |
|
||||||
TemporalError::range().with_message("monthsInYear exceeded valid range.") |
|
||||||
})?; |
|
||||||
|
|
||||||
Ok(result) |
|
||||||
} |
|
||||||
|
|
||||||
fn months_in_year( |
|
||||||
&self, |
|
||||||
date_like: &CalendarDateLike<JsObject>, |
|
||||||
context: &mut Context, |
|
||||||
) -> TemporalResult<u16> { |
|
||||||
let date_like = date_like_to_object(date_like, context)?; |
|
||||||
|
|
||||||
let method = self |
|
||||||
.get(PropertyKey::from(js_str!("monthsInYear")), context) |
|
||||||
.expect("method must exist on a object that implements the CalendarProtocol."); |
|
||||||
|
|
||||||
let val = method |
|
||||||
.as_callable() |
|
||||||
.expect("is method") |
|
||||||
.call(&self.clone().into(), &[date_like], context) |
|
||||||
.map_err(|err| TemporalError::general(err.to_string()))?; |
|
||||||
|
|
||||||
// Validate the return value.
|
|
||||||
// 3. If Type(result) is not Number, throw a TypeError exception.
|
|
||||||
// 4. If IsIntegralNumber(result) is false, throw a RangeError exception.
|
|
||||||
// 5. If result < 1𝔽, throw a RangeError exception.
|
|
||||||
// 6. Return ℝ(result).
|
|
||||||
|
|
||||||
let Some(number) = val.as_number() else { |
|
||||||
return Err(TemporalError::r#type().with_message("monthsInYear must return a number.")); |
|
||||||
}; |
|
||||||
|
|
||||||
if !number.is_finite() || number.fract() != 0.0 { |
|
||||||
return Err( |
|
||||||
TemporalError::r#type().with_message("monthsInYear return must be integral.") |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
if number < 1f64 { |
|
||||||
return Err( |
|
||||||
TemporalError::r#type().with_message("monthsInYear return must be larger than 1.") |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
let result = number.to_u16().ok_or_else(|| { |
|
||||||
TemporalError::range().with_message("monthsInYear exceeded valid range.") |
|
||||||
})?; |
|
||||||
|
|
||||||
Ok(result) |
|
||||||
} |
|
||||||
|
|
||||||
fn in_leap_year( |
|
||||||
&self, |
|
||||||
date_like: &CalendarDateLike<JsObject>, |
|
||||||
context: &mut Context, |
|
||||||
) -> TemporalResult<bool> { |
|
||||||
let date_like = date_like_to_object(date_like, context)?; |
|
||||||
|
|
||||||
let method = self |
|
||||||
.get(PropertyKey::from(js_str!("inLeapYear")), context) |
|
||||||
.expect("method must exist on a object that implements the CalendarProtocol."); |
|
||||||
|
|
||||||
let val = method |
|
||||||
.as_callable() |
|
||||||
.expect("is method") |
|
||||||
.call(&self.clone().into(), &[date_like], context) |
|
||||||
.map_err(|err| TemporalError::general(err.to_string()))?; |
|
||||||
|
|
||||||
let JsValue::Boolean(result) = val else { |
|
||||||
return Err( |
|
||||||
TemporalError::r#type().with_message("inLeapYear must return a valid boolean.") |
|
||||||
); |
|
||||||
}; |
|
||||||
|
|
||||||
Ok(result) |
|
||||||
} |
|
||||||
|
|
||||||
fn fields(&self, fields: Vec<String>, context: &mut Context) -> TemporalResult<Vec<String>> { |
|
||||||
let fields_js = Array::create_array_from_list( |
|
||||||
fields.iter().map(|s| JsString::from(s.clone()).into()), |
|
||||||
context, |
|
||||||
); |
|
||||||
|
|
||||||
let method = self |
|
||||||
.get(PropertyKey::from(js_str!("fields")), context) |
|
||||||
.expect("method must exist on an object that implements the CalendarProtocol."); |
|
||||||
|
|
||||||
let result = method |
|
||||||
.as_callable() |
|
||||||
.expect("is method") |
|
||||||
.call(&self.clone().into(), &[fields_js.into()], context) |
|
||||||
.map_err(|e| TemporalError::general(e.to_string()))?; |
|
||||||
|
|
||||||
// validate result and map to a `Vec<String>`
|
|
||||||
let mut iterator = result |
|
||||||
.get_iterator(context, Some(IteratorHint::Sync), None) |
|
||||||
.map_err(|e| TemporalError::general(e.to_string()))?; |
|
||||||
|
|
||||||
let mut result = Vec::default(); |
|
||||||
while iterator |
|
||||||
.step(context) |
|
||||||
.map_err(|e| TemporalError::general(e.to_string()))? |
|
||||||
{ |
|
||||||
let next_value = iterator |
|
||||||
.value(context) |
|
||||||
.map_err(|e| TemporalError::general(e.to_string()))?; |
|
||||||
|
|
||||||
let JsValue::String(s) = next_value else { |
|
||||||
return Err(TemporalError::r#type() |
|
||||||
.with_message("Invalid return type in fields method implementation.")); |
|
||||||
}; |
|
||||||
|
|
||||||
result.push(s.to_std_string_escaped()); |
|
||||||
} |
|
||||||
|
|
||||||
Ok(result) |
|
||||||
} |
|
||||||
|
|
||||||
fn merge_fields( |
|
||||||
&self, |
|
||||||
fields: &TemporalFields, |
|
||||||
additional_fields: &TemporalFields, |
|
||||||
context: &mut Context, |
|
||||||
) -> TemporalResult<TemporalFields> { |
|
||||||
let fields = JsObject::from_temporal_fields(fields, context) |
|
||||||
.map_err(|e| TemporalError::general(e.to_string()))?; |
|
||||||
let add_fields = JsObject::from_temporal_fields(additional_fields, context) |
|
||||||
.map_err(|e| TemporalError::general(e.to_string()))?; |
|
||||||
|
|
||||||
let method = self |
|
||||||
.get(PropertyKey::from(js_str!("mergeFields")), context) |
|
||||||
.expect("method must exist on an object that implements the CalendarProtocol."); |
|
||||||
|
|
||||||
let value = method |
|
||||||
.as_callable() |
|
||||||
.expect("is method") |
|
||||||
.call( |
|
||||||
&self.clone().into(), |
|
||||||
&[fields.into(), add_fields.into()], |
|
||||||
context, |
|
||||||
) |
|
||||||
.map_err(|e| TemporalError::general(e.to_string()))?; |
|
||||||
|
|
||||||
let JsValue::Object(o) = value else { |
|
||||||
return Err( |
|
||||||
TemporalError::r#type().with_message("mergeFields did not return an object.") |
|
||||||
); |
|
||||||
}; |
|
||||||
|
|
||||||
object_to_temporal_fields(&o, context).map_err(|e| TemporalError::general(e.to_string())) |
|
||||||
} |
|
||||||
|
|
||||||
fn identifier(&self, context: &mut Context) -> TemporalResult<String> { |
|
||||||
let identifier = self |
|
||||||
.__get__( |
|
||||||
&PropertyKey::from(js_str!("id")), |
|
||||||
self.clone().into(), |
|
||||||
&mut context.into(), |
|
||||||
) |
|
||||||
.expect("method must exist on a object that implements the CalendarProtocol."); |
|
||||||
|
|
||||||
let JsValue::String(s) = identifier else { |
|
||||||
return Err(TemporalError::range().with_message("Identifier was not a string")); |
|
||||||
}; |
|
||||||
|
|
||||||
Ok(s.to_std_string_escaped()) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// Utility function for converting `Temporal`'s `CalendarDateLike` to it's `Boa` specific `JsObject`.
|
|
||||||
pub(crate) fn date_like_to_object( |
|
||||||
date_like: &CalendarDateLike<JsObject>, |
|
||||||
context: &mut Context, |
|
||||||
) -> TemporalResult<JsValue> { |
|
||||||
match date_like { |
|
||||||
CalendarDateLike::Date(d) => plain_date::create_temporal_date(d.clone(), None, context) |
|
||||||
.map_err(|e| TemporalError::general(e.to_string())) |
|
||||||
.map(Into::into), |
|
||||||
CalendarDateLike::DateTime(dt) => { |
|
||||||
plain_date_time::create_temporal_datetime(dt.clone(), None, context) |
|
||||||
.map_err(|e| TemporalError::general(e.to_string())) |
|
||||||
.map(Into::into) |
|
||||||
} |
|
||||||
CalendarDateLike::CustomMonthDay(md) => Ok(md.clone().upcast().into()), |
|
||||||
CalendarDateLike::CustomYearMonth(ym) => Ok(ym.clone().upcast().into()), |
|
||||||
CalendarDateLike::CustomDate(pd) => Ok(pd.clone().upcast().into()), |
|
||||||
CalendarDateLike::CustomDateTime(pdt) => Ok(pdt.clone().upcast().into()), |
|
||||||
} |
|
||||||
} |
|
@ -1,61 +0,0 @@ |
|||||||
use crate::{js_string, run_test_actions, TestAction}; |
|
||||||
|
|
||||||
#[test] |
|
||||||
fn calendar_constructor() { |
|
||||||
// TODO: Add other BuiltinCalendars
|
|
||||||
run_test_actions([TestAction::assert_eq( |
|
||||||
"new Temporal.Calendar('iso8601').id", |
|
||||||
js_string!("iso8601"), |
|
||||||
)]); |
|
||||||
} |
|
||||||
|
|
||||||
#[test] |
|
||||||
fn calendar_methods() { |
|
||||||
run_test_actions([ |
|
||||||
TestAction::run("let iso = new Temporal.Calendar('iso8601');"), |
|
||||||
TestAction::assert_eq("iso.inLeapYear('2020-11-20')", true), |
|
||||||
TestAction::assert_eq("iso.daysInYear('2020-11-20')", 366), |
|
||||||
TestAction::assert_eq("iso.daysInYear('2021-11-20')", 365), |
|
||||||
TestAction::assert_eq("iso.monthsInYear('2021-11-20')", 12), |
|
||||||
TestAction::assert_eq("iso.daysInWeek('2021-11-20')", 7), |
|
||||||
]); |
|
||||||
} |
|
||||||
|
|
||||||
#[test] |
|
||||||
fn run_custom_calendar() { |
|
||||||
run_test_actions([ |
|
||||||
TestAction::run( |
|
||||||
r#"const custom = { |
|
||||||
dateAdd() {}, |
|
||||||
dateFromFields() {}, |
|
||||||
dateUntil() {}, |
|
||||||
day() {}, |
|
||||||
dayOfWeek() {}, |
|
||||||
dayOfYear() {}, |
|
||||||
daysInMonth() { return 14 }, |
|
||||||
daysInWeek() {return 6}, |
|
||||||
daysInYear() {return 360}, |
|
||||||
fields() {}, |
|
||||||
id: "custom-calendar", |
|
||||||
inLeapYear() {}, |
|
||||||
mergeFields() {}, |
|
||||||
month() {}, |
|
||||||
monthCode() {}, |
|
||||||
monthDayFromFields() {}, |
|
||||||
monthsInYear() {}, |
|
||||||
weekOfYear() {}, |
|
||||||
year() {}, |
|
||||||
yearMonthFromFields() {}, |
|
||||||
yearOfWeek() {}, |
|
||||||
}; |
|
||||||
|
|
||||||
let cal = Temporal.Calendar.from(custom); |
|
||||||
let date = "1972-05-01"; |
|
||||||
"#, |
|
||||||
), |
|
||||||
TestAction::assert_eq("cal.id", js_string!("custom-calendar")), |
|
||||||
TestAction::assert_eq("cal.daysInMonth(date)", 14), |
|
||||||
TestAction::assert_eq("cal.daysInWeek(date)", 6), |
|
||||||
TestAction::assert_eq("cal.daysInYear(date)", 360), |
|
||||||
]); |
|
||||||
} |
|
@ -1,250 +0,0 @@ |
|||||||
//! A Rust native implementation of the `fields` object used in `Temporal`.
|
|
||||||
|
|
||||||
use std::str::FromStr; |
|
||||||
|
|
||||||
use crate::{ |
|
||||||
js_string, object::internal_methods::InternalMethodContext, property::PropertyKey, |
|
||||||
value::PreferredType, Context, JsNativeError, JsObject, JsResult, JsString, JsValue, |
|
||||||
}; |
|
||||||
|
|
||||||
use rustc_hash::FxHashSet; |
|
||||||
|
|
||||||
use temporal_rs::fields::{FieldConversion, FieldValue, TemporalFields}; |
|
||||||
|
|
||||||
use super::{to_integer_with_truncation, to_positive_integer_with_trunc}; |
|
||||||
|
|
||||||
// TODO: Move extended and required fields into the temporal library?
|
|
||||||
/// `PrepareTemporalFeilds`
|
|
||||||
pub(crate) fn prepare_temporal_fields( |
|
||||||
fields: &JsObject, |
|
||||||
field_names: &mut Vec<JsString>, |
|
||||||
required_fields: &mut Vec<JsString>, |
|
||||||
extended_fields: Option<Vec<(String, bool)>>, |
|
||||||
partial: bool, |
|
||||||
dup_behaviour: Option<JsString>, |
|
||||||
context: &mut Context, |
|
||||||
) -> JsResult<TemporalFields> { |
|
||||||
// 1. If duplicateBehaviour is not present, set duplicateBehaviour to throw.
|
|
||||||
let dup_option = dup_behaviour.unwrap_or_else(|| js_string!("throw")); |
|
||||||
|
|
||||||
// 2. Let result be OrdinaryObjectCreate(null).
|
|
||||||
let mut result = TemporalFields::default(); |
|
||||||
|
|
||||||
// 3. Let any be false.
|
|
||||||
let mut any = false; |
|
||||||
// 4. If extraFieldDescriptors is present, then
|
|
||||||
if let Some(extra_fields) = extended_fields { |
|
||||||
for (field_name, required) in extra_fields { |
|
||||||
// a. For each Calendar Field Descriptor Record desc of extraFieldDescriptors, do
|
|
||||||
// i. Assert: fieldNames does not contain desc.[[Property]].
|
|
||||||
// ii. Append desc.[[Property]] to fieldNames.
|
|
||||||
field_names.push(JsString::from(field_name.clone())); |
|
||||||
|
|
||||||
// iii. If desc.[[Required]] is true and requiredFields is a List, then
|
|
||||||
if required && !partial { |
|
||||||
// 1. Append desc.[[Property]] to requiredFields.
|
|
||||||
required_fields.push(JsString::from(field_name)); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// 5. Let sortedFieldNames be SortStringListByCodeUnit(fieldNames).
|
|
||||||
// 6. Let previousProperty be undefined.
|
|
||||||
let mut dups_map = FxHashSet::default(); |
|
||||||
|
|
||||||
// 7. For each property name property of sortedFieldNames, do
|
|
||||||
for field in &*field_names { |
|
||||||
// a. If property is one of "constructor" or "__proto__", then
|
|
||||||
if field.to_std_string_escaped().as_str() == "constructor" |
|
||||||
|| field.to_std_string_escaped().as_str() == "__proto__" |
|
||||||
{ |
|
||||||
// i. Throw a RangeError exception.
|
|
||||||
return Err(JsNativeError::range() |
|
||||||
.with_message("constructor or proto is out of field range.") |
|
||||||
.into()); |
|
||||||
} |
|
||||||
|
|
||||||
let new_value = dups_map.insert(field); |
|
||||||
|
|
||||||
// b. If property is not equal to previousProperty, then
|
|
||||||
if new_value { |
|
||||||
// i. Let value be ? Get(fields, property).
|
|
||||||
let value = fields.get(PropertyKey::from(field.clone()), context)?; |
|
||||||
// ii. If value is not undefined, then
|
|
||||||
if !value.is_undefined() { |
|
||||||
// 1. Set any to true.
|
|
||||||
any = true; |
|
||||||
|
|
||||||
// 2. If property is in the Property column of Table 17 and there is a Conversion value in the same row, then
|
|
||||||
// a. Let Conversion be the Conversion value of the same row.
|
|
||||||
|
|
||||||
// TODO: Conversion from TemporalError -> JsError
|
|
||||||
let conversion = FieldConversion::from_str(field.to_std_string_escaped().as_str()) |
|
||||||
.map_err(|_| JsNativeError::range().with_message("wrong field value"))?; |
|
||||||
// b. If Conversion is ToIntegerWithTruncation, then
|
|
||||||
let converted_value = match conversion { |
|
||||||
FieldConversion::ToIntegerWithTruncation => { |
|
||||||
// i. Set value to ? ToIntegerWithTruncation(value).
|
|
||||||
let v = to_integer_with_truncation(&value, context)?; |
|
||||||
// ii. Set value to 𝔽(value).
|
|
||||||
FieldValue::Integer(v) |
|
||||||
} |
|
||||||
// c. Else if Conversion is ToPositiveIntegerWithTruncation, then
|
|
||||||
FieldConversion::ToPositiveIntegerWithTruncation => { |
|
||||||
// i. Set value to ? ToPositiveIntegerWithTruncation(value).
|
|
||||||
let v = to_positive_integer_with_trunc(&value, context)?; |
|
||||||
// ii. Set value to 𝔽(value).
|
|
||||||
FieldValue::Integer(v) |
|
||||||
} |
|
||||||
// d. Else,
|
|
||||||
// i. Assert: Conversion is ToPrimitiveAndRequireString.
|
|
||||||
FieldConversion::ToPrimativeAndRequireString => { |
|
||||||
// ii. NOTE: Non-primitive values are supported here for consistency with other fields, but such values must coerce to Strings.
|
|
||||||
// iii. Set value to ? ToPrimitive(value, string).
|
|
||||||
let primitive = value.to_primitive(context, PreferredType::String)?; |
|
||||||
// iv. If value is not a String, throw a TypeError exception.
|
|
||||||
FieldValue::String(primitive.to_string(context)?.to_std_string_escaped()) |
|
||||||
} |
|
||||||
FieldConversion::None => { |
|
||||||
unreachable!("todo need to implement conversion handling for tz.") |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
// 3. Perform ! CreateDataPropertyOrThrow(result, property, value).
|
|
||||||
result |
|
||||||
.set_field_value(&field.to_std_string_escaped(), &converted_value) |
|
||||||
.expect("FieldConversion enforces the appropriate type"); |
|
||||||
// iii. Else if requiredFields is a List, then
|
|
||||||
} else if !partial { |
|
||||||
// 1. If requiredFields contains property, then
|
|
||||||
if required_fields.contains(field) { |
|
||||||
// a. Throw a TypeError exception.
|
|
||||||
return Err(JsNativeError::typ() |
|
||||||
.with_message("A required TemporalField was not provided.") |
|
||||||
.into()); |
|
||||||
} |
|
||||||
|
|
||||||
// NOTE: flag that the value is active and the default should be used.
|
|
||||||
// 2. If property is in the Property column of Table 17, then
|
|
||||||
// a. Set value to the corresponding Default value of the same row.
|
|
||||||
// 3. Perform ! CreateDataPropertyOrThrow(result, property, value).
|
|
||||||
result.require_field(&field.to_std_string_escaped()); |
|
||||||
} |
|
||||||
// c. Else if duplicateBehaviour is throw, then
|
|
||||||
} else if dup_option.to_std_string_escaped() == "throw" { |
|
||||||
// i. Throw a RangeError exception.
|
|
||||||
return Err(JsNativeError::range() |
|
||||||
.with_message("Cannot have a duplicate field") |
|
||||||
.into()); |
|
||||||
} |
|
||||||
// d. Set previousProperty to property.
|
|
||||||
} |
|
||||||
|
|
||||||
// 8. If requiredFields is partial and any is false, then
|
|
||||||
if partial && !any { |
|
||||||
// a. Throw a TypeError exception.
|
|
||||||
return Err(JsNativeError::range() |
|
||||||
.with_message("requiredFields cannot be partial when any is false") |
|
||||||
.into()); |
|
||||||
} |
|
||||||
|
|
||||||
// 9. Return result.
|
|
||||||
Ok(result) |
|
||||||
} |
|
||||||
|
|
||||||
// NOTE(nekevss): The below serves as a replacement for `Snapshot` on `Calendar.prototype.mergeFields`.
|
|
||||||
//
|
|
||||||
// Some potential issues here: `Calendar.prototype.mergeFields` appears to allow extra fields that
|
|
||||||
// are not part of a `TemporalFields` record; however, the specification only calls `mergeFields` on an
|
|
||||||
// object returned by `PrepareTemporalFields`, so the translation should be fine sound.
|
|
||||||
//
|
|
||||||
// The restriction/trade-off would occur if someone wanted to include non-normative calendar fields (i.e. something
|
|
||||||
// not accounted for in the specification) in a Custom Calendar or use `Calendar.prototype.mergeFields` in
|
|
||||||
// general as a way to merge two objects.
|
|
||||||
pub(crate) fn object_to_temporal_fields( |
|
||||||
source: &JsObject, |
|
||||||
context: &mut Context, |
|
||||||
) -> JsResult<TemporalFields> { |
|
||||||
// Adapted from `CopyDataProperties` with ExcludedKeys -> << >> && ExcludedValues -> << Undefined >>
|
|
||||||
const VALID_FIELDS: [&str; 14] = [ |
|
||||||
"year", |
|
||||||
"month", |
|
||||||
"monthCode", |
|
||||||
"day", |
|
||||||
"hour", |
|
||||||
"minute", |
|
||||||
"second", |
|
||||||
"millisecond", |
|
||||||
"microsecond", |
|
||||||
"nanosecond", |
|
||||||
"offset", |
|
||||||
"timeZone", |
|
||||||
"era", |
|
||||||
"eraYear", |
|
||||||
]; |
|
||||||
let mut copy = TemporalFields::default(); |
|
||||||
|
|
||||||
let keys = source.__own_property_keys__(context)?; |
|
||||||
|
|
||||||
for key in &keys { |
|
||||||
let desc = source.__get_own_property__(key, &mut InternalMethodContext::new(context))?; |
|
||||||
match desc { |
|
||||||
// Enforce that the `PropertyKey` is a valid field here.
|
|
||||||
Some(desc) |
|
||||||
if desc.expect_enumerable() && VALID_FIELDS.contains(&key.to_string().as_str()) => |
|
||||||
{ |
|
||||||
let value = source.get(key.clone(), context)?; |
|
||||||
// Below is repurposed from `PrepareTemporalFields`.
|
|
||||||
if !value.is_undefined() { |
|
||||||
let conversion = FieldConversion::from_str(&key.to_string())?; |
|
||||||
let converted_value = match conversion { |
|
||||||
FieldConversion::ToIntegerWithTruncation => { |
|
||||||
let v = to_integer_with_truncation(&value, context)?; |
|
||||||
FieldValue::Integer(v) |
|
||||||
} |
|
||||||
FieldConversion::ToPositiveIntegerWithTruncation => { |
|
||||||
let v = to_positive_integer_with_trunc(&value, context)?; |
|
||||||
FieldValue::Integer(v) |
|
||||||
} |
|
||||||
FieldConversion::ToPrimativeAndRequireString => { |
|
||||||
let primitive = value.to_primitive(context, PreferredType::String)?; |
|
||||||
FieldValue::String( |
|
||||||
primitive.to_string(context)?.to_std_string_escaped(), |
|
||||||
) |
|
||||||
} |
|
||||||
FieldConversion::None => { |
|
||||||
unreachable!("todo need to implement conversion handling for tz.") |
|
||||||
} |
|
||||||
}; |
|
||||||
// TODO: Test the below further and potentially expand handling.
|
|
||||||
copy.set_field_value(&key.to_string(), &converted_value) |
|
||||||
.expect("FieldConversion enforces the appropriate type"); |
|
||||||
} |
|
||||||
} |
|
||||||
_ => {} |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
Ok(copy) |
|
||||||
} |
|
||||||
|
|
||||||
impl JsObject { |
|
||||||
pub(crate) fn from_temporal_fields( |
|
||||||
fields: &TemporalFields, |
|
||||||
context: &mut Context, |
|
||||||
) -> JsResult<Self> { |
|
||||||
let obj = JsObject::with_null_proto(); |
|
||||||
|
|
||||||
for (key, value) in fields.active_kvs() { |
|
||||||
let js_value = match value { |
|
||||||
FieldValue::Undefined => JsValue::undefined(), |
|
||||||
FieldValue::Integer(x) => JsValue::Integer(x), |
|
||||||
FieldValue::String(s) => JsValue::String(s.into()), |
|
||||||
}; |
|
||||||
|
|
||||||
obj.create_data_property_or_throw(JsString::from(key), js_value, context)?; |
|
||||||
} |
|
||||||
|
|
||||||
Ok(obj) |
|
||||||
} |
|
||||||
} |
|
@ -1,63 +0,0 @@ |
|||||||
//! A custom `TimeZone` object.
|
|
||||||
use crate::{property::PropertyKey, Context, JsObject, JsValue}; |
|
||||||
|
|
||||||
use boa_gc::{Finalize, Trace}; |
|
||||||
use boa_macros::js_str; |
|
||||||
use num_bigint::BigInt; |
|
||||||
use temporal_rs::{ |
|
||||||
components::{tz::TzProtocol, Instant}, |
|
||||||
TemporalError, TemporalResult, |
|
||||||
}; |
|
||||||
|
|
||||||
#[derive(Debug, Clone, Trace, Finalize)] |
|
||||||
pub(crate) struct JsCustomTimeZone { |
|
||||||
tz: JsObject, |
|
||||||
} |
|
||||||
|
|
||||||
impl TzProtocol for JsCustomTimeZone { |
|
||||||
type Context = Context; |
|
||||||
fn get_offset_nanos_for(&self, context: &mut Context) -> TemporalResult<BigInt> { |
|
||||||
let method = self |
|
||||||
.tz |
|
||||||
.get(js_str!("getOffsetNanosFor"), context) |
|
||||||
.expect("Method must exist for the custom calendar to be valid."); |
|
||||||
|
|
||||||
let result = method |
|
||||||
.as_callable() |
|
||||||
.expect("is method") |
|
||||||
.call(&method, &[], context) |
|
||||||
.map_err(|e| TemporalError::general(e.to_string()))?; |
|
||||||
|
|
||||||
// TODO (nekevss): Validate that the below conversion is fine vs. matching to JsValue::BigInt()
|
|
||||||
let Some(bigint) = result.as_bigint() else { |
|
||||||
return Err(TemporalError::r#type() |
|
||||||
.with_message("Expected BigInt return from getOffsetNanosFor")); |
|
||||||
}; |
|
||||||
|
|
||||||
Ok(bigint.as_inner().clone()) |
|
||||||
} |
|
||||||
|
|
||||||
fn get_possible_instant_for(&self, _context: &mut Context) -> TemporalResult<Vec<Instant>> { |
|
||||||
// TODO: Implement once Instant has been migrated to `boa_temporal`'s Instant.
|
|
||||||
Err(TemporalError::range().with_message("Not yet implemented.")) |
|
||||||
} |
|
||||||
|
|
||||||
fn id(&self, context: &mut Context) -> TemporalResult<String> { |
|
||||||
let ident = self |
|
||||||
.tz |
|
||||||
.__get__( |
|
||||||
&PropertyKey::from(js_str!("id")), |
|
||||||
JsValue::undefined(), |
|
||||||
&mut context.into(), |
|
||||||
) |
|
||||||
.expect("Method must exist for the custom calendar to be valid."); |
|
||||||
|
|
||||||
let JsValue::String(id) = ident else { |
|
||||||
return Err( |
|
||||||
TemporalError::r#type().with_message("Invalid custom Time Zone identifier type.") |
|
||||||
); |
|
||||||
}; |
|
||||||
|
|
||||||
Ok(id.to_std_string_escaped()) |
|
||||||
} |
|
||||||
} |
|
Loading…
Reference in new issue