From 8fd5533460af8c0c424f6ddf4f3d45ed84815f8f Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Fri, 7 Aug 2020 06:42:51 -0700 Subject: [PATCH] Date Implementation (#596) Co-authored-by: Jonathan Dickinson --- Cargo.lock | 22 + boa/Cargo.toml | 1 + boa/src/builtins/date/mod.rs | 1428 +++++++++++++++++++++++++ boa/src/builtins/date/tests.rs | 1431 ++++++++++++++++++++++++++ boa/src/builtins/mod.rs | 3 + boa/src/builtins/object/mod.rs | 6 +- boa/src/builtins/object/tests.rs | 2 + boa/src/builtins/value/mod.rs | 27 +- boa/src/builtins/value/operations.rs | 8 +- boa/src/builtins/value/val_type.rs | 2 + boa/src/realm.rs | 4 + 11 files changed, 2930 insertions(+), 4 deletions(-) create mode 100644 boa/src/builtins/date/mod.rs create mode 100644 boa/src/builtins/date/tests.rs diff --git a/Cargo.lock b/Cargo.lock index e7b28546ff..7c61e2043a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,6 +5,7 @@ name = "Boa" version = "0.9.0" dependencies = [ "bitflags", + "chrono", "criterion", "gc", "indexmap", @@ -160,6 +161,17 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +[[package]] +name = "chrono" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c74d84029116787153e02106bf53e66828452a4b325cc8652b788b5967c0a0b6" +dependencies = [ + "num-integer", + "num-traits", + "time", +] + [[package]] name = "clap" version = "2.33.1" @@ -1010,6 +1022,16 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "tinytemplate" version = "1.1.0" diff --git a/boa/Cargo.toml b/boa/Cargo.toml index dad44d38e2..cbb18af9b8 100644 --- a/boa/Cargo.toml +++ b/boa/Cargo.toml @@ -25,6 +25,7 @@ num-integer = "0.1.43" bitflags = "1.2.1" indexmap = "1.4.0" ryu-js = "0.2.0" +chrono = "0.4" # Optional Dependencies serde = { version = "1.0.114", features = ["derive"], optional = true } diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs new file mode 100644 index 0000000000..d15ead4b36 --- /dev/null +++ b/boa/src/builtins/date/mod.rs @@ -0,0 +1,1428 @@ +#[cfg(test)] +mod tests; + +use crate::{ + builtins::{ + function::{make_builtin_fn, make_constructor_fn}, + object::ObjectData, + ResultValue, Value, + }, + exec::PreferredType, + BoaProfiler, Interpreter, +}; +use chrono::{prelude::*, Duration, LocalResult}; +use gc::{unsafe_empty_trace, Finalize, Trace}; +use std::fmt::Display; + +const NANOS_IN_MS: f64 = 1_000_000f64; + +#[inline] +fn is_zero_or_normal_opt(value: Option) -> bool { + value + .map(|value| value == 0f64 || value.is_normal()) + .unwrap_or(true) +} + +macro_rules! check_normal_opt { + ($($v:expr),+) => { + $(is_zero_or_normal_opt($v.into()) &&)+ true + }; +} + +#[inline] +fn ignore_ambiguity(result: LocalResult) -> Option { + match result { + LocalResult::Ambiguous(v, _) => Some(v), + LocalResult::Single(v) => Some(v), + LocalResult::None => None, + } +} + +macro_rules! getter_method { + ($name:ident) => {{ + fn get_value(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + Ok(Value::from(this_time_value(this, ctx)?.$name())) + } + get_value + }}; + (Self::$name:ident) => {{ + fn get_value(_: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(Date::$name())) + } + get_value + }}; +} + +macro_rules! setter_method { + ($name:ident($($e:expr),* $(,)?)) => {{ + fn set_value(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + let mut result = this_time_value(this, ctx)?; + result.$name( + $( + args + .get($e) + .and_then(|value| { + ctx.to_numeric_number(value).map_or_else( + |_| None, + |value| { + if value == 0f64 || value.is_normal() { + Some(value) + } else { + None + } + }, + ) + }) + ),* + ); + + this.set_data(ObjectData::Date(result)); + Ok(Value::from(result.get_time())) + } + set_value + }}; +} + +#[derive(Debug, Finalize, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Date(Option); + +impl Display for Date { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.to_local() { + Some(v) => write!(f, "{}", v.format("%a %b %d %Y %H:%M:%S GMT%:z")), + _ => write!(f, "Invalid Date"), + } + } +} + +unsafe impl Trace for Date { + // Date is a stack value, it doesn't require tracing. + // only safe if `chrono` never implements `Trace` for `NaiveDateTime` + unsafe_empty_trace!(); +} + +impl Default for Date { + fn default() -> Self { + Self(Some(Utc::now().naive_utc())) + } +} + +impl Date { + /// The name of the object. + pub(crate) const NAME: &'static str = "Date"; + + /// The amount of arguments this function object takes. + pub(crate) const LENGTH: usize = 7; + + /// Converts the `Date` to a local `DateTime`. + /// + /// If the `Date` is invalid (i.e. NAN), this function will return `None`. + pub fn to_local(&self) -> Option> { + self.0 + .map(|utc| Local::now().timezone().from_utc_datetime(&utc)) + } + + /// Converts the `Date` to a UTC `DateTime`. + /// + /// If the `Date` is invalid (i.e. NAN), this function will return `None`. + pub fn to_utc(&self) -> Option> { + self.0 + .map(|utc| Utc::now().timezone().from_utc_datetime(&utc)) + } + + /// Optionally sets the individual components of the `Date`. + /// + /// Each component does not have to be within the range of valid values. For example, if `month` is too large + /// then `year` will be incremented by the required amount. + #[allow(clippy::too_many_arguments)] + pub fn set_components( + &mut self, + utc: bool, + year: Option, + month: Option, + day: Option, + hour: Option, + minute: Option, + second: Option, + millisecond: Option, + ) { + #[inline] + fn num_days_in(year: i32, month: u32) -> i32 { + let month = month + 1; // zero-based for calculations + NaiveDate::from_ymd( + match month { + 12 => year + 1, + _ => year, + }, + match month { + 12 => 1, + _ => month + 1, + }, + 1, + ) + .signed_duration_since(NaiveDate::from_ymd(year, month, 1)) + .num_days() as i32 + } + + #[inline] + fn fix_month(year: &mut i32, month: &mut i32) { + *year += *month / 12; + *month = if *month < 0 { + *year -= 1; + 11 + (*month + 1) % 12 + } else { + *month % 12 + } + } + + #[inline] + fn fix_day(year: &mut i32, month: &mut i32, day: &mut i32) { + fix_month(year, month); + loop { + if *day < 0 { + *month -= 1; + fix_month(year, month); + *day += num_days_in(*year, *month as u32); + } else { + let num_days = num_days_in(*year, *month as u32); + if *day >= num_days { + *day -= num_days_in(*year, *month as u32); + *month += 1; + fix_month(year, month); + } else { + break; + } + } + } + } + + // If any of the args are infinity or NaN, return an invalid date. + if !check_normal_opt!(year, month, day, hour, minute, second, millisecond) { + self.0 = None; + return; + } + + let naive = if utc { + self.to_utc().map(|dt| dt.naive_utc()) + } else { + self.to_local().map(|dt| dt.naive_local()) + }; + + self.0 = naive.and_then(|naive| { + let mut year = year.unwrap_or_else(|| naive.year() as f64) as i32; + let mut month = month.unwrap_or_else(|| naive.month0() as f64) as i32; + let mut day = day.unwrap_or_else(|| naive.day() as f64) as i32 - 1; + let hour = hour.unwrap_or_else(|| naive.hour() as f64) as i64; + let minute = minute.unwrap_or_else(|| naive.minute() as f64) as i64; + let second = second.unwrap_or_else(|| naive.second() as f64) as i64; + let millisecond = + millisecond.unwrap_or_else(|| naive.nanosecond() as f64 / NANOS_IN_MS) as i64; + + fix_day(&mut year, &mut month, &mut day); + + let duration = Duration::hours(hour) + + Duration::minutes(minute) + + Duration::seconds(second) + + Duration::milliseconds(millisecond); + NaiveDate::from_ymd_opt(year, month as u32 + 1, day as u32 + 1) + .and_then(|dt| dt.and_hms(0, 0, 0).checked_add_signed(duration)) + .and_then(|dt| { + if utc { + Some(Utc.from_utc_datetime(&dt).naive_utc()) + } else { + ignore_ambiguity(Local.from_local_datetime(&dt)).map(|dt| dt.naive_utc()) + } + }) + }); + } + + /// `Date()` + /// + /// Creates a JavaScript `Date` instance that represents a single moment in time in a platform-independent format. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date-constructor + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date + pub(crate) fn make_date(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + if this.is_global() { + Self::make_date_string() + } else if args.is_empty() { + Self::make_date_now(this) + } else if args.len() == 1 { + Self::make_date_single(this, args, ctx) + } else { + Self::make_date_multiple(this, args, ctx) + } + } + + /// `Date()` + /// + /// The `Date()` function is used to create a string that represent the current date and time. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date-constructor + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date + pub(crate) fn make_date_string() -> ResultValue { + Ok(Value::from(Local::now().to_rfc3339())) + } + + /// `Date()` + /// + /// The newly-created `Date` object represents the current date and time as of the time of instantiation. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date-constructor + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date + pub(crate) fn make_date_now(this: &Value) -> ResultValue { + let date = Date::default(); + this.set_data(ObjectData::Date(date)); + Ok(this.clone()) + } + + /// `Date(value)` + /// + /// The newly-created `Date` object represents the value provided to the constructor. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date-constructor + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date + pub(crate) fn make_date_single( + this: &Value, + args: &[Value], + ctx: &mut Interpreter, + ) -> ResultValue { + let value = &args[0]; + let tv = match this_time_value(value, ctx) { + Ok(dt) => dt.0, + _ => match &ctx.to_primitive(value, PreferredType::Default)? { + Value::String(str) => match chrono::DateTime::parse_from_rfc3339(&str) { + Ok(dt) => Some(dt.naive_utc()), + _ => None, + }, + tv => { + let tv = ctx.to_number(&tv)?; + let secs = (tv / 1_000f64) as i64; + let nsecs = ((tv % 1_000f64) * 1_000_000f64) as u32; + NaiveDateTime::from_timestamp_opt(secs, nsecs) + } + }, + }; + + let date = Date(tv); + this.set_data(ObjectData::Date(date)); + Ok(this.clone()) + } + + /// `Date(year, month [ , date [ , hours [ , minutes [ , seconds [ , ms ] ] ] ] ])` + /// + /// The newly-created `Date` object represents the date components provided to the constructor. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date-constructor + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date + pub(crate) fn make_date_multiple( + this: &Value, + args: &[Value], + ctx: &mut Interpreter, + ) -> ResultValue { + let year = ctx.to_number(&args[0])?; + let month = ctx.to_number(&args[1])?; + let day = args.get(2).map_or(Ok(1f64), |value| ctx.to_number(value))?; + let hour = args.get(3).map_or(Ok(0f64), |value| ctx.to_number(value))?; + let min = args.get(4).map_or(Ok(0f64), |value| ctx.to_number(value))?; + let sec = args.get(5).map_or(Ok(0f64), |value| ctx.to_number(value))?; + let milli = args.get(6).map_or(Ok(0f64), |value| ctx.to_number(value))?; + + // If any of the args are infinity or NaN, return an invalid date. + if !check_normal_opt!(year, month, day, hour, min, sec, milli) { + let date = Date(None); + this.set_data(ObjectData::Date(date)); + return Ok(this.clone()); + } + + let year = year as i32; + let month = month as u32; + let day = day as u32; + let hour = hour as u32; + let min = min as u32; + let sec = sec as u32; + let milli = milli as u32; + + let year = if 0 <= year && year <= 99 { + 1900 + year + } else { + year + }; + + let final_date = NaiveDate::from_ymd_opt(year, month + 1, day) + .and_then(|naive_date| naive_date.and_hms_milli_opt(hour, min, sec, milli)) + .and_then(|local| ignore_ambiguity(Local.from_local_datetime(&local))) + .map(|local| local.naive_utc()); + + let date = Date(final_date); + this.set_data(ObjectData::Date(date)); + Ok(this.clone()) + } + + /// `Date.prototype.getDate()` + /// + /// The `getDate()` method returns the day of the month for the specified date according to local time. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.getdate + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getDate + pub fn get_date(&self) -> f64 { + self.to_local().map_or(f64::NAN, |dt| dt.day() as f64) + } + + /// `Date.prototype.getDay()` + /// + /// The `getDay()` method returns the day of the week for the specified date according to local time, where 0 + /// represents Sunday. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.getday + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getDay + pub fn get_day(&self) -> f64 { + self.to_local().map_or(f64::NAN, |dt| { + let weekday = dt.weekday() as u32; + let weekday = (weekday + 1) % 7; // 0 represents Monday in Chrono + weekday as f64 + }) + } + + /// `Date.prototype.getFullYear()` + /// + /// The `getFullYear()` method returns the year of the specified date according to local time. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.getfullyear + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getFullYear + pub fn get_full_year(&self) -> f64 { + self.to_local().map_or(f64::NAN, |dt| dt.year() as f64) + } + + /// `Date.prototype.getHours()` + /// + /// The `getHours()` method returns the hour for the specified date, according to local time. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.gethours + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getHours + pub fn get_hours(&self) -> f64 { + self.to_local().map_or(f64::NAN, |dt| dt.hour() as f64) + } + + /// `Date.prototype.getMilliseconds()` + /// + /// The `getMilliseconds()` method returns the milliseconds in the specified date according to local time. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.getmilliseconds + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getMilliseconds + pub fn get_milliseconds(&self) -> f64 { + self.to_local() + .map_or(f64::NAN, |dt| dt.nanosecond() as f64 / NANOS_IN_MS) + } + + /// `Date.prototype.getMinutes()` + /// + /// The `getMinutes()` method returns the minutes in the specified date according to local time. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.getminutes + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getMinutes + pub fn get_minutes(&self) -> f64 { + self.to_local().map_or(f64::NAN, |dt| dt.minute() as f64) + } + + /// `Date.prototype.getMonth()` + /// + /// The `getMonth()` method returns the month in the specified date according to local time, as a zero-based value + /// (where zero indicates the first month of the year). + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.getmonth + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getMonth + pub fn get_month(&self) -> f64 { + self.to_local().map_or(f64::NAN, |dt| dt.month0() as f64) + } + + /// `Date.prototype.getSeconds()` + /// + /// The `getSeconds()` method returns the seconds in the specified date according to local time. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.getseconds + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getSeconds + pub fn get_seconds(&self) -> f64 { + self.to_local().map_or(f64::NAN, |dt| dt.second() as f64) + } + + /// `Date.prototype.getYear()` + /// + /// The getYear() method returns the year in the specified date according to local time. Because getYear() does not + /// return full years ("year 2000 problem"), it is no longer used and has been replaced by the getFullYear() method. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.getyear + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getYear + pub fn get_year(&self) -> f64 { + self.to_local() + .map_or(f64::NAN, |dt| dt.year() as f64 - 1900f64) + } + + /// `Date.prototype.getTime()` + /// + /// The `getTime()` method returns the number of milliseconds since the Unix Epoch. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.gettime + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTime + pub fn get_time(&self) -> f64 { + self.to_utc() + .map_or(f64::NAN, |dt| dt.timestamp_millis() as f64) + } + + /// `Date.prototype.getTimeZoneOffset()` + /// + /// The getTimezoneOffset() method returns the time zone difference, in minutes, from current locale (host system + /// settings) to UTC. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.gettimezoneoffset + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset + #[inline] + pub fn get_timezone_offset() -> f64 { + let offset_seconds = chrono::Local::now().offset().local_minus_utc() as f64; + offset_seconds / 60f64 + } + + /// `Date.prototype.getUTCDate()` + /// + /// The `getUTCDate()` method returns the day (date) of the month in the specified date according to universal time. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.getutcdate + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getUTCDate + pub fn get_utc_date(&self) -> f64 { + self.to_utc().map_or(f64::NAN, |dt| dt.day() as f64) + } + + /// `Date.prototype.getUTCDay()` + /// + /// The `getUTCDay()` method returns the day of the week in the specified date according to universal time, where 0 + /// represents Sunday. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.getutcday + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getUTCDay + pub fn get_utc_day(&self) -> f64 { + self.to_utc().map_or(f64::NAN, |dt| { + let weekday = dt.weekday() as u32; + let weekday = (weekday + 1) % 7; // 0 represents Monday in Chrono + weekday as f64 + }) + } + + /// `Date.prototype.getUTCFullYear()` + /// + /// The `getUTCFullYear()` method returns the year in the specified date according to universal time. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.getutcfullyear + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getUTCFullYear + pub fn get_utc_full_year(&self) -> f64 { + self.to_utc().map_or(f64::NAN, |dt| dt.year() as f64) + } + + /// `Date.prototype.getUTCHours()` + /// + /// The `getUTCHours()` method returns the hours in the specified date according to universal time. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.getutchours + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getUTCHours + pub fn get_utc_hours(&self) -> f64 { + self.to_utc().map_or(f64::NAN, |dt| dt.hour() as f64) + } + + /// `Date.prototype.getUTCMilliseconds()` + /// + /// The `getUTCMilliseconds()` method returns the milliseconds portion of the time object's value. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.getutcmilliseconds + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getUTCMilliseconds + pub fn get_utc_milliseconds(&self) -> f64 { + self.to_utc() + .map_or(f64::NAN, |dt| dt.nanosecond() as f64 / NANOS_IN_MS) + } + + /// `Date.prototype.getUTCMinutes()` + /// + /// The `getUTCMinutes()` method returns the minutes in the specified date according to universal time. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.getutcminutes + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getUTCMinutes + pub fn get_utc_minutes(&self) -> f64 { + self.to_utc().map_or(f64::NAN, |dt| dt.minute() as f64) + } + + /// `Date.prototype.getUTCMonth()` + /// + /// The `getUTCMonth()` returns the month of the specified date according to universal time, as a zero-based value + /// (where zero indicates the first month of the year). + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.getutcmonth + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getUTCMonth + pub fn get_utc_month(&self) -> f64 { + self.to_utc().map_or(f64::NAN, |dt| dt.month0() as f64) + } + + /// `Date.prototype.getUTCSeconds()` + /// + /// The `getUTCSeconds()` method returns the seconds in the specified date according to universal time. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.getutcseconds + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getUTCSeconds + pub fn get_utc_seconds(&self) -> f64 { + self.to_utc().map_or(f64::NAN, |dt| dt.second() as f64) + } + + /// `Date.prototype.setDate()` + /// + /// The `setDate()` method sets the day of the `Date` object relative to the beginning of the currently set + /// month. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setdate + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setDate + pub fn set_date(&mut self, day: Option) { + if let Some(day) = day { + self.set_components(false, None, None, Some(day), None, None, None, None) + } else { + self.0 = None + } + } + + /// `Date.prototype.setFullYear()` + /// + /// The `setFullYear()` method sets the full year for a specified date according to local time. Returns new + /// timestamp. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setfullyear + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setFullYear + pub fn set_full_year(&mut self, year: Option, month: Option, day: Option) { + if let Some(year) = year { + self.set_components(false, Some(year), month, day, None, None, None, None) + } else { + self.0 = None + } + } + + /// `Date.prototype.setHours()` + /// + /// The `setHours()` method sets the hours for a specified date according to local time, and returns the number + /// of milliseconds since January 1, 1970 00:00:00 UTC until the time represented by the updated `Date` + /// instance. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.sethours + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setHours + pub fn set_hours( + &mut self, + hour: Option, + minute: Option, + second: Option, + millisecond: Option, + ) { + if let Some(hour) = hour { + self.set_components( + false, + None, + None, + None, + Some(hour), + minute, + second, + millisecond, + ) + } else { + self.0 = None + } + } + + /// `Date.prototype.setMilliseconds()` + /// + /// The `setMilliseconds()` method sets the milliseconds for a specified date according to local time. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setmilliseconds + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setMilliseconds + pub fn set_milliseconds(&mut self, millisecond: Option) { + if let Some(millisecond) = millisecond { + self.set_components(false, None, None, None, None, None, None, Some(millisecond)) + } else { + self.0 = None + } + } + + /// `Date.prototype.setMinutes()` + /// + /// The `setMinutes()` method sets the minutes for a specified date according to local time. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setminutes + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setMinutes + pub fn set_minutes( + &mut self, + minute: Option, + second: Option, + millisecond: Option, + ) { + if let Some(minute) = minute { + self.set_components( + false, + None, + None, + None, + None, + Some(minute), + second, + millisecond, + ) + } else { + self.0 = None + } + } + + /// `Date.prototype.setMonth()` + /// + /// The `setMonth()` method sets the month for a specified date according to the currently set year. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setmonth + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setMonth + pub fn set_month(&mut self, month: Option, day: Option) { + if let Some(month) = month { + self.set_components(false, None, Some(month), day, None, None, None, None) + } else { + self.0 = None + } + } + + /// `Date.prototype.setSeconds()` + /// + /// The `setSeconds()` method sets the seconds for a specified date according to local time. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setseconds + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setSeconds + pub fn set_seconds(&mut self, second: Option, millisecond: Option) { + if let Some(second) = second { + self.set_components( + false, + None, + None, + None, + None, + None, + Some(second), + millisecond, + ) + } else { + self.0 = None + } + } + + /// `Date.prototype.setYear()` + /// + /// The `setYear()` method sets the year for a specified date according to local time. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setyear + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setYear + pub fn set_year(&mut self, year: Option, month: Option, day: Option) { + if let Some(mut year) = year { + year += if 0f64 <= year && year < 100f64 { + 1900f64 + } else { + 0f64 + }; + self.set_components(false, Some(year), month, day, None, None, None, None) + } else { + self.0 = None + } + } + + /// `Date.prototype.setTime()` + /// + /// The `setTime()` method sets the Date object to the time represented by a number of milliseconds since + /// January 1, 1970, 00:00:00 UTC. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.settime + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setTime + pub fn set_time(&mut self, time: Option) { + if let Some(time) = time { + let secs = (time / 1_000f64) as i64; + let nsecs = ((time % 1_000f64) * 1_000_000f64) as u32; + self.0 = ignore_ambiguity(Local.timestamp_opt(secs, nsecs)).map(|dt| dt.naive_utc()); + } else { + self.0 = None + } + } + + /// `Date.prototype.setUTCDate()` + /// + /// The `setUTCDate()` method sets the day of the month for a specified date according to universal time. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setutcdate + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setUTCDate + pub fn set_utc_date(&mut self, day: Option) { + if let Some(day) = day { + self.set_components(true, None, None, Some(day), None, None, None, None) + } else { + self.0 = None + } + } + + /// `Date.prototype.setFullYear()` + /// + /// The `setFullYear()` method sets the full year for a specified date according to local time. Returns new + /// timestamp. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setutcfullyear + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setUTCFullYear + pub fn set_utc_full_year(&mut self, year: Option, month: Option, day: Option) { + if let Some(year) = year { + self.set_components(true, Some(year), month, day, None, None, None, None) + } else { + self.0 = None + } + } + + /// `Date.prototype.setUTCHours()` + /// + /// The `setUTCHours()` method sets the hour for a specified date according to universal time, and returns the + /// number of milliseconds since January 1, 1970 00:00:00 UTC until the time represented by the updated `Date` + /// instance. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setutchours + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setUTCHours + pub fn set_utc_hours( + &mut self, + hour: Option, + minute: Option, + second: Option, + millisecond: Option, + ) { + if let Some(hour) = hour { + self.set_components( + true, + None, + None, + None, + Some(hour), + minute, + second, + millisecond, + ) + } else { + self.0 = None + } + } + + /// `Date.prototype.setUTCMilliseconds()` + /// + /// The `setUTCMilliseconds()` method sets the milliseconds for a specified date according to universal time. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setutcmilliseconds + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setUTCMilliseconds + pub fn set_utc_milliseconds(&mut self, millisecond: Option) { + if let Some(millisecond) = millisecond { + self.set_components(true, None, None, None, None, None, None, Some(millisecond)) + } else { + self.0 = None + } + } + + /// `Date.prototype.setUTCMinutes()` + /// + /// The `setUTCMinutes()` method sets the minutes for a specified date according to universal time. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setutcminutes + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setUTCMinutes + pub fn set_utc_minutes( + &mut self, + minute: Option, + second: Option, + millisecond: Option, + ) { + if let Some(minute) = minute { + self.set_components( + true, + None, + None, + None, + None, + Some(minute), + second, + millisecond, + ) + } else { + self.0 = None + } + } + + /// `Date.prototype.setUTCMonth()` + /// + /// The `setUTCMonth()` method sets the month for a specified date according to universal time. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setutcmonth + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setUTCMonth + pub fn set_utc_month(&mut self, month: Option, day: Option) { + if let Some(month) = month { + self.set_components(true, None, Some(month), day, None, None, None, None) + } else { + self.0 = None + } + } + + /// `Date.prototype.setUTCSeconds()` + /// + /// The `setUTCSeconds()` method sets the seconds for a specified date according to universal time. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setutcseconds + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setUTCSeconds + pub fn set_utc_seconds(&mut self, second: Option, millisecond: Option) { + if let Some(second) = second { + self.set_components( + true, + None, + None, + None, + None, + None, + Some(second), + millisecond, + ) + } else { + self.0 = None + } + } + + /// `Date.prototype.toDateString()` + /// + /// The `toDateString()` method returns the date portion of a Date object in English. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.todatestring + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toDateString + pub fn to_date_string(&self) -> String { + self.to_local() + .map(|date_time| date_time.format("%a %b %d %Y").to_string()) + .unwrap_or_else(|| "Invalid Date".to_string()) + } + + /// `Date.prototype.toGMTString()` + /// + /// The `toGMTString()` method converts a date to a string, using Internet Greenwich Mean Time (GMT) conventions. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.togmtstring + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toGMTString + pub fn to_gmt_string(&self) -> String { + self.to_utc_string() + } + + /// `Date.prototype.toISOString()` + /// + /// The `toISOString()` method returns a string in simplified extended ISO format (ISO 8601). + /// + /// More information: + /// - [ISO 8601][iso8601] + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [iso8601]: http://en.wikipedia.org/wiki/ISO_8601 + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.toisostring + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString + pub fn to_iso_string(&self) -> String { + self.to_utc() + // RFC 3389 uses +0.00 for UTC, where JS expects Z, so we can't use the built-in chrono function. + .map(|f| f.format("%Y-%m-%dT%H:%M:%S.%3fZ").to_string()) + .unwrap_or_else(|| "Invalid Date".to_string()) + } + + /// `Date.prototype.toJSON()` + /// + /// The `toJSON()` method returns a string representation of the `Date` object. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.tojson + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toJSON + pub fn to_json(&self) -> String { + self.to_iso_string() + } + + /// `Date.prototype.toTimeString()` + /// + /// The `toTimeString()` method returns the time portion of a Date object in human readable form in American + /// English. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.totimestring + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toTimeString + pub fn to_time_string(&self) -> String { + self.to_local() + .map(|date_time| date_time.format("%H:%M:%S GMT%:z").to_string()) + .unwrap_or_else(|| "Invalid Date".to_string()) + } + + /// `Date.prototype.toUTCString()` + /// + /// The `toUTCString()` method returns a string representing the specified Date object. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.toutcstring + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toUTCString + pub fn to_utc_string(&self) -> String { + self.to_utc() + .map(|date_time| date_time.format("%a, %d %b %Y %H:%M:%S GMT").to_string()) + .unwrap_or_else(|| "Invalid Date".to_string()) + } + + /// `Date.prototype.valueOf()` + /// + /// The `valueOf()` method returns the primitive value of a `Date` object. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.valueof + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/valueOf + pub fn value_of(&self) -> f64 { + self.get_time() + } + + /// `Date.now()` + /// + /// The static `Date.now()` method returns the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.now + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now + pub(crate) fn now(_: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(Utc::now().timestamp_millis() as f64)) + } + + /// `Date.parse()` + /// + /// The `Date.parse()` method parses a string representation of a date, and returns the number of milliseconds since + /// January 1, 1970, 00:00:00 UTC or NaN if the string is unrecognized or, in some cases, contains illegal date + /// values. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.parse + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse + pub(crate) fn parse(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + // This method is implementation-defined and discouraged, so we just require the same format as the string + // constructor. + + if args.is_empty() { + return Ok(Value::number(f64::NAN)); + } + + match DateTime::parse_from_rfc3339(&ctx.to_string(&args[0])?) { + Ok(v) => Ok(Value::number(v.naive_utc().timestamp_millis() as f64)), + _ => Ok(Value::number(f64::NAN)), + } + } + + /// `Date.UTC()` + /// + /// The `Date.UTC()` method accepts parameters similar to the `Date` constructor, but treats them as UTC. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-date.utc + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/UTC + pub(crate) fn utc(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + let year = args + .get(0) + .map_or(Ok(f64::NAN), |value| ctx.to_number(value))?; + let month = args.get(1).map_or(Ok(1f64), |value| ctx.to_number(value))?; + let day = args.get(2).map_or(Ok(1f64), |value| ctx.to_number(value))?; + let hour = args.get(3).map_or(Ok(0f64), |value| ctx.to_number(value))?; + let min = args.get(4).map_or(Ok(0f64), |value| ctx.to_number(value))?; + let sec = args.get(5).map_or(Ok(0f64), |value| ctx.to_number(value))?; + let milli = args.get(6).map_or(Ok(0f64), |value| ctx.to_number(value))?; + + if !check_normal_opt!(year, month, day, hour, min, sec, milli) { + return Ok(Value::number(f64::NAN)); + } + + let year = year as i32; + let month = month as u32; + let day = day as u32; + let hour = hour as u32; + let min = min as u32; + let sec = sec as u32; + let milli = milli as u32; + + let year = if 0 <= year && year <= 99 { + 1900 + year + } else { + year + }; + + NaiveDate::from_ymd_opt(year, month + 1, day) + .and_then(|f| f.and_hms_milli_opt(hour, min, sec, milli)) + .map_or(Ok(Value::number(f64::NAN)), |f| { + Ok(Value::number(f.timestamp_millis() as f64)) + }) + } + + /// Initialise the `Date` object on the global object. + #[inline] + pub(crate) fn init(interpreter: &mut Interpreter) -> (&'static str, Value) { + let global = interpreter.global(); + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); + + let prototype = Value::new_object(Some(global)); + + make_builtin_fn(getter_method!(get_date), "getDate", &prototype, 0); + make_builtin_fn(getter_method!(get_day), "getDay", &prototype, 0); + make_builtin_fn(getter_method!(get_full_year), "getFullYear", &prototype, 0); + make_builtin_fn(getter_method!(get_hours), "getHours", &prototype, 0); + make_builtin_fn( + getter_method!(get_milliseconds), + "getMilliseconds", + &prototype, + 0, + ); + make_builtin_fn(getter_method!(get_minutes), "getMinutes", &prototype, 0); + make_builtin_fn(getter_method!(get_month), "getMonth", &prototype, 0); + make_builtin_fn(getter_method!(get_seconds), "getSeconds", &prototype, 0); + make_builtin_fn(getter_method!(get_time), "getTime", &prototype, 0); + make_builtin_fn(getter_method!(get_year), "getYear", &prototype, 0); + make_builtin_fn( + getter_method!(Self::get_timezone_offset), + "getTimezoneOffset", + &prototype, + 0, + ); + make_builtin_fn(getter_method!(get_utc_date), "getUTCDate", &prototype, 0); + make_builtin_fn(getter_method!(get_utc_day), "getUTCDay", &prototype, 0); + make_builtin_fn( + getter_method!(get_utc_full_year), + "getUTCFullYear", + &prototype, + 0, + ); + make_builtin_fn(getter_method!(get_utc_hours), "getUTCHours", &prototype, 0); + make_builtin_fn( + getter_method!(get_utc_milliseconds), + "getUTCMilliseconds", + &prototype, + 0, + ); + make_builtin_fn( + getter_method!(get_utc_minutes), + "getUTCMinutes", + &prototype, + 0, + ); + make_builtin_fn(getter_method!(get_utc_month), "getUTCMonth", &prototype, 0); + make_builtin_fn( + getter_method!(get_utc_seconds), + "getUTCSeconds", + &prototype, + 0, + ); + make_builtin_fn(setter_method!(set_date(0)), "setDate", &prototype, 1); + make_builtin_fn( + setter_method!(set_full_year(0, 1, 2)), + "setFullYear", + &prototype, + 1, + ); + make_builtin_fn( + setter_method!(set_hours(0, 1, 2, 3)), + "setHours", + &prototype, + 1, + ); + make_builtin_fn( + setter_method!(set_milliseconds(0)), + "setMilliseconds", + &prototype, + 1, + ); + make_builtin_fn( + setter_method!(set_minutes(0, 1, 2)), + "setMinutes", + &prototype, + 1, + ); + make_builtin_fn(setter_method!(set_month(0, 1)), "setMonth", &prototype, 1); + make_builtin_fn( + setter_method!(set_seconds(0, 1)), + "setSeconds", + &prototype, + 1, + ); + make_builtin_fn(setter_method!(set_year(0, 1, 2)), "setYear", &prototype, 1); + make_builtin_fn(setter_method!(set_time(0)), "setTime", &prototype, 1); + make_builtin_fn(setter_method!(set_utc_date(0)), "setUTCDate", &prototype, 1); + make_builtin_fn( + setter_method!(set_utc_full_year(0, 1, 2)), + "setUTCFullYear", + &prototype, + 1, + ); + make_builtin_fn( + setter_method!(set_utc_hours(0, 1, 2, 3)), + "setUTCHours", + &prototype, + 1, + ); + make_builtin_fn( + setter_method!(set_utc_milliseconds(0)), + "setUTCMilliseconds", + &prototype, + 1, + ); + make_builtin_fn( + setter_method!(set_utc_minutes(0, 1, 2)), + "setUTCMinutes", + &prototype, + 1, + ); + make_builtin_fn( + setter_method!(set_utc_month(0, 1)), + "setUTCMonth", + &prototype, + 1, + ); + make_builtin_fn( + setter_method!(set_utc_seconds(0, 1)), + "setUTCSeconds", + &prototype, + 1, + ); + make_builtin_fn( + getter_method!(to_date_string), + "toDateString", + &prototype, + 0, + ); + make_builtin_fn(getter_method!(to_gmt_string), "toGMTString", &prototype, 0); + make_builtin_fn(getter_method!(to_iso_string), "toISOString", &prototype, 0); + make_builtin_fn(getter_method!(to_json), "toJSON", &prototype, 0); + // Locale strings + make_builtin_fn(getter_method!(to_string), "toString", &prototype, 0); + make_builtin_fn( + getter_method!(to_time_string), + "toTimeString", + &prototype, + 0, + ); + make_builtin_fn(getter_method!(to_utc_string), "toUTCString", &prototype, 0); + make_builtin_fn(getter_method!(value_of), "valueOf", &prototype, 0); + + let date_time_object = make_constructor_fn( + Self::NAME, + Self::LENGTH, + Self::make_date, + global, + prototype, + true, + true, + ); + + make_builtin_fn(Self::now, "now", &date_time_object, 0); + make_builtin_fn(Self::parse, "parse", &date_time_object, 1); + make_builtin_fn(Self::utc, "UTC", &date_time_object, 7); + (Self::NAME, date_time_object) + } +} + +/// The abstract operation `thisTimeValue` takes argument value. +/// +/// In following descriptions of functions that are properties of the Date prototype object, the phrase “this +/// Date object” refers to the object that is the this value for the invocation of the function. If the `Type` of +/// the this value is not `Object`, a `TypeError` exception is thrown. The phrase “this time value” within the +/// specification of a method refers to the result returned by calling the abstract operation `thisTimeValue` with +/// the this value of the method invocation passed as the argument. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-thistimevalue +#[inline] +pub fn this_time_value(value: &Value, ctx: &mut Interpreter) -> Result { + if let Value::Object(ref object) = value { + if let ObjectData::Date(ref date) = object.borrow().data { + return Ok(*date); + } + } + Err(ctx.construct_type_error("'this' is not a Date")) +} diff --git a/boa/src/builtins/date/tests.rs b/boa/src/builtins/date/tests.rs new file mode 100644 index 0000000000..45c2b16abc --- /dev/null +++ b/boa/src/builtins/date/tests.rs @@ -0,0 +1,1431 @@ +use crate::{ + builtins::{object::ObjectData, Value}, + forward, forward_val, Interpreter, Realm, +}; +use chrono::prelude::*; + +// NB: Javascript Uses 0-based months, where chrono uses 1-based months. Many of the assertions look wrong because of +// this. + +fn forward_dt_utc(engine: &mut Interpreter, src: &str) -> Option { + let date_time = if let Ok(v) = forward_val(engine, src) { + v + } else { + panic!("expected success") + }; + + let date_time = if let Value::Object(date_time) = &date_time { + date_time + } else { + panic!("expected object") + }; + + let date_time = if let ObjectData::Date(date_time) = &date_time.borrow().data { + date_time.0 + } else { + panic!("expected date") + }; + + date_time.clone() +} + +fn forward_dt_local(engine: &mut Interpreter, src: &str) -> Option { + let date_time = forward_dt_utc(engine, src); + + // The timestamp is converted to UTC for internal representation + date_time.map(|utc| { + Local::now() + .timezone() + .from_utc_datetime(&utc) + .naive_local() + }) +} + +#[test] +fn date_display() { + let dt = super::Date(None); + assert_eq!("[Invalid Date]", format!("[{}]", dt)); + + let cd = super::Date::default(); + assert_eq!( + format!( + "[{}]", + cd.to_local().unwrap().format("%a %b %d %Y %H:%M:%S GMT%:z") + ), + format!("[{}]", cd) + ); +} + +#[test] +fn date_this_time_value() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let error = forward_val( + &mut engine, + "({toString: Date.prototype.toString}).toString()", + ) + .expect_err("Expected error"); + let message_property = &error + .get_property("message") + .expect("Expected 'message' property") + .value; + + assert_eq!( + &Some(Value::string("\'this\' is not a Date")), + message_property + ); +} + +#[test] +fn date_call() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let dt1 = forward(&mut engine, "Date()"); + + std::thread::sleep(std::time::Duration::from_millis(1)); + + let dt2 = forward(&mut engine, "Date()"); + + assert_ne!(dt1, dt2); + Ok(()) +} + +#[test] +fn date_ctor_call() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let dt1 = forward_dt_local(&mut engine, "new Date()"); + + std::thread::sleep(std::time::Duration::from_millis(1)); + + let dt2 = forward_dt_local(&mut engine, "new Date()"); + + assert_ne!(dt1, dt2); + Ok(()) +} + +#[test] +fn date_ctor_call_string() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let date_time = forward_dt_utc(&mut engine, "new Date('2020-06-08T09:16:15.779-06:30')"); + + // Internal date is expressed as UTC + assert_eq!( + Some(NaiveDate::from_ymd(2020, 06, 08).and_hms_milli(15, 46, 15, 779)), + date_time + ); + Ok(()) +} + +#[test] +fn date_ctor_call_string_invalid() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let date_time = forward_dt_local(&mut engine, "new Date('nope')"); + assert_eq!(None, date_time); + Ok(()) +} + +#[test] +fn date_ctor_call_number() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let date_time = forward_dt_utc(&mut engine, "new Date(1594199775779)"); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 07, 08).and_hms_milli(09, 16, 15, 779)), + date_time + ); + Ok(()) +} + +#[test] +fn date_ctor_call_date() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let date_time = forward_dt_utc(&mut engine, "new Date(new Date(1594199775779))"); + + assert_eq!( + Some(NaiveDate::from_ymd(2020, 07, 08).and_hms_milli(09, 16, 15, 779)), + date_time + ); + Ok(()) +} + +#[test] +fn date_ctor_call_multiple() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let date_time = forward_dt_local(&mut engine, "new Date(2020, 06, 08, 09, 16, 15, 779)"); + + assert_eq!( + Some(NaiveDate::from_ymd(2020, 07, 08).and_hms_milli(09, 16, 15, 779)), + date_time + ); + Ok(()) +} + +#[test] +fn date_ctor_call_multiple_90s() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let date_time = forward_dt_local(&mut engine, "new Date(99, 06, 08, 09, 16, 15, 779)"); + + assert_eq!( + Some(NaiveDate::from_ymd(1999, 07, 08).and_hms_milli(09, 16, 15, 779)), + date_time + ); + Ok(()) +} + +#[test] +fn date_ctor_call_multiple_nan() -> Result<(), Box> { + fn check(src: &str) { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + let date_time = forward_dt_local(&mut engine, src); + assert_eq!(None, date_time); + } + + check("new Date(1/0, 06, 08, 09, 16, 15, 779)"); + check("new Date(2020, 1/0, 08, 09, 16, 15, 779)"); + check("new Date(2020, 06, 1/0, 09, 16, 15, 779)"); + check("new Date(2020, 06, 08, 1/0, 16, 15, 779)"); + check("new Date(2020, 06, 08, 09, 1/0, 15, 779)"); + check("new Date(2020, 06, 08, 09, 16, 1/0, 779)"); + check("new Date(2020, 06, 08, 09, 16, 15, 1/0)"); + + Ok(()) +} + +#[test] +fn date_ctor_now_call() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let date_time = forward(&mut engine, "Date.now()"); + let dt1 = u64::from_str_radix(&date_time, 10)?; + + std::thread::sleep(std::time::Duration::from_millis(1)); + + let date_time = forward(&mut engine, "Date.now()"); + let dt2 = u64::from_str_radix(&date_time, 10)?; + + assert_ne!(dt1, dt2); + Ok(()) +} + +#[test] +fn date_ctor_parse_call() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let date_time = forward_val(&mut engine, "Date.parse('2020-06-08T09:16:15.779-07:30')"); + + assert_eq!(Ok(Value::Rational(1591634775779f64)), date_time); + Ok(()) +} + +#[test] +fn date_ctor_utc_call() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let date_time = forward_val(&mut engine, "Date.UTC(2020, 06, 08, 09, 16, 15, 779)"); + + assert_eq!(Ok(Value::Rational(1594199775779f64)), date_time); + Ok(()) +} + +#[test] +fn date_ctor_utc_call_nan() -> Result<(), Box> { + fn check(src: &str) { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + let date_time = forward_val(&mut engine, src).expect("Expected Success"); + assert_eq!(Value::Rational(f64::NAN), date_time); + } + + check("Date.UTC(1/0, 06, 08, 09, 16, 15, 779)"); + check("Date.UTC(2020, 1/0, 08, 09, 16, 15, 779)"); + check("Date.UTC(2020, 06, 1/0, 09, 16, 15, 779)"); + check("Date.UTC(2020, 06, 08, 1/0, 16, 15, 779)"); + check("Date.UTC(2020, 06, 08, 09, 1/0, 15, 779)"); + check("Date.UTC(2020, 06, 08, 09, 16, 1/0, 779)"); + check("Date.UTC(2020, 06, 08, 09, 16, 15, 1/0)"); + + Ok(()) +} + +#[test] +fn date_proto_get_date_call() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_val( + &mut engine, + "new Date(2020, 06, 08, 09, 16, 15, 779).getDate()", + ); + assert_eq!(Ok(Value::Rational(08f64)), actual); + + let actual = forward_val(&mut engine, "new Date(1/0).getDate()"); + assert_eq!(Ok(Value::Rational(f64::NAN)), actual); + + Ok(()) +} + +#[test] +fn date_proto_get_day_call() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_val( + &mut engine, + "new Date(2020, 06, 08, 09, 16, 15, 779).getDay()", + ); + assert_eq!(Ok(Value::Rational(3f64)), actual); + + let actual = forward_val(&mut engine, "new Date(1/0).getDay()"); + assert_eq!(Ok(Value::Rational(f64::NAN)), actual); + Ok(()) +} + +#[test] +fn date_proto_get_full_year_call() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_val( + &mut engine, + "new Date(2020, 06, 08, 09, 16, 15, 779).getFullYear()", + ); + assert_eq!(Ok(Value::Rational(2020f64)), actual); + + let actual = forward_val(&mut engine, "new Date(1/0).getFullYear()"); + assert_eq!(Ok(Value::Rational(f64::NAN)), actual); + Ok(()) +} + +#[test] +fn date_proto_get_hours_call() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_val( + &mut engine, + "new Date(2020, 06, 08, 09, 16, 15, 779).getHours()", + ); + assert_eq!(Ok(Value::Rational(09f64)), actual); + + let actual = forward_val(&mut engine, "new Date(1/0).getHours()"); + assert_eq!(Ok(Value::Rational(f64::NAN)), actual); + Ok(()) +} + +#[test] +fn date_proto_get_milliseconds_call() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_val( + &mut engine, + "new Date(2020, 06, 08, 09, 16, 15, 779).getMilliseconds()", + ); + assert_eq!(Ok(Value::Rational(779f64)), actual); + + let actual = forward_val(&mut engine, "new Date(1/0).getMilliseconds()"); + assert_eq!(Ok(Value::Rational(f64::NAN)), actual); + Ok(()) +} + +#[test] +fn date_proto_get_minutes_call() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_val( + &mut engine, + "new Date(2020, 06, 08, 09, 16, 15, 779).getMinutes()", + ); + assert_eq!(Ok(Value::Rational(16f64)), actual); + + let actual = forward_val(&mut engine, "new Date(1/0).getMinutes()"); + assert_eq!(Ok(Value::Rational(f64::NAN)), actual); + Ok(()) +} + +#[test] +fn date_proto_get_month() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_val( + &mut engine, + "new Date(2020, 06, 08, 09, 16, 15, 779).getMonth()", + ); + assert_eq!(Ok(Value::Rational(06f64)), actual); + + let actual = forward_val(&mut engine, "new Date(1/0).getMonth()"); + assert_eq!(Ok(Value::Rational(f64::NAN)), actual); + + Ok(()) +} + +#[test] +fn date_proto_get_seconds() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_val( + &mut engine, + "new Date(2020, 06, 08, 09, 16, 15, 779).getSeconds()", + ); + assert_eq!(Ok(Value::Rational(15f64)), actual); + + let actual = forward_val(&mut engine, "new Date(1/0).getSeconds()"); + assert_eq!(Ok(Value::Rational(f64::NAN)), actual); + Ok(()) +} + +#[test] +fn date_proto_get_time() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_val( + &mut engine, + "new Date(2020, 06, 08, 09, 16, 15, 779).getTime()", + ); + + let ts = Local + .ymd(2020, 07, 08) + .and_hms_milli(09, 16, 15, 779) + .timestamp_millis() as f64; + assert_eq!(Ok(Value::Rational(ts)), actual); + + let actual = forward_val(&mut engine, "new Date(1/0).getTime()"); + assert_eq!(Ok(Value::Rational(f64::NAN)), actual); + Ok(()) +} + +#[test] +fn date_proto_get_year() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_val( + &mut engine, + "new Date(2020, 06, 08, 09, 16, 15, 779).getYear()", + ); + assert_eq!(Ok(Value::Rational(120f64)), actual); + + let actual = forward_val(&mut engine, "new Date(1/0).getYear()"); + assert_eq!(Ok(Value::Rational(f64::NAN)), actual); + Ok(()) +} + +#[test] +fn date_proto_get_timezone_offset() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_val( + &mut engine, + "new Date('August 19, 1975 23:15:30 GMT+07:00').getTimezoneOffset() === new Date('August 19, 1975 23:15:30 GMT-02:00').getTimezoneOffset()", + ); + + // NB: Host Settings, not TZ specified in the DateTime. + assert_eq!(Ok(Value::Boolean(true)), actual); + + let actual = forward_val( + &mut engine, + "new Date('August 19, 1975 23:15:30 GMT+07:00').getTimezoneOffset()", + ); + + // The value of now().offset() depends on the host machine, so we have to replicate the method code here. + let offset_seconds = chrono::Local::now().offset().local_minus_utc() as f64; + let offset_minutes = offset_seconds / 60f64; + assert_eq!(Ok(Value::Rational(offset_minutes)), actual); + + let actual = forward_val( + &mut engine, + "new Date(1/0, 06, 08, 09, 16, 15, 779).getTimezoneOffset()", + ); + assert_eq!(Ok(Value::Rational(offset_minutes)), actual); + Ok(()) +} + +#[test] +fn date_proto_get_utc_date_call() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_val( + &mut engine, + "new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)).getUTCDate()", + ); + assert_eq!(Ok(Value::Rational(08f64)), actual); + + let actual = forward_val(&mut engine, "new Date(1/0).getUTCDate()"); + assert_eq!(Ok(Value::Rational(f64::NAN)), actual); + + Ok(()) +} + +#[test] +fn date_proto_get_utc_day_call() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_val( + &mut engine, + "new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)).getUTCDay()", + ); + assert_eq!(Ok(Value::Rational(3f64)), actual); + + let actual = forward_val(&mut engine, "new Date(1/0).getUTCDay()"); + assert_eq!(Ok(Value::Rational(f64::NAN)), actual); + Ok(()) +} + +#[test] +fn date_proto_get_utc_full_year_call() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_val( + &mut engine, + "new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)).getUTCFullYear()", + ); + assert_eq!(Ok(Value::Rational(2020f64)), actual); + + let actual = forward_val(&mut engine, "new Date(1/0).getUTCFullYear()"); + assert_eq!(Ok(Value::Rational(f64::NAN)), actual); + Ok(()) +} + +#[test] +fn date_proto_get_utc_hours_call() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_val( + &mut engine, + "new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)).getUTCHours()", + ); + assert_eq!(Ok(Value::Rational(09f64)), actual); + + let actual = forward_val(&mut engine, "new Date(1/0).getUTCHours()"); + assert_eq!(Ok(Value::Rational(f64::NAN)), actual); + Ok(()) +} + +#[test] +fn date_proto_get_utc_milliseconds_call() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_val( + &mut engine, + "new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)).getUTCMilliseconds()", + ); + assert_eq!(Ok(Value::Rational(779f64)), actual); + + let actual = forward_val(&mut engine, "new Date(1/0).getUTCMilliseconds()"); + assert_eq!(Ok(Value::Rational(f64::NAN)), actual); + Ok(()) +} + +#[test] +fn date_proto_get_utc_minutes_call() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_val( + &mut engine, + "new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)).getUTCMinutes()", + ); + assert_eq!(Ok(Value::Rational(16f64)), actual); + + let actual = forward_val(&mut engine, "new Date(1/0).getUTCMinutes()"); + assert_eq!(Ok(Value::Rational(f64::NAN)), actual); + Ok(()) +} + +#[test] +fn date_proto_get_utc_month() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_val( + &mut engine, + "new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)).getUTCMonth()", + ); + assert_eq!(Ok(Value::Rational(06f64)), actual); + + let actual = forward_val(&mut engine, "new Date(1/0).getUTCMonth()"); + assert_eq!(Ok(Value::Rational(f64::NAN)), actual); + + Ok(()) +} + +#[test] +fn date_proto_get_utc_seconds() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_val( + &mut engine, + "new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)).getUTCSeconds()", + ); + assert_eq!(Ok(Value::Rational(15f64)), actual); + + let actual = forward_val(&mut engine, "new Date(1/0).getUTCSeconds()"); + assert_eq!(Ok(Value::Rational(f64::NAN)), actual); + Ok(()) +} + +#[test] +fn date_proto_set_date() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_dt_local( + &mut engine, + "let dt = new Date(2020, 06, 08, 09, 16, 15, 779); dt.setDate(21); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 07, 21).and_hms_milli(09, 16, 15, 779)), + actual + ); + + // Date wraps to previous month for 0. + let actual = forward_dt_local( + &mut engine, + "dt = new Date(2020, 06, 08, 09, 16, 15, 779); dt.setDate(0); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 06, 30).and_hms_milli(09, 16, 15, 779)), + actual + ); + + let actual = forward_dt_local( + &mut engine, + "dt = new Date(2020, 06, 08, 09, 16, 15, 779); dt.setDate(1/0); dt", + ); + assert_eq!(None, actual); + + Ok(()) +} + +#[test] +fn date_proto_set_full_year() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_dt_local( + &mut engine, + "let dt = new Date(2020, 06, 08, 09, 16, 15, 779); dt.setFullYear(2012); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2012, 07, 08).and_hms_milli(09, 16, 15, 779)), + actual + ); + + let actual = forward_dt_local( + &mut engine, + "dt = new Date(2020, 06, 08, 09, 16, 15, 779); dt.setFullYear(2012, 8); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2012, 09, 08).and_hms_milli(09, 16, 15, 779)), + actual + ); + + let actual = forward_dt_local( + &mut engine, + "dt = new Date(2020, 06, 08, 09, 16, 15, 779); dt.setFullYear(2012, 8, 10); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2012, 09, 10).and_hms_milli(09, 16, 15, 779)), + actual + ); + + // Out-of-bounds + + let actual = forward_dt_local( + &mut engine, + "dt = new Date(2020, 07, 08, 09, 16, 15, 779); dt.setFullYear(2012, 35); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2014, 12, 08).and_hms_milli(09, 16, 15, 779)), + actual + ); + + let actual = forward_dt_local( + &mut engine, + "dt = new Date(2020, 07, 08, 09, 16, 15, 779); dt.setFullYear(2012, -35); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2009, 02, 08).and_hms_milli(09, 16, 15, 779)), + actual + ); + + let actual = forward_dt_local( + &mut engine, + "dt = new Date(2020, 07, 08, 09, 16, 15, 779); dt.setFullYear(2012, 9, 950); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2015, 05, 08).and_hms_milli(09, 16, 15, 779)), + actual + ); + + let actual = forward_dt_local( + &mut engine, + "dt = new Date(2020, 07, 08, 09, 16, 15, 779); dt.setFullYear(2012, 9, -950); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2010, 02, 23).and_hms_milli(09, 16, 15, 779)), + actual + ); + + Ok(()) +} + +#[test] +fn date_proto_set_hours() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_dt_local( + &mut engine, + "let dt = new Date(2020, 06, 08, 09, 16, 15, 779); dt.setHours(11); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 07, 08).and_hms_milli(11, 16, 15, 779)), + actual + ); + + let actual = forward_dt_local( + &mut engine, + "dt = new Date(2020, 06, 08, 09, 16, 15, 779); dt.setHours(11, 35); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 07, 08).and_hms_milli(11, 35, 15, 779)), + actual + ); + + let actual = forward_dt_local( + &mut engine, + "dt = new Date(2020, 06, 08, 09, 16, 15, 779); dt.setHours(11, 35, 23); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 07, 08).and_hms_milli(11, 35, 23, 779)), + actual + ); + + let actual = forward_dt_local( + &mut engine, + "dt = new Date(2020, 06, 08, 09, 16, 15, 779); dt.setHours(11, 35, 23, 537); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 07, 08).and_hms_milli(11, 35, 23, 537)), + actual + ); + + // Out-of-bounds + + let actual = forward_dt_local( + &mut engine, + "dt = new Date(2020, 06, 08, 09, 16, 15, 779); dt.setHours(10000, 20000, 30000, 40123); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2021, 09, 11).and_hms_milli(21, 40, 40, 123)), + actual + ); + + Ok(()) +} + +#[test] +fn date_proto_set_milliseconds() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_dt_local( + &mut engine, + "let dt = new Date(2020, 06, 08, 09, 16, 15, 779); dt.setMilliseconds(597); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 07, 08).and_hms_milli(09, 16, 15, 597)), + actual + ); + + // Out-of-bounds + // Thorough tests are done by setHours + + let actual = forward_dt_local( + &mut engine, + "dt = new Date(2020, 06, 08, 09, 16, 15, 779); dt.setMilliseconds(40123); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 07, 08).and_hms_milli(09, 16, 55, 123)), + actual + ); + + Ok(()) +} + +#[test] +fn date_proto_set_minutes() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_dt_local( + &mut engine, + "let dt = new Date(2020, 06, 08, 09, 16, 15, 779); dt.setMinutes(11); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 07, 08).and_hms_milli(09, 11, 15, 779)), + actual + ); + + let actual = forward_dt_local( + &mut engine, + "dt = new Date(2020, 06, 08, 09, 16, 15, 779); dt.setMinutes(11, 35); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 07, 08).and_hms_milli(09, 11, 35, 779)), + actual + ); + + let actual = forward_dt_local( + &mut engine, + "dt = new Date(2020, 06, 08, 09, 16, 15, 779); dt.setMinutes(11, 35, 537); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 07, 08).and_hms_milli(09, 11, 35, 537)), + actual + ); + + // Out-of-bounds + // Thorough tests are done by setHours + + let actual = forward_dt_local( + &mut engine, + "dt = new Date(2020, 06, 08, 09, 16, 15, 779); dt.setMinutes(600000, 30000, 40123); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2021, 08, 29).and_hms_milli(09, 20, 40, 123)), + actual + ); + + Ok(()) +} + +#[test] +fn date_proto_set_month() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_dt_local( + &mut engine, + "let dt = new Date(2020, 06, 08, 09, 16, 15, 779); dt.setMonth(11); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 12, 08).and_hms_milli(09, 16, 15, 779)), + actual + ); + + let actual = forward_dt_local( + &mut engine, + "dt = new Date(2020, 06, 08, 09, 16, 15, 779); dt.setMonth(11, 16); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 12, 16).and_hms_milli(09, 16, 15, 779)), + actual + ); + + // Out-of-bounds + // Thorough tests are done by setFullYear + + let actual = forward_dt_local( + &mut engine, + "dt = new Date(2020, 07, 08, 09, 16, 15, 779); dt.setMonth(40, 83); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2023, 07, 22).and_hms_milli(09, 16, 15, 779)), + actual + ); + + Ok(()) +} + +#[test] +fn date_proto_set_seconds() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_dt_local( + &mut engine, + "let dt = new Date(2020, 06, 08, 09, 16, 15, 779); dt.setSeconds(11); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 07, 08).and_hms_milli(09, 16, 11, 779)), + actual + ); + + let actual = forward_dt_local( + &mut engine, + "dt = new Date(2020, 06, 08, 09, 16, 15, 779); dt.setSeconds(11, 487); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 07, 08).and_hms_milli(09, 16, 11, 487)), + actual + ); + + // Out-of-bounds + // Thorough tests are done by setHour + + let actual = forward_dt_local( + &mut engine, + "dt = new Date(2020, 07, 08, 09, 16, 15, 779); dt.setSeconds(40000000, 40123); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2021, 11, 14).and_hms_milli(08, 23, 20, 123)), + actual + ); + + Ok(()) +} + +#[test] +fn set_year() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_dt_local( + &mut engine, + "let dt = new Date(2020, 06, 08, 09, 16, 15, 779); dt.setYear(98); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(1998, 07, 08).and_hms_milli(09, 16, 15, 779)), + actual + ); + + let actual = forward_dt_local( + &mut engine, + "dt = new Date(2020, 06, 08, 09, 16, 15, 779); dt.setYear(2001); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2001, 07, 08).and_hms_milli(09, 16, 15, 779)), + actual + ); + + Ok(()) +} + +#[test] +fn date_proto_set_time() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_dt_local( + &mut engine, + "let dt = new Date(); dt.setTime(new Date(2020, 06, 08, 09, 16, 15, 779).getTime()); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 07, 08).and_hms_milli(09, 16, 15, 779)), + actual + ); + + Ok(()) +} + +#[test] +fn date_proto_set_utc_date() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_dt_utc( + &mut engine, + "let dt = new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)); dt.setUTCDate(21); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 07, 21).and_hms_milli(09, 16, 15, 779)), + actual + ); + + // Date wraps to previous month for 0. + let actual = forward_dt_utc( + &mut engine, + "dt = new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)); dt.setUTCDate(0); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 06, 30).and_hms_milli(09, 16, 15, 779)), + actual + ); + + let actual = forward_dt_utc( + &mut engine, + "dt = new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)); dt.setUTCDate(1/0); dt", + ); + assert_eq!(None, actual); + + Ok(()) +} + +#[test] +fn date_proto_set_utc_full_year() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_dt_utc( + &mut engine, + "let dt = new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)); dt.setUTCFullYear(2012); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2012, 07, 08).and_hms_milli(09, 16, 15, 779)), + actual + ); + + let actual = forward_dt_utc( + &mut engine, + "dt = new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)); dt.setUTCFullYear(2012, 8); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2012, 09, 08).and_hms_milli(09, 16, 15, 779)), + actual + ); + + let actual = forward_dt_utc( + &mut engine, + "dt = new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)); dt.setUTCFullYear(2012, 8, 10); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2012, 09, 10).and_hms_milli(09, 16, 15, 779)), + actual + ); + + // Out-of-bounds + + let actual = forward_dt_utc( + &mut engine, + "dt = new Date(Date.UTC(2020, 07, 08, 09, 16, 15, 779)); dt.setUTCFullYear(2012, 35); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2014, 12, 08).and_hms_milli(09, 16, 15, 779)), + actual + ); + + let actual = forward_dt_utc( + &mut engine, + "dt = new Date(Date.UTC(2020, 07, 08, 09, 16, 15, 779)); dt.setUTCFullYear(2012, -35); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2009, 02, 08).and_hms_milli(09, 16, 15, 779)), + actual + ); + + let actual = forward_dt_utc( + &mut engine, + "dt = new Date(Date.UTC(2020, 07, 08, 09, 16, 15, 779)); dt.setUTCFullYear(2012, 9, 950); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2015, 05, 08).and_hms_milli(09, 16, 15, 779)), + actual + ); + + let actual = forward_dt_utc( + &mut engine, + "dt = new Date(Date.UTC(2020, 07, 08, 09, 16, 15, 779)); dt.setUTCFullYear(2012, 9, -950); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2010, 02, 23).and_hms_milli(09, 16, 15, 779)), + actual + ); + + Ok(()) +} + +#[test] +fn date_proto_set_utc_hours() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_dt_utc( + &mut engine, + "let dt = new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)); dt.setUTCHours(11); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 07, 08).and_hms_milli(11, 16, 15, 779)), + actual + ); + + let actual = forward_dt_utc( + &mut engine, + "dt = new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)); dt.setUTCHours(11, 35); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 07, 08).and_hms_milli(11, 35, 15, 779)), + actual + ); + + let actual = forward_dt_utc( + &mut engine, + "dt = new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)); dt.setUTCHours(11, 35, 23); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 07, 08).and_hms_milli(11, 35, 23, 779)), + actual + ); + + let actual = forward_dt_utc( + &mut engine, + "dt = new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)); dt.setUTCHours(11, 35, 23, 537); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 07, 08).and_hms_milli(11, 35, 23, 537)), + actual + ); + + // Out-of-bounds + + let actual = forward_dt_utc( + &mut engine, + "dt = new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)); dt.setUTCHours(10000, 20000, 30000, 40123); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2021, 09, 11).and_hms_milli(21, 40, 40, 123)), + actual + ); + + Ok(()) +} + +#[test] +fn date_proto_set_utc_milliseconds() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_dt_utc( + &mut engine, + "let dt = new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)); dt.setUTCMilliseconds(597); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 07, 08).and_hms_milli(09, 16, 15, 597)), + actual + ); + + // Out-of-bounds + // Thorough tests are done by setHours + + let actual = forward_dt_utc( + &mut engine, + "dt = new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)); dt.setUTCMilliseconds(40123); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 07, 08).and_hms_milli(09, 16, 55, 123)), + actual + ); + + Ok(()) +} + +#[test] +fn date_proto_set_utc_minutes() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_dt_utc( + &mut engine, + "let dt = new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)); dt.setUTCMinutes(11); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 07, 08).and_hms_milli(09, 11, 15, 779)), + actual + ); + + let actual = forward_dt_utc( + &mut engine, + "dt = new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)); dt.setUTCMinutes(11, 35); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 07, 08).and_hms_milli(09, 11, 35, 779)), + actual + ); + + let actual = forward_dt_utc( + &mut engine, + "dt = new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)); dt.setUTCMinutes(11, 35, 537); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 07, 08).and_hms_milli(09, 11, 35, 537)), + actual + ); + + // Out-of-bounds + // Thorough tests are done by setHours + + let actual = forward_dt_utc( + &mut engine, + "dt = new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)); dt.setUTCMinutes(600000, 30000, 40123); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2021, 08, 29).and_hms_milli(09, 20, 40, 123)), + actual + ); + + Ok(()) +} + +#[test] +fn date_proto_set_utc_month() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_dt_utc( + &mut engine, + "let dt = new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)); dt.setUTCMonth(11); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 12, 08).and_hms_milli(09, 16, 15, 779)), + actual + ); + + let actual = forward_dt_utc( + &mut engine, + "dt = new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)); dt.setUTCMonth(11, 16); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 12, 16).and_hms_milli(09, 16, 15, 779)), + actual + ); + + // Out-of-bounds + // Thorough tests are done by setFullYear + + let actual = forward_dt_utc( + &mut engine, + "dt = new Date(Date.UTC(2020, 07, 08, 09, 16, 15, 779)); dt.setUTCMonth(40, 83); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2023, 07, 22).and_hms_milli(09, 16, 15, 779)), + actual + ); + + Ok(()) +} + +#[test] +fn date_proto_set_utc_seconds() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_dt_utc( + &mut engine, + "let dt = new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)); dt.setUTCSeconds(11); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 07, 08).and_hms_milli(09, 16, 11, 779)), + actual + ); + + let actual = forward_dt_utc( + &mut engine, + "dt = new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)); dt.setUTCSeconds(11, 487); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2020, 07, 08).and_hms_milli(09, 16, 11, 487)), + actual + ); + + // Out-of-bounds + // Thorough tests are done by setHour + + let actual = forward_dt_utc( + &mut engine, + "dt = new Date(Date.UTC(2020, 07, 08, 09, 16, 15, 779)); dt.setUTCSeconds(40000000, 40123); dt", + ); + assert_eq!( + Some(NaiveDate::from_ymd(2021, 11, 14).and_hms_milli(08, 23, 20, 123)), + actual + ); + + Ok(()) +} + +#[test] +fn date_proto_to_date_string() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_val( + &mut engine, + "let dt = new Date(2020, 06, 08, 09, 16, 15, 779); dt.toDateString()", + ) + .expect("Successful eval"); + assert_eq!(Value::string("Wed Jul 08 2020"), actual); + + Ok(()) +} + +#[test] +fn date_proto_to_gmt_string() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_val( + &mut engine, + "let dt = new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)); dt.toGMTString()", + ) + .expect("Successful eval"); + assert_eq!(Value::string("Wed, 08 Jul 2020 09:16:15 GMT"), actual); + + Ok(()) +} + +#[test] +fn date_proto_to_iso_string() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_val( + &mut engine, + "let dt = new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)); dt.toISOString()", + ) + .expect("Successful eval"); + assert_eq!(Value::string("2020-07-08T09:16:15.779Z"), actual); + + Ok(()) +} + +#[test] +fn date_proto_to_json() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_val( + &mut engine, + "let dt = new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)); dt.toJSON()", + ) + .expect("Successful eval"); + assert_eq!(Value::string("2020-07-08T09:16:15.779Z"), actual); + + Ok(()) +} + +#[test] +fn date_proto_to_string() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_val( + &mut engine, + "let dt = new Date(2020, 06, 08, 09, 16, 15, 779); dt.toString()", + ) + .ok(); + + assert_eq!( + Some(Value::string( + Local::now() + .format("Wed Jul 08 2020 09:16:15 GMT%:z") + .to_string() + )), + actual + ); + + Ok(()) +} + +#[test] +fn date_proto_to_time_string() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_val( + &mut engine, + "let dt = new Date(2020, 06, 08, 09, 16, 15, 779); dt.toTimeString()", + ) + .ok(); + + assert_eq!( + Some(Value::string( + Local::now().format("09:16:15 GMT%:z").to_string() + )), + actual + ); + + Ok(()) +} + +#[test] +fn date_proto_to_utc_string() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_val( + &mut engine, + "let dt = new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)); dt.toUTCString()", + ) + .expect("Successful eval"); + assert_eq!(Value::string("Wed, 08 Jul 2020 09:16:15 GMT"), actual); + + Ok(()) +} + +#[test] +fn date_proto_value_of() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_val( + &mut engine, + "new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)).valueOf()", + ) + .expect("Successful eval"); + assert_eq!(Value::number(1594199775779f64), actual); + + Ok(()) +} + +#[test] +fn date_neg() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_val( + &mut engine, + "-new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779))", + ) + .expect("Successful eval"); + assert_eq!(Value::number(-1594199775779f64), actual); + + Ok(()) +} + +#[test] +fn date_json() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let actual = forward_val( + &mut engine, + "JSON.stringify({ date: new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)) })", + ) + .expect("Successful eval"); + assert_eq!( + Value::string(r#"{"date":"2020-07-08T09:16:15.779Z"}"#), + actual + ); + + Ok(()) +} diff --git a/boa/src/builtins/mod.rs b/boa/src/builtins/mod.rs index 91449bb704..878bb541d2 100644 --- a/boa/src/builtins/mod.rs +++ b/boa/src/builtins/mod.rs @@ -4,6 +4,7 @@ pub mod array; pub mod bigint; pub mod boolean; pub mod console; +pub mod date; pub mod error; pub mod function; pub mod global_this; @@ -26,6 +27,7 @@ pub(crate) use self::{ bigint::BigInt, boolean::Boolean, console::Console, + date::Date, error::{Error, RangeError, ReferenceError, SyntaxError, TypeError}, global_this::GlobalThis, infinity::Infinity, @@ -52,6 +54,7 @@ pub fn init(interpreter: &mut Interpreter) { Array::init, BigInt::init, Boolean::init, + Date::init, Json::init, Map::init, Math::init, diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index 77ac66274d..7d6ddcb214 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -19,7 +19,7 @@ use crate::{ map::ordered_map::OrderedMap, property::Property, value::{RcBigInt, RcString, RcSymbol, ResultValue, Value}, - BigInt, RegExp, + BigInt, Date, RegExp, }, exec::Interpreter, BoaProfiler, @@ -78,6 +78,8 @@ pub enum ObjectData { Symbol(RcSymbol), Error, Ordinary, + Date(Date), + Global, } impl Display for ObjectData { @@ -97,6 +99,8 @@ impl Display for ObjectData { Self::Boolean(_) => "Boolean", Self::Number(_) => "Number", Self::BigInt(_) => "BigInt", + Self::Date(_) => "Date", + Self::Global => "Global", } ) } diff --git a/boa/src/builtins/object/tests.rs b/boa/src/builtins/object/tests.rs index 9ed1156f31..ae405a67e7 100644 --- a/boa/src/builtins/object/tests.rs +++ b/boa/src/builtins/object/tests.rs @@ -96,6 +96,8 @@ fn object_is() { assert_eq!(forward(&mut engine, "Object.is(NaN, 0/0)"), "true"); assert_eq!(forward(&mut engine, "Object.is()"), "true"); assert_eq!(forward(&mut engine, "Object.is(undefined)"), "true"); + assert!(engine.realm.global_obj.is_global()); + assert!(!engine.realm.global_obj.get_field("Object").is_global()); } #[test] fn object_has_own_property() { diff --git a/boa/src/builtins/value/mod.rs b/boa/src/builtins/value/mod.rs index e78e922623..494d4b7aeb 100644 --- a/boa/src/builtins/value/mod.rs +++ b/boa/src/builtins/value/mod.rs @@ -155,6 +155,14 @@ impl Value { Self::Symbol(RcSymbol::from(symbol)) } + /// Helper function to convert the `Value` to a number and compute its power. + pub fn as_num_to_power(&self, other: Self) -> Self { + match (self, other) { + (Self::BigInt(ref a), Self::BigInt(ref b)) => Self::bigint(a.as_inner().clone().pow(b)), + (a, b) => Self::rational(a.to_number().powf(b.to_number())), + } + } + /// Returns a new empty object pub fn new_object(global: Option<&Value>) -> Self { let _timer = BoaProfiler::global().start_event("new_object", "value"); @@ -231,8 +239,14 @@ impl Value { } } - /// Conversts the `Value` to `JSON`. + /// Converts the `Value` to `JSON`. pub fn to_json(&self, interpreter: &mut Interpreter) -> Result { + let to_json = self.get_field("toJSON"); + if to_json.is_function() { + let json_value = interpreter.call(&to_json, self, &[])?; + return json_value.to_json(interpreter); + } + match *self { Self::Null => Ok(JSONValue::Null), Self::Boolean(b) => Ok(JSONValue::Bool(b)), @@ -289,6 +303,17 @@ impl Value { true } + /// Returns true if the value the global for a Realm + pub fn is_global(&self) -> bool { + match self { + Value::Object(object) => match object.borrow().data { + ObjectData::Global => true, + _ => false, + }, + _ => false, + } + } + /// Returns true if the value is an object #[inline] pub fn is_object(&self) -> bool { diff --git a/boa/src/builtins/value/operations.rs b/boa/src/builtins/value/operations.rs index 7f24323429..e4f562865e 100644 --- a/boa/src/builtins/value/operations.rs +++ b/boa/src/builtins/value/operations.rs @@ -382,9 +382,13 @@ impl Value { } #[inline] - pub fn neg(&self, _: &mut Interpreter) -> ResultValue { + pub fn neg(&self, interpreter: &mut Interpreter) -> ResultValue { Ok(match *self { - Self::Object(_) | Self::Symbol(_) | Self::Undefined => Self::rational(NAN), + Self::Symbol(_) | Self::Undefined => Self::rational(NAN), + Self::Object(_) => Self::rational(match interpreter.to_numeric_number(self) { + Ok(num) => -num, + Err(_) => NAN, + }), Self::String(ref str) => Self::rational(match f64::from_str(str) { Ok(num) => -num, Err(_) => NAN, diff --git a/boa/src/builtins/value/val_type.rs b/boa/src/builtins/value/val_type.rs index 134a81cd00..2861abd338 100644 --- a/boa/src/builtins/value/val_type.rs +++ b/boa/src/builtins/value/val_type.rs @@ -14,6 +14,7 @@ pub enum Type { BigInt, Object, Function, + Date, } impl Type { @@ -28,6 +29,7 @@ impl Type { Self::Function => "function", Self::Object => "object", Self::BigInt => "bigint", + Self::Date => "date", } } } diff --git a/boa/src/realm.rs b/boa/src/realm.rs index 5147be86ff..f4f1f1839c 100644 --- a/boa/src/realm.rs +++ b/boa/src/realm.rs @@ -36,6 +36,10 @@ impl Realm { // Create brand new global object // Global has no prototype to pass None to new_obj let global = Value::new_object(None); + + // Allow identification of the global object easily + global.set_data(crate::builtins::object::ObjectData::Global); + // We need to clone the global here because its referenced from separate places (only pointer is cloned) let global_env = new_global_environment(global.clone(), global.clone());