use crate::{
builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject},
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
error::JsNativeError,
js_string,
object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData},
realm::Realm,
string::utf16,
Context, JsResult, JsString, JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_profiler::Profiler;
use icu_datetime::options::preferences::HourCycle;
use super::options::OptionType;
impl OptionType for HourCycle {
fn from_value(value: JsValue, context: &mut Context<'_>) -> JsResult<Self> {
match value.to_string(context)?.to_std_string_escaped().as_str() {
"h11" => Ok(Self::H11),
"h12" => Ok(Self::H12),
"h23" => Ok(Self::H23),
"h24" => Ok(Self::H24),
_ => Err(JsNativeError::range()
.with_message("provided string was not `h11`, `h12`, `h23` or `h24`")
.into()),
}
}
}
#[derive(Debug, Clone, Trace, Finalize)]
pub struct DateTimeFormat {
initialized_date_time_format: bool,
locale: JsString,
calendar: JsString,
numbering_system: JsString,
time_zone: JsString,
weekday: JsString,
era: JsString,
year: JsString,
month: JsString,
day: JsString,
day_period: JsString,
hour: JsString,
minute: JsString,
second: JsString,
fractional_second_digits: JsString,
time_zone_name: JsString,
hour_cycle: JsString,
pattern: JsString,
bound_format: JsString,
}
impl IntrinsicObject for DateTimeFormat {
fn init(realm: &Realm) {
let _timer = Profiler::global().start_event(Self::NAME, "init");
BuiltInBuilder::from_standard_constructor::<Self>(realm).build();
}
fn get(intrinsics: &Intrinsics) -> JsObject {
Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor()
}
}
impl BuiltInObject for DateTimeFormat {
const NAME: &'static str = "DateTimeFormat";
}
impl BuiltInConstructor for DateTimeFormat {
const LENGTH: usize = 0;
const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
StandardConstructors::date_time_format;
fn constructor(
new_target: &JsValue,
_args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
let new_target = &if new_target.is_undefined() {
context
.vm
.active_function
.clone()
.unwrap_or_else(|| {
context
.intrinsics()
.constructors()
.date_time_format()
.constructor()
})
.into()
} else {
new_target.clone()
};
let prototype = get_prototype_from_constructor(
new_target,
StandardConstructors::date_time_format,
context,
)?;
let date_time_format = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
prototype,
ObjectData::date_time_format(Box::new(Self {
initialized_date_time_format: true,
locale: js_string!("en-US"),
calendar: js_string!("gregory"),
numbering_system: js_string!("arab"),
time_zone: js_string!("UTC"),
weekday: js_string!("narrow"),
era: js_string!("narrow"),
year: js_string!("numeric"),
month: js_string!("narrow"),
day: js_string!("numeric"),
day_period: js_string!("narrow"),
hour: js_string!("numeric"),
minute: js_string!("numeric"),
second: js_string!("numeric"),
fractional_second_digits: js_string!(""),
time_zone_name: js_string!(""),
hour_cycle: js_string!("h24"),
pattern: js_string!("{hour}:{minute}"),
bound_format: js_string!("undefined"),
})),
);
Ok(date_time_format.into())
}
}
#[allow(unused)]
#[derive(Debug, PartialEq)]
pub(crate) enum DateTimeReqs {
Date,
Time,
AnyAll,
}
#[allow(unused)]
pub(crate) fn to_date_time_options(
options: &JsValue,
required: &DateTimeReqs,
defaults: &DateTimeReqs,
context: &mut Context<'_>,
) -> JsResult<JsObject> {
let options = if options.is_undefined() {
None
} else {
Some(options.to_object(context)?)
};
let options = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
options,
ObjectData::ordinary(),
);
let mut need_defaults = true;
if [DateTimeReqs::Date, DateTimeReqs::AnyAll].contains(required) {
for property in [
utf16!("weekday"),
utf16!("year"),
utf16!("month"),
utf16!("day"),
] {
let value = options.get(property, context)?;
if !value.is_undefined() {
need_defaults = false;
}
}
}
if [DateTimeReqs::Time, DateTimeReqs::AnyAll].contains(required) {
for property in [
utf16!("dayPeriod"),
utf16!("hour"),
utf16!("minute"),
utf16!("second"),
utf16!("fractionalSecondDigits"),
] {
let value = options.get(property, context)?;
if !value.is_undefined() {
need_defaults = false;
}
}
}
let date_style = options.get(utf16!("dateStyle"), context)?;
let time_style = options.get(utf16!("timeStyle"), context)?;
if !date_style.is_undefined() || !time_style.is_undefined() {
need_defaults = false;
}
if required == &DateTimeReqs::Date && !time_style.is_undefined() {
return Err(JsNativeError::typ()
.with_message("'date' is required, but timeStyle was defined")
.into());
}
if required == &DateTimeReqs::Time && !date_style.is_undefined() {
return Err(JsNativeError::typ()
.with_message("'time' is required, but dateStyle was defined")
.into());
}
if need_defaults && [DateTimeReqs::Date, DateTimeReqs::AnyAll].contains(defaults) {
for property in [utf16!("year"), utf16!("month"), utf16!("day")] {
options.create_data_property_or_throw(property, "numeric", context)?;
}
}
if need_defaults && [DateTimeReqs::Time, DateTimeReqs::AnyAll].contains(defaults) {
for property in [utf16!("hour"), utf16!("minute"), utf16!("second")] {
options.create_data_property_or_throw(property, "numeric", context)?;
}
}
Ok(options)
}