From efe61022ef71c456d7cc76a8ae6de4ecf8bad2d6 Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Fri, 24 Jul 2020 09:27:46 -0700 Subject: [PATCH 01/33] Allow easy identification of global --- boa/src/builtins/object/mod.rs | 2 ++ boa/src/builtins/object/tests.rs | 2 ++ boa/src/builtins/value/mod.rs | 11 +++++++++++ boa/src/realm.rs | 4 ++++ 4 files changed, 19 insertions(+) diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index 77ac66274db..02cb405da92 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -78,6 +78,7 @@ pub enum ObjectData { Symbol(RcSymbol), Error, Ordinary, + Global, } impl Display for ObjectData { @@ -97,6 +98,7 @@ impl Display for ObjectData { Self::Boolean(_) => "Boolean", Self::Number(_) => "Number", Self::BigInt(_) => "BigInt", + Self::Global => "Global", } ) } diff --git a/boa/src/builtins/object/tests.rs b/boa/src/builtins/object/tests.rs index 9ed1156f31c..ae405a67e7b 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 e78e9226230..645cc9cbf22 100644 --- a/boa/src/builtins/value/mod.rs +++ b/boa/src/builtins/value/mod.rs @@ -289,6 +289,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/realm.rs b/boa/src/realm.rs index 5147be86ff9..2485107c451 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(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()); From b2a51aae9caf92707eef2765c9ca980d10910cf0 Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Mon, 27 Jul 2020 10:49:15 -0700 Subject: [PATCH 02/33] Date, complete to `getTimezoneOffset()` --- Cargo.lock | 22 + boa/Cargo.toml | 1 + boa/src/builtins/date/mod.rs | 615 ++++++++++++++++++++++++++ boa/src/builtins/date/tests.rs | 311 +++++++++++++ boa/src/builtins/mod.rs | 3 + boa/src/builtins/object/mod.rs | 4 +- boa/src/builtins/value/conversions.rs | 12 + boa/src/builtins/value/display.rs | 7 + boa/src/builtins/value/hash.rs | 1 + boa/src/builtins/value/mod.rs | 29 +- boa/src/builtins/value/operations.rs | 1 + boa/src/builtins/value/rcdate.rs | 36 ++ boa/src/builtins/value/val_type.rs | 3 + boa/src/exec/mod.rs | 14 + 14 files changed, 1056 insertions(+), 3 deletions(-) create mode 100644 boa/src/builtins/date/mod.rs create mode 100644 boa/src/builtins/date/tests.rs create mode 100644 boa/src/builtins/value/rcdate.rs diff --git a/Cargo.lock b/Cargo.lock index e7b28546ff4..3f195a8807a 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.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" +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 dad44d38e2a..cbb18af9b8f 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 00000000000..9917dafc030 --- /dev/null +++ b/boa/src/builtins/date/mod.rs @@ -0,0 +1,615 @@ +#[cfg(test)] +mod tests; + +use crate::{ + builtins::{ + function::{make_builtin_fn, make_constructor_fn}, + object::ObjectData, + value::RcDate, + ResultValue, Value, + }, + exec::PreferredType, + BoaProfiler, Interpreter, +}; +use chrono::{prelude::*, LocalResult}; +use std::fmt::Display; + +#[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 + }; +} + +#[derive(Debug, 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), + _ => write!(f, "Invalid Date"), + } + } +} + +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; + + /// The local date time. + pub fn to_local(&self) -> Option> { + self.0 + .map(|utc| Local::now().timezone().from_utc_datetime(&utc)) + } + + // The UTC date time. + pub fn to_utc(&self) -> Option> { + self.0 + .map(|utc| Utc::now().timezone().from_utc_datetime(&utc)) + } + + /// 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] + fn this_time_value_opt(value: &Value, _: &mut Interpreter) -> Option { + match value { + // 1. If Type(value) is Date, return value. + Value::Date(ref date) => return Some(date.clone()), + + // 2. If Type(value) is Object and value has a [[DateData]] internal slot, then + // a. Assert: Type(value.[[DateData]]) is Date. + // b. Return value.[[DateData]]. + Value::Object(ref object) => { + if let ObjectData::Date(ref date) = object.borrow().data { + return Some(date.clone()); + } + } + _ => {} + } + None + } + + /// 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] + fn this_time_value(value: &Value, ctx: &mut Interpreter) -> Result { + match Self::this_time_value_opt(value, ctx) { + Some(date) => Ok(Date(date.0)), + _ => Err(ctx.construct_type_error("'this' is not a Date")), + } + } + + /// `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.len() == 0 { + 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(RcDate::from(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 Self::this_time_value_opt(value, ctx) { + Some(dt) => { + println!("{:?}", dt); + dt.0 + } + None => 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(RcDate::from(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(RcDate::from(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, day) + .map(|naive_date| naive_date.and_hms_milli_opt(hour, min, sec, milli)) + .flatten() + .map( + |local| match Local::now().timezone().from_local_datetime(&local) { + LocalResult::Single(v) => Some(v.naive_utc()), + // JS simply hopes for the best + LocalResult::Ambiguous(v, _) => Some(v.naive_utc()), + _ => None, + }, + ) + .flatten(); + + let date = Date(final_date); + this.set_data(ObjectData::Date(RcDate::from(date))); + Ok(this.clone()) + } + + /// `Date.prototype.toString()` + /// + /// The `toString()` 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.tostring + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toString + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_string(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + let dt_str = Self::this_time_value(this, ctx)? + .to_local() + .map(|f| f.to_rfc3339()) + .unwrap_or("Invalid Date".to_string()); + Ok(Value::from(dt_str)) + } + + /// `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 + #[inline] + fn get_date(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + Ok(Value::number( + Self::this_time_value(this, ctx)? + .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 + #[inline] + fn get_day(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + Ok(Value::number( + Self::this_time_value(this, ctx)? + .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 + #[inline] + fn get_full_year(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + Ok(Value::number( + Self::this_time_value(this, ctx)? + .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 + #[inline] + fn get_hours(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + Ok(Value::number( + Self::this_time_value(this, ctx)? + .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 + #[inline] + fn get_milliseconds(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + Ok(Value::number( + Self::this_time_value(this, ctx)? + .to_local() + .map_or(f64::NAN, |dt| dt.nanosecond() as f64 / 1_000_000f64), + )) + } + + /// `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 + #[inline] + fn get_minutes(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + Ok(Value::number( + Self::this_time_value(this, ctx)? + .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 + #[inline] + fn get_month(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + Ok(Value::number( + Self::this_time_value(this, ctx)? + .to_local() + .map_or(f64::NAN, |dt| dt.month() 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 + #[inline] + fn get_seconds(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + Ok(Value::number( + Self::this_time_value(this, ctx)? + .to_local() + .map_or(f64::NAN, |dt| dt.second() as f64), + )) + } + + /// `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 + #[inline] + fn get_time(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + Ok(Value::number( + Self::this_time_value(this, ctx)? + .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] + fn get_timezone_offset(_: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { + let offset_seconds = chrono::Local::now().offset().local_minus_utc() as f64; + let offset_minutes = offset_seconds / 60f64; + Ok(Value::number(offset_minutes)) + } + + /// `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.len() == 0 { + 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, day) + .map(|f| f.and_hms_milli_opt(hour, min, sec, milli)) + .flatten() + .map_or(Ok(Value::number(f64::NAN)), |f| { + Ok(Value::number(f.timestamp_millis() as f64)) + }) + } + + pub(crate) fn create(global: &Value) -> Value { + let prototype = Value::new_object(Some(global)); + + make_builtin_fn(Self::to_string, "toString", &prototype, 0); + make_builtin_fn(Self::get_date, "getDate", &prototype, 0); + make_builtin_fn(Self::get_day, "getDay", &prototype, 0); + make_builtin_fn(Self::get_full_year, "getFullYear", &prototype, 0); + make_builtin_fn(Self::get_hours, "getHours", &prototype, 0); + make_builtin_fn(Self::get_milliseconds, "getMilliseconds", &prototype, 0); + make_builtin_fn(Self::get_minutes, "getMinutes", &prototype, 0); + make_builtin_fn(Self::get_month, "getMonth", &prototype, 0); + make_builtin_fn(Self::get_seconds, "getSeconds", &prototype, 0); + make_builtin_fn(Self::get_time, "getTime", &prototype, 0); + make_builtin_fn( + Self::get_timezone_offset, + "getTimezoneOffset", + &prototype, + 0, + ); + + let constructor = make_constructor_fn( + Self::NAME, + Self::LENGTH, + Self::make_date, + global, + prototype, + true, + true, + ); + + make_builtin_fn(Self::now, "now", &constructor, 0); + make_builtin_fn(Self::parse, "parse", &constructor, 1); + make_builtin_fn(Self::utc, "UTC", &constructor, 7); + constructor + } + + /// Initialise the `Date` object on the global object. + #[inline] + pub(crate) fn init(global: &Value) -> (&str, Value) { + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); + + (Self::NAME, Self::create(global)) + } +} diff --git a/boa/src/builtins/date/tests.rs b/boa/src/builtins/date/tests.rs new file mode 100644 index 00000000000..5fcef031ba5 --- /dev/null +++ b/boa/src/builtins/date/tests.rs @@ -0,0 +1,311 @@ +use crate::{ + builtins::{object::ObjectData, Value}, + forward, forward_val, Interpreter, Realm, +}; +use chrono::prelude::*; + +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_call() -> Result<(), Box> { + use chrono::prelude::*; + + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let date_time = forward(&mut engine, "Date()"); + let dt1 = DateTime::parse_from_rfc3339(&date_time)?; + + std::thread::sleep(std::time::Duration::from_millis(1)); + + let date_time = forward(&mut engine, "Date()"); + let dt2 = DateTime::parse_from_rfc3339(&date_time)?; + + assert_ne!(dt1, dt2); + Ok(()) +} + +#[test] +fn date_ctor_call() -> Result<(), Box> { + use chrono::prelude::*; + + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let date_time = forward(&mut engine, "new Date().toString()"); + let dt1 = DateTime::parse_from_rfc3339(&date_time)?; + + std::thread::sleep(std::time::Duration::from_millis(1)); + + let date_time = forward(&mut engine, "new Date().toString()"); + let dt2 = DateTime::parse_from_rfc3339(&date_time)?; + + 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-07-08T09:16:15.779-07:30')"); + + // Internal date is expressed as UTC + assert_eq!( + Some(NaiveDate::from_ymd(2020, 07, 08).and_hms_milli(16, 46, 15, 779)), + 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, 07, 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_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-07-08T09:16:15.779-07:30')"); + + assert_eq!(Ok(Value::Rational(1594226775779f64)), 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, 07, 08, 09, 16, 15, 779)"); + + assert_eq!(Ok(Value::Rational(1594199775779f64)), date_time); + 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, 07, 08, 09).getDate()"); + + assert_eq!(Ok(Value::Rational(08f64)), 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, 07, 08, 09).getDay()"); + + assert_eq!(Ok(Value::Rational(3f64)), 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, 07).getFullYear()"); + + assert_eq!(Ok(Value::Rational(2020f64)), 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, 07, 08, 09, 16).getHours()"); + + assert_eq!(Ok(Value::Rational(09f64)), 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, 07, 08, 09, 16, 15, 779).getMilliseconds()", + ); + + assert_eq!(Ok(Value::Rational(779f64)), 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, 07, 08, 09, 16, 15, 779).getMinutes()", + ); + + assert_eq!(Ok(Value::Rational(16f64)), 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, 07, 08, 09, 16, 15, 779).getMonth()", + ); + + assert_eq!(Ok(Value::Rational(07f64)), 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, 07, 08, 09, 16, 15, 779).getSeconds()", + ); + + assert_eq!(Ok(Value::Rational(15f64)), 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, 07, 08, 09, 16, 15, 779).getTime()", + ); + + assert_eq!(Ok(Value::Rational(1594224975779f64)), 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); + Ok(()) +} diff --git a/boa/src/builtins/mod.rs b/boa/src/builtins/mod.rs index 91449bb7048..878bb541d20 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 02cb405da92..f8adce2ee15 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -18,7 +18,7 @@ use crate::{ function::Function, map::ordered_map::OrderedMap, property::Property, - value::{RcBigInt, RcString, RcSymbol, ResultValue, Value}, + value::{RcBigInt, RcDate, RcString, RcSymbol, ResultValue, Value}, BigInt, RegExp, }, exec::Interpreter, @@ -78,6 +78,7 @@ pub enum ObjectData { Symbol(RcSymbol), Error, Ordinary, + Date(RcDate), Global, } @@ -98,6 +99,7 @@ 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/value/conversions.rs b/boa/src/builtins/value/conversions.rs index 3a0a04872fa..17e21203795 100644 --- a/boa/src/builtins/value/conversions.rs +++ b/boa/src/builtins/value/conversions.rs @@ -126,6 +126,18 @@ impl From for Value { } } +impl From for Value { + fn from(value: Date) -> Self { + Value::date(value) + } +} + +impl From for Value { + fn from(value: RcDate) -> Self { + Value::Date(value) + } +} + impl From for Value { fn from(value: usize) -> Value { Value::integer(value as i32) diff --git a/boa/src/builtins/value/display.rs b/boa/src/builtins/value/display.rs index 8b0f1e3a2cf..f9912ebc083 100644 --- a/boa/src/builtins/value/display.rs +++ b/boa/src/builtins/value/display.rs @@ -241,6 +241,13 @@ impl Display for Value { Self::Object(_) => write!(f, "{}", log_string_from(self, true, true)), Self::Integer(v) => write!(f, "{}", v), Self::BigInt(ref num) => write!(f, "{}n", num), + Self::Date(ref date) => write!( + f, + "{}", + date.to_local() + .map(|f| f.to_rfc3339()) + .unwrap_or("Invalid Date".to_string()) + ), } } } diff --git a/boa/src/builtins/value/hash.rs b/boa/src/builtins/value/hash.rs index 96ce6ebb5fa..16a3e537f35 100644 --- a/boa/src/builtins/value/hash.rs +++ b/boa/src/builtins/value/hash.rs @@ -48,6 +48,7 @@ impl Hash for Value { Self::Rational(rational) => RationalHashable(*rational).hash(state), Self::Symbol(ref symbol) => Hash::hash(symbol, state), Self::Object(ref object) => std::ptr::hash(object.as_ref(), state), + Self::Date(ref date) => Hash::hash(date, state), } } } diff --git a/boa/src/builtins/value/mod.rs b/boa/src/builtins/value/mod.rs index 645cc9cbf22..612fc7a0e02 100644 --- a/boa/src/builtins/value/mod.rs +++ b/boa/src/builtins/value/mod.rs @@ -13,7 +13,7 @@ use crate::builtins::{ function::Function, object::{GcObject, InternalState, InternalStateCell, Object, ObjectData, PROTOTYPE}, property::{Attribute, Property, PropertyKey}, - BigInt, Symbol, + BigInt, Date, Symbol, }; use crate::exec::Interpreter; use crate::BoaProfiler; @@ -34,6 +34,7 @@ pub mod equality; pub mod hash; pub mod operations; pub mod rcbigint; +pub mod rcdate; pub mod rcstring; pub mod rcsymbol; @@ -43,6 +44,7 @@ pub use equality::*; pub use hash::*; pub use operations::*; pub use rcbigint::RcBigInt; +pub use rcdate::RcDate; pub use rcstring::RcString; pub use rcsymbol::RcSymbol; @@ -71,6 +73,8 @@ pub enum Value { Object(GcObject), /// `Symbol` - A Symbol Primitive type. Symbol(RcSymbol), + /// `Date` - A Date type. + Date(RcDate), } impl Value { @@ -155,6 +159,20 @@ impl Value { Self::Symbol(RcSymbol::from(symbol)) } + /// Creates a new date value. + #[inline] + pub(crate) fn date(date: Date) -> Self { + Self::Date(RcDate::from(date)) + } + + /// 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,7 +249,7 @@ impl Value { } } - /// Conversts the `Value` to `JSON`. + /// Converts the `Value` to `JSON`. pub fn to_json(&self, interpreter: &mut Interpreter) -> Result { match *self { Self::Null => Ok(JSONValue::Null), @@ -273,6 +291,7 @@ impl Value { Self::Symbol(_) | Self::Undefined => { unreachable!("Symbols and Undefined JSON Values depend on parent type"); } + Self::Date(ref dt) => Ok(JSONValue::String(dt.to_string())), } } @@ -436,6 +455,9 @@ impl Value { Self::BigInt(_) => { panic!("TypeError: Cannot mix BigInt and other types, use explicit conversions") } + Self::Date(_) => { + todo!("DateTime"); + } } } @@ -457,6 +479,9 @@ impl Value { Self::BigInt(_) => { panic!("TypeError: Cannot mix BigInt and other types, use explicit conversions") } + Self::Date(_) => { + todo!("DateTime"); + } } } diff --git a/boa/src/builtins/value/operations.rs b/boa/src/builtins/value/operations.rs index 7f243234294..9b7f85bc3c3 100644 --- a/boa/src/builtins/value/operations.rs +++ b/boa/src/builtins/value/operations.rs @@ -394,6 +394,7 @@ impl Value { Self::Boolean(true) => Self::integer(1), Self::Boolean(false) | Self::Null => Self::integer(0), Self::BigInt(ref num) => Self::bigint(-num.as_inner().clone()), + Self::Date(_) => todo!("Date"), }) } diff --git a/boa/src/builtins/value/rcdate.rs b/boa/src/builtins/value/rcdate.rs new file mode 100644 index 00000000000..e4b04b84c66 --- /dev/null +++ b/boa/src/builtins/value/rcdate.rs @@ -0,0 +1,36 @@ +use crate::builtins::Date; + +use std::fmt::{self, Display}; +use std::ops::Deref; +use std::rc::Rc; + +use gc::{unsafe_empty_trace, Finalize, Trace}; + +#[derive(Debug, Finalize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct RcDate(Rc); + +unsafe impl Trace for RcDate { + unsafe_empty_trace!(); +} + +impl Display for RcDate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Display::fmt(&self.0, f) + } +} + +impl Deref for RcDate { + type Target = Date; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From for RcDate { + #[inline] + fn from(date: Date) -> Self { + Self(Rc::from(date)) + } +} diff --git a/boa/src/builtins/value/val_type.rs b/boa/src/builtins/value/val_type.rs index 134a81cd006..ff238ebfc2e 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" } } } @@ -54,6 +56,7 @@ impl Value { } } Self::BigInt(_) => Type::BigInt, + Self::Date(_) => Type::Date } } } diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index c33117cb748..990ac3c0bba 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -213,6 +213,7 @@ impl Interpreter { let primitive = self.to_primitive(value, PreferredType::String)?; self.to_string(&primitive) } + Value::Date(_) => todo!("Date"), } } @@ -243,6 +244,7 @@ impl Interpreter { self.to_bigint(&primitive) } Value::Symbol(_) => Err(self.construct_type_error("cannot convert Symbol to a BigInt")), + Value::Date(_) => todo!("Date"), } } @@ -355,6 +357,7 @@ impl Interpreter { let primitive = self.to_primitive(value, PreferredType::Number)?; self.to_number(&primitive) } + Value::Date(_) => todo!("Date"), } } @@ -612,6 +615,17 @@ impl Interpreter { Ok(bigint_obj) } Value::Object(_) => Ok(value.clone()), + Value::Date(ref date) => { + let proto = self + .realm + .environment + .get_binding_value(crate::builtins::date::Date::NAME) + .expect("Date was not initialized") + .get_field(PROTOTYPE); + let date_obj = + Value::new_object_from_prototype(proto, ObjectData::Date(date.clone())); + Ok(date_obj) + } } } From 9881e4416083691b931ac151f832941a5f5eb9f6 Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Mon, 27 Jul 2020 11:13:07 -0700 Subject: [PATCH 03/33] Fix formatting --- boa/src/builtins/value/val_type.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/boa/src/builtins/value/val_type.rs b/boa/src/builtins/value/val_type.rs index ff238ebfc2e..5cc1a7e619f 100644 --- a/boa/src/builtins/value/val_type.rs +++ b/boa/src/builtins/value/val_type.rs @@ -14,7 +14,7 @@ pub enum Type { BigInt, Object, Function, - Date + Date, } impl Type { @@ -29,7 +29,7 @@ impl Type { Self::Function => "function", Self::Object => "object", Self::BigInt => "bigint", - Self::Date => "date" + Self::Date => "date", } } } @@ -56,7 +56,7 @@ impl Value { } } Self::BigInt(_) => Type::BigInt, - Self::Date(_) => Type::Date + Self::Date(_) => Type::Date, } } } From 0a4af189676494b30818f1aae55661db943e8e3a Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Mon, 27 Jul 2020 11:34:06 -0700 Subject: [PATCH 04/33] Fix lints and test failures --- boa/src/builtins/date/mod.rs | 6 +++--- boa/src/builtins/date/tests.rs | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index 9917dafc030..150b389a5d6 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -127,7 +127,7 @@ impl 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.len() == 0 { + } else if args.is_empty() { Self::make_date_now(this) } else if args.len() == 1 { Self::make_date_single(this, args, ctx) @@ -283,7 +283,7 @@ impl Date { let dt_str = Self::this_time_value(this, ctx)? .to_local() .map(|f| f.to_rfc3339()) - .unwrap_or("Invalid Date".to_string()); + .unwrap_or_else(|| "Invalid Date".to_string()); Ok(Value::from(dt_str)) } @@ -512,7 +512,7 @@ impl Date { // This method is implementation-defined and discouraged, so we just require the same format as the string // constructor. - if args.len() == 0 { + if args.is_empty() { return Ok(Value::number(f64::NAN)); } diff --git a/boa/src/builtins/date/tests.rs b/boa/src/builtins/date/tests.rs index 5fcef031ba5..2402239f1b8 100644 --- a/boa/src/builtins/date/tests.rs +++ b/boa/src/builtins/date/tests.rs @@ -281,7 +281,11 @@ fn date_proto_get_time() -> Result<(), Box> { "new Date(2020, 07, 08, 09, 16, 15, 779).getTime()", ); - assert_eq!(Ok(Value::Rational(1594224975779f64)), actual); + 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); Ok(()) } From 92081e3e3809a345d05ab4d702d884c32676cb16 Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Mon, 27 Jul 2020 16:10:15 -0700 Subject: [PATCH 05/33] Expand tests --- boa/src/builtins/date/mod.rs | 5 +- boa/src/builtins/date/tests.rs | 153 +++++++++++++++++++++++++++++++-- 2 files changed, 146 insertions(+), 12 deletions(-) diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index 150b389a5d6..224142f4f50 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -183,10 +183,7 @@ impl Date { ) -> ResultValue { let value = &args[0]; let tv = match Self::this_time_value_opt(value, ctx) { - Some(dt) => { - println!("{:?}", dt); - dt.0 - } + Some(dt) => dt.0, None => match &ctx.to_primitive(value, PreferredType::Default)? { Value::String(str) => match chrono::DateTime::parse_from_rfc3339(&str) { Ok(dt) => Some(dt.naive_utc()), diff --git a/boa/src/builtins/date/tests.rs b/boa/src/builtins/date/tests.rs index 2402239f1b8..d88658262ac 100644 --- a/boa/src/builtins/date/tests.rs +++ b/boa/src/builtins/date/tests.rs @@ -38,6 +38,36 @@ fn forward_dt_local(engine: &mut Interpreter, src: &str) -> Option Result<(), Box> { use chrono::prelude::*; @@ -91,6 +121,17 @@ fn date_ctor_call_string() -> Result<(), Box> { Ok(()) } +#[test] +fn date_ctor_call_string_invalid() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let date_time = + forward_val(&mut engine, "new Date('nope').toString()").expect("Expected Success"); + assert_eq!(Value::string("Invalid Date"), date_time); + Ok(()) +} + #[test] fn date_ctor_call_number() -> Result<(), Box> { let realm = Realm::create(); @@ -132,6 +173,40 @@ fn date_ctor_call_multiple() -> Result<(), Box> { 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, 07, 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_val(&mut engine, src).expect("Expected Success"); + assert_eq!(Value::string("Invalid Date"), date_time); + } + + check("new Date(1/0, 07, 08, 09, 16, 15, 779).toString()"); + check("new Date(2020, 1/0, 08, 09, 16, 15, 779).toString()"); + check("new Date(2020, 07, 1/0, 09, 16, 15, 779).toString()"); + check("new Date(2020, 07, 08, 1/0, 16, 15, 779).toString()"); + check("new Date(2020, 07, 08, 09, 1/0, 15, 779).toString()"); + check("new Date(2020, 07, 08, 09, 16, 1/0, 779).toString()"); + check("new Date(2020, 07, 08, 09, 16, 15, 1/0).toString()"); + + Ok(()) +} + #[test] fn date_ctor_now_call() -> Result<(), Box> { let realm = Realm::create(); @@ -171,14 +246,37 @@ fn date_ctor_utc_call() -> Result<(), Box> { 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::string("NaN"), date_time); + } + + check("Date.UTC(1/0, 07, 08, 09, 16, 15, 779).toString()"); + check("Date.UTC(2020, 1/0, 08, 09, 16, 15, 779).toString()"); + check("Date.UTC(2020, 07, 1/0, 09, 16, 15, 779).toString()"); + check("Date.UTC(2020, 07, 08, 1/0, 16, 15, 779).toString()"); + check("Date.UTC(2020, 07, 08, 09, 1/0, 15, 779).toString()"); + check("Date.UTC(2020, 07, 08, 09, 16, 1/0, 779).toString()"); + check("Date.UTC(2020, 07, 08, 09, 16, 15, 1/0).toString()"); + + 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, 07, 08, 09).getDate()"); - assert_eq!(Ok(Value::Rational(08f64)), actual); + + let actual = forward_val(&mut engine, "new Date(1/0, 07, 08, 09).getDate()"); + assert_eq!(Ok(Value::Rational(f64::NAN)), actual); + Ok(()) } @@ -188,8 +286,10 @@ fn date_proto_get_day_call() -> Result<(), Box> { let mut engine = Interpreter::new(realm); let actual = forward_val(&mut engine, "new Date(2020, 07, 08, 09).getDay()"); - assert_eq!(Ok(Value::Rational(3f64)), actual); + + let actual = forward_val(&mut engine, "new Date(1/0, 07, 08, 09).getDay()"); + assert_eq!(Ok(Value::Rational(f64::NAN)), actual); Ok(()) } @@ -199,8 +299,10 @@ fn date_proto_get_full_year_call() -> Result<(), Box> { let mut engine = Interpreter::new(realm); let actual = forward_val(&mut engine, "new Date(2020, 07).getFullYear()"); - assert_eq!(Ok(Value::Rational(2020f64)), actual); + + let actual = forward_val(&mut engine, "new Date(1/0, 07).getFullYear()"); + assert_eq!(Ok(Value::Rational(f64::NAN)), actual); Ok(()) } @@ -210,8 +312,10 @@ fn date_proto_get_hours_call() -> Result<(), Box> { let mut engine = Interpreter::new(realm); let actual = forward_val(&mut engine, "new Date(2020, 07, 08, 09, 16).getHours()"); - assert_eq!(Ok(Value::Rational(09f64)), actual); + + let actual = forward_val(&mut engine, "new Date(1/0, 07, 08, 09, 16).getHours()"); + assert_eq!(Ok(Value::Rational(f64::NAN)), actual); Ok(()) } @@ -224,8 +328,13 @@ fn date_proto_get_milliseconds_call() -> Result<(), Box> &mut engine, "new Date(2020, 07, 08, 09, 16, 15, 779).getMilliseconds()", ); - assert_eq!(Ok(Value::Rational(779f64)), actual); + + let actual = forward_val( + &mut engine, + "new Date(1/0, 07, 08, 09, 16, 15, 779).getMilliseconds()", + ); + assert_eq!(Ok(Value::Rational(f64::NAN)), actual); Ok(()) } @@ -238,8 +347,13 @@ fn date_proto_get_minutes_call() -> Result<(), Box> { &mut engine, "new Date(2020, 07, 08, 09, 16, 15, 779).getMinutes()", ); - assert_eq!(Ok(Value::Rational(16f64)), actual); + + let actual = forward_val( + &mut engine, + "new Date(1/0, 07, 08, 09, 16, 15, 779).getMinutes()", + ); + assert_eq!(Ok(Value::Rational(f64::NAN)), actual); Ok(()) } @@ -252,8 +366,14 @@ fn date_proto_get_month() -> Result<(), Box> { &mut engine, "new Date(2020, 07, 08, 09, 16, 15, 779).getMonth()", ); - assert_eq!(Ok(Value::Rational(07f64)), actual); + + let actual = forward_val( + &mut engine, + "new Date(1/0, 07, 08, 09, 16, 15, 779).getMonth()", + ); + assert_eq!(Ok(Value::Rational(f64::NAN)), actual); + Ok(()) } @@ -266,8 +386,13 @@ fn date_proto_get_seconds() -> Result<(), Box> { &mut engine, "new Date(2020, 07, 08, 09, 16, 15, 779).getSeconds()", ); - assert_eq!(Ok(Value::Rational(15f64)), actual); + + let actual = forward_val( + &mut engine, + "new Date(1/0, 07, 08, 09, 16, 15, 779).getSeconds()", + ); + assert_eq!(Ok(Value::Rational(f64::NAN)), actual); Ok(()) } @@ -286,6 +411,12 @@ fn date_proto_get_time() -> Result<(), Box> { .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, 07, 08, 09, 16, 15, 779).getTime()", + ); + assert_eq!(Ok(Value::Rational(f64::NAN)), actual); Ok(()) } @@ -311,5 +442,11 @@ fn date_proto_get_timezone_offset() -> Result<(), Box> { 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, 07, 08, 09, 16, 15, 779).getTimezoneOffset()", + ); + assert_eq!(Ok(Value::Rational(offset_minutes)), actual); Ok(()) } From 4cd0f3f71989953330a3d14cca77f4642e5810ac Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Tue, 28 Jul 2020 23:32:05 -0700 Subject: [PATCH 06/33] getYear() --- boa/src/builtins/date/mod.rs | 20 ++++++++++++++++++++ boa/src/builtins/date/tests.rs | 19 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index 224142f4f50..e825d77a6f0 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -461,6 +461,25 @@ impl Date { )) } + /// `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.getyear + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getYear + #[inline] + fn get_year(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + Ok(Value::number( + Self::this_time_value(this, ctx)? + .to_local() + .map_or(f64::NAN, |dt| dt.year() as f64 - 1900f64), + )) + } + /// `Date.prototype.getTimeZoneOffset()` /// /// The getTimezoneOffset() method returns the time zone difference, in minutes, from current locale (host system @@ -579,6 +598,7 @@ impl Date { make_builtin_fn(Self::get_month, "getMonth", &prototype, 0); make_builtin_fn(Self::get_seconds, "getSeconds", &prototype, 0); make_builtin_fn(Self::get_time, "getTime", &prototype, 0); + make_builtin_fn(Self::get_year, "getYear", &prototype, 0); make_builtin_fn( Self::get_timezone_offset, "getTimezoneOffset", diff --git a/boa/src/builtins/date/tests.rs b/boa/src/builtins/date/tests.rs index d88658262ac..9c772ba5a67 100644 --- a/boa/src/builtins/date/tests.rs +++ b/boa/src/builtins/date/tests.rs @@ -420,6 +420,25 @@ fn date_proto_get_time() -> Result<(), Box> { 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, 07, 08, 09, 16, 15, 779).getYear()", + ); + assert_eq!(Ok(Value::Rational(120f64)), actual); + + let actual = forward_val( + &mut engine, + "new Date(1/0, 07, 08, 09, 16, 15, 779).getYear()", + ); + assert_eq!(Ok(Value::Rational(f64::NAN)), actual); + Ok(()) +} + #[test] fn date_proto_get_timezone_offset() -> Result<(), Box> { let realm = Realm::create(); From 158512ab9bfe326e530378bae1f36a0a1c36fcc5 Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Tue, 28 Jul 2020 23:39:41 -0700 Subject: [PATCH 07/33] Cleanup --- boa/src/builtins/date/mod.rs | 5 +++-- boa/src/builtins/date/tests.rs | 28 ++++++++++++++++++++-------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index e825d77a6f0..bf397ad985a 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -461,9 +461,10 @@ impl Date { )) } - /// `Date.prototype.getTime()` + /// `Date.prototype.getYear()` /// - /// The `getTime()` method returns the number of milliseconds since the Unix Epoch. + /// 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] diff --git a/boa/src/builtins/date/tests.rs b/boa/src/builtins/date/tests.rs index 9c772ba5a67..b7348612c93 100644 --- a/boa/src/builtins/date/tests.rs +++ b/boa/src/builtins/date/tests.rs @@ -271,10 +271,13 @@ 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, 07, 08, 09).getDate()"); + let actual = forward_val( + &mut engine, + "new Date(2020, 07, 08, 09, 16, 15, 779).getDate()", + ); assert_eq!(Ok(Value::Rational(08f64)), actual); - let actual = forward_val(&mut engine, "new Date(1/0, 07, 08, 09).getDate()"); + let actual = forward_val(&mut engine, "new Date(1/0).getDate()"); assert_eq!(Ok(Value::Rational(f64::NAN)), actual); Ok(()) @@ -285,10 +288,13 @@ 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, 07, 08, 09).getDay()"); + let actual = forward_val( + &mut engine, + "new Date(2020, 07, 08, 09, 16, 15, 779).getDay()", + ); assert_eq!(Ok(Value::Rational(3f64)), actual); - let actual = forward_val(&mut engine, "new Date(1/0, 07, 08, 09).getDay()"); + let actual = forward_val(&mut engine, "new Date(1/0).getDay()"); assert_eq!(Ok(Value::Rational(f64::NAN)), actual); Ok(()) } @@ -298,10 +304,13 @@ 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, 07).getFullYear()"); + let actual = forward_val( + &mut engine, + "new Date(2020, 07, 08, 09, 16, 15, 779).getFullYear()", + ); assert_eq!(Ok(Value::Rational(2020f64)), actual); - let actual = forward_val(&mut engine, "new Date(1/0, 07).getFullYear()"); + let actual = forward_val(&mut engine, "new Date(1/0).getFullYear()"); assert_eq!(Ok(Value::Rational(f64::NAN)), actual); Ok(()) } @@ -311,10 +320,13 @@ 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, 07, 08, 09, 16).getHours()"); + let actual = forward_val( + &mut engine, + "new Date(2020, 07, 08, 09, 16, 15, 779).getHours()", + ); assert_eq!(Ok(Value::Rational(09f64)), actual); - let actual = forward_val(&mut engine, "new Date(1/0, 07, 08, 09, 16).getHours()"); + let actual = forward_val(&mut engine, "new Date(1/0).getHours()"); assert_eq!(Ok(Value::Rational(f64::NAN)), actual); Ok(()) } From 7474d19df2274ad46f599ebedf01eaa225561a3f Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Tue, 28 Jul 2020 23:49:48 -0700 Subject: [PATCH 08/33] getUTC methods --- boa/src/builtins/date/mod.rs | 171 +++++++++++++++++++++++++++++++++ boa/src/builtins/date/tests.rs | 160 +++++++++++++++++++++++++----- 2 files changed, 307 insertions(+), 24 deletions(-) diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index bf397ad985a..490d6ae9a0b 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -499,6 +499,164 @@ impl Date { Ok(Value::number(offset_minutes)) } + /// `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 + #[inline] + fn get_utc_date(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + Ok(Value::number( + Self::this_time_value(this, ctx)? + .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 + #[inline] + fn get_utc_day(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + Ok(Value::number( + Self::this_time_value(this, ctx)? + .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 + #[inline] + fn get_utc_full_year(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + Ok(Value::number( + Self::this_time_value(this, ctx)? + .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 + #[inline] + fn get_utc_hours(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + Ok(Value::number( + Self::this_time_value(this, ctx)? + .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 + #[inline] + fn get_utc_milliseconds(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + Ok(Value::number( + Self::this_time_value(this, ctx)? + .to_utc() + .map_or(f64::NAN, |dt| dt.nanosecond() as f64 / 1_000_000f64), + )) + } + + /// `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 + #[inline] + fn get_utc_minutes(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + Ok(Value::number( + Self::this_time_value(this, ctx)? + .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 + #[inline] + fn get_utc_month(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + Ok(Value::number( + Self::this_time_value(this, ctx)? + .to_utc() + .map_or(f64::NAN, |dt| dt.month() 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 + #[inline] + fn get_utc_seconds(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + Ok(Value::number( + Self::this_time_value(this, ctx)? + .to_utc() + .map_or(f64::NAN, |dt| dt.second() as f64), + )) + } + /// `Date.now()` /// /// The static `Date.now()` method returns the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC. @@ -606,6 +764,19 @@ impl Date { &prototype, 0, ); + make_builtin_fn(Self::get_utc_date, "getUTCDate", &prototype, 0); + make_builtin_fn(Self::get_utc_day, "getUTCDay", &prototype, 0); + make_builtin_fn(Self::get_utc_full_year, "getUTCFullYear", &prototype, 0); + make_builtin_fn(Self::get_utc_hours, "getUTCHours", &prototype, 0); + make_builtin_fn( + Self::get_utc_milliseconds, + "getUTCMilliseconds", + &prototype, + 0, + ); + make_builtin_fn(Self::get_utc_minutes, "getUTCMinutes", &prototype, 0); + make_builtin_fn(Self::get_utc_month, "getUTCMonth", &prototype, 0); + make_builtin_fn(Self::get_utc_seconds, "getUTCSeconds", &prototype, 0); let constructor = make_constructor_fn( Self::NAME, diff --git a/boa/src/builtins/date/tests.rs b/boa/src/builtins/date/tests.rs index b7348612c93..46f340213cb 100644 --- a/boa/src/builtins/date/tests.rs +++ b/boa/src/builtins/date/tests.rs @@ -342,10 +342,7 @@ fn date_proto_get_milliseconds_call() -> Result<(), Box> ); assert_eq!(Ok(Value::Rational(779f64)), actual); - let actual = forward_val( - &mut engine, - "new Date(1/0, 07, 08, 09, 16, 15, 779).getMilliseconds()", - ); + let actual = forward_val(&mut engine, "new Date(1/0).getMilliseconds()"); assert_eq!(Ok(Value::Rational(f64::NAN)), actual); Ok(()) } @@ -361,10 +358,7 @@ fn date_proto_get_minutes_call() -> Result<(), Box> { ); assert_eq!(Ok(Value::Rational(16f64)), actual); - let actual = forward_val( - &mut engine, - "new Date(1/0, 07, 08, 09, 16, 15, 779).getMinutes()", - ); + let actual = forward_val(&mut engine, "new Date(1/0).getMinutes()"); assert_eq!(Ok(Value::Rational(f64::NAN)), actual); Ok(()) } @@ -380,10 +374,7 @@ fn date_proto_get_month() -> Result<(), Box> { ); assert_eq!(Ok(Value::Rational(07f64)), actual); - let actual = forward_val( - &mut engine, - "new Date(1/0, 07, 08, 09, 16, 15, 779).getMonth()", - ); + let actual = forward_val(&mut engine, "new Date(1/0).getMonth()"); assert_eq!(Ok(Value::Rational(f64::NAN)), actual); Ok(()) @@ -400,10 +391,7 @@ fn date_proto_get_seconds() -> Result<(), Box> { ); assert_eq!(Ok(Value::Rational(15f64)), actual); - let actual = forward_val( - &mut engine, - "new Date(1/0, 07, 08, 09, 16, 15, 779).getSeconds()", - ); + let actual = forward_val(&mut engine, "new Date(1/0).getSeconds()"); assert_eq!(Ok(Value::Rational(f64::NAN)), actual); Ok(()) } @@ -424,10 +412,7 @@ fn date_proto_get_time() -> Result<(), Box> { .timestamp_millis() as f64; assert_eq!(Ok(Value::Rational(ts)), actual); - let actual = forward_val( - &mut engine, - "new Date(1/0, 07, 08, 09, 16, 15, 779).getTime()", - ); + let actual = forward_val(&mut engine, "new Date(1/0).getTime()"); assert_eq!(Ok(Value::Rational(f64::NAN)), actual); Ok(()) } @@ -443,10 +428,7 @@ fn date_proto_get_year() -> Result<(), Box> { ); assert_eq!(Ok(Value::Rational(120f64)), actual); - let actual = forward_val( - &mut engine, - "new Date(1/0, 07, 08, 09, 16, 15, 779).getYear()", - ); + let actual = forward_val(&mut engine, "new Date(1/0).getYear()"); assert_eq!(Ok(Value::Rational(f64::NAN)), actual); Ok(()) } @@ -481,3 +463,133 @@ fn date_proto_get_timezone_offset() -> Result<(), Box> { 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, 07, 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, 07, 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, 07, 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, 07, 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, 07, 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, 07, 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, 07, 08, 09, 16, 15, 779)).getUTCMonth()", + ); + assert_eq!(Ok(Value::Rational(07f64)), 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, 07, 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(()) +} From 799b7d01e2417653b3a02c4b2f2d99dc1fb2490e Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Wed, 29 Jul 2020 00:46:48 -0700 Subject: [PATCH 09/33] setDate --- boa/src/builtins/date/mod.rs | 69 +++++++++++++++++++++++++++++++++- boa/src/builtins/date/tests.rs | 27 +++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index 490d6ae9a0b..3c17510358d 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -11,7 +11,7 @@ use crate::{ exec::PreferredType, BoaProfiler, Interpreter, }; -use chrono::{prelude::*, LocalResult}; +use chrono::{prelude::*, Duration, LocalResult}; use std::fmt::Display; #[inline] @@ -20,6 +20,16 @@ fn is_zero_or_normal_opt(value: Option) -> bool { .map(|value| value == 0f64 || value.is_normal()) .unwrap_or(true) } + +#[inline] +fn to_opt(value: f64) -> Option { + if value == 0f64 || value.is_normal() { + Some(value) + } else { + None + } +} + macro_rules! check_normal_opt { ($($v:expr),+) => { $(is_zero_or_normal_opt($v.into()) &&)+ true @@ -114,6 +124,34 @@ impl Date { } } + fn map_this_time_value( + value: &Value, + ctx: &mut Interpreter, + f: impl FnOnce(NaiveDateTime) -> Option, + ) -> Result<(), Value> { + let date = match value { + // 1. If Type(value) is Date, return value. + Value::Date(cell) => cell.0, + + // 2. If Type(value) is Object and value has a [[DateData]] internal slot, then + // a. Assert: Type(value.[[DateData]]) is Date. + // b. Return value.[[DateData]]. + Value::Object(object) => { + if let ObjectData::Date(cell) = &object.borrow().data { + cell.0 + } else { + None + } + } + _ => return Err(ctx.construct_type_error("'this' is not a Date")), + }; + + let date = date.map(f).flatten(); + value.set_data(ObjectData::Date(RcDate::from(Date(date)))); + + Ok(()) + } + /// `Date()` /// /// Creates a JavaScript `Date` instance that represents a single moment in time in a platform-independent format. @@ -657,6 +695,34 @@ impl Date { )) } + /// `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 + #[inline] + fn set_date(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + let day = args + .get(0) + .map(|day| ctx.to_numeric_number(day).map_or_else(|_| None, to_opt)) + .flatten(); + + Self::map_this_time_value(this, ctx, |date_time| { + day.map(|days| date_time.with_day(1).unwrap() + Duration::days(days as i64 - 1)) + })?; + + Ok(Value::number( + Self::this_time_value(this, ctx)? + .to_utc() + .map_or(f64::NAN, |f| f.timestamp_millis() as f64), + )) + } + /// `Date.now()` /// /// The static `Date.now()` method returns the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC. @@ -777,6 +843,7 @@ impl Date { make_builtin_fn(Self::get_utc_minutes, "getUTCMinutes", &prototype, 0); make_builtin_fn(Self::get_utc_month, "getUTCMonth", &prototype, 0); make_builtin_fn(Self::get_utc_seconds, "getUTCSeconds", &prototype, 0); + make_builtin_fn(Self::set_date, "setDate", &prototype, 0); let constructor = make_constructor_fn( Self::NAME, diff --git a/boa/src/builtins/date/tests.rs b/boa/src/builtins/date/tests.rs index 46f340213cb..f1afad2741f 100644 --- a/boa/src/builtins/date/tests.rs +++ b/boa/src/builtins/date/tests.rs @@ -593,3 +593,30 @@ fn date_proto_get_utc_seconds() -> Result<(), Box> { 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_val( + &mut engine, + "let dt = new Date(2020, 07, 08, 09, 16, 15, 779); dt.setDate(21); dt.getDate()", + ); + assert_eq!(Ok(Value::Rational(21f64)), actual); + + // Date wraps to previous month for 0. + let actual = forward_val( + &mut engine, + "dt = new Date(2020, 07, 08, 09, 16, 15, 779); dt.setDate(0); dt.getDate()", + ); + assert_eq!(Ok(Value::Rational(30f64)), actual); + + let actual = forward_val( + &mut engine, + "dt = new Date(2020, 07, 08, 09, 16, 15, 779); dt.setDate(1/0); dt.getDate()", + ); + assert_eq!(Ok(Value::Rational(f64::NAN)), actual); + + Ok(()) +} From 02c3216073d8b638a61d8b7eefe034447fb4b23c Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Wed, 29 Jul 2020 00:54:53 -0700 Subject: [PATCH 10/33] Fix tests --- boa/src/builtins/date/tests.rs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/boa/src/builtins/date/tests.rs b/boa/src/builtins/date/tests.rs index f1afad2741f..ca7fa05bf74 100644 --- a/boa/src/builtins/date/tests.rs +++ b/boa/src/builtins/date/tests.rs @@ -70,18 +70,14 @@ fn date_this_time_value() { #[test] fn date_call() -> Result<(), Box> { - use chrono::prelude::*; - let realm = Realm::create(); let mut engine = Interpreter::new(realm); - let date_time = forward(&mut engine, "Date()"); - let dt1 = DateTime::parse_from_rfc3339(&date_time)?; + let dt1 = forward(&mut engine, "Date()"); std::thread::sleep(std::time::Duration::from_millis(1)); - let date_time = forward(&mut engine, "Date()"); - let dt2 = DateTime::parse_from_rfc3339(&date_time)?; + let dt2 = forward(&mut engine, "Date()"); assert_ne!(dt1, dt2); Ok(()) @@ -89,18 +85,14 @@ fn date_call() -> Result<(), Box> { #[test] fn date_ctor_call() -> Result<(), Box> { - use chrono::prelude::*; - let realm = Realm::create(); let mut engine = Interpreter::new(realm); - let date_time = forward(&mut engine, "new Date().toString()"); - let dt1 = DateTime::parse_from_rfc3339(&date_time)?; + let dt1 = forward(&mut engine, "new Date().toString()"); std::thread::sleep(std::time::Duration::from_millis(1)); - let date_time = forward(&mut engine, "new Date().toString()"); - let dt2 = DateTime::parse_from_rfc3339(&date_time)?; + let dt2 = forward(&mut engine, "new Date().toString()"); assert_ne!(dt1, dt2); Ok(()) From 5bbd0bb1ff6117390637775b14f03671bc182b32 Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Wed, 29 Jul 2020 00:58:50 -0700 Subject: [PATCH 11/33] Fix lint --- boa/src/builtins/value/display.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boa/src/builtins/value/display.rs b/boa/src/builtins/value/display.rs index f9912ebc083..a0d4f7f2240 100644 --- a/boa/src/builtins/value/display.rs +++ b/boa/src/builtins/value/display.rs @@ -246,7 +246,7 @@ impl Display for Value { "{}", date.to_local() .map(|f| f.to_rfc3339()) - .unwrap_or("Invalid Date".to_string()) + .unwrap_or_else(|| "Invalid Date".to_string()) ), } } From bc6982cb77d65c0d34546c44a2c6eb1c19343abb Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Wed, 29 Jul 2020 14:56:06 -0700 Subject: [PATCH 12/33] Clean up getters and setters --- boa/src/builtins/date/mod.rs | 769 +++++++++++++++-------------------- 1 file changed, 330 insertions(+), 439 deletions(-) diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index 3c17510358d..1cebfb65da6 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -21,21 +21,60 @@ fn is_zero_or_normal_opt(value: Option) -> bool { .unwrap_or(true) } -#[inline] -fn to_opt(value: f64) -> Option { - if value == 0f64 || value.is_normal() { - Some(value) - } else { - None - } -} - macro_rules! check_normal_opt { ($($v:expr),+) => { $(is_zero_or_normal_opt($v.into()) &&)+ true }; } +macro_rules! getter_method { + ($(#[$outer:meta])* fn $name:ident ($tz:ident, $var:ident) $get:expr) => { + $(#[$outer])* + fn $name(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + Ok(Value::number( + Self::this_time_value(this, ctx)? + .$tz() + .map_or(f64::NAN, |$var| $get), + )) + } + }; +} + +macro_rules! setter_method { + ($(#[$outer:meta])* fn $name:ident ($tz:ident, $date_time:ident, $var:ident) $mutate:expr) => { + $(#[$outer])* + fn $name(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + // If the first arg is not present or NaN, the Date becomes NaN itself. + let arg = args + .get(0) + .map(|value| { + ctx.to_numeric_number(value).map_or_else( + |_| None, + |value| { + if value == 0f64 || value.is_normal() { + Some(value) + } else { + None + } + }, + ) + }) + .flatten(); + + let inner = Date::this_time_value(this, ctx)?.$tz(); + let new_value = inner.map(|$date_time| arg.map(|$var| $mutate)).flatten(); + let new_value = new_value.map(|date_time| date_time.naive_utc()); + this.set_data(ObjectData::Date(RcDate::from(Date(new_value)))); + + Ok(Value::number( + Self::this_time_value(this, ctx)? + .to_utc() + .map_or(f64::NAN, |f| f.timestamp_millis() as f64), + )) + } + }; +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Date(Option); @@ -86,72 +125,25 @@ impl Date { /// /// [spec]: https://tc39.es/ecma262/#sec-thistimevalue #[inline] - fn this_time_value_opt(value: &Value, _: &mut Interpreter) -> Option { + fn this_time_value(value: &Value, ctx: &mut Interpreter) -> Result { match value { // 1. If Type(value) is Date, return value. - Value::Date(ref date) => return Some(date.clone()), + Value::Date(ref date) => Ok(date.clone()), // 2. If Type(value) is Object and value has a [[DateData]] internal slot, then // a. Assert: Type(value.[[DateData]]) is Date. // b. Return value.[[DateData]]. Value::Object(ref object) => { if let ObjectData::Date(ref date) = object.borrow().data { - return Some(date.clone()); + Ok(date.clone()) + } else { + Err(ctx.construct_type_error("'this' is not a Date")) } } - _ => {} - } - None - } - - /// 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] - fn this_time_value(value: &Value, ctx: &mut Interpreter) -> Result { - match Self::this_time_value_opt(value, ctx) { - Some(date) => Ok(Date(date.0)), _ => Err(ctx.construct_type_error("'this' is not a Date")), } } - fn map_this_time_value( - value: &Value, - ctx: &mut Interpreter, - f: impl FnOnce(NaiveDateTime) -> Option, - ) -> Result<(), Value> { - let date = match value { - // 1. If Type(value) is Date, return value. - Value::Date(cell) => cell.0, - - // 2. If Type(value) is Object and value has a [[DateData]] internal slot, then - // a. Assert: Type(value.[[DateData]]) is Date. - // b. Return value.[[DateData]]. - Value::Object(object) => { - if let ObjectData::Date(cell) = &object.borrow().data { - cell.0 - } else { - None - } - } - _ => return Err(ctx.construct_type_error("'this' is not a Date")), - }; - - let date = date.map(f).flatten(); - value.set_data(ObjectData::Date(RcDate::from(Date(date)))); - - Ok(()) - } - /// `Date()` /// /// Creates a JavaScript `Date` instance that represents a single moment in time in a platform-independent format. @@ -220,9 +212,9 @@ impl Date { ctx: &mut Interpreter, ) -> ResultValue { let value = &args[0]; - let tv = match Self::this_time_value_opt(value, ctx) { - Some(dt) => dt.0, - None => match &ctx.to_primitive(value, PreferredType::Default)? { + let tv = match Self::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, @@ -322,201 +314,151 @@ impl Date { Ok(Value::from(dt_str)) } - /// `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 - #[inline] - fn get_date(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - Ok(Value::number( - Self::this_time_value(this, ctx)? - .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 - #[inline] - fn get_day(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - Ok(Value::number( - Self::this_time_value(this, ctx)? - .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 - #[inline] - fn get_full_year(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - Ok(Value::number( - Self::this_time_value(this, ctx)? - .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 - #[inline] - fn get_hours(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - Ok(Value::number( - Self::this_time_value(this, ctx)? - .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 - #[inline] - fn get_milliseconds(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - Ok(Value::number( - Self::this_time_value(this, ctx)? - .to_local() - .map_or(f64::NAN, |dt| dt.nanosecond() as f64 / 1_000_000f64), - )) - } - - /// `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 - #[inline] - fn get_minutes(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - Ok(Value::number( - Self::this_time_value(this, ctx)? - .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 - #[inline] - fn get_month(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - Ok(Value::number( - Self::this_time_value(this, ctx)? - .to_local() - .map_or(f64::NAN, |dt| dt.month() 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 - #[inline] - fn get_seconds(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - Ok(Value::number( - Self::this_time_value(this, ctx)? - .to_local() - .map_or(f64::NAN, |dt| dt.second() as f64), - )) - } - - /// `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 - #[inline] - fn get_time(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - Ok(Value::number( - Self::this_time_value(this, ctx)? - .to_utc() - .map_or(f64::NAN, |dt| dt.timestamp_millis() as f64), - )) + getter_method! { + /// `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 + fn get_date(to_local, dt) { dt.day() as f64 } + } + + getter_method! { + /// `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 + fn get_day(to_local, dt) { + let weekday = dt.weekday() as u32; + let weekday = (weekday + 1) % 7; // 0 represents Monday in Chrono + weekday 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 - #[inline] - fn get_year(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - Ok(Value::number( - Self::this_time_value(this, ctx)? - .to_local() - .map_or(f64::NAN, |dt| dt.year() as f64 - 1900f64), - )) + getter_method! { + /// `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 + fn get_full_year(to_local, dt) { dt.year() as f64 } + } + + getter_method! { + /// `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 + fn get_hours(to_local, dt) { dt.hour() as f64 } + } + + getter_method! { + /// `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 + fn get_milliseconds(to_local, dt) { dt.nanosecond() as f64 / 1_000_000f64 } + } + + getter_method! { + /// `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 + fn get_minutes(to_local, dt) { dt.minute() as f64 } + } + + getter_method! { + /// `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 + fn get_month(to_local, dt) { dt.month() as f64 } + } + + getter_method! { + /// `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 + fn get_seconds(to_local, dt) { dt.second() as f64 } + } + + getter_method! { + /// `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 + fn get_year(to_local, dt) { dt.year() as f64 - 1900f64 } + } + + getter_method! { + /// `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 + fn get_time(to_utc, dt) { dt.timestamp_millis() as f64 } } /// `Date.prototype.getTimeZoneOffset()` @@ -537,190 +479,139 @@ impl Date { Ok(Value::number(offset_minutes)) } - /// `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 - #[inline] - fn get_utc_date(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - Ok(Value::number( - Self::this_time_value(this, ctx)? - .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 - #[inline] - fn get_utc_day(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - Ok(Value::number( - Self::this_time_value(this, ctx)? - .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 - #[inline] - fn get_utc_full_year(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - Ok(Value::number( - Self::this_time_value(this, ctx)? - .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 - #[inline] - fn get_utc_hours(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - Ok(Value::number( - Self::this_time_value(this, ctx)? - .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 - #[inline] - fn get_utc_milliseconds(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - Ok(Value::number( - Self::this_time_value(this, ctx)? - .to_utc() - .map_or(f64::NAN, |dt| dt.nanosecond() as f64 / 1_000_000f64), - )) - } - - /// `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 - #[inline] - fn get_utc_minutes(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - Ok(Value::number( - Self::this_time_value(this, ctx)? - .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 - #[inline] - fn get_utc_month(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - Ok(Value::number( - Self::this_time_value(this, ctx)? - .to_utc() - .map_or(f64::NAN, |dt| dt.month() 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 - #[inline] - fn get_utc_seconds(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - Ok(Value::number( - Self::this_time_value(this, ctx)? - .to_utc() - .map_or(f64::NAN, |dt| dt.second() as f64), - )) + getter_method! { + /// `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 + fn get_utc_date(to_utc, dt) { dt.day() as f64 } + } + + getter_method! { + /// `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 + fn get_utc_day(to_utc, dt) { + let weekday = dt.weekday() as u32; + let weekday = (weekday + 1) % 7; // 0 represents Monday in Chrono + weekday 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 - #[inline] - fn set_date(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - let day = args - .get(0) - .map(|day| ctx.to_numeric_number(day).map_or_else(|_| None, to_opt)) - .flatten(); - - Self::map_this_time_value(this, ctx, |date_time| { - day.map(|days| date_time.with_day(1).unwrap() + Duration::days(days as i64 - 1)) - })?; - - Ok(Value::number( - Self::this_time_value(this, ctx)? - .to_utc() - .map_or(f64::NAN, |f| f.timestamp_millis() as f64), - )) + getter_method! { + /// `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 + fn get_utc_full_year(to_utc, dt) { dt.year() as f64 } + } + + getter_method! { + /// `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 + fn get_utc_hours(to_utc, dt) { dt.hour() as f64 } + } + + getter_method! { + /// `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 + fn get_utc_milliseconds(to_utc, dt) { dt.nanosecond() as f64 / 1_000_000f64 } + } + + getter_method! { + /// `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 + fn get_utc_minutes(to_utc, dt) { dt.minute() as f64 } + } + + getter_method! { + /// `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 + fn get_utc_month(to_utc, dt) { dt.month() as f64 } + } + + getter_method! { + /// `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 + fn get_utc_seconds(to_utc, dt) { dt.second() as f64 } + } + + setter_method! { + /// `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 + fn set_date (to_local, date_time, day) { + date_time.with_day(1).unwrap() + Duration::days(day as i64 - 1) + } } /// `Date.now()` From 27e04172eb9496646426ccc65cdeb5be8b55e092 Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Wed, 29 Jul 2020 15:04:54 -0700 Subject: [PATCH 13/33] Integrate latest breaking changes --- boa/src/builtins/date/mod.rs | 25 +++++++++++-------------- boa/src/realm.rs | 2 +- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index 1cebfb65da6..449ebb9d954 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -701,7 +701,12 @@ impl Date { }) } - pub(crate) fn create(global: &Value) -> Value { + /// 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(Self::to_string, "toString", &prototype, 0); @@ -736,7 +741,7 @@ impl Date { make_builtin_fn(Self::get_utc_seconds, "getUTCSeconds", &prototype, 0); make_builtin_fn(Self::set_date, "setDate", &prototype, 0); - let constructor = make_constructor_fn( + let date_time_object = make_constructor_fn( Self::NAME, Self::LENGTH, Self::make_date, @@ -746,17 +751,9 @@ impl Date { true, ); - make_builtin_fn(Self::now, "now", &constructor, 0); - make_builtin_fn(Self::parse, "parse", &constructor, 1); - make_builtin_fn(Self::utc, "UTC", &constructor, 7); - constructor - } - - /// Initialise the `Date` object on the global object. - #[inline] - pub(crate) fn init(global: &Value) -> (&str, Value) { - let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); - - (Self::NAME, Self::create(global)) + 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) } } diff --git a/boa/src/realm.rs b/boa/src/realm.rs index 2485107c451..f4f1f1839cb 100644 --- a/boa/src/realm.rs +++ b/boa/src/realm.rs @@ -38,7 +38,7 @@ impl Realm { let global = Value::new_object(None); // Allow identification of the global object easily - global.set_data(builtins::object::ObjectData::Global); + 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()); From 0fef8962cb3486950687ccb4465c46b5ae41b57e Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Thu, 30 Jul 2020 10:20:09 -0700 Subject: [PATCH 14/33] SetFullYear --- boa/src/builtins/date/mod.rs | 114 +++++++++++++++++++++++++++++---- boa/src/builtins/date/tests.rs | 70 ++++++++++++++++++++ 2 files changed, 173 insertions(+), 11 deletions(-) diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index 449ebb9d954..b9f6fa69651 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -21,6 +21,62 @@ fn is_zero_or_normal_opt(value: Option) -> bool { .unwrap_or(true) } +/// Some JS functions allow completely invalid dates, and the runtime is expected to make sense of this. This function +/// constrains a date to correct values. +fn fix_date(year: &mut i32, month: &mut i32, day: &mut i32) { + #[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) + *day; + } 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; + } + } + } + } + + fix_day(year, month, day); +} + macro_rules! check_normal_opt { ($($v:expr),+) => { $(is_zero_or_normal_opt($v.into()) &&)+ true @@ -41,12 +97,13 @@ macro_rules! getter_method { } macro_rules! setter_method { - ($(#[$outer:meta])* fn $name:ident ($tz:ident, $date_time:ident, $var:ident) $mutate:expr) => { + ($(#[$outer:meta])* fn $name:ident ($tz:ident, $date_time:ident, $var:ident[$count:literal]) $mutate:expr) => { $(#[$outer])* fn $name(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { // If the first arg is not present or NaN, the Date becomes NaN itself. - let arg = args - .get(0) + fn get_arg(i: usize, args: &[Value], ctx: &mut Interpreter) -> Option { + args + .get(i) .map(|value| { ctx.to_numeric_number(value).map_or_else( |_| None, @@ -59,10 +116,16 @@ macro_rules! setter_method { }, ) }) - .flatten(); + .flatten() + } + + let mut $var = [None; $count]; + for i in 0..$count { + $var[i] = get_arg(i, args, ctx); + } let inner = Date::this_time_value(this, ctx)?.$tz(); - let new_value = inner.map(|$date_time| arg.map(|$var| $mutate)).flatten(); + let new_value = inner.map(|$date_time| $mutate).flatten(); let new_value = new_value.map(|date_time| date_time.naive_utc()); this.set_data(ObjectData::Date(RcDate::from(Date(new_value)))); @@ -277,7 +340,7 @@ impl Date { year }; - let final_date = NaiveDate::from_ymd_opt(year, month, day) + let final_date = NaiveDate::from_ymd_opt(year, month + 1, day) .map(|naive_date| naive_date.and_hms_milli_opt(hour, min, sec, milli)) .flatten() .map( @@ -415,7 +478,7 @@ impl Date { /// /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.getmonth /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getMonth - fn get_month(to_local, dt) { dt.month() as f64 } + fn get_month(to_local, dt) { dt.month0() as f64 } } getter_method! { @@ -580,7 +643,7 @@ impl Date { /// /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.getutcmonth /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getUTCMonth - fn get_utc_month(to_utc, dt) { dt.month() as f64 } + fn get_utc_month(to_utc, dt) { dt.month0() as f64 } } getter_method! { @@ -609,8 +672,36 @@ impl Date { /// /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setdate /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setDate - fn set_date (to_local, date_time, day) { - date_time.with_day(1).unwrap() + Duration::days(day as i64 - 1) + fn set_date (to_local, date_time, args[1]) { + args[0].map_or(None, |day| Some(date_time.with_day(1).unwrap() + Duration::days(day as i64 - 1))) + } + } + + setter_method! { + /// `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 + fn set_full_year (to_local, date_time, args[3]) { + args[0].map_or(None, |year| { + let mut year = year as i32; + let mut month = args[1].unwrap_or_else(|| date_time.month0() as f64) as i32; + let mut day = args[2].unwrap_or_else(|| date_time.day() as f64) as i32 - 1; + + fix_date(&mut year, &mut month, &mut day); + + date_time + .with_year(year) + .map_or(None, |date_time| date_time.with_month0(month as u32)) + .map_or(None, |date_time| date_time.with_day(day as u32 + 1)) + }) } } @@ -739,7 +830,8 @@ impl Date { make_builtin_fn(Self::get_utc_minutes, "getUTCMinutes", &prototype, 0); make_builtin_fn(Self::get_utc_month, "getUTCMonth", &prototype, 0); make_builtin_fn(Self::get_utc_seconds, "getUTCSeconds", &prototype, 0); - make_builtin_fn(Self::set_date, "setDate", &prototype, 0); + make_builtin_fn(Self::set_date, "setDate", &prototype, 1); + make_builtin_fn(Self::set_full_year, "setFullYear", &prototype, 1); let date_time_object = make_constructor_fn( Self::NAME, diff --git a/boa/src/builtins/date/tests.rs b/boa/src/builtins/date/tests.rs index ca7fa05bf74..f6d296ae03e 100644 --- a/boa/src/builtins/date/tests.rs +++ b/boa/src/builtins/date/tests.rs @@ -612,3 +612,73 @@ fn date_proto_set_date() -> Result<(), Box> { Ok(()) } + +#[test] +fn date_proto_set_full_year_in_bounds() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + forward_val( + &mut engine, + "function fmt(dt) { return dt.getFullYear() + '-' + dt.getMonth() + '-' + dt.getDate(); }", + ) + .expect("Helper function installed"); + + let actual = forward_val( + &mut engine, + "let dt = new Date(2020, 07, 08, 09, 16, 15, 779); dt.setFullYear(2012); fmt(dt)", + ); + assert_eq!(Ok(Value::string("2012-7-8")), actual); + + let actual = forward_val( + &mut engine, + "dt = new Date(2020, 07, 08, 09, 16, 15, 779); dt.setFullYear(2012, 8); fmt(dt)", + ); + assert_eq!(Ok(Value::string("2012-8-8")), actual); + + let actual = forward_val( + &mut engine, + "dt = new Date(2020, 07, 08, 09, 16, 15, 779); dt.setFullYear(2012, 8, 9); fmt(dt)", + ); + assert_eq!(Ok(Value::string("2012-8-9")), actual); + + Ok(()) +} + +#[test] +fn date_proto_set_full_year_out_of_bounds() -> Result<(), Box> { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + forward_val( + &mut engine, + "function fmt(dt) { return dt.getFullYear() + '-' + dt.getMonth() + '-' + dt.getDate(); }", + ) + .expect("Helper function installed"); + + let actual = forward_val( + &mut engine, + "let dt = new Date(2020, 07, 08, 09, 16, 15, 779); dt.setFullYear(2012, 33); fmt(dt)", + ); + assert_eq!(Ok(Value::string("2014-9-8")), actual); + + let actual = forward_val( + &mut engine, + "dt = new Date(2020, 07, 08, 09, 16, 15, 779); dt.setFullYear(2012, -33); fmt(dt)", + ); + assert_eq!(Ok(Value::string("2009-3-8")), actual); + + let actual = forward_val( + &mut engine, + "dt = new Date(2020, 07, 08, 09, 16, 15, 779); dt.setFullYear(2012, 9, 950); fmt(dt)", + ); + assert_eq!(Ok(Value::string("2015-4-8")), actual); + + let actual = forward_val( + &mut engine, + "dt = new Date(2020, 07, 08, 09, 16, 15, 779); dt.setFullYear(2012, 9, -950); fmt(dt)", + ); + assert_eq!(Ok(Value::string("2010-1-23")), actual); + + Ok(()) +} From 00e471305ffc2edc1251038785287dd61a73bbd7 Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Thu, 30 Jul 2020 16:45:41 -0700 Subject: [PATCH 15/33] `setHours()` and fix month zeroness --- boa/src/builtins/date/mod.rs | 54 +++++++- boa/src/builtins/date/tests.rs | 227 +++++++++++++++++++++------------ 2 files changed, 198 insertions(+), 83 deletions(-) diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index b9f6fa69651..e0bb5078b33 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -14,6 +14,8 @@ use crate::{ use chrono::{prelude::*, Duration, LocalResult}; use std::fmt::Display; +const NANOS_IN_MS: f64 = 1_000_000f64; + #[inline] fn is_zero_or_normal_opt(value: Option) -> bool { value @@ -377,6 +379,25 @@ impl Date { Ok(Value::from(dt_str)) } + /// `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 + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_utc_string(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + let dt_str = Self::this_time_value(this, ctx)? + .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()); + Ok(Value::from(dt_str)) + } + getter_method! { /// `Date.prototype.getDate()` /// @@ -449,7 +470,7 @@ impl Date { /// /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.getmilliseconds /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getMilliseconds - fn get_milliseconds(to_local, dt) { dt.nanosecond() as f64 / 1_000_000f64 } + fn get_milliseconds(to_local, dt) { dt.nanosecond() as f64 / NANOS_IN_MS } } getter_method! { @@ -614,7 +635,7 @@ impl Date { /// /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.getutcmilliseconds /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getUTCMilliseconds - fn get_utc_milliseconds(to_utc, dt) { dt.nanosecond() as f64 / 1_000_000f64 } + fn get_utc_milliseconds(to_utc, dt) { dt.nanosecond() as f64 / NANOS_IN_MS } } getter_method! { @@ -705,6 +726,32 @@ impl Date { } } + setter_method! { + /// `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 + fn set_hours (to_local, date_time, args[4]) { + args[0].map_or(None, |hour| { + let hour = hour as i64; + let minute = args[1].map_or_else(|| date_time.minute() as i64, |minute| minute as i64); + let second = args[2].map_or_else(|| date_time.second() as i64, |second| second as i64); + let ms = args[3].map_or_else(|| (date_time.nanosecond() as f64 / NANOS_IN_MS) as i64, |second| second as i64); + + let duration = Duration::hours(hour) + Duration::minutes(minute) + Duration::seconds(second) + Duration::milliseconds(ms); + date_time.date().and_hms(0, 0, 0).checked_add_signed(duration) + }) + } + } + /// `Date.now()` /// /// The static `Date.now()` method returns the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC. @@ -784,7 +831,7 @@ impl Date { year }; - NaiveDate::from_ymd_opt(year, month, day) + NaiveDate::from_ymd_opt(year, month + 1, day) .map(|f| f.and_hms_milli_opt(hour, min, sec, milli)) .flatten() .map_or(Ok(Value::number(f64::NAN)), |f| { @@ -832,6 +879,7 @@ impl Date { make_builtin_fn(Self::get_utc_seconds, "getUTCSeconds", &prototype, 0); make_builtin_fn(Self::set_date, "setDate", &prototype, 1); make_builtin_fn(Self::set_full_year, "setFullYear", &prototype, 1); + make_builtin_fn(Self::set_hours, "setHours", &prototype, 1); let date_time_object = make_constructor_fn( Self::NAME, diff --git a/boa/src/builtins/date/tests.rs b/boa/src/builtins/date/tests.rs index f6d296ae03e..bae01b05490 100644 --- a/boa/src/builtins/date/tests.rs +++ b/boa/src/builtins/date/tests.rs @@ -4,6 +4,9 @@ use crate::{ }; 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 @@ -103,11 +106,11 @@ 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-07-08T09:16:15.779-07:30')"); + 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, 07, 08).and_hms_milli(16, 46, 15, 779)), + Some(NaiveDate::from_ymd(2020, 06, 08).and_hms_milli(15, 46, 15, 779)), date_time ); Ok(()) @@ -156,7 +159,7 @@ 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, 07, 08, 09, 16, 15, 779)"); + 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)), @@ -170,7 +173,7 @@ 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, 07, 08, 09, 16, 15, 779)"); + 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)), @@ -188,13 +191,13 @@ fn date_ctor_call_multiple_nan() -> Result<(), Box> { assert_eq!(Value::string("Invalid Date"), date_time); } - check("new Date(1/0, 07, 08, 09, 16, 15, 779).toString()"); + check("new Date(1/0, 06, 08, 09, 16, 15, 779).toString()"); check("new Date(2020, 1/0, 08, 09, 16, 15, 779).toString()"); - check("new Date(2020, 07, 1/0, 09, 16, 15, 779).toString()"); - check("new Date(2020, 07, 08, 1/0, 16, 15, 779).toString()"); - check("new Date(2020, 07, 08, 09, 1/0, 15, 779).toString()"); - check("new Date(2020, 07, 08, 09, 16, 1/0, 779).toString()"); - check("new Date(2020, 07, 08, 09, 16, 15, 1/0).toString()"); + check("new Date(2020, 06, 1/0, 09, 16, 15, 779).toString()"); + check("new Date(2020, 06, 08, 1/0, 16, 15, 779).toString()"); + check("new Date(2020, 06, 08, 09, 1/0, 15, 779).toString()"); + check("new Date(2020, 06, 08, 09, 16, 1/0, 779).toString()"); + check("new Date(2020, 06, 08, 09, 16, 15, 1/0).toString()"); Ok(()) } @@ -221,9 +224,9 @@ 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-07-08T09:16:15.779-07:30')"); + let date_time = forward_val(&mut engine, "Date.parse('2020-06-08T09:16:15.779-07:30')"); - assert_eq!(Ok(Value::Rational(1594226775779f64)), date_time); + assert_eq!(Ok(Value::Rational(1591634775779f64)), date_time); Ok(()) } @@ -232,7 +235,7 @@ 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, 07, 08, 09, 16, 15, 779)"); + 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(()) @@ -247,13 +250,13 @@ fn date_ctor_utc_call_nan() -> Result<(), Box> { assert_eq!(Value::string("NaN"), date_time); } - check("Date.UTC(1/0, 07, 08, 09, 16, 15, 779).toString()"); + check("Date.UTC(1/0, 06, 08, 09, 16, 15, 779).toString()"); check("Date.UTC(2020, 1/0, 08, 09, 16, 15, 779).toString()"); - check("Date.UTC(2020, 07, 1/0, 09, 16, 15, 779).toString()"); - check("Date.UTC(2020, 07, 08, 1/0, 16, 15, 779).toString()"); - check("Date.UTC(2020, 07, 08, 09, 1/0, 15, 779).toString()"); - check("Date.UTC(2020, 07, 08, 09, 16, 1/0, 779).toString()"); - check("Date.UTC(2020, 07, 08, 09, 16, 15, 1/0).toString()"); + check("Date.UTC(2020, 06, 1/0, 09, 16, 15, 779).toString()"); + check("Date.UTC(2020, 06, 08, 1/0, 16, 15, 779).toString()"); + check("Date.UTC(2020, 06, 08, 09, 1/0, 15, 779).toString()"); + check("Date.UTC(2020, 06, 08, 09, 16, 1/0, 779).toString()"); + check("Date.UTC(2020, 06, 08, 09, 16, 15, 1/0).toString()"); Ok(()) } @@ -265,7 +268,7 @@ fn date_proto_get_date_call() -> Result<(), Box> { let actual = forward_val( &mut engine, - "new Date(2020, 07, 08, 09, 16, 15, 779).getDate()", + "new Date(2020, 06, 08, 09, 16, 15, 779).getDate()", ); assert_eq!(Ok(Value::Rational(08f64)), actual); @@ -282,7 +285,7 @@ fn date_proto_get_day_call() -> Result<(), Box> { let actual = forward_val( &mut engine, - "new Date(2020, 07, 08, 09, 16, 15, 779).getDay()", + "new Date(2020, 06, 08, 09, 16, 15, 779).getDay()", ); assert_eq!(Ok(Value::Rational(3f64)), actual); @@ -298,7 +301,7 @@ fn date_proto_get_full_year_call() -> Result<(), Box> { let actual = forward_val( &mut engine, - "new Date(2020, 07, 08, 09, 16, 15, 779).getFullYear()", + "new Date(2020, 06, 08, 09, 16, 15, 779).getFullYear()", ); assert_eq!(Ok(Value::Rational(2020f64)), actual); @@ -314,7 +317,7 @@ fn date_proto_get_hours_call() -> Result<(), Box> { let actual = forward_val( &mut engine, - "new Date(2020, 07, 08, 09, 16, 15, 779).getHours()", + "new Date(2020, 06, 08, 09, 16, 15, 779).getHours()", ); assert_eq!(Ok(Value::Rational(09f64)), actual); @@ -330,7 +333,7 @@ fn date_proto_get_milliseconds_call() -> Result<(), Box> let actual = forward_val( &mut engine, - "new Date(2020, 07, 08, 09, 16, 15, 779).getMilliseconds()", + "new Date(2020, 06, 08, 09, 16, 15, 779).getMilliseconds()", ); assert_eq!(Ok(Value::Rational(779f64)), actual); @@ -346,7 +349,7 @@ fn date_proto_get_minutes_call() -> Result<(), Box> { let actual = forward_val( &mut engine, - "new Date(2020, 07, 08, 09, 16, 15, 779).getMinutes()", + "new Date(2020, 06, 08, 09, 16, 15, 779).getMinutes()", ); assert_eq!(Ok(Value::Rational(16f64)), actual); @@ -362,9 +365,9 @@ fn date_proto_get_month() -> Result<(), Box> { let actual = forward_val( &mut engine, - "new Date(2020, 07, 08, 09, 16, 15, 779).getMonth()", + "new Date(2020, 06, 08, 09, 16, 15, 779).getMonth()", ); - assert_eq!(Ok(Value::Rational(07f64)), actual); + 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); @@ -379,7 +382,7 @@ fn date_proto_get_seconds() -> Result<(), Box> { let actual = forward_val( &mut engine, - "new Date(2020, 07, 08, 09, 16, 15, 779).getSeconds()", + "new Date(2020, 06, 08, 09, 16, 15, 779).getSeconds()", ); assert_eq!(Ok(Value::Rational(15f64)), actual); @@ -395,7 +398,7 @@ fn date_proto_get_time() -> Result<(), Box> { let actual = forward_val( &mut engine, - "new Date(2020, 07, 08, 09, 16, 15, 779).getTime()", + "new Date(2020, 06, 08, 09, 16, 15, 779).getTime()", ); let ts = Local @@ -416,7 +419,7 @@ fn date_proto_get_year() -> Result<(), Box> { let actual = forward_val( &mut engine, - "new Date(2020, 07, 08, 09, 16, 15, 779).getYear()", + "new Date(2020, 06, 08, 09, 16, 15, 779).getYear()", ); assert_eq!(Ok(Value::Rational(120f64)), actual); @@ -450,7 +453,7 @@ fn date_proto_get_timezone_offset() -> Result<(), Box> { let actual = forward_val( &mut engine, - "new Date(1/0, 07, 08, 09, 16, 15, 779).getTimezoneOffset()", + "new Date(1/0, 06, 08, 09, 16, 15, 779).getTimezoneOffset()", ); assert_eq!(Ok(Value::Rational(offset_minutes)), actual); Ok(()) @@ -463,7 +466,7 @@ fn date_proto_get_utc_date_call() -> Result<(), Box> { let actual = forward_val( &mut engine, - "new Date(Date.UTC(2020, 07, 08, 09, 16, 15, 779)).getUTCDate()", + "new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)).getUTCDate()", ); assert_eq!(Ok(Value::Rational(08f64)), actual); @@ -480,7 +483,7 @@ fn date_proto_get_utc_day_call() -> Result<(), Box> { let actual = forward_val( &mut engine, - "new Date(Date.UTC(2020, 07, 08, 09, 16, 15, 779)).getUTCDay()", + "new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)).getUTCDay()", ); assert_eq!(Ok(Value::Rational(3f64)), actual); @@ -496,7 +499,7 @@ fn date_proto_get_utc_full_year_call() -> Result<(), Box> let actual = forward_val( &mut engine, - "new Date(Date.UTC(2020, 07, 08, 09, 16, 15, 779)).getUTCFullYear()", + "new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)).getUTCFullYear()", ); assert_eq!(Ok(Value::Rational(2020f64)), actual); @@ -512,7 +515,7 @@ fn date_proto_get_utc_hours_call() -> Result<(), Box> { let actual = forward_val( &mut engine, - "new Date(Date.UTC(2020, 07, 08, 09, 16, 15, 779)).getUTCHours()", + "new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)).getUTCHours()", ); assert_eq!(Ok(Value::Rational(09f64)), actual); @@ -528,7 +531,7 @@ fn date_proto_get_utc_milliseconds_call() -> Result<(), Box Result<(), Box> { let actual = forward_val( &mut engine, - "new Date(Date.UTC(2020, 07, 08, 09, 16, 15, 779)).getUTCMinutes()", + "new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)).getUTCMinutes()", ); assert_eq!(Ok(Value::Rational(16f64)), actual); @@ -560,9 +563,9 @@ fn date_proto_get_utc_month() -> Result<(), Box> { let actual = forward_val( &mut engine, - "new Date(Date.UTC(2020, 07, 08, 09, 16, 15, 779)).getUTCMonth()", + "new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)).getUTCMonth()", ); - assert_eq!(Ok(Value::Rational(07f64)), actual); + 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); @@ -577,7 +580,7 @@ fn date_proto_get_utc_seconds() -> Result<(), Box> { let actual = forward_val( &mut engine, - "new Date(Date.UTC(2020, 07, 08, 09, 16, 15, 779)).getUTCSeconds()", + "new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)).getUTCSeconds()", ); assert_eq!(Ok(Value::Rational(15f64)), actual); @@ -591,94 +594,158 @@ fn date_proto_set_date() -> Result<(), Box> { let realm = Realm::create(); let mut engine = Interpreter::new(realm); - let actual = forward_val( + let actual = forward_dt_local( &mut engine, - "let dt = new Date(2020, 07, 08, 09, 16, 15, 779); dt.setDate(21); dt.getDate()", + "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 ); - assert_eq!(Ok(Value::Rational(21f64)), actual); // Date wraps to previous month for 0. - let actual = forward_val( + let actual = forward_dt_local( &mut engine, - "dt = new Date(2020, 07, 08, 09, 16, 15, 779); dt.setDate(0); dt.getDate()", + "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 ); - assert_eq!(Ok(Value::Rational(30f64)), actual); - let actual = forward_val( + let actual = forward_dt_local( &mut engine, - "dt = new Date(2020, 07, 08, 09, 16, 15, 779); dt.setDate(1/0); dt.getDate()", + "dt = new Date(2020, 06, 08, 09, 16, 15, 779); dt.setDate(1/0); dt", ); - assert_eq!(Ok(Value::Rational(f64::NAN)), actual); + assert_eq!(None, actual); Ok(()) } #[test] -fn date_proto_set_full_year_in_bounds() -> Result<(), Box> { +fn date_proto_set_full_year() -> Result<(), Box> { let realm = Realm::create(); let mut engine = Interpreter::new(realm); - forward_val( + let actual = forward_dt_local( &mut engine, - "function fmt(dt) { return dt.getFullYear() + '-' + dt.getMonth() + '-' + dt.getDate(); }", - ) - .expect("Helper function installed"); + "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_val( + let actual = forward_dt_local( &mut engine, - "let dt = new Date(2020, 07, 08, 09, 16, 15, 779); dt.setFullYear(2012); fmt(dt)", + "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 ); - assert_eq!(Ok(Value::string("2012-7-8")), actual); - let actual = forward_val( + let actual = forward_dt_local( &mut engine, - "dt = new Date(2020, 07, 08, 09, 16, 15, 779); dt.setFullYear(2012, 8); fmt(dt)", + "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 ); - assert_eq!(Ok(Value::string("2012-8-8")), actual); - let actual = forward_val( + // Out-of-bounds + + let actual = forward_dt_local( &mut engine, - "dt = new Date(2020, 07, 08, 09, 16, 15, 779); dt.setFullYear(2012, 8, 9); fmt(dt)", + "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 ); - assert_eq!(Ok(Value::string("2012-8-9")), actual); Ok(()) } #[test] -fn date_proto_set_full_year_out_of_bounds() -> Result<(), Box> { +fn date_proto_set_hours() -> Result<(), Box> { let realm = Realm::create(); let mut engine = Interpreter::new(realm); - forward_val( + let actual = forward_dt_local( &mut engine, - "function fmt(dt) { return dt.getFullYear() + '-' + dt.getMonth() + '-' + dt.getDate(); }", - ) - .expect("Helper function installed"); + "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_val( + let actual = forward_dt_local( &mut engine, - "let dt = new Date(2020, 07, 08, 09, 16, 15, 779); dt.setFullYear(2012, 33); fmt(dt)", + "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 ); - assert_eq!(Ok(Value::string("2014-9-8")), actual); - let actual = forward_val( + let actual = forward_dt_local( &mut engine, - "dt = new Date(2020, 07, 08, 09, 16, 15, 779); dt.setFullYear(2012, -33); fmt(dt)", + "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 ); - assert_eq!(Ok(Value::string("2009-3-8")), actual); - let actual = forward_val( + let actual = forward_dt_local( &mut engine, - "dt = new Date(2020, 07, 08, 09, 16, 15, 779); dt.setFullYear(2012, 9, 950); fmt(dt)", + "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 ); - assert_eq!(Ok(Value::string("2015-4-8")), actual); - let actual = forward_val( + // Out-of-bounds + + let actual = forward_dt_local( &mut engine, - "dt = new Date(2020, 07, 08, 09, 16, 15, 779); dt.setFullYear(2012, 9, -950); fmt(dt)", + "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 ); - assert_eq!(Ok(Value::string("2010-1-23")), actual); Ok(()) } From a5a2fefc174f6d70cf6888b8f6c20837b5a3607c Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Thu, 30 Jul 2020 17:25:30 -0700 Subject: [PATCH 16/33] `setMilliseconds()` --- boa/src/builtins/date/mod.rs | 22 +++++++++++++++++++++- boa/src/builtins/date/tests.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index e0bb5078b33..b6ac6abe00a 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -694,7 +694,7 @@ impl Date { /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setdate /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setDate fn set_date (to_local, date_time, args[1]) { - args[0].map_or(None, |day| Some(date_time.with_day(1).unwrap() + Duration::days(day as i64 - 1))) + args[0].map_or(None, |day| date_time.with_day(1).unwrap().checked_add_signed(Duration::days(day as i64 - 1))) } } @@ -752,6 +752,25 @@ impl Date { } } + setter_method! { + /// `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 + fn set_milliseconds (to_local, date_time, args[4]) { + args[0].map_or(None, |ms| { + let ms = ms as i64; + date_time.with_nanosecond(0).unwrap().checked_add_signed(Duration::milliseconds(ms)) + }) + } + } + /// `Date.now()` /// /// The static `Date.now()` method returns the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC. @@ -880,6 +899,7 @@ impl Date { make_builtin_fn(Self::set_date, "setDate", &prototype, 1); make_builtin_fn(Self::set_full_year, "setFullYear", &prototype, 1); make_builtin_fn(Self::set_hours, "setHours", &prototype, 1); + make_builtin_fn(Self::set_milliseconds, "setMilliseconds", &prototype, 1); let date_time_object = make_constructor_fn( Self::NAME, diff --git a/boa/src/builtins/date/tests.rs b/boa/src/builtins/date/tests.rs index bae01b05490..6991c4260c4 100644 --- a/boa/src/builtins/date/tests.rs +++ b/boa/src/builtins/date/tests.rs @@ -749,3 +749,31 @@ fn date_proto_set_hours() -> Result<(), Box> { 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 + + 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(()) +} From 5624c1d337c9f9e98aaf81755aac97a86415cac0 Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Thu, 30 Jul 2020 17:33:05 -0700 Subject: [PATCH 17/33] `setMilliseconds()` --- boa/src/builtins/date/mod.rs | 26 ++++++++++++++++++- boa/src/builtins/date/tests.rs | 46 ++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index b6ac6abe00a..1e82ad94c1b 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -763,7 +763,7 @@ impl Date { /// /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setmilliseconds /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setMilliseconds - fn set_milliseconds (to_local, date_time, args[4]) { + fn set_milliseconds (to_local, date_time, args[1]) { args[0].map_or(None, |ms| { let ms = ms as i64; date_time.with_nanosecond(0).unwrap().checked_add_signed(Duration::milliseconds(ms)) @@ -771,6 +771,29 @@ impl Date { } } + setter_method! { + /// `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 + fn set_minutes (to_local, date_time, args[3]) { + args[0].map_or(None, |minute| { + let minute = minute as i64; + let second = args[1].map_or_else(|| date_time.second() as i64, |second| second as i64); + let ms = args[2].map_or_else(|| (date_time.nanosecond() as f64 / NANOS_IN_MS) as i64, |second| second as i64); + + let duration = Duration::minutes(minute) + Duration::seconds(second) + Duration::milliseconds(ms); + date_time.date().and_hms(date_time.hour(), 0, 0).checked_add_signed(duration) + }) + } + } + /// `Date.now()` /// /// The static `Date.now()` method returns the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC. @@ -900,6 +923,7 @@ impl Date { make_builtin_fn(Self::set_full_year, "setFullYear", &prototype, 1); make_builtin_fn(Self::set_hours, "setHours", &prototype, 1); make_builtin_fn(Self::set_milliseconds, "setMilliseconds", &prototype, 1); + make_builtin_fn(Self::set_minutes, "setMinutes", &prototype, 1); let date_time_object = make_constructor_fn( Self::NAME, diff --git a/boa/src/builtins/date/tests.rs b/boa/src/builtins/date/tests.rs index 6991c4260c4..bd75b8f91fe 100644 --- a/boa/src/builtins/date/tests.rs +++ b/boa/src/builtins/date/tests.rs @@ -777,3 +777,49 @@ fn date_proto_set_milliseconds() -> Result<(), Box> { 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 + + 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(()) +} From b887059945f95ec9df745cc3437c9927264c52d4 Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Thu, 30 Jul 2020 17:55:56 -0700 Subject: [PATCH 18/33] `setMonth()` --- boa/src/builtins/date/mod.rs | 52 ++++++++++++++++++++++++++++++---- boa/src/builtins/date/tests.rs | 40 ++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 5 deletions(-) diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index 1e82ad94c1b..c5a50b56917 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -694,7 +694,19 @@ impl Date { /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setdate /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setDate fn set_date (to_local, date_time, args[1]) { - args[0].map_or(None, |day| date_time.with_day(1).unwrap().checked_add_signed(Duration::days(day as i64 - 1))) + args[0].map_or(None, |day| { + let mut year = date_time.year(); + let mut month = date_time.month0() as i32; + let mut day = day as i32 - 1; + + fix_date(&mut year, &mut month, &mut day); + + match Local.ymd_opt(year, month as u32 + 1, day as u32 + 1).and_time(date_time.time()) { + LocalResult::Ambiguous(v, _) => Some(v), + LocalResult::Single(v) => Some(v), + LocalResult::None => None + } + }) } } @@ -718,10 +730,11 @@ impl Date { fix_date(&mut year, &mut month, &mut day); - date_time - .with_year(year) - .map_or(None, |date_time| date_time.with_month0(month as u32)) - .map_or(None, |date_time| date_time.with_day(day as u32 + 1)) + match Local.ymd_opt(year, month as u32 + 1, day as u32 + 1).and_time(date_time.time()) { + LocalResult::Ambiguous(v, _) => Some(v), + LocalResult::Single(v) => Some(v), + LocalResult::None => None + } }) } } @@ -794,6 +807,34 @@ impl Date { } } + setter_method! { + /// `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 + fn set_month (to_local, date_time, args[2]) { + args[0].map_or(None, |month| { + let mut year = date_time.year(); + let mut month = month as i32; + let mut day = args[1].unwrap_or_else(|| date_time.day() as f64) as i32 - 1; + + fix_date(&mut year, &mut month, &mut day); + + match Local.ymd_opt(year, month as u32 + 1, day as u32 + 1).and_time(date_time.time()) { + LocalResult::Ambiguous(v, _) => Some(v), + LocalResult::Single(v) => Some(v), + LocalResult::None => None + } + }) + } + } + /// `Date.now()` /// /// The static `Date.now()` method returns the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC. @@ -924,6 +965,7 @@ impl Date { make_builtin_fn(Self::set_hours, "setHours", &prototype, 1); make_builtin_fn(Self::set_milliseconds, "setMilliseconds", &prototype, 1); make_builtin_fn(Self::set_minutes, "setMinutes", &prototype, 1); + make_builtin_fn(Self::set_month, "setMonth", &prototype, 1); let date_time_object = make_constructor_fn( Self::NAME, diff --git a/boa/src/builtins/date/tests.rs b/boa/src/builtins/date/tests.rs index bd75b8f91fe..c144e0f0c3b 100644 --- a/boa/src/builtins/date/tests.rs +++ b/boa/src/builtins/date/tests.rs @@ -765,6 +765,7 @@ fn date_proto_set_milliseconds() -> Result<(), Box> { ); // Out-of-bounds + // Thorough tests are done by setHours let actual = forward_dt_local( &mut engine, @@ -811,6 +812,7 @@ fn date_proto_set_minutes() -> Result<(), Box> { ); // Out-of-bounds + // Thorough tests are done by setHours let actual = forward_dt_local( &mut engine, @@ -823,3 +825,41 @@ fn date_proto_set_minutes() -> Result<(), Box> { 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(()) +} From 21e961dedff79921de4a9443f242e1f9a81df97e Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Thu, 30 Jul 2020 18:01:54 -0700 Subject: [PATCH 19/33] Move `toString` around --- boa/src/builtins/date/mod.rs | 79 +++++++++++++++++----------------- boa/src/builtins/date/tests.rs | 43 +++++++++--------- 2 files changed, 61 insertions(+), 61 deletions(-) diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index c5a50b56917..bdb3ab007b3 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -360,44 +360,6 @@ impl Date { Ok(this.clone()) } - /// `Date.prototype.toString()` - /// - /// The `toString()` 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.tostring - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toString - #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_string(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - let dt_str = Self::this_time_value(this, ctx)? - .to_local() - .map(|f| f.to_rfc3339()) - .unwrap_or_else(|| "Invalid Date".to_string()); - Ok(Value::from(dt_str)) - } - - /// `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 - #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_utc_string(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - let dt_str = Self::this_time_value(this, ctx)? - .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()); - Ok(Value::from(dt_str)) - } - getter_method! { /// `Date.prototype.getDate()` /// @@ -835,6 +797,44 @@ impl Date { } } + /// `Date.prototype.toString()` + /// + /// The `toString()` 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.tostring + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toString + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_string(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + let dt_str = Self::this_time_value(this, ctx)? + .to_local() + .map(|f| f.to_rfc3339()) + .unwrap_or_else(|| "Invalid Date".to_string()); + Ok(Value::from(dt_str)) + } + + /// `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 + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_utc_string(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + let dt_str = Self::this_time_value(this, ctx)? + .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()); + Ok(Value::from(dt_str)) + } + /// `Date.now()` /// /// The static `Date.now()` method returns the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC. @@ -930,7 +930,6 @@ impl Date { let prototype = Value::new_object(Some(global)); - make_builtin_fn(Self::to_string, "toString", &prototype, 0); make_builtin_fn(Self::get_date, "getDate", &prototype, 0); make_builtin_fn(Self::get_day, "getDay", &prototype, 0); make_builtin_fn(Self::get_full_year, "getFullYear", &prototype, 0); @@ -967,6 +966,8 @@ impl Date { make_builtin_fn(Self::set_minutes, "setMinutes", &prototype, 1); make_builtin_fn(Self::set_month, "setMonth", &prototype, 1); + make_builtin_fn(Self::to_string, "toString", &prototype, 0); + let date_time_object = make_constructor_fn( Self::NAME, Self::LENGTH, diff --git a/boa/src/builtins/date/tests.rs b/boa/src/builtins/date/tests.rs index c144e0f0c3b..0f18b112bc0 100644 --- a/boa/src/builtins/date/tests.rs +++ b/boa/src/builtins/date/tests.rs @@ -91,11 +91,11 @@ fn date_ctor_call() -> Result<(), Box> { let realm = Realm::create(); let mut engine = Interpreter::new(realm); - let dt1 = forward(&mut engine, "new Date().toString()"); + let dt1 = forward_dt_local(&mut engine, "new Date()"); std::thread::sleep(std::time::Duration::from_millis(1)); - let dt2 = forward(&mut engine, "new Date().toString()"); + let dt2 = forward_dt_local(&mut engine, "new Date()"); assert_ne!(dt1, dt2); Ok(()) @@ -121,9 +121,8 @@ fn date_ctor_call_string_invalid() -> Result<(), Box> { let realm = Realm::create(); let mut engine = Interpreter::new(realm); - let date_time = - forward_val(&mut engine, "new Date('nope').toString()").expect("Expected Success"); - assert_eq!(Value::string("Invalid Date"), date_time); + let date_time = forward_dt_local(&mut engine, "new Date('nope')"); + assert_eq!(None, date_time); Ok(()) } @@ -187,17 +186,17 @@ 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_val(&mut engine, src).expect("Expected Success"); - assert_eq!(Value::string("Invalid Date"), date_time); + 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).toString()"); - check("new Date(2020, 1/0, 08, 09, 16, 15, 779).toString()"); - check("new Date(2020, 06, 1/0, 09, 16, 15, 779).toString()"); - check("new Date(2020, 06, 08, 1/0, 16, 15, 779).toString()"); - check("new Date(2020, 06, 08, 09, 1/0, 15, 779).toString()"); - check("new Date(2020, 06, 08, 09, 16, 1/0, 779).toString()"); - check("new Date(2020, 06, 08, 09, 16, 15, 1/0).toString()"); + 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(()) } @@ -247,16 +246,16 @@ fn date_ctor_utc_call_nan() -> Result<(), Box> { let realm = Realm::create(); let mut engine = Interpreter::new(realm); let date_time = forward_val(&mut engine, src).expect("Expected Success"); - assert_eq!(Value::string("NaN"), date_time); + assert_eq!(Value::Rational(f64::NAN), date_time); } - check("Date.UTC(1/0, 06, 08, 09, 16, 15, 779).toString()"); - check("Date.UTC(2020, 1/0, 08, 09, 16, 15, 779).toString()"); - check("Date.UTC(2020, 06, 1/0, 09, 16, 15, 779).toString()"); - check("Date.UTC(2020, 06, 08, 1/0, 16, 15, 779).toString()"); - check("Date.UTC(2020, 06, 08, 09, 1/0, 15, 779).toString()"); - check("Date.UTC(2020, 06, 08, 09, 16, 1/0, 779).toString()"); - check("Date.UTC(2020, 06, 08, 09, 16, 15, 1/0).toString()"); + 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(()) } From b733e8ee4d95155bdbb4ad2ef90b86f9c4c56788 Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Fri, 31 Jul 2020 11:39:12 -0700 Subject: [PATCH 20/33] Make DST calculations consistent with JS bugs --- Cargo.lock | 4 +- boa/src/builtins/date/mod.rs | 121 +++++++++++++++++++++------------ boa/src/builtins/date/tests.rs | 38 +++++++++++ 3 files changed, 119 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3f195a8807a..7c61e2043a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -163,9 +163,9 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "chrono" -version = "0.4.11" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" +checksum = "c74d84029116787153e02106bf53e66828452a4b325cc8652b788b5967c0a0b6" dependencies = [ "num-integer", "num-traits", diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index bdb3ab007b3..1c7f34f6d9b 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -23,6 +23,15 @@ fn is_zero_or_normal_opt(value: Option) -> bool { .unwrap_or(true) } +#[inline] +fn ignore_ambiguity(result: LocalResult) -> Option { + match result { + LocalResult::Ambiguous(v, _) => Some(v), + LocalResult::Single(v) => Some(v), + LocalResult::None => None, + } +} + /// Some JS functions allow completely invalid dates, and the runtime is expected to make sense of this. This function /// constrains a date to correct values. fn fix_date(year: &mut i32, month: &mut i32, day: &mut i32) { @@ -99,7 +108,7 @@ macro_rules! getter_method { } macro_rules! setter_method { - ($(#[$outer:meta])* fn $name:ident ($tz:ident, $date_time:ident, $var:ident[$count:literal]) $mutate:expr) => { + ($(#[$outer:meta])* fn $name:ident ($tz: ident, $date_time:ident, $var:ident[$count:literal]) $mutate:expr) => { $(#[$outer])* fn $name(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { // If the first arg is not present or NaN, the Date becomes NaN itself. @@ -345,15 +354,9 @@ impl Date { let final_date = NaiveDate::from_ymd_opt(year, month + 1, day) .map(|naive_date| naive_date.and_hms_milli_opt(hour, min, sec, milli)) .flatten() - .map( - |local| match Local::now().timezone().from_local_datetime(&local) { - LocalResult::Single(v) => Some(v.naive_utc()), - // JS simply hopes for the best - LocalResult::Ambiguous(v, _) => Some(v.naive_utc()), - _ => None, - }, - ) - .flatten(); + .map(|local| ignore_ambiguity(Local.from_local_datetime(&local))) + .flatten() + .map(|local| local.naive_utc()); let date = Date(final_date); this.set_data(ObjectData::Date(RcDate::from(date))); @@ -657,17 +660,14 @@ impl Date { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setDate fn set_date (to_local, date_time, args[1]) { args[0].map_or(None, |day| { - let mut year = date_time.year(); - let mut month = date_time.month0() as i32; + // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. + let local = date_time.naive_local(); + let mut year = local.year(); + let mut month = local.month0() as i32; let mut day = day as i32 - 1; fix_date(&mut year, &mut month, &mut day); - - match Local.ymd_opt(year, month as u32 + 1, day as u32 + 1).and_time(date_time.time()) { - LocalResult::Ambiguous(v, _) => Some(v), - LocalResult::Single(v) => Some(v), - LocalResult::None => None - } + ignore_ambiguity(Local.ymd_opt(year, month as u32 + 1, day as u32 + 1).and_time(local.time())) }) } } @@ -686,17 +686,14 @@ impl Date { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setFullYear fn set_full_year (to_local, date_time, args[3]) { args[0].map_or(None, |year| { + // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. + let local = date_time.naive_local(); let mut year = year as i32; - let mut month = args[1].unwrap_or_else(|| date_time.month0() as f64) as i32; - let mut day = args[2].unwrap_or_else(|| date_time.day() as f64) as i32 - 1; + let mut month = args[1].unwrap_or_else(|| local.month0() as f64) as i32; + let mut day = args[2].unwrap_or_else(|| local.day() as f64) as i32 - 1; fix_date(&mut year, &mut month, &mut day); - - match Local.ymd_opt(year, month as u32 + 1, day as u32 + 1).and_time(date_time.time()) { - LocalResult::Ambiguous(v, _) => Some(v), - LocalResult::Single(v) => Some(v), - LocalResult::None => None - } + ignore_ambiguity(Local.ymd_opt(year, month as u32 + 1, day as u32 + 1).and_time(local.time())) }) } } @@ -716,13 +713,16 @@ impl Date { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setHours fn set_hours (to_local, date_time, args[4]) { args[0].map_or(None, |hour| { + // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. + let local = date_time.naive_local(); let hour = hour as i64; - let minute = args[1].map_or_else(|| date_time.minute() as i64, |minute| minute as i64); - let second = args[2].map_or_else(|| date_time.second() as i64, |second| second as i64); - let ms = args[3].map_or_else(|| (date_time.nanosecond() as f64 / NANOS_IN_MS) as i64, |second| second as i64); + let minute = args[1].map_or_else(|| local.minute() as i64, |minute| minute as i64); + let second = args[2].map_or_else(|| local.second() as i64, |second| second as i64); + let ms = args[3].map_or_else(|| (local.nanosecond() as f64 / NANOS_IN_MS) as i64, |ms| ms as i64); let duration = Duration::hours(hour) + Duration::minutes(minute) + Duration::seconds(second) + Duration::milliseconds(ms); - date_time.date().and_hms(0, 0, 0).checked_add_signed(duration) + let local = local.date().and_hms(0, 0, 0).checked_add_signed(duration); + local.map_or(None, |local| ignore_ambiguity(Local.from_local_datetime(&local))) }) } } @@ -740,8 +740,16 @@ impl Date { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setMilliseconds fn set_milliseconds (to_local, date_time, args[1]) { args[0].map_or(None, |ms| { + // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. + let local = date_time.naive_local(); + let hour = local.hour() as i64; + let minute = local.minute() as i64; + let second = local.second() as i64; let ms = ms as i64; - date_time.with_nanosecond(0).unwrap().checked_add_signed(Duration::milliseconds(ms)) + + let duration = Duration::hours(hour) + Duration::minutes(minute) + Duration::seconds(second) + Duration::milliseconds(ms); + let local = local.date().and_hms(0, 0, 0).checked_add_signed(duration); + local.map_or(None, |local| ignore_ambiguity(Local.from_local_datetime(&local))) }) } } @@ -759,12 +767,16 @@ impl Date { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setMinutes fn set_minutes (to_local, date_time, args[3]) { args[0].map_or(None, |minute| { + // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. + let local = date_time.naive_local(); + let hour = local.hour() as i64; let minute = minute as i64; - let second = args[1].map_or_else(|| date_time.second() as i64, |second| second as i64); - let ms = args[2].map_or_else(|| (date_time.nanosecond() as f64 / NANOS_IN_MS) as i64, |second| second as i64); + let second = args[1].map_or_else(|| local.second() as i64, |second| second as i64); + let ms = args[2].map_or_else(|| (local.nanosecond() as f64 / NANOS_IN_MS) as i64, |ms| ms as i64); - let duration = Duration::minutes(minute) + Duration::seconds(second) + Duration::milliseconds(ms); - date_time.date().and_hms(date_time.hour(), 0, 0).checked_add_signed(duration) + let duration = Duration::hours(hour) + Duration::minutes(minute) + Duration::seconds(second) + Duration::milliseconds(ms); + let local = local.date().and_hms(0, 0, 0).checked_add_signed(duration); + local.map_or(None, |local| ignore_ambiguity(Local.from_local_datetime(&local))) }) } } @@ -782,17 +794,41 @@ impl Date { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setMonth fn set_month (to_local, date_time, args[2]) { args[0].map_or(None, |month| { - let mut year = date_time.year(); + // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. + let local = date_time.naive_local(); + let mut year = local.year(); let mut month = month as i32; - let mut day = args[1].unwrap_or_else(|| date_time.day() as f64) as i32 - 1; + let mut day = args[1].unwrap_or_else(|| local.day() as f64) as i32 - 1; fix_date(&mut year, &mut month, &mut day); + ignore_ambiguity(Local.ymd_opt(year, month as u32 + 1, day as u32 + 1).and_time(local.time())) + }) + } + } - match Local.ymd_opt(year, month as u32 + 1, day as u32 + 1).and_time(date_time.time()) { - LocalResult::Ambiguous(v, _) => Some(v), - LocalResult::Single(v) => Some(v), - LocalResult::None => None - } + setter_method! { + /// `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 + fn set_seconds (to_local, date_time, args[2]) { + args[0].map_or(None, |second| { + // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. + let local = date_time.naive_local(); + let hour = local.hour() as i64; + let minute = local.minute() as i64; + let second = second as i64; + let ms = args[1].map_or_else(|| (local.nanosecond() as f64 / NANOS_IN_MS) as i64, |ms| ms as i64); + + let duration = Duration::hours(hour) + Duration::minutes(minute) + Duration::seconds(second) + Duration::milliseconds(ms); + let local = local.date().and_hms(0, 0, 0).checked_add_signed(duration); + local.map_or(None, |local| ignore_ambiguity(Local.from_local_datetime(&local))) }) } } @@ -965,6 +1001,7 @@ impl Date { make_builtin_fn(Self::set_milliseconds, "setMilliseconds", &prototype, 1); make_builtin_fn(Self::set_minutes, "setMinutes", &prototype, 1); make_builtin_fn(Self::set_month, "setMonth", &prototype, 1); + make_builtin_fn(Self::set_seconds, "setSeconds", &prototype, 1); make_builtin_fn(Self::to_string, "toString", &prototype, 0); diff --git a/boa/src/builtins/date/tests.rs b/boa/src/builtins/date/tests.rs index 0f18b112bc0..7a96a56d3c0 100644 --- a/boa/src/builtins/date/tests.rs +++ b/boa/src/builtins/date/tests.rs @@ -862,3 +862,41 @@ fn date_proto_set_month() -> Result<(), Box> { 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(()) +} From f825267c141ad9c6b2cf7ed4433237df061d9e83 Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Fri, 31 Jul 2020 12:05:08 -0700 Subject: [PATCH 21/33] `setTime()` --- boa/src/builtins/date/mod.rs | 60 ++++++++++++++++++++++++++++++++++ boa/src/builtins/date/tests.rs | 17 ++++++++++ 2 files changed, 77 insertions(+) diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index 1c7f34f6d9b..dba93a7109b 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -140,6 +140,44 @@ macro_rules! setter_method { let new_value = new_value.map(|date_time| date_time.naive_utc()); this.set_data(ObjectData::Date(RcDate::from(Date(new_value)))); + Ok(Value::number( + Self::this_time_value(this, ctx)? + .to_utc() + .map_or(f64::NAN, |f| f.timestamp_millis() as f64), + )) + } + }; + ($(#[$outer:meta])* fn $name:ident ($var:ident[$count:literal]) $mutate:expr) => { + $(#[$outer])* + fn $name(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + // If the first arg is not present or NaN, the Date becomes NaN itself. + fn get_arg(i: usize, args: &[Value], ctx: &mut Interpreter) -> Option { + args + .get(i) + .map(|value| { + ctx.to_numeric_number(value).map_or_else( + |_| None, + |value| { + if value == 0f64 || value.is_normal() { + Some(value) + } else { + None + } + }, + ) + }) + .flatten() + } + + let mut $var = [None; $count]; + for i in 0..$count { + $var[i] = get_arg(i, args, ctx); + } + + let new_value = $mutate; + let new_value = new_value.map(|date_time| date_time.naive_utc()); + this.set_data(ObjectData::Date(RcDate::from(Date(new_value)))); + Ok(Value::number( Self::this_time_value(this, ctx)? .to_utc() @@ -833,6 +871,27 @@ impl Date { } } + setter_method! { + /// `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 + fn set_time (args[1]) { + args[0].map_or(None, |tv| { + let secs = (tv / 1_000f64) as i64; + let nsecs = ((tv % 1_000f64) * 1_000_000f64) as u32; + ignore_ambiguity(Local.timestamp_opt(secs, nsecs)) + }) + } + } + /// `Date.prototype.toString()` /// /// The `toString()` method returns a string representing the specified Date object. @@ -1002,6 +1061,7 @@ impl Date { make_builtin_fn(Self::set_minutes, "setMinutes", &prototype, 1); make_builtin_fn(Self::set_month, "setMonth", &prototype, 1); make_builtin_fn(Self::set_seconds, "setSeconds", &prototype, 1); + make_builtin_fn(Self::set_time, "setTime", &prototype, 1); make_builtin_fn(Self::to_string, "toString", &prototype, 0); diff --git a/boa/src/builtins/date/tests.rs b/boa/src/builtins/date/tests.rs index 7a96a56d3c0..959e8c33f9d 100644 --- a/boa/src/builtins/date/tests.rs +++ b/boa/src/builtins/date/tests.rs @@ -900,3 +900,20 @@ fn date_proto_set_seconds() -> Result<(), Box> { 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(()) +} From 510328206ff1538f155a8087fa93e4be67eccb44 Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Fri, 31 Jul 2020 12:34:06 -0700 Subject: [PATCH 22/33] `setYear()` --- boa/src/builtins/date/mod.rs | 30 +++++++++++++++++++++++++++++- boa/src/builtins/date/tests.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index dba93a7109b..35f9040ac1f 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -713,7 +713,7 @@ impl Date { setter_method! { /// `Date.prototype.setFullYear()` /// - /// The setFullYear() method sets the full year for a specified date according to local time. Returns new + /// The `setFullYear()` method sets the full year for a specified date according to local time. Returns new /// timestamp. /// /// More information: @@ -871,6 +871,33 @@ impl Date { } } + setter_method! { + /// `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 + fn set_year (to_local, date_time, args[3]) { + args[0].map_or(None, |year| { + // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. + let local = date_time.naive_local(); + let mut year = year as i32; + year += if 0 <= year && year <= 99 { + 1900 + } else { + 0 + }; + + local.with_year(year).map(|local| ignore_ambiguity(Local.from_local_datetime(&local))).flatten() + }) + } + } + setter_method! { /// `Date.prototype.setTime()` /// @@ -1061,6 +1088,7 @@ impl Date { make_builtin_fn(Self::set_minutes, "setMinutes", &prototype, 1); make_builtin_fn(Self::set_month, "setMonth", &prototype, 1); make_builtin_fn(Self::set_seconds, "setSeconds", &prototype, 1); + make_builtin_fn(Self::set_year, "setYear", &prototype, 1); make_builtin_fn(Self::set_time, "setTime", &prototype, 1); make_builtin_fn(Self::to_string, "toString", &prototype, 0); diff --git a/boa/src/builtins/date/tests.rs b/boa/src/builtins/date/tests.rs index 959e8c33f9d..d9b07bfc001 100644 --- a/boa/src/builtins/date/tests.rs +++ b/boa/src/builtins/date/tests.rs @@ -901,6 +901,32 @@ fn date_proto_set_seconds() -> Result<(), Box> { 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(); From 83168c2d814f6590c805d1c239b10738305be288 Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Sun, 2 Aug 2020 13:00:11 -0700 Subject: [PATCH 23/33] `setUTC` methods --- boa/src/builtins/date/mod.rs | 198 +++++++++++++++++++++ boa/src/builtins/date/tests.rs | 313 +++++++++++++++++++++++++++++++++ 2 files changed, 511 insertions(+) diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index 35f9040ac1f..073edd3e23a 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -919,6 +919,192 @@ impl Date { } } + setter_method! { + /// `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 + fn set_utc_date (to_utc, date_time, args[1]) { + args[0].map_or(None, |day| { + // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. + let utc = date_time.naive_utc(); + let mut year = utc.year(); + let mut month = utc.month0() as i32; + let mut day = day as i32 - 1; + + fix_date(&mut year, &mut month, &mut day); + ignore_ambiguity(Utc.ymd_opt(year, month as u32 + 1, day as u32 + 1).and_time(utc.time())) + }) + } + } + + setter_method! { + /// `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 + fn set_utc_full_year (to_utc, date_time, args[3]) { + args[0].map_or(None, |year| { + // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. + let utc = date_time.naive_utc(); + let mut year = year as i32; + let mut month = args[1].unwrap_or_else(|| utc.month0() as f64) as i32; + let mut day = args[2].unwrap_or_else(|| utc.day() as f64) as i32 - 1; + + fix_date(&mut year, &mut month, &mut day); + ignore_ambiguity(Utc.ymd_opt(year, month as u32 + 1, day as u32 + 1).and_time(utc.time())) + }) + } + } + + setter_method! { + /// `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 + fn set_utc_hours (to_utc, date_time, args[4]) { + args[0].map_or(None, |hour| { + // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. + let utc = date_time.naive_utc(); + let hour = hour as i64; + let minute = args[1].map_or_else(|| utc.minute() as i64, |minute| minute as i64); + let second = args[2].map_or_else(|| utc.second() as i64, |second| second as i64); + let ms = args[3].map_or_else(|| (utc.nanosecond() as f64 / NANOS_IN_MS) as i64, |ms| ms as i64); + + let duration = Duration::hours(hour) + Duration::minutes(minute) + Duration::seconds(second) + Duration::milliseconds(ms); + let utc = utc.date().and_hms(0, 0, 0).checked_add_signed(duration); + utc.map(|utc| Utc.from_utc_datetime(&utc)) + }) + } + } + + setter_method! { + /// `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 + fn set_utc_milliseconds (to_utc, date_time, args[1]) { + args[0].map_or(None, |ms| { + // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. + let utc = date_time.naive_utc(); + let hour = utc.hour() as i64; + let minute = utc.minute() as i64; + let second = utc.second() as i64; + let ms = ms as i64; + + let duration = Duration::hours(hour) + Duration::minutes(minute) + Duration::seconds(second) + Duration::milliseconds(ms); + let utc = utc.date().and_hms(0, 0, 0).checked_add_signed(duration); + utc.map(|utc| Utc.from_utc_datetime(&utc)) + }) + } + } + + setter_method! { + /// `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 + fn set_utc_minutes (to_utc, date_time, args[3]) { + args[0].map_or(None, |minute| { + // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. + let utc = date_time.naive_utc(); + let hour = utc.hour() as i64; + let minute = minute as i64; + let second = args[1].map_or_else(|| utc.second() as i64, |second| second as i64); + let ms = args[2].map_or_else(|| (utc.nanosecond() as f64 / NANOS_IN_MS) as i64, |ms| ms as i64); + + let duration = Duration::hours(hour) + Duration::minutes(minute) + Duration::seconds(second) + Duration::milliseconds(ms); + let utc = utc.date().and_hms(0, 0, 0).checked_add_signed(duration); + utc.map(|utc| Utc.from_utc_datetime(&utc)) + }) + } + } + + setter_method! { + /// `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 + fn set_utc_month (to_utc, date_time, args[2]) { + args[0].map_or(None, |month| { + // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. + let utc = date_time.naive_utc(); + let mut year = utc.year(); + let mut month = month as i32; + let mut day = args[1].unwrap_or_else(|| utc.day() as f64) as i32 - 1; + + fix_date(&mut year, &mut month, &mut day); + ignore_ambiguity(Utc.ymd_opt(year, month as u32 + 1, day as u32 + 1).and_time(utc.time())) + }) + } + } + + setter_method! { + /// `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 + fn set_utc_seconds (to_utc, date_time, args[2]) { + args[0].map_or(None, |second| { + // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. + let utc = date_time.naive_utc(); + let hour = utc.hour() as i64; + let minute = utc.minute() as i64; + let second = second as i64; + let ms = args[1].map_or_else(|| (utc.nanosecond() as f64 / NANOS_IN_MS) as i64, |ms| ms as i64); + + let duration = Duration::hours(hour) + Duration::minutes(minute) + Duration::seconds(second) + Duration::milliseconds(ms); + let utc = utc.date().and_hms(0, 0, 0).checked_add_signed(duration); + utc.map(|utc| Utc.from_utc_datetime(&utc)) + }) + } + } + /// `Date.prototype.toString()` /// /// The `toString()` method returns a string representing the specified Date object. @@ -1090,6 +1276,18 @@ impl Date { make_builtin_fn(Self::set_seconds, "setSeconds", &prototype, 1); make_builtin_fn(Self::set_year, "setYear", &prototype, 1); make_builtin_fn(Self::set_time, "setTime", &prototype, 1); + make_builtin_fn(Self::set_utc_date, "setUTCDate", &prototype, 1); + make_builtin_fn(Self::set_utc_full_year, "setUTCFullYear", &prototype, 1); + make_builtin_fn(Self::set_utc_hours, "setUTCHours", &prototype, 1); + make_builtin_fn( + Self::set_utc_milliseconds, + "setUTCMilliseconds", + &prototype, + 1, + ); + make_builtin_fn(Self::set_utc_minutes, "setUTCMinutes", &prototype, 1); + make_builtin_fn(Self::set_utc_month, "setUTCMonth", &prototype, 1); + make_builtin_fn(Self::set_utc_seconds, "setUTCSeconds", &prototype, 1); make_builtin_fn(Self::to_string, "toString", &prototype, 0); diff --git a/boa/src/builtins/date/tests.rs b/boa/src/builtins/date/tests.rs index d9b07bfc001..4a776168ebd 100644 --- a/boa/src/builtins/date/tests.rs +++ b/boa/src/builtins/date/tests.rs @@ -943,3 +943,316 @@ fn date_proto_set_time() -> Result<(), Box> { 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(()) +} From 81b707dcfb4e7fd2c1c7b9646a3eff38528be744 Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Sun, 2 Aug 2020 13:29:17 -0700 Subject: [PATCH 24/33] `toString()` functions, except locale --- boa/src/builtins/date/mod.rs | 105 ++++++++++++++++++++++++++++- boa/src/builtins/date/tests.rs | 119 +++++++++++++++++++++++++++++++++ 2 files changed, 222 insertions(+), 2 deletions(-) diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index 073edd3e23a..78922071f45 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -1105,6 +1105,81 @@ impl Date { } } + /// `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 + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_date_string(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + let dt_str = Self::this_time_value(this, ctx)? + .to_local() + .map(|date_time| date_time.format("%a %b %d %Y").to_string()) + .unwrap_or_else(|| "Invalid Date".to_string()); + Ok(Value::from(dt_str)) + } + + /// `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 + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_gmt_string( + this: &Value, + args: &[Value], + ctx: &mut Interpreter, + ) -> ResultValue { + Self::to_utc_string(this, args, ctx) + } + + /// `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 + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_iso_string(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + let dt_str = Self::this_time_value(this, ctx)? + .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()); + Ok(Value::from(dt_str)) + } + + /// `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 + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_json(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + Self::to_iso_string(this, args, ctx) + } + /// `Date.prototype.toString()` /// /// The `toString()` method returns a string representing the specified Date object. @@ -1119,7 +1194,27 @@ impl Date { pub(crate) fn to_string(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { let dt_str = Self::this_time_value(this, ctx)? .to_local() - .map(|f| f.to_rfc3339()) + .map(|date_time| date_time.format("%a %b %d %Y %H:%M:%S GMT%:z").to_string()) + .unwrap_or_else(|| "Invalid Date".to_string()); + Ok(Value::from(dt_str)) + } + + /// `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 + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_time_string(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + let dt_str = Self::this_time_value(this, ctx)? + .to_local() + .map(|date_time| date_time.format("%H:%M:%S GMT%:z").to_string()) .unwrap_or_else(|| "Invalid Date".to_string()); Ok(Value::from(dt_str)) } @@ -1288,8 +1383,14 @@ impl Date { make_builtin_fn(Self::set_utc_minutes, "setUTCMinutes", &prototype, 1); make_builtin_fn(Self::set_utc_month, "setUTCMonth", &prototype, 1); make_builtin_fn(Self::set_utc_seconds, "setUTCSeconds", &prototype, 1); - + make_builtin_fn(Self::to_date_string, "toDateString", &prototype, 0); + make_builtin_fn(Self::to_gmt_string, "toGMTString", &prototype, 0); + make_builtin_fn(Self::to_iso_string, "toISOString", &prototype, 0); + make_builtin_fn(Self::to_json, "toJSON", &prototype, 0); + // Locale strings make_builtin_fn(Self::to_string, "toString", &prototype, 0); + make_builtin_fn(Self::to_time_string, "toTimeString", &prototype, 0); + make_builtin_fn(Self::to_utc_string, "toUTCString", &prototype, 0); let date_time_object = make_constructor_fn( Self::NAME, diff --git a/boa/src/builtins/date/tests.rs b/boa/src/builtins/date/tests.rs index 4a776168ebd..4d7be3b92b3 100644 --- a/boa/src/builtins/date/tests.rs +++ b/boa/src/builtins/date/tests.rs @@ -1256,3 +1256,122 @@ fn date_proto_set_utc_seconds() -> Result<(), Box> { 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(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(()) +} From 5c9819b5486a18a58c35619f4173bdbe1e280775 Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Sun, 2 Aug 2020 13:35:34 -0700 Subject: [PATCH 25/33] Fix `to_primitive()` --- boa/src/builtins/date/mod.rs | 2 +- boa/src/builtins/date/tests.rs | 8 +++++++- boa/src/exec/mod.rs | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index 78922071f45..48dc67edf90 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -193,7 +193,7 @@ 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), + Some(v) => write!(f, "{}", v.format("%a %b %d %Y %H:%M:%S GMT%:z")), _ => write!(f, "Invalid Date"), } } diff --git a/boa/src/builtins/date/tests.rs b/boa/src/builtins/date/tests.rs index 4d7be3b92b3..c22534a8939 100644 --- a/boa/src/builtins/date/tests.rs +++ b/boa/src/builtins/date/tests.rs @@ -47,7 +47,13 @@ fn date_display() { assert_eq!("[Invalid Date]", format!("[{}]", dt)); let cd = super::Date::default(); - assert_eq!(format!("[{}]", cd.to_local().unwrap()), format!("[{}]", cd)); + assert_eq!( + format!( + "[{}]", + cd.to_local().unwrap().format("%a %b %d %Y %H:%M:%S GMT%:z") + ), + format!("[{}]", cd) + ); } #[test] diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 990ac3c0bba..2360a0b152c 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -213,7 +213,7 @@ impl Interpreter { let primitive = self.to_primitive(value, PreferredType::String)?; self.to_string(&primitive) } - Value::Date(_) => todo!("Date"), + Value::Date(ref dt) => Ok(RcString::from(dt.to_string())), } } From 164aeec2a8d42b860e3d8f954b2a8a1f7289a51f Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Sun, 2 Aug 2020 13:40:15 -0700 Subject: [PATCH 26/33] Fix lints --- boa/src/builtins/date/mod.rs | 42 ++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index 48dc67edf90..d826eb207fe 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -71,7 +71,7 @@ fn fix_date(year: &mut i32, month: &mut i32, day: &mut i32) { if *day < 0 { *month -= 1; fix_month(year, month); - *day = num_days_in(*year, *month as u32) + *day; + *day += num_days_in(*year, *month as u32); } else { let num_days = num_days_in(*year, *month as u32); if *day >= num_days { @@ -697,7 +697,7 @@ impl Date { /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setdate /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setDate fn set_date (to_local, date_time, args[1]) { - args[0].map_or(None, |day| { + args[0].and_then(|day| { // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. let local = date_time.naive_local(); let mut year = local.year(); @@ -723,7 +723,7 @@ impl Date { /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setfullyear /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setFullYear fn set_full_year (to_local, date_time, args[3]) { - args[0].map_or(None, |year| { + args[0].and_then(|year| { // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. let local = date_time.naive_local(); let mut year = year as i32; @@ -750,7 +750,7 @@ impl Date { /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.sethours /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setHours fn set_hours (to_local, date_time, args[4]) { - args[0].map_or(None, |hour| { + args[0].and_then(|hour| { // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. let local = date_time.naive_local(); let hour = hour as i64; @@ -760,7 +760,7 @@ impl Date { let duration = Duration::hours(hour) + Duration::minutes(minute) + Duration::seconds(second) + Duration::milliseconds(ms); let local = local.date().and_hms(0, 0, 0).checked_add_signed(duration); - local.map_or(None, |local| ignore_ambiguity(Local.from_local_datetime(&local))) + local.and_then(|local| ignore_ambiguity(Local.from_local_datetime(&local))) }) } } @@ -777,7 +777,7 @@ impl Date { /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setmilliseconds /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setMilliseconds fn set_milliseconds (to_local, date_time, args[1]) { - args[0].map_or(None, |ms| { + args[0].and_then(|ms| { // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. let local = date_time.naive_local(); let hour = local.hour() as i64; @@ -787,7 +787,7 @@ impl Date { let duration = Duration::hours(hour) + Duration::minutes(minute) + Duration::seconds(second) + Duration::milliseconds(ms); let local = local.date().and_hms(0, 0, 0).checked_add_signed(duration); - local.map_or(None, |local| ignore_ambiguity(Local.from_local_datetime(&local))) + local.and_then(|local| ignore_ambiguity(Local.from_local_datetime(&local))) }) } } @@ -804,7 +804,7 @@ impl Date { /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setminutes /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setMinutes fn set_minutes (to_local, date_time, args[3]) { - args[0].map_or(None, |minute| { + args[0].and_then(|minute| { // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. let local = date_time.naive_local(); let hour = local.hour() as i64; @@ -814,7 +814,7 @@ impl Date { let duration = Duration::hours(hour) + Duration::minutes(minute) + Duration::seconds(second) + Duration::milliseconds(ms); let local = local.date().and_hms(0, 0, 0).checked_add_signed(duration); - local.map_or(None, |local| ignore_ambiguity(Local.from_local_datetime(&local))) + local.and_then(|local| ignore_ambiguity(Local.from_local_datetime(&local))) }) } } @@ -831,7 +831,7 @@ impl Date { /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setmonth /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setMonth fn set_month (to_local, date_time, args[2]) { - args[0].map_or(None, |month| { + args[0].and_then(|month| { // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. let local = date_time.naive_local(); let mut year = local.year(); @@ -856,7 +856,7 @@ impl Date { /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setseconds /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setSeconds fn set_seconds (to_local, date_time, args[2]) { - args[0].map_or(None, |second| { + args[0].and_then(|second| { // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. let local = date_time.naive_local(); let hour = local.hour() as i64; @@ -866,7 +866,7 @@ impl Date { let duration = Duration::hours(hour) + Duration::minutes(minute) + Duration::seconds(second) + Duration::milliseconds(ms); let local = local.date().and_hms(0, 0, 0).checked_add_signed(duration); - local.map_or(None, |local| ignore_ambiguity(Local.from_local_datetime(&local))) + local.and_then(|local| ignore_ambiguity(Local.from_local_datetime(&local))) }) } } @@ -883,7 +883,7 @@ impl Date { /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setyear /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setYear fn set_year (to_local, date_time, args[3]) { - args[0].map_or(None, |year| { + args[0].and_then(|year| { // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. let local = date_time.naive_local(); let mut year = year as i32; @@ -911,7 +911,7 @@ impl Date { /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.settime /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setTime fn set_time (args[1]) { - args[0].map_or(None, |tv| { + args[0].and_then(|tv| { let secs = (tv / 1_000f64) as i64; let nsecs = ((tv % 1_000f64) * 1_000_000f64) as u32; ignore_ambiguity(Local.timestamp_opt(secs, nsecs)) @@ -931,7 +931,7 @@ impl Date { /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setutcdate /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setUTCDate fn set_utc_date (to_utc, date_time, args[1]) { - args[0].map_or(None, |day| { + args[0].and_then(|day| { // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. let utc = date_time.naive_utc(); let mut year = utc.year(); @@ -957,7 +957,7 @@ impl Date { /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setutcfullyear /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setUTCFullYear fn set_utc_full_year (to_utc, date_time, args[3]) { - args[0].map_or(None, |year| { + args[0].and_then(|year| { // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. let utc = date_time.naive_utc(); let mut year = year as i32; @@ -984,7 +984,7 @@ impl Date { /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setutchours /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setUTCHours fn set_utc_hours (to_utc, date_time, args[4]) { - args[0].map_or(None, |hour| { + args[0].and_then(|hour| { // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. let utc = date_time.naive_utc(); let hour = hour as i64; @@ -1011,7 +1011,7 @@ impl Date { /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setutcmilliseconds /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setUTCMilliseconds fn set_utc_milliseconds (to_utc, date_time, args[1]) { - args[0].map_or(None, |ms| { + args[0].and_then(|ms| { // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. let utc = date_time.naive_utc(); let hour = utc.hour() as i64; @@ -1038,7 +1038,7 @@ impl Date { /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setutcminutes /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setUTCMinutes fn set_utc_minutes (to_utc, date_time, args[3]) { - args[0].map_or(None, |minute| { + args[0].and_then(|minute| { // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. let utc = date_time.naive_utc(); let hour = utc.hour() as i64; @@ -1065,7 +1065,7 @@ impl Date { /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setutcmonth /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setUTCMonth fn set_utc_month (to_utc, date_time, args[2]) { - args[0].map_or(None, |month| { + args[0].and_then(|month| { // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. let utc = date_time.naive_utc(); let mut year = utc.year(); @@ -1090,7 +1090,7 @@ impl Date { /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setutcseconds /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setUTCSeconds fn set_utc_seconds (to_utc, date_time, args[2]) { - args[0].map_or(None, |second| { + args[0].and_then(|second| { // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. let utc = date_time.naive_utc(); let hour = utc.hour() as i64; From f1d3becc160c3c203288456083bb4b06164761d2 Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Sun, 2 Aug 2020 20:22:24 -0700 Subject: [PATCH 27/33] Date neg and toJSON --- boa/src/builtins/date/mod.rs | 69 +++++++++++++++++----------- boa/src/builtins/date/tests.rs | 47 ++++++++++++++++++- boa/src/builtins/value/mod.rs | 10 ++-- boa/src/builtins/value/operations.rs | 2 +- boa/src/exec/mod.rs | 4 +- 5 files changed, 95 insertions(+), 37 deletions(-) diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index d826eb207fe..bdb6b360c9a 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -115,7 +115,7 @@ macro_rules! setter_method { fn get_arg(i: usize, args: &[Value], ctx: &mut Interpreter) -> Option { args .get(i) - .map(|value| { + .and_then(|value| { ctx.to_numeric_number(value).map_or_else( |_| None, |value| { @@ -127,7 +127,6 @@ macro_rules! setter_method { }, ) }) - .flatten() } let mut $var = [None; $count]; @@ -136,14 +135,12 @@ macro_rules! setter_method { } let inner = Date::this_time_value(this, ctx)?.$tz(); - let new_value = inner.map(|$date_time| $mutate).flatten(); + let new_value = inner.and_then(|$date_time| $mutate); let new_value = new_value.map(|date_time| date_time.naive_utc()); this.set_data(ObjectData::Date(RcDate::from(Date(new_value)))); Ok(Value::number( - Self::this_time_value(this, ctx)? - .to_utc() - .map_or(f64::NAN, |f| f.timestamp_millis() as f64), + Self::this_time_value(this, ctx)?.timestamp(), )) } }; @@ -154,7 +151,7 @@ macro_rules! setter_method { fn get_arg(i: usize, args: &[Value], ctx: &mut Interpreter) -> Option { args .get(i) - .map(|value| { + .and_then(|value| { ctx.to_numeric_number(value).map_or_else( |_| None, |value| { @@ -166,7 +163,6 @@ macro_rules! setter_method { }, ) }) - .flatten() } let mut $var = [None; $count]; @@ -179,9 +175,7 @@ macro_rules! setter_method { this.set_data(ObjectData::Date(RcDate::from(Date(new_value)))); Ok(Value::number( - Self::this_time_value(this, ctx)? - .to_utc() - .map_or(f64::NAN, |f| f.timestamp_millis() as f64), + Self::this_time_value(this, ctx)?.timestamp(), )) } }; @@ -224,6 +218,19 @@ impl Date { .map(|utc| Utc::now().timezone().from_utc_datetime(&utc)) } + // The UTC timestamp. + pub fn timestamp(&self) -> f64 { + self.to_utc() + .map_or(f64::NAN, |dt| dt.timestamp_millis() as f64) + } + + pub fn to_json_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()) + } + /// The abstract operation `thisTimeValue` takes argument value. /// /// In following descriptions of functions that are properties of the Date prototype object, the phrase “this @@ -305,7 +312,7 @@ impl Date { pub(crate) fn make_date_now(this: &Value) -> ResultValue { let date = Date::default(); this.set_data(ObjectData::Date(RcDate::from(date))); - Ok(this.clone()) + Ok(Value::from(date)) } /// `Date(value)` @@ -342,7 +349,7 @@ impl Date { let date = Date(tv); this.set_data(ObjectData::Date(RcDate::from(date))); - Ok(this.clone()) + Ok(Value::from(date)) } /// `Date(year, month [ , date [ , hours [ , minutes [ , seconds [ , ms ] ] ] ] ])` @@ -390,15 +397,13 @@ impl Date { }; let final_date = NaiveDate::from_ymd_opt(year, month + 1, day) - .map(|naive_date| naive_date.and_hms_milli_opt(hour, min, sec, milli)) - .flatten() - .map(|local| ignore_ambiguity(Local.from_local_datetime(&local))) - .flatten() + .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(RcDate::from(date))); - Ok(this.clone()) + Ok(Value::from(date)) } getter_method! { @@ -893,7 +898,7 @@ impl Date { 0 }; - local.with_year(year).map(|local| ignore_ambiguity(Local.from_local_datetime(&local))).flatten() + local.with_year(year).and_then(|local| ignore_ambiguity(Local.from_local_datetime(&local))) }) } } @@ -1157,11 +1162,7 @@ impl Date { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString #[allow(clippy::wrong_self_convention)] pub(crate) fn to_iso_string(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - let dt_str = Self::this_time_value(this, ctx)? - .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()); + let dt_str = Self::this_time_value(this, ctx)?.to_json_string(); Ok(Value::from(dt_str)) } @@ -1238,6 +1239,22 @@ impl Date { Ok(Value::from(dt_str)) } + /// `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 + #[allow(clippy::wrong_self_convention)] + pub(crate) fn value_of(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + let dt = Self::this_time_value(this, ctx)?.timestamp(); + Ok(Value::from(dt)) + } + /// `Date.now()` /// /// The static `Date.now()` method returns the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC. @@ -1318,8 +1335,7 @@ impl Date { }; NaiveDate::from_ymd_opt(year, month + 1, day) - .map(|f| f.and_hms_milli_opt(hour, min, sec, milli)) - .flatten() + .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)) }) @@ -1391,6 +1407,7 @@ impl Date { make_builtin_fn(Self::to_string, "toString", &prototype, 0); make_builtin_fn(Self::to_time_string, "toTimeString", &prototype, 0); make_builtin_fn(Self::to_utc_string, "toUTCString", &prototype, 0); + make_builtin_fn(Self::value_of, "valueOf", &prototype, 0); let date_time_object = make_constructor_fn( Self::NAME, diff --git a/boa/src/builtins/date/tests.rs b/boa/src/builtins/date/tests.rs index c22534a8939..76f1536265b 100644 --- a/boa/src/builtins/date/tests.rs +++ b/boa/src/builtins/date/tests.rs @@ -1285,7 +1285,7 @@ fn date_proto_to_gmt_string() -> Result<(), Box> { let actual = forward_val( &mut engine, - "let dt = new Date(2020, 06, 08, 09, 16, 15, 779); dt.toGMTString()", + "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); @@ -1381,3 +1381,48 @@ fn date_proto_to_utc_string() -> Result<(), Box> { 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::number(1594199775779f64), actual); + + Ok(()) +} diff --git a/boa/src/builtins/value/mod.rs b/boa/src/builtins/value/mod.rs index 612fc7a0e02..4901a70b71c 100644 --- a/boa/src/builtins/value/mod.rs +++ b/boa/src/builtins/value/mod.rs @@ -291,7 +291,7 @@ impl Value { Self::Symbol(_) | Self::Undefined => { unreachable!("Symbols and Undefined JSON Values depend on parent type"); } - Self::Date(ref dt) => Ok(JSONValue::String(dt.to_string())), + Self::Date(ref dt) => Ok(JSONValue::String(dt.to_json_string())), } } @@ -455,9 +455,7 @@ impl Value { Self::BigInt(_) => { panic!("TypeError: Cannot mix BigInt and other types, use explicit conversions") } - Self::Date(_) => { - todo!("DateTime"); - } + Self::Date(ref dt) => dt.timestamp(), } } @@ -479,9 +477,7 @@ impl Value { Self::BigInt(_) => { panic!("TypeError: Cannot mix BigInt and other types, use explicit conversions") } - Self::Date(_) => { - todo!("DateTime"); - } + Self::Date(ref dt) => dt.timestamp() as i32, } } diff --git a/boa/src/builtins/value/operations.rs b/boa/src/builtins/value/operations.rs index 9b7f85bc3c3..cc468635082 100644 --- a/boa/src/builtins/value/operations.rs +++ b/boa/src/builtins/value/operations.rs @@ -394,7 +394,7 @@ impl Value { Self::Boolean(true) => Self::integer(1), Self::Boolean(false) | Self::Null => Self::integer(0), Self::BigInt(ref num) => Self::bigint(-num.as_inner().clone()), - Self::Date(_) => todo!("Date"), + Self::Date(ref dt) => Self::number(-dt.timestamp()), }) } diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 2360a0b152c..cdd076abcbc 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -244,7 +244,7 @@ impl Interpreter { self.to_bigint(&primitive) } Value::Symbol(_) => Err(self.construct_type_error("cannot convert Symbol to a BigInt")), - Value::Date(_) => todo!("Date"), + Value::Date(dt) => Ok(RcBigInt::from(BigInt::from(dt.timestamp() as i64))), } } @@ -357,7 +357,7 @@ impl Interpreter { let primitive = self.to_primitive(value, PreferredType::Number)?; self.to_number(&primitive) } - Value::Date(_) => todo!("Date"), + Value::Date(ref dt) => Ok(dt.timestamp()), } } From 92741a8e308816f75144ce6a030a5c208235c9dc Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Sun, 2 Aug 2020 21:25:32 -0700 Subject: [PATCH 28/33] Remove RcDate --- boa/src/builtins/date/mod.rs | 24 ++++++++++-------- boa/src/builtins/object/mod.rs | 6 ++--- boa/src/builtins/value/conversions.rs | 6 ----- boa/src/builtins/value/mod.rs | 6 ++--- boa/src/builtins/value/rcdate.rs | 36 --------------------------- 5 files changed, 19 insertions(+), 59 deletions(-) delete mode 100644 boa/src/builtins/value/rcdate.rs diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index bdb6b360c9a..0eacaa0a56d 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -5,13 +5,13 @@ use crate::{ builtins::{ function::{make_builtin_fn, make_constructor_fn}, object::ObjectData, - value::RcDate, 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; @@ -137,7 +137,7 @@ macro_rules! setter_method { let inner = Date::this_time_value(this, ctx)?.$tz(); let new_value = inner.and_then(|$date_time| $mutate); let new_value = new_value.map(|date_time| date_time.naive_utc()); - this.set_data(ObjectData::Date(RcDate::from(Date(new_value)))); + this.set_data(ObjectData::Date(Date(new_value))); Ok(Value::number( Self::this_time_value(this, ctx)?.timestamp(), @@ -172,7 +172,7 @@ macro_rules! setter_method { let new_value = $mutate; let new_value = new_value.map(|date_time| date_time.naive_utc()); - this.set_data(ObjectData::Date(RcDate::from(Date(new_value)))); + this.set_data(ObjectData::Date(Date(new_value))); Ok(Value::number( Self::this_time_value(this, ctx)?.timestamp(), @@ -181,7 +181,7 @@ macro_rules! setter_method { }; } -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Finalize, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Date(Option); impl Display for Date { @@ -193,6 +193,10 @@ impl Display for Date { } } +unsafe impl Trace for Date { + unsafe_empty_trace!(); +} + impl Default for Date { fn default() -> Self { Self(Some(Utc::now().naive_utc())) @@ -244,7 +248,7 @@ impl Date { /// /// [spec]: https://tc39.es/ecma262/#sec-thistimevalue #[inline] - fn this_time_value(value: &Value, ctx: &mut Interpreter) -> Result { + fn this_time_value(value: &Value, ctx: &mut Interpreter) -> Result { match value { // 1. If Type(value) is Date, return value. Value::Date(ref date) => Ok(date.clone()), @@ -311,7 +315,7 @@ impl Date { /// [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(RcDate::from(date))); + this.set_data(ObjectData::Date(date)); Ok(Value::from(date)) } @@ -348,7 +352,7 @@ impl Date { }; let date = Date(tv); - this.set_data(ObjectData::Date(RcDate::from(date))); + this.set_data(ObjectData::Date(date)); Ok(Value::from(date)) } @@ -378,8 +382,8 @@ impl Date { // 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(RcDate::from(date))); - return Ok(this.clone()); + this.set_data(ObjectData::Date(date)); + return Ok(Value::from(date)); } let year = year as i32; @@ -402,7 +406,7 @@ impl Date { .map(|local| local.naive_utc()); let date = Date(final_date); - this.set_data(ObjectData::Date(RcDate::from(date))); + this.set_data(ObjectData::Date(date)); Ok(Value::from(date)) } diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index f8adce2ee15..7d6ddcb214f 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -18,8 +18,8 @@ use crate::{ function::Function, map::ordered_map::OrderedMap, property::Property, - value::{RcBigInt, RcDate, RcString, RcSymbol, ResultValue, Value}, - BigInt, RegExp, + value::{RcBigInt, RcString, RcSymbol, ResultValue, Value}, + BigInt, Date, RegExp, }, exec::Interpreter, BoaProfiler, @@ -78,7 +78,7 @@ pub enum ObjectData { Symbol(RcSymbol), Error, Ordinary, - Date(RcDate), + Date(Date), Global, } diff --git a/boa/src/builtins/value/conversions.rs b/boa/src/builtins/value/conversions.rs index 17e21203795..f6127dc2faf 100644 --- a/boa/src/builtins/value/conversions.rs +++ b/boa/src/builtins/value/conversions.rs @@ -132,12 +132,6 @@ impl From for Value { } } -impl From for Value { - fn from(value: RcDate) -> Self { - Value::Date(value) - } -} - impl From for Value { fn from(value: usize) -> Value { Value::integer(value as i32) diff --git a/boa/src/builtins/value/mod.rs b/boa/src/builtins/value/mod.rs index 4901a70b71c..7b6d2e872d2 100644 --- a/boa/src/builtins/value/mod.rs +++ b/boa/src/builtins/value/mod.rs @@ -34,7 +34,6 @@ pub mod equality; pub mod hash; pub mod operations; pub mod rcbigint; -pub mod rcdate; pub mod rcstring; pub mod rcsymbol; @@ -44,7 +43,6 @@ pub use equality::*; pub use hash::*; pub use operations::*; pub use rcbigint::RcBigInt; -pub use rcdate::RcDate; pub use rcstring::RcString; pub use rcsymbol::RcSymbol; @@ -74,7 +72,7 @@ pub enum Value { /// `Symbol` - A Symbol Primitive type. Symbol(RcSymbol), /// `Date` - A Date type. - Date(RcDate), + Date(Date), } impl Value { @@ -162,7 +160,7 @@ impl Value { /// Creates a new date value. #[inline] pub(crate) fn date(date: Date) -> Self { - Self::Date(RcDate::from(date)) + Self::Date(date) } /// Helper function to convert the `Value` to a number and compute its power. diff --git a/boa/src/builtins/value/rcdate.rs b/boa/src/builtins/value/rcdate.rs deleted file mode 100644 index e4b04b84c66..00000000000 --- a/boa/src/builtins/value/rcdate.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::builtins::Date; - -use std::fmt::{self, Display}; -use std::ops::Deref; -use std::rc::Rc; - -use gc::{unsafe_empty_trace, Finalize, Trace}; - -#[derive(Debug, Finalize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct RcDate(Rc); - -unsafe impl Trace for RcDate { - unsafe_empty_trace!(); -} - -impl Display for RcDate { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - Display::fmt(&self.0, f) - } -} - -impl Deref for RcDate { - type Target = Date; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl From for RcDate { - #[inline] - fn from(date: Date) -> Self { - Self(Rc::from(date)) - } -} From 2bd74e4e5461fc9350548c6febea09e50b461bc9 Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Sun, 2 Aug 2020 22:28:15 -0700 Subject: [PATCH 29/33] Remove `Value::Date` as `Date` is not a primitive Correctly handle `toJSON` Correctly handle `valueOf` in neg --- boa/src/builtins/date/mod.rs | 26 ++++++++------------------ boa/src/builtins/date/tests.rs | 5 ++++- boa/src/builtins/value/conversions.rs | 6 ------ boa/src/builtins/value/display.rs | 7 ------- boa/src/builtins/value/hash.rs | 1 - boa/src/builtins/value/mod.rs | 19 +++++++------------ boa/src/builtins/value/operations.rs | 9 ++++++--- boa/src/builtins/value/val_type.rs | 1 - boa/src/exec/mod.rs | 14 -------------- 9 files changed, 25 insertions(+), 63 deletions(-) diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index 0eacaa0a56d..7bd684a2e83 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -249,22 +249,12 @@ impl Date { /// [spec]: https://tc39.es/ecma262/#sec-thistimevalue #[inline] fn this_time_value(value: &Value, ctx: &mut Interpreter) -> Result { - match value { - // 1. If Type(value) is Date, return value. - Value::Date(ref date) => Ok(date.clone()), - - // 2. If Type(value) is Object and value has a [[DateData]] internal slot, then - // a. Assert: Type(value.[[DateData]]) is Date. - // b. Return value.[[DateData]]. - Value::Object(ref object) => { - if let ObjectData::Date(ref date) = object.borrow().data { - Ok(date.clone()) - } else { - Err(ctx.construct_type_error("'this' is not a Date")) - } + if let Value::Object(ref object) = value { + if let ObjectData::Date(ref date) = object.borrow().data { + return Ok(date.clone()); } - _ => Err(ctx.construct_type_error("'this' is not a Date")), } + Err(ctx.construct_type_error("'this' is not a Date")) } /// `Date()` @@ -316,7 +306,7 @@ impl Date { pub(crate) fn make_date_now(this: &Value) -> ResultValue { let date = Date::default(); this.set_data(ObjectData::Date(date)); - Ok(Value::from(date)) + Ok(this.clone()) } /// `Date(value)` @@ -353,7 +343,7 @@ impl Date { let date = Date(tv); this.set_data(ObjectData::Date(date)); - Ok(Value::from(date)) + Ok(this.clone()) } /// `Date(year, month [ , date [ , hours [ , minutes [ , seconds [ , ms ] ] ] ] ])` @@ -383,7 +373,7 @@ impl Date { if !check_normal_opt!(year, month, day, hour, min, sec, milli) { let date = Date(None); this.set_data(ObjectData::Date(date)); - return Ok(Value::from(date)); + return Ok(this.clone()); } let year = year as i32; @@ -407,7 +397,7 @@ impl Date { let date = Date(final_date); this.set_data(ObjectData::Date(date)); - Ok(Value::from(date)) + Ok(this.clone()) } getter_method! { diff --git a/boa/src/builtins/date/tests.rs b/boa/src/builtins/date/tests.rs index 76f1536265b..45c2b16abc0 100644 --- a/boa/src/builtins/date/tests.rs +++ b/boa/src/builtins/date/tests.rs @@ -1422,7 +1422,10 @@ fn date_json() -> Result<(), Box> { "JSON.stringify({ date: new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)) })", ) .expect("Successful eval"); - assert_eq!(Value::number(1594199775779f64), actual); + assert_eq!( + Value::string(r#"{"date":"2020-07-08T09:16:15.779Z"}"#), + actual + ); Ok(()) } diff --git a/boa/src/builtins/value/conversions.rs b/boa/src/builtins/value/conversions.rs index f6127dc2faf..3a0a04872fa 100644 --- a/boa/src/builtins/value/conversions.rs +++ b/boa/src/builtins/value/conversions.rs @@ -126,12 +126,6 @@ impl From for Value { } } -impl From for Value { - fn from(value: Date) -> Self { - Value::date(value) - } -} - impl From for Value { fn from(value: usize) -> Value { Value::integer(value as i32) diff --git a/boa/src/builtins/value/display.rs b/boa/src/builtins/value/display.rs index a0d4f7f2240..8b0f1e3a2cf 100644 --- a/boa/src/builtins/value/display.rs +++ b/boa/src/builtins/value/display.rs @@ -241,13 +241,6 @@ impl Display for Value { Self::Object(_) => write!(f, "{}", log_string_from(self, true, true)), Self::Integer(v) => write!(f, "{}", v), Self::BigInt(ref num) => write!(f, "{}n", num), - Self::Date(ref date) => write!( - f, - "{}", - date.to_local() - .map(|f| f.to_rfc3339()) - .unwrap_or_else(|| "Invalid Date".to_string()) - ), } } } diff --git a/boa/src/builtins/value/hash.rs b/boa/src/builtins/value/hash.rs index 16a3e537f35..96ce6ebb5fa 100644 --- a/boa/src/builtins/value/hash.rs +++ b/boa/src/builtins/value/hash.rs @@ -48,7 +48,6 @@ impl Hash for Value { Self::Rational(rational) => RationalHashable(*rational).hash(state), Self::Symbol(ref symbol) => Hash::hash(symbol, state), Self::Object(ref object) => std::ptr::hash(object.as_ref(), state), - Self::Date(ref date) => Hash::hash(date, state), } } } diff --git a/boa/src/builtins/value/mod.rs b/boa/src/builtins/value/mod.rs index 7b6d2e872d2..494d4b7aeb6 100644 --- a/boa/src/builtins/value/mod.rs +++ b/boa/src/builtins/value/mod.rs @@ -13,7 +13,7 @@ use crate::builtins::{ function::Function, object::{GcObject, InternalState, InternalStateCell, Object, ObjectData, PROTOTYPE}, property::{Attribute, Property, PropertyKey}, - BigInt, Date, Symbol, + BigInt, Symbol, }; use crate::exec::Interpreter; use crate::BoaProfiler; @@ -71,8 +71,6 @@ pub enum Value { Object(GcObject), /// `Symbol` - A Symbol Primitive type. Symbol(RcSymbol), - /// `Date` - A Date type. - Date(Date), } impl Value { @@ -157,12 +155,6 @@ impl Value { Self::Symbol(RcSymbol::from(symbol)) } - /// Creates a new date value. - #[inline] - pub(crate) fn date(date: Date) -> Self { - Self::Date(date) - } - /// 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) { @@ -249,6 +241,12 @@ impl Value { /// 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,7 +287,6 @@ impl Value { Self::Symbol(_) | Self::Undefined => { unreachable!("Symbols and Undefined JSON Values depend on parent type"); } - Self::Date(ref dt) => Ok(JSONValue::String(dt.to_json_string())), } } @@ -453,7 +450,6 @@ impl Value { Self::BigInt(_) => { panic!("TypeError: Cannot mix BigInt and other types, use explicit conversions") } - Self::Date(ref dt) => dt.timestamp(), } } @@ -475,7 +471,6 @@ impl Value { Self::BigInt(_) => { panic!("TypeError: Cannot mix BigInt and other types, use explicit conversions") } - Self::Date(ref dt) => dt.timestamp() as i32, } } diff --git a/boa/src/builtins/value/operations.rs b/boa/src/builtins/value/operations.rs index cc468635082..e4f562865e5 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, @@ -394,7 +398,6 @@ impl Value { Self::Boolean(true) => Self::integer(1), Self::Boolean(false) | Self::Null => Self::integer(0), Self::BigInt(ref num) => Self::bigint(-num.as_inner().clone()), - Self::Date(ref dt) => Self::number(-dt.timestamp()), }) } diff --git a/boa/src/builtins/value/val_type.rs b/boa/src/builtins/value/val_type.rs index 5cc1a7e619f..2861abd338e 100644 --- a/boa/src/builtins/value/val_type.rs +++ b/boa/src/builtins/value/val_type.rs @@ -56,7 +56,6 @@ impl Value { } } Self::BigInt(_) => Type::BigInt, - Self::Date(_) => Type::Date, } } } diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index cdd076abcbc..c33117cb748 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -213,7 +213,6 @@ impl Interpreter { let primitive = self.to_primitive(value, PreferredType::String)?; self.to_string(&primitive) } - Value::Date(ref dt) => Ok(RcString::from(dt.to_string())), } } @@ -244,7 +243,6 @@ impl Interpreter { self.to_bigint(&primitive) } Value::Symbol(_) => Err(self.construct_type_error("cannot convert Symbol to a BigInt")), - Value::Date(dt) => Ok(RcBigInt::from(BigInt::from(dt.timestamp() as i64))), } } @@ -357,7 +355,6 @@ impl Interpreter { let primitive = self.to_primitive(value, PreferredType::Number)?; self.to_number(&primitive) } - Value::Date(ref dt) => Ok(dt.timestamp()), } } @@ -615,17 +612,6 @@ impl Interpreter { Ok(bigint_obj) } Value::Object(_) => Ok(value.clone()), - Value::Date(ref date) => { - let proto = self - .realm - .environment - .get_binding_value(crate::builtins::date::Date::NAME) - .expect("Date was not initialized") - .get_field(PROTOTYPE); - let date_obj = - Value::new_object_from_prototype(proto, ObjectData::Date(date.clone())); - Ok(date_obj) - } } } From d4e49681a6841c8b065ef669c01217dac108bc36 Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Sun, 2 Aug 2020 22:32:21 -0700 Subject: [PATCH 30/33] Fix lints --- boa/src/builtins/date/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index 7bd684a2e83..0124be11546 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -251,7 +251,7 @@ impl Date { 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.clone()); + return Ok(date); } } Err(ctx.construct_type_error("'this' is not a Date")) From c07857a3399cf6608ce9397481da0aaae0511e1d Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Sun, 2 Aug 2020 22:32:46 -0700 Subject: [PATCH 31/33] Fix lints --- boa/src/builtins/date/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index 0124be11546..b161da70ae4 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -251,7 +251,7 @@ impl Date { 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); + return Ok(*date); } } Err(ctx.construct_type_error("'this' is not a Date")) From 84dfdcc16c03355676cba8a2d128c4b6c78e2174 Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Mon, 3 Aug 2020 14:35:27 -0700 Subject: [PATCH 32/33] refactor all date methods --- boa/src/builtins/date/mod.rs | 1736 +++++++++++++++++----------------- 1 file changed, 879 insertions(+), 857 deletions(-) diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index b161da70ae4..29fc0e33f37 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -23,6 +23,12 @@ fn is_zero_or_normal_opt(value: Option) -> bool { .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 { @@ -32,153 +38,49 @@ fn ignore_ambiguity(result: LocalResult) -> Option { } } -/// Some JS functions allow completely invalid dates, and the runtime is expected to make sense of this. This function -/// constrains a date to correct values. -fn fix_date(year: &mut i32, month: &mut i32, day: &mut i32) { - #[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; - } - } - } - } - - fix_day(year, month, day); -} - -macro_rules! check_normal_opt { - ($($v:expr),+) => { - $(is_zero_or_normal_opt($v.into()) &&)+ true - }; -} - macro_rules! getter_method { - ($(#[$outer:meta])* fn $name:ident ($tz:ident, $var:ident) $get:expr) => { - $(#[$outer])* - fn $name(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - Ok(Value::number( - Self::this_time_value(this, ctx)? - .$tz() - .map_or(f64::NAN, |$var| $get), - )) + ($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 { - ($(#[$outer:meta])* fn $name:ident ($tz: ident, $date_time:ident, $var:ident[$count:literal]) $mutate:expr) => { - $(#[$outer])* - fn $name(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - // If the first arg is not present or NaN, the Date becomes NaN itself. - fn get_arg(i: usize, args: &[Value], ctx: &mut Interpreter) -> Option { - args - .get(i) - .and_then(|value| { - ctx.to_numeric_number(value).map_or_else( - |_| None, - |value| { - if value == 0f64 || value.is_normal() { - Some(value) - } else { - None - } - }, - ) - }) - } - - let mut $var = [None; $count]; - for i in 0..$count { - $var[i] = get_arg(i, args, ctx); - } - - let inner = Date::this_time_value(this, ctx)?.$tz(); - let new_value = inner.and_then(|$date_time| $mutate); - let new_value = new_value.map(|date_time| date_time.naive_utc()); - this.set_data(ObjectData::Date(Date(new_value))); - - Ok(Value::number( - Self::this_time_value(this, ctx)?.timestamp(), - )) + ($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())) } - }; - ($(#[$outer:meta])* fn $name:ident ($var:ident[$count:literal]) $mutate:expr) => { - $(#[$outer])* - fn $name(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - // If the first arg is not present or NaN, the Date becomes NaN itself. - fn get_arg(i: usize, args: &[Value], ctx: &mut Interpreter) -> Option { - args - .get(i) - .and_then(|value| { - ctx.to_numeric_number(value).map_or_else( - |_| None, - |value| { - if value == 0f64 || value.is_normal() { - Some(value) - } else { - None - } - }, - ) - }) - } - - let mut $var = [None; $count]; - for i in 0..$count { - $var[i] = get_arg(i, args, ctx); - } - - let new_value = $mutate; - let new_value = new_value.map(|date_time| date_time.naive_utc()); - this.set_data(ObjectData::Date(Date(new_value))); - - Ok(Value::number( - Self::this_time_value(this, ctx)?.timestamp(), - )) - } - }; + set_value + }}; } #[derive(Debug, Finalize, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -194,6 +96,8 @@ impl Display for 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!(); } @@ -210,51 +114,125 @@ impl Date { /// The amount of arguments this function object takes. pub(crate) const LENGTH: usize = 7; - /// The local date time. + /// 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)) } - // The UTC date time. + /// 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)) } - // The UTC timestamp. - pub fn timestamp(&self) -> f64 { - self.to_utc() - .map_or(f64::NAN, |dt| dt.timestamp_millis() as f64) - } + /// 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. + 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 + } - pub fn to_json_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()) - } + #[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 + } + } - /// 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] - 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); + #[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; + } + } } } - Err(ctx.construct_type_error("'this' is not a Date")) + + // 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()` @@ -325,7 +303,7 @@ impl Date { ctx: &mut Interpreter, ) -> ResultValue { let value = &args[0]; - let tv = match Self::this_time_value(value, ctx) { + 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) { @@ -400,151 +378,154 @@ impl Date { Ok(this.clone()) } - getter_method! { - /// `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 - fn get_date(to_local, dt) { dt.day() as f64 } + /// `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) } - getter_method! { - /// `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 - fn get_day(to_local, dt) { + /// `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 - } + }) } - getter_method! { - /// `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 - fn get_full_year(to_local, dt) { dt.year() 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) } - getter_method! { - /// `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 - fn get_hours(to_local, dt) { dt.hour() 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) } - getter_method! { - /// `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 - fn get_milliseconds(to_local, dt) { dt.nanosecond() as f64 / NANOS_IN_MS } + /// `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) } - getter_method! { - /// `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 - fn get_minutes(to_local, dt) { dt.minute() as f64 } + /// `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) } - getter_method! { - /// `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 - fn get_month(to_local, dt) { dt.month0() 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) } - getter_method! { - /// `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 - fn get_seconds(to_local, dt) { dt.second() 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) } - getter_method! { - /// `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 - fn get_year(to_local, dt) { dt.year() as f64 - 1900f64 } + /// `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) } - getter_method! { - /// `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 - fn get_time(to_utc, dt) { dt.timestamp_millis() as f64 } + /// `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()` @@ -559,548 +540,506 @@ impl Date { /// [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] - fn get_timezone_offset(_: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { + pub fn get_timezone_offset() -> f64 { let offset_seconds = chrono::Local::now().offset().local_minus_utc() as f64; - let offset_minutes = offset_seconds / 60f64; - Ok(Value::number(offset_minutes)) + offset_seconds / 60f64 } - getter_method! { - /// `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 - fn get_utc_date(to_utc, dt) { dt.day() as f64 } + /// `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) } - getter_method! { - /// `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 - fn get_utc_day(to_utc, dt) { + /// `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 - } + }) } - getter_method! { - /// `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 - fn get_utc_full_year(to_utc, dt) { dt.year() 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) } - getter_method! { - /// `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 - fn get_utc_hours(to_utc, dt) { dt.hour() 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) } - getter_method! { - /// `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 - fn get_utc_milliseconds(to_utc, dt) { dt.nanosecond() as f64 / NANOS_IN_MS } + /// `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) } - getter_method! { - /// `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 - fn get_utc_minutes(to_utc, dt) { dt.minute() as f64 } + /// `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) } - getter_method! { - /// `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 - fn get_utc_month(to_utc, dt) { dt.month0() 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) } - getter_method! { - /// `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 - fn get_utc_seconds(to_utc, dt) { dt.second() 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) } - setter_method! { - /// `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 - fn set_date (to_local, date_time, args[1]) { - args[0].and_then(|day| { - // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. - let local = date_time.naive_local(); - let mut year = local.year(); - let mut month = local.month0() as i32; - let mut day = day as i32 - 1; - - fix_date(&mut year, &mut month, &mut day); - ignore_ambiguity(Local.ymd_opt(year, month as u32 + 1, day as u32 + 1).and_time(local.time())) - }) + /// `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 } } - setter_method! { - /// `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 - fn set_full_year (to_local, date_time, args[3]) { - args[0].and_then(|year| { - // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. - let local = date_time.naive_local(); - let mut year = year as i32; - let mut month = args[1].unwrap_or_else(|| local.month0() as f64) as i32; - let mut day = args[2].unwrap_or_else(|| local.day() as f64) as i32 - 1; - - fix_date(&mut year, &mut month, &mut day); - ignore_ambiguity(Local.ymd_opt(year, month as u32 + 1, day as u32 + 1).and_time(local.time())) - }) + /// `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 } } - setter_method! { - /// `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 - fn set_hours (to_local, date_time, args[4]) { - args[0].and_then(|hour| { - // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. - let local = date_time.naive_local(); - let hour = hour as i64; - let minute = args[1].map_or_else(|| local.minute() as i64, |minute| minute as i64); - let second = args[2].map_or_else(|| local.second() as i64, |second| second as i64); - let ms = args[3].map_or_else(|| (local.nanosecond() as f64 / NANOS_IN_MS) as i64, |ms| ms as i64); - - let duration = Duration::hours(hour) + Duration::minutes(minute) + Duration::seconds(second) + Duration::milliseconds(ms); - let local = local.date().and_hms(0, 0, 0).checked_add_signed(duration); - local.and_then(|local| ignore_ambiguity(Local.from_local_datetime(&local))) - }) + /// `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 } } - setter_method! { - /// `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 - fn set_milliseconds (to_local, date_time, args[1]) { - args[0].and_then(|ms| { - // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. - let local = date_time.naive_local(); - let hour = local.hour() as i64; - let minute = local.minute() as i64; - let second = local.second() as i64; - let ms = ms as i64; - - let duration = Duration::hours(hour) + Duration::minutes(minute) + Duration::seconds(second) + Duration::milliseconds(ms); - let local = local.date().and_hms(0, 0, 0).checked_add_signed(duration); - local.and_then(|local| ignore_ambiguity(Local.from_local_datetime(&local))) - }) + /// `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 } } - setter_method! { - /// `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 - fn set_minutes (to_local, date_time, args[3]) { - args[0].and_then(|minute| { - // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. - let local = date_time.naive_local(); - let hour = local.hour() as i64; - let minute = minute as i64; - let second = args[1].map_or_else(|| local.second() as i64, |second| second as i64); - let ms = args[2].map_or_else(|| (local.nanosecond() as f64 / NANOS_IN_MS) as i64, |ms| ms as i64); - - let duration = Duration::hours(hour) + Duration::minutes(minute) + Duration::seconds(second) + Duration::milliseconds(ms); - let local = local.date().and_hms(0, 0, 0).checked_add_signed(duration); - local.and_then(|local| ignore_ambiguity(Local.from_local_datetime(&local))) - }) + /// `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 } } - setter_method! { - /// `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 - fn set_month (to_local, date_time, args[2]) { - args[0].and_then(|month| { - // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. - let local = date_time.naive_local(); - let mut year = local.year(); - let mut month = month as i32; - let mut day = args[1].unwrap_or_else(|| local.day() as f64) as i32 - 1; - - fix_date(&mut year, &mut month, &mut day); - ignore_ambiguity(Local.ymd_opt(year, month as u32 + 1, day as u32 + 1).and_time(local.time())) - }) + /// `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 } } - setter_method! { - /// `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 - fn set_seconds (to_local, date_time, args[2]) { - args[0].and_then(|second| { - // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. - let local = date_time.naive_local(); - let hour = local.hour() as i64; - let minute = local.minute() as i64; - let second = second as i64; - let ms = args[1].map_or_else(|| (local.nanosecond() as f64 / NANOS_IN_MS) as i64, |ms| ms as i64); - - let duration = Duration::hours(hour) + Duration::minutes(minute) + Duration::seconds(second) + Duration::milliseconds(ms); - let local = local.date().and_hms(0, 0, 0).checked_add_signed(duration); - local.and_then(|local| ignore_ambiguity(Local.from_local_datetime(&local))) - }) + /// `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 } } - setter_method! { - /// `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 - fn set_year (to_local, date_time, args[3]) { - args[0].and_then(|year| { - // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. - let local = date_time.naive_local(); - let mut year = year as i32; - year += if 0 <= year && year <= 99 { - 1900 - } else { - 0 - }; - - local.with_year(year).and_then(|local| ignore_ambiguity(Local.from_local_datetime(&local))) - }) + /// `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 } } - setter_method! { - /// `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 - fn set_time (args[1]) { - args[0].and_then(|tv| { - let secs = (tv / 1_000f64) as i64; - let nsecs = ((tv % 1_000f64) * 1_000_000f64) as u32; - ignore_ambiguity(Local.timestamp_opt(secs, nsecs)) - }) + /// `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 } } - setter_method! { - /// `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 - fn set_utc_date (to_utc, date_time, args[1]) { - args[0].and_then(|day| { - // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. - let utc = date_time.naive_utc(); - let mut year = utc.year(); - let mut month = utc.month0() as i32; - let mut day = day as i32 - 1; - - fix_date(&mut year, &mut month, &mut day); - ignore_ambiguity(Utc.ymd_opt(year, month as u32 + 1, day as u32 + 1).and_time(utc.time())) - }) + /// `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 } } - setter_method! { - /// `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 - fn set_utc_full_year (to_utc, date_time, args[3]) { - args[0].and_then(|year| { - // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. - let utc = date_time.naive_utc(); - let mut year = year as i32; - let mut month = args[1].unwrap_or_else(|| utc.month0() as f64) as i32; - let mut day = args[2].unwrap_or_else(|| utc.day() as f64) as i32 - 1; - - fix_date(&mut year, &mut month, &mut day); - ignore_ambiguity(Utc.ymd_opt(year, month as u32 + 1, day as u32 + 1).and_time(utc.time())) - }) + /// `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 } } - setter_method! { - /// `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 - fn set_utc_hours (to_utc, date_time, args[4]) { - args[0].and_then(|hour| { - // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. - let utc = date_time.naive_utc(); - let hour = hour as i64; - let minute = args[1].map_or_else(|| utc.minute() as i64, |minute| minute as i64); - let second = args[2].map_or_else(|| utc.second() as i64, |second| second as i64); - let ms = args[3].map_or_else(|| (utc.nanosecond() as f64 / NANOS_IN_MS) as i64, |ms| ms as i64); - - let duration = Duration::hours(hour) + Duration::minutes(minute) + Duration::seconds(second) + Duration::milliseconds(ms); - let utc = utc.date().and_hms(0, 0, 0).checked_add_signed(duration); - utc.map(|utc| Utc.from_utc_datetime(&utc)) - }) + /// `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 } } - setter_method! { - /// `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 - fn set_utc_milliseconds (to_utc, date_time, args[1]) { - args[0].and_then(|ms| { - // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. - let utc = date_time.naive_utc(); - let hour = utc.hour() as i64; - let minute = utc.minute() as i64; - let second = utc.second() as i64; - let ms = ms as i64; - - let duration = Duration::hours(hour) + Duration::minutes(minute) + Duration::seconds(second) + Duration::milliseconds(ms); - let utc = utc.date().and_hms(0, 0, 0).checked_add_signed(duration); - utc.map(|utc| Utc.from_utc_datetime(&utc)) - }) + /// `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 } } - setter_method! { - /// `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 - fn set_utc_minutes (to_utc, date_time, args[3]) { - args[0].and_then(|minute| { - // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. - let utc = date_time.naive_utc(); - let hour = utc.hour() as i64; - let minute = minute as i64; - let second = args[1].map_or_else(|| utc.second() as i64, |second| second as i64); - let ms = args[2].map_or_else(|| (utc.nanosecond() as f64 / NANOS_IN_MS) as i64, |ms| ms as i64); - - let duration = Duration::hours(hour) + Duration::minutes(minute) + Duration::seconds(second) + Duration::milliseconds(ms); - let utc = utc.date().and_hms(0, 0, 0).checked_add_signed(duration); - utc.map(|utc| Utc.from_utc_datetime(&utc)) - }) + /// `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 } } - setter_method! { - /// `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 - fn set_utc_month (to_utc, date_time, args[2]) { - args[0].and_then(|month| { - // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. - let utc = date_time.naive_utc(); - let mut year = utc.year(); - let mut month = month as i32; - let mut day = args[1].unwrap_or_else(|| utc.day() as f64) as i32 - 1; - - fix_date(&mut year, &mut month, &mut day); - ignore_ambiguity(Utc.ymd_opt(year, month as u32 + 1, day as u32 + 1).and_time(utc.time())) - }) + /// `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 } } - setter_method! { - /// `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 - fn set_utc_seconds (to_utc, date_time, args[2]) { - args[0].and_then(|second| { - // Setters have to work in naive time because chrono [correctly] deals with DST, where JS does not. - let utc = date_time.naive_utc(); - let hour = utc.hour() as i64; - let minute = utc.minute() as i64; - let second = second as i64; - let ms = args[1].map_or_else(|| (utc.nanosecond() as f64 / NANOS_IN_MS) as i64, |ms| ms as i64); - - let duration = Duration::hours(hour) + Duration::minutes(minute) + Duration::seconds(second) + Duration::milliseconds(ms); - let utc = utc.date().and_hms(0, 0, 0).checked_add_signed(duration); - utc.map(|utc| Utc.from_utc_datetime(&utc)) - }) + /// `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 } } @@ -1114,13 +1053,10 @@ impl Date { /// /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.todatestring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toDateString - #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_date_string(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - let dt_str = Self::this_time_value(this, ctx)? - .to_local() + 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()); - Ok(Value::from(dt_str)) + .unwrap_or_else(|| "Invalid Date".to_string()) } /// `Date.prototype.toGMTString()` @@ -1133,13 +1069,8 @@ impl Date { /// /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.togmtstring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toGMTString - #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_gmt_string( - this: &Value, - args: &[Value], - ctx: &mut Interpreter, - ) -> ResultValue { - Self::to_utc_string(this, args, ctx) + pub fn to_gmt_string(&self) -> String { + self.to_utc_string() } /// `Date.prototype.toISOString()` @@ -1154,10 +1085,11 @@ impl Date { /// [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 - #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_iso_string(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - let dt_str = Self::this_time_value(this, ctx)?.to_json_string(); - Ok(Value::from(dt_str)) + 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()` @@ -1170,9 +1102,8 @@ impl Date { /// /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.tojson /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toJSON - #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_json(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - Self::to_iso_string(this, args, ctx) + pub fn to_json(&self) -> String { + self.to_iso_string() } /// `Date.prototype.toString()` @@ -1185,13 +1116,10 @@ impl Date { /// /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.tostring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toString - #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_string(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - let dt_str = Self::this_time_value(this, ctx)? - .to_local() + pub fn to_string(&self) -> String { + self.to_local() .map(|date_time| date_time.format("%a %b %d %Y %H:%M:%S GMT%:z").to_string()) - .unwrap_or_else(|| "Invalid Date".to_string()); - Ok(Value::from(dt_str)) + .unwrap_or_else(|| "Invalid Date".to_string()) } /// `Date.prototype.toTimeString()` @@ -1205,13 +1133,10 @@ impl Date { /// /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.totimestring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toTimeString - #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_time_string(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - let dt_str = Self::this_time_value(this, ctx)? - .to_local() + 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()); - Ok(Value::from(dt_str)) + .unwrap_or_else(|| "Invalid Date".to_string()) } /// `Date.prototype.toUTCString()` @@ -1224,13 +1149,10 @@ impl Date { /// /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.toutcstring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toUTCString - #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_utc_string(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - let dt_str = Self::this_time_value(this, ctx)? - .to_utc() + 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()); - Ok(Value::from(dt_str)) + .unwrap_or_else(|| "Invalid Date".to_string()) } /// `Date.prototype.valueOf()` @@ -1243,10 +1165,8 @@ impl Date { /// /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.valueof /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/valueOf - #[allow(clippy::wrong_self_convention)] - pub(crate) fn value_of(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - let dt = Self::this_time_value(this, ctx)?.timestamp(); - Ok(Value::from(dt)) + pub fn value_of(&self) -> f64 { + self.get_time() } /// `Date.now()` @@ -1343,65 +1263,145 @@ impl Date { let prototype = Value::new_object(Some(global)); - make_builtin_fn(Self::get_date, "getDate", &prototype, 0); - make_builtin_fn(Self::get_day, "getDay", &prototype, 0); - make_builtin_fn(Self::get_full_year, "getFullYear", &prototype, 0); - make_builtin_fn(Self::get_hours, "getHours", &prototype, 0); - make_builtin_fn(Self::get_milliseconds, "getMilliseconds", &prototype, 0); - make_builtin_fn(Self::get_minutes, "getMinutes", &prototype, 0); - make_builtin_fn(Self::get_month, "getMonth", &prototype, 0); - make_builtin_fn(Self::get_seconds, "getSeconds", &prototype, 0); - make_builtin_fn(Self::get_time, "getTime", &prototype, 0); - make_builtin_fn(Self::get_year, "getYear", &prototype, 0); + 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( - Self::get_timezone_offset, + 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(Self::get_utc_date, "getUTCDate", &prototype, 0); - make_builtin_fn(Self::get_utc_day, "getUTCDay", &prototype, 0); - make_builtin_fn(Self::get_utc_full_year, "getUTCFullYear", &prototype, 0); - make_builtin_fn(Self::get_utc_hours, "getUTCHours", &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( - Self::get_utc_milliseconds, + 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(Self::get_utc_minutes, "getUTCMinutes", &prototype, 0); - make_builtin_fn(Self::get_utc_month, "getUTCMonth", &prototype, 0); - make_builtin_fn(Self::get_utc_seconds, "getUTCSeconds", &prototype, 0); - make_builtin_fn(Self::set_date, "setDate", &prototype, 1); - make_builtin_fn(Self::set_full_year, "setFullYear", &prototype, 1); - make_builtin_fn(Self::set_hours, "setHours", &prototype, 1); - make_builtin_fn(Self::set_milliseconds, "setMilliseconds", &prototype, 1); - make_builtin_fn(Self::set_minutes, "setMinutes", &prototype, 1); - make_builtin_fn(Self::set_month, "setMonth", &prototype, 1); - make_builtin_fn(Self::set_seconds, "setSeconds", &prototype, 1); - make_builtin_fn(Self::set_year, "setYear", &prototype, 1); - make_builtin_fn(Self::set_time, "setTime", &prototype, 1); - make_builtin_fn(Self::set_utc_date, "setUTCDate", &prototype, 1); - make_builtin_fn(Self::set_utc_full_year, "setUTCFullYear", &prototype, 1); - make_builtin_fn(Self::set_utc_hours, "setUTCHours", &prototype, 1); make_builtin_fn( - Self::set_utc_milliseconds, + 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(Self::set_utc_minutes, "setUTCMinutes", &prototype, 1); - make_builtin_fn(Self::set_utc_month, "setUTCMonth", &prototype, 1); - make_builtin_fn(Self::set_utc_seconds, "setUTCSeconds", &prototype, 1); - make_builtin_fn(Self::to_date_string, "toDateString", &prototype, 0); - make_builtin_fn(Self::to_gmt_string, "toGMTString", &prototype, 0); - make_builtin_fn(Self::to_iso_string, "toISOString", &prototype, 0); - make_builtin_fn(Self::to_json, "toJSON", &prototype, 0); + 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(Self::to_string, "toString", &prototype, 0); - make_builtin_fn(Self::to_time_string, "toTimeString", &prototype, 0); - make_builtin_fn(Self::to_utc_string, "toUTCString", &prototype, 0); - make_builtin_fn(Self::value_of, "valueOf", &prototype, 0); + 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, @@ -1419,3 +1419,25 @@ impl Date { (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")) +} From 84489dc0fc7357c19493ee30186c8e90c1caee95 Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Mon, 3 Aug 2020 14:48:07 -0700 Subject: [PATCH 33/33] Fix lints --- boa/src/builtins/date/mod.rs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index 29fc0e33f37..d15ead4b36b 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -134,6 +134,7 @@ impl 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, @@ -1106,22 +1107,6 @@ impl Date { self.to_iso_string() } - /// `Date.prototype.toString()` - /// - /// The `toString()` 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.tostring - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toString - pub fn to_string(&self) -> String { - self.to_local() - .map(|date_time| date_time.format("%a %b %d %Y %H:%M:%S GMT%:z").to_string()) - .unwrap_or_else(|| "Invalid Date".to_string()) - } - /// `Date.prototype.toTimeString()` /// /// The `toTimeString()` method returns the time portion of a Date object in human readable form in American