use crate::{
Context, JsData, JsResult, JsString, JsValue,
builtins::{
BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, OrdinaryObject,
options::OptionType,
},
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
error::JsNativeError,
js_string,
object::{JsObject, internal_methods::get_prototype_from_constructor},
realm::Realm,
string::StaticJsStrings,
};
use boa_gc::{Finalize, Trace};
use icu_calendar::preferences::CalendarAlgorithm;
use icu_datetime::preferences::HourCycle;
use icu_locale::extensions::unicode::Value;
#[derive(Debug, Clone, Trace, Finalize, JsData)]
pub(crate) struct DateTimeFormat {
initialized: 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) {
BuiltInBuilder::from_standard_constructor::<Self>(realm).build();
}
fn get(intrinsics: &Intrinsics) -> JsObject {
Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor()
}
}
impl BuiltInObject for DateTimeFormat {
const NAME: JsString = StaticJsStrings::DATE_TIME_FORMAT;
}
impl BuiltInConstructor for DateTimeFormat {
const CONSTRUCTOR_ARGUMENTS: usize = 0;
const PROTOTYPE_STORAGE_SLOTS: usize = 0;
const CONSTRUCTOR_STORAGE_SLOTS: 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
.active_function_object()
.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,
Self {
initialized: 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,
OrdinaryObject,
);
let mut need_defaults = true;
if [DateTimeReqs::Date, DateTimeReqs::AnyAll].contains(required) {
for property in [
js_string!("weekday"),
js_string!("year"),
js_string!("month"),
js_string!("day"),
] {
let value = options.get(property, context)?;
if !value.is_undefined() {
need_defaults = false;
}
}
}
if [DateTimeReqs::Time, DateTimeReqs::AnyAll].contains(required) {
for property in [
js_string!("dayPeriod"),
js_string!("hour"),
js_string!("minute"),
js_string!("second"),
js_string!("fractionalSecondDigits"),
] {
let value = options.get(property, context)?;
if !value.is_undefined() {
need_defaults = false;
}
}
}
let date_style = options.get(js_string!("dateStyle"), context)?;
let time_style = options.get(js_string!("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 [js_string!("year"), js_string!("month"), js_string!("day")] {
options.create_data_property_or_throw(property, js_string!("numeric"), context)?;
}
}
if need_defaults && [DateTimeReqs::Time, DateTimeReqs::AnyAll].contains(defaults) {
for property in [
js_string!("hour"),
js_string!("minute"),
js_string!("second"),
] {
options.create_data_property_or_throw(property, js_string!("numeric"), context)?;
}
}
Ok(options)
}
impl OptionType for CalendarAlgorithm {
fn from_value(value: JsValue, context: &mut Context) -> JsResult<Self> {
let s = value.to_string(context)?.to_std_string_escaped();
Value::try_from_str(&s)
.ok()
.and_then(|v| CalendarAlgorithm::try_from(&v).ok())
.ok_or_else(|| {
JsNativeError::range()
.with_message(format!("provided calendar `{s}` is invalid"))
.into()
})
}
}
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(HourCycle::H11),
"h12" => Ok(HourCycle::H12),
"h23" => Ok(HourCycle::H23),
_ => Err(JsNativeError::range()
.with_message("provided hour cycle was not `h11`, `h12` or `h23`")
.into()),
}
}
}