Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - DateTimeFormat helpers #2064

Closed
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions boa_engine/src/builtins/intl/date_time_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,123 @@ impl DateTimeFormat {
Ok(date_time_format.into())
}
}

/// Enumeration for `required` and `defaults` arguments in `toDateTimeOptions` subroutine
/// `required` can take Date/Time/Any values, `defaults` can take Date/Time/All
/// `Any` and `All` are merged into one `AnyAll` value.
NorbertGarfield marked this conversation as resolved.
Show resolved Hide resolved
#[cfg(test)]
NorbertGarfield marked this conversation as resolved.
Show resolved Hide resolved
#[derive(Debug, PartialEq)]
pub(crate) enum DateTimeReqs {
Date,
Time,
AnyAll,
}

/// The abstract operation `toDateTimeOptions` is called with arguments `options`, `required` and
/// `defaults`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma402/#sec-todatetimeoptions
#[cfg(test)]
NorbertGarfield marked this conversation as resolved.
Show resolved Hide resolved
pub(crate) fn to_date_time_options(
options: &JsValue,
required: &DateTimeReqs,
defaults: &DateTimeReqs,
context: &mut Context,
) -> JsResult<JsObject> {
// 1. If options is undefined, let options be null;
// otherwise let options be ? ToObject(options).
// 2. Let options be ! OrdinaryObjectCreate(options).
let options = if options.is_undefined() {
JsObject::from_proto_and_data(None, ObjectData::ordinary())
} else {
let opt = options.to_object(context)?;
JsObject::from_proto_and_data(opt, ObjectData::ordinary())
};
NorbertGarfield marked this conversation as resolved.
Show resolved Hide resolved

// 3. Let needDefaults be true.
let mut need_defaults = true;

// 4. If required is "date" or "any", then
if [DateTimeReqs::Date, DateTimeReqs::AnyAll].contains(&required) {
// a. For each property name prop of « "weekday", "year", "month", "day" », do
for property in ["weekday", "year", "month", "day"] {
// i. Let value be ? Get(options, prop).
let value = options.get(property, context)?;

// ii. If value is not undefined, let needDefaults be false.
if !value.is_undefined() {
need_defaults = false;
}
}
}

// 5. If required is "time" or "any", then
if [DateTimeReqs::Time, DateTimeReqs::AnyAll].contains(&required) {
// a. For each property name prop of « "dayPeriod", "hour", "minute", "second",
// "fractionalSecondDigits" », do
for property in [
"dayPeriod",
"hour",
"minute",
"second",
"fractionalSecondDigits",
] {
// i. Let value be ? Get(options, prop).
let value = options.get(property, context)?;

// ii. If value is not undefined, let needDefaults be false.
if !value.is_undefined() {
need_defaults = false;
}
}
}

// 6. Let dateStyle be ? Get(options, "dateStyle").
let date_style = options
.get("dateStyle", context)
.unwrap_or_else(|_| JsValue::undefined());
NorbertGarfield marked this conversation as resolved.
Show resolved Hide resolved

// 7. Let timeStyle be ? Get(options, "timeStyle").
let time_style = options.get("timeStyle", context)?;

// 8. If dateStyle is not undefined or timeStyle is not undefined, let needDefaults be false.
if !date_style.is_undefined() || !time_style.is_undefined() {
need_defaults = false;
}

// 9. If required is "date" and timeStyle is not undefined, then
if required == &DateTimeReqs::Date && !time_style.is_undefined() {
// a. Throw a TypeError exception.
return context.throw_type_error("'date' is required, but timeStyle was defined");
}

// 10. If required is "time" and dateStyle is not undefined, then
if required == &DateTimeReqs::Time && !date_style.is_undefined() {
// a. Throw a TypeError exception.
return context.throw_type_error("'time' is required, but dateStyle was defined");
}

// 11. If needDefaults is true and defaults is either "date" or "all", then
if need_defaults && [DateTimeReqs::Date, DateTimeReqs::AnyAll].contains(&defaults) {
// a. For each property name prop of « "year", "month", "day" », do
for property in ["year", "month", "day"] {
// i. Perform ? CreateDataPropertyOrThrow(options, prop, "numeric").
options.create_data_property_or_throw(property, "numeric", context)?;
}
}

// 12. If needDefaults is true and defaults is either "time" or "all", then
if need_defaults && [DateTimeReqs::Time, DateTimeReqs::AnyAll].contains(&defaults) {
// a. For each property name prop of « "hour", "minute", "second" », do
for property in ["hour", "minute", "second"] {
// i. Perform ? CreateDataPropertyOrThrow(options, prop, "numeric").
options.create_data_property_or_throw(property, "numeric", context)?;
}
}

// 13. Return options.
Ok(options)
}
115 changes: 115 additions & 0 deletions boa_engine/src/builtins/intl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ use crate::{
Context, JsResult, JsString, JsValue,
};

#[cfg(test)]
use crate::object::JsObject;

pub mod date_time_format;
#[cfg(test)]
mod tests;
Expand Down Expand Up @@ -653,3 +656,115 @@ fn resolve_locale(
// 12. Return result.
result
}

#[cfg(test)]
NorbertGarfield marked this conversation as resolved.
Show resolved Hide resolved
pub(crate) enum GetOptionType {
String,
Boolean,
}

/// The abstract operation `GetOption` extracts the value of the property named `property` from the
/// provided `options` object, converts it to the required `type`, checks whether it is one of a
/// `List` of allowed `values`, and fills in a `fallback` value if necessary. If `values` is
/// undefined, there is no fixed set of values and any is permitted.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma402/#sec-getoption
#[cfg(test)]
NorbertGarfield marked this conversation as resolved.
Show resolved Hide resolved
pub(crate) fn get_option(
options: &JsObject,
property: &str,
r#type: &GetOptionType,
values: &[JsString],
fallback: &JsValue,
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Assert: Type(options) is Object.
// 2. Let value be ? Get(options, property).
let mut value = options.get(property, context)?;

// 3. If value is undefined, return fallback.
if value.is_undefined() {
return Ok(fallback.clone());
}

// 4. Assert: type is "boolean" or "string".
// 5. If type is "boolean", then
// a. Set value to ! ToBoolean(value).
// 6. If type is "string", then
// a. Set value to ? ToString(value).
// 7. If values is not undefined and values does not contain an element equal to value,
// throw a RangeError exception.
value = match r#type {
GetOptionType::Boolean => JsValue::Boolean(value.to_boolean()),
GetOptionType::String => {
let string_value = value.to_string(context)?;
if !values.is_empty() && !values.contains(&string_value) {
return context.throw_range_error("GetOption: values array does not contain value");
}
JsValue::String(string_value)
}
};

// 8. Return value.
Ok(value)
}

/// The abstract operation `GetNumberOption` extracts the value of the property named `property`
/// from the provided `options` object, converts it to a `Number value`, checks whether it is in
/// the allowed range, and fills in a `fallback` value if necessary.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma402/#sec-getnumberoption
#[cfg(test)]
NorbertGarfield marked this conversation as resolved.
Show resolved Hide resolved
pub(crate) fn get_number_option(
options: &JsObject,
property: &str,
minimum: f64,
maximum: f64,
fallback: Option<f64>,
context: &mut Context,
) -> JsResult<Option<f64>> {
// 1. Assert: Type(options) is Object.
// 2. Let value be ? Get(options, property).
let value = options.get(property, context)?;

// 3. Return ? DefaultNumberOption(value, minimum, maximum, fallback).
default_number_option(&value, minimum, maximum, fallback, context)
}

/// The abstract operation `DefaultNumberOption` converts `value` to a `Number value`, checks
/// whether it is in the allowed range, and fills in a `fallback` value if necessary.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma402/#sec-defaultnumberoption
#[cfg(test)]
NorbertGarfield marked this conversation as resolved.
Show resolved Hide resolved
pub(crate) fn default_number_option(
value: &JsValue,
minimum: f64,
maximum: f64,
fallback: Option<f64>,
context: &mut Context,
) -> JsResult<Option<f64>> {
// 1. If value is undefined, return fallback.
if value.is_undefined() {
return Ok(fallback);
}

// 2. Set value to ? ToNumber(value).
let value = value.to_number(context)?;

// 3. If value is NaN or less than minimum or greater than maximum, throw a RangeError exception.
if value.is_nan() || value.lt(&minimum) || value.gt(&maximum) {
NorbertGarfield marked this conversation as resolved.
Show resolved Hide resolved
return context.throw_range_error("DefaultNumberOption: value is out of range.");
}

// 4. Return floor(value).
Ok(Some(value.floor()))
}
Loading