diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index c3e06a7f6..3f751cdc4 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -519,7 +519,6 @@ pub fn load_player_globals<'gc>( let namespace_class = namespace::create_class(activation); let array_class = array::create_class(activation); let vector_generic_class = vector::create_generic_class(activation); - let date_class = date::create_class(activation); let vector_int_class = vector::create_builtin_class(activation, Some(int_class)); let vector_uint_class = vector::create_builtin_class(activation, Some(uint_class)); @@ -545,7 +544,6 @@ pub fn load_player_globals<'gc>( (public_ns, "uint", uint_class), (public_ns, "Namespace", namespace_class), (public_ns, "Array", array_class), - (public_ns, "Date", date_class), (vector_public_ns, "Vector", vector_generic_class), (vector_internal_ns, "Vector$int", vector_int_class), (vector_internal_ns, "Vector$uint", vector_uint_class), @@ -728,8 +726,6 @@ pub fn load_player_globals<'gc>( .unwrap() .object_vector = object_vector; - avm2_system_class!(date, activation, date_class, script); - // Inside this call, the macro `avm2_system_classes_playerglobal` // triggers classloading. Therefore, we run `load_playerglobal` // relatively late, so that it can access classes defined before @@ -814,6 +810,7 @@ fn load_playerglobal<'gc>( avm2_system_classes_playerglobal!( &mut *activation, [ + ("", "Date", date), ("", "Error", error), ("", "ArgumentError", argumenterror), ("", "QName", qname), diff --git a/core/src/avm2/globals/Date.as b/core/src/avm2/globals/Date.as index 735261d89..08d83e523 100644 --- a/core/src/avm2/globals/Date.as +++ b/core/src/avm2/globals/Date.as @@ -1,5 +1,460 @@ -// This is a stub - the actual class is defined in `date.rs` package { - public class Date { + [Ruffle(InstanceAllocator)] + [Ruffle(CallHandler)] + public dynamic class Date { + public static const length:int = 7; + + prototype.valueOf = function():* { + var d:Date = this; + return d.AS3::valueOf(); + } + prototype.toString = function():String { + var d:Date = this; + return d.AS3::toString(); + } + prototype.toDateString = function():String { + var d:Date = this; + return d.AS3::toDateString(); + } + prototype.toTimeString = function():String { + var d:Date = this; + return d.AS3::toTimeString(); + } + prototype.toLocaleString = function():String { + var d:Date = this; + return d.AS3::toLocaleString(); + } + prototype.toLocaleDateString = function():String { + var d:Date = this; + return d.AS3::toLocaleDateString(); + } + prototype.toLocaleTimeString = function():String { + var d:Date = this; + return d.AS3::toLocaleTimeString(); + } + prototype.toUTCString = function():String { + var d:Date = this; + return d.AS3::toUTCString(); + } + prototype.toJSON = function(k:String):* { + var d:Date = this; + return d.AS3::toString(); + } + prototype.getUTCFullYear = function():Number { + var d:Date = this; + return d.AS3::getUTCFullYear(); + } + prototype.getUTCMonth = function():Number { + var d:Date = this; + return d.AS3::getUTCMonth(); + } + prototype.getUTCDate = function():Number { + var d:Date = this; + return d.AS3::getUTCDate(); + } + prototype.getUTCDay = function():Number { + var d:Date = this; + return d.AS3::getUTCDay(); + } + prototype.getUTCHours = function():Number { + var d:Date = this; + return d.AS3::getUTCHours(); + } + prototype.getUTCMinutes = function():Number { + var d:Date = this; + return d.AS3::getUTCMinutes(); + } + prototype.getUTCSeconds = function():Number { + var d:Date = this; + return d.AS3::getUTCSeconds(); + } + prototype.getUTCMilliseconds = function():Number { + var d:Date = this; + return d.AS3::getUTCMilliseconds(); + } + prototype.getFullYear = function():Number { + var d:Date = this; + return d.AS3::getFullYear(); + } + prototype.getMonth = function():Number { + var d:Date = this; + return d.AS3::getMonth(); + } + prototype.getDate = function():Number { + var d:Date = this; + return d.AS3::getDate(); + } + prototype.getDay = function():Number { + var d:Date = this; + return d.AS3::getDay(); + } + prototype.getHours = function():Number { + var d:Date = this; + return d.AS3::getHours(); + } + prototype.getMinutes = function():Number { + var d:Date = this; + return d.AS3::getMinutes(); + } + prototype.getSeconds = function():Number { + var d:Date = this; + return d.AS3::getSeconds(); + } + prototype.getMilliseconds = function():Number { + var d:Date = this; + return d.AS3::getMilliseconds(); + } + prototype.getTimezoneOffset = function():Number { + var d:Date = this; + return d.AS3::getTimezoneOffset(); + } + prototype.getTime = function():Number { + var d:Date = this; + return d.AS3::getTime(); + } + prototype.setTime = function(t:* = undefined):Number { + var d:Date = this; + return d.AS3::setTime(t); + } + prototype.setFullYear = function(year:* = undefined, month:* = undefined, day:* = undefined):Number { + var d:Date = this; + return d._setFullYear(arguments); + } + prototype.setMonth = function(month:* = undefined, day:* = undefined):Number { + var d:Date = this; + return d._setMonth(arguments); + } + prototype.setDate = function(day:* = undefined):Number { + var d:Date = this; + return d._setDate(day); + } + prototype.setHours = function(hour:* = undefined, min:* = undefined, sec:* = undefined, ms:* = undefined):Number { + var d:Date = this; + return d._setHours(arguments); + } + prototype.setMinutes = function(min:* = undefined, sec:* = undefined, ms:* = undefined):Number { + var d:Date = this; + return d._setMinutes(arguments); + } + prototype.setSeconds = function(sec:* = undefined, ms:* = undefined):Number { + var d:Date = this; + return d._setSeconds(arguments); + } + prototype.setMilliseconds = function(ms:* = undefined):Number { + var d:Date = this; + return d._setMilliseconds(arguments); + } + prototype.setUTCFullYear = function(year:* = undefined, month:* = undefined, day:* = undefined):Number { + var d:Date = this; + return d._setUTCFullYear(arguments); + } + prototype.setUTCMonth = function(month:* = undefined, day:* = undefined):Number { + var d:Date = this; + return d._setUTCMonth(arguments); + } + prototype.setUTCDate = function(day:* = undefined):Number { + var d:Date = this; + return d._setUTCDate(arguments); + } + prototype.setUTCHours = function(hour:* = undefined, min:* = undefined, sec:* = undefined, ms:* = undefined):Number { + var d:Date = this; + return d._setUTCHours(arguments); + } + prototype.setUTCMinutes = function(min:* = undefined, sec:* = undefined, ms:* = undefined):Number { + var d:Date = this; + return d._setUTCMinutes(arguments); + } + prototype.setUTCSeconds = function(sec:* = undefined, ms:* = undefined):Number { + var d:Date = this; + return d._setUTCSeconds(arguments); + } + prototype.setUTCMilliseconds = function(ms:* = undefined):Number { + var d:Date = this; + return d._setUTCMilliseconds(arguments); + } + + prototype.setPropertyIsEnumerable("valueOf", false); + prototype.setPropertyIsEnumerable("toString", false); + prototype.setPropertyIsEnumerable("toDateString", false); + prototype.setPropertyIsEnumerable("toTimeString", false); + prototype.setPropertyIsEnumerable("toLocaleString", false); + prototype.setPropertyIsEnumerable("toLocaleDateString", false); + prototype.setPropertyIsEnumerable("toLocaleTimeString", false); + prototype.setPropertyIsEnumerable("toUTCString", false); + prototype.setPropertyIsEnumerable("toJSON", false); + prototype.setPropertyIsEnumerable("getUTCFullYear", false); + prototype.setPropertyIsEnumerable("getUTCMonth", false); + prototype.setPropertyIsEnumerable("getUTCDate", false); + prototype.setPropertyIsEnumerable("getUTCDay", false); + prototype.setPropertyIsEnumerable("getUTCHours", false); + prototype.setPropertyIsEnumerable("getUTCMinutes", false); + prototype.setPropertyIsEnumerable("getUTCSeconds", false); + prototype.setPropertyIsEnumerable("getUTCMilliseconds", false); + prototype.setPropertyIsEnumerable("getFullYear", false); + prototype.setPropertyIsEnumerable("getMonth", false); + prototype.setPropertyIsEnumerable("getDate", false); + prototype.setPropertyIsEnumerable("getDay", false); + prototype.setPropertyIsEnumerable("getHours", false); + prototype.setPropertyIsEnumerable("getMinutes", false); + prototype.setPropertyIsEnumerable("getSeconds", false); + prototype.setPropertyIsEnumerable("getMilliseconds", false); + prototype.setPropertyIsEnumerable("getTimezoneOffset", false); + prototype.setPropertyIsEnumerable("getTime", false); + prototype.setPropertyIsEnumerable("setTime", false); + prototype.setPropertyIsEnumerable("setFullYear", false); + prototype.setPropertyIsEnumerable("setMonth", false); + prototype.setPropertyIsEnumerable("setDate", false); + prototype.setPropertyIsEnumerable("setHours", false); + prototype.setPropertyIsEnumerable("setMinutes", false); + prototype.setPropertyIsEnumerable("setSeconds", false); + prototype.setPropertyIsEnumerable("setMilliseconds", false); + prototype.setPropertyIsEnumerable("setUTCFullYear", false); + prototype.setPropertyIsEnumerable("setUTCMonth", false); + prototype.setPropertyIsEnumerable("setUTCDate", false); + prototype.setPropertyIsEnumerable("setUTCHours", false); + prototype.setPropertyIsEnumerable("setUTCMinutes", false); + prototype.setPropertyIsEnumerable("setUTCSeconds", false); + prototype.setPropertyIsEnumerable("setUTCMilliseconds", false); + + + public function Date(year:* = undefined, month:* = undefined, day:* = undefined, hours:* = undefined, minutes:* = undefined, seconds:* = undefined, ms:* = undefined) { + this.init(arguments); + } + private native function init(args:Array); + + public static native function parse(date:*):Number; + + public static native function UTC(year:*, month:*, date:* = 1, hour:* = 0, minute:* = 0, second:* = 0, millisecond:* = 0, ... rest):Number; + + AS3 function valueOf():Number { + return this.AS3::getTime(); + } + + AS3 native function toString():String; + + AS3 native function toDateString():String; + + AS3 native function toTimeString():String; + + AS3 native function toLocaleString():String; + + AS3 function toLocaleDateString():String { + return this.AS3::toDateString(); + } + + AS3 native function toLocaleTimeString():String; + + AS3 native function toUTCString():String; + + AS3 native function getUTCDay():Number; + + AS3 native function getDay():Number; + + AS3 native function getTimezoneOffset():Number; + + AS3 native function getTime():Number; + AS3 native function setTime(time:* = undefined):Number; + + AS3 native function getFullYear():Number; + private native function _setFullYear(args:Array):Number; + AS3 function setFullYear(year:* = undefined, month:* = undefined, day:* = undefined):Number { + return _setFullYear(arguments); + } + + AS3 native function getMonth():Number; + private native function _setMonth(args:Array):Number; + AS3 function setMonth(month:* = undefined, day:* = undefined):Number { + return _setMonth(arguments); + } + + AS3 native function getDate():Number; + private native function _setDate(args:Array):Number; + AS3 function setDate(day:* = undefined):Number { + return _setDate(arguments); + } + + AS3 native function getHours():Number; + private native function _setHours(args:Array):Number; + AS3 function setHours(hour:* = undefined, min:* = undefined, sec:* = undefined, ms:* = undefined):Number { + return _setHours(arguments); + } + + AS3 native function getMinutes():Number; + private native function _setMinutes(args:Array):Number; + AS3 function setMinutes(min:* = undefined, sec:* = undefined, ms:* = undefined):Number { + return _setMinutes(arguments); + } + + AS3 native function getSeconds():Number; + private native function _setSeconds(args:Array):Number; + AS3 function setSeconds(sec:* = undefined, ms:* = undefined):Number { + return _setSeconds(arguments); + } + + AS3 native function getMilliseconds():Number; + private native function _setMilliseconds(args:Array):Number; + AS3 function setMilliseconds(ms:* = undefined):Number { + return _setMilliseconds(arguments); + } + + AS3 native function getUTCFullYear():Number; + private native function _setUTCFullYear(args:Array):Number; + AS3 function setUTCFullYear(year:* = undefined, month:* = undefined, day:* = undefined):Number { + return _setUTCFullYear(arguments); + } + + AS3 native function getUTCMonth():Number; + private native function _setUTCMonth(args:Array):Number; + AS3 function setUTCMonth(month:* = undefined, day:* = undefined):Number { + return _setUTCMonth(arguments); + } + + AS3 native function getUTCDate():Number; + private native function _setUTCDate(args:Array):Number; + AS3 function setUTCDate(day:* = undefined):Number { + return _setUTCDate(arguments); + } + + AS3 native function getUTCHours():Number; + private native function _setUTCHours(args:Array):Number; + AS3 function setUTCHours(hour:* = undefined, min:* = undefined, sec:* = undefined, ms:* = undefined):Number { + return _setUTCHours(arguments); + } + + AS3 native function getUTCMinutes():Number; + private native function _setUTCMinutes(args:Array):Number; + AS3 function setUTCMinutes(min:* = undefined, sec:* = undefined, ms:* = undefined):Number { + return _setUTCMinutes(arguments); + } + + AS3 native function getUTCSeconds():Number; + private native function _setUTCSeconds(args:Array):Number; + AS3 function setUTCSeconds(sec:* = undefined, ms:* = undefined):Number { + return _setUTCSeconds(arguments); + } + + AS3 native function getUTCMilliseconds():Number; + private native function _setUTCMilliseconds(args:Array):Number; + AS3 function setUTCMilliseconds(ms:* = undefined):Number { + return _setUTCMilliseconds(arguments); + } + + + public function get fullYear():Number { + return this.AS3::getFullYear(); + } + public function set fullYear(value:Number):void { + this.AS3::setFullYear(value); + } + + public function get month():Number { + return this.AS3::getMonth(); + } + public function set month(value:Number):void { + this.AS3::setMonth(value); + } + + public function get date():Number { + return this.AS3::getDate(); + } + public function set date(value:Number):void { + this.AS3::setDate(value); + } + + public function get hours():Number { + return this.AS3::getHours(); + } + public function set hours(value:Number):void { + this.AS3::setHours(value); + } + + public function get minutes():Number { + return this.AS3::getMinutes(); + } + public function set minutes(value:Number):void { + this.AS3::setMinutes(value); + } + + public function get seconds():Number { + return this.AS3::getSeconds(); + } + public function set seconds(value:Number):void { + this.AS3::setSeconds(value); + } + + public function get milliseconds():Number { + return this.AS3::getMilliseconds(); + } + public function set milliseconds(value:Number):void { + this.AS3::setMilliseconds(value); + } + + public function get fullYearUTC():Number { + return this.AS3::getUTCFullYear(); + } + public function set fullYearUTC(value:Number):void { + this.AS3::setUTCFullYear(value); + } + + public function get monthUTC():Number { + return this.AS3::getUTCMonth(); + } + public function set monthUTC(value:Number):void { + this.AS3::setUTCMonth(value); + } + + public function get dateUTC():Number { + return this.AS3::getUTCDate(); + } + public function set dateUTC(value:Number):void { + this.AS3::setUTCDate(value); + } + + public function get hoursUTC():Number { + return this.AS3::getUTCHours(); + } + public function set hoursUTC(value:Number):void { + this.AS3::setUTCHours(value); + } + + public function get minutesUTC():Number { + return this.AS3::getUTCMinutes(); + } + public function set minutesUTC(value:Number):void { + this.AS3::setUTCMinutes(value); + } + + public function get secondsUTC():Number { + return this.AS3::getUTCSeconds(); + } + public function set secondsUTC(value:Number):void { + this.AS3::setUTCSeconds(value); + } + + public function get millisecondsUTC():Number { + return this.AS3::getUTCMilliseconds(); + } + public function set millisecondsUTC(value:Number):void { + this.AS3::setUTCMilliseconds(value); + } + + public function get time():Number { + return this.AS3::getTime(); + } + public function set time(value:Number):void { + this.AS3::setTime(value); + } + + public function get timezoneOffset():Number { + return this.AS3::getTimezoneOffset(); + } + + public function get day():Number { + return this.AS3::getDay(); + } + + public function get dayUTC():Number { + return this.AS3::getUTCDay(); + } } } diff --git a/core/src/avm2/globals/date.rs b/core/src/avm2/globals/date.rs index 2bd786ef2..4596eb6f0 100644 --- a/core/src/avm2/globals/date.rs +++ b/core/src/avm2/globals/date.rs @@ -1,63 +1,15 @@ //! `Date` class use crate::avm2::activation::Activation; -use crate::avm2::class::Class; -use crate::avm2::method::{Method, NativeMethodImpl}; -use crate::avm2::object::{date_allocator, DateObject, FunctionObject, Object, TObject}; +pub use crate::avm2::object::date_allocator; +use crate::avm2::object::{DateObject, Object, TObject}; use crate::avm2::value::Value; use crate::avm2::Error; -use crate::avm2::QName; use crate::locale::{get_current_date_time, get_timezone}; use crate::string::{utils as string_utils, AvmString, WStr}; use chrono::{DateTime, Datelike, Duration, FixedOffset, LocalResult, TimeZone, Timelike, Utc}; use num_traits::ToPrimitive; -// All of these methods will be defined as both -// AS3 instance methods and methods on the `Date` class prototype. -const PUBLIC_INSTANCE_AND_PROTO_METHODS: &[(&str, NativeMethodImpl)] = &[ - ("getTime", time), - ("setTime", set_time), - ("getMilliseconds", milliseconds), - ("setMilliseconds", set_milliseconds), - ("getSeconds", seconds), - ("setSeconds", set_seconds), - ("getMinutes", minutes), - ("setMinutes", set_minutes), - ("getHours", hours), - ("setHours", set_hours), - ("getDate", date), - ("setDate", set_date), - ("getMonth", month), - ("setMonth", set_month), - ("getFullYear", full_year), - ("setFullYear", set_full_year), - ("getDay", day), - ("getUTCMilliseconds", milliseconds_utc), - ("setUTCMilliseconds", set_milliseconds_utc), - ("getUTCSeconds", seconds_utc), - ("setUTCSeconds", set_seconds_utc), - ("getUTCMinutes", minutes_utc), - ("setUTCMinutes", set_minutes_utc), - ("getUTCHours", hours_utc), - ("setUTCHours", set_hours_utc), - ("getUTCDate", date_utc), - ("setUTCDate", set_date_utc), - ("getUTCMonth", month_utc), - ("setUTCMonth", set_month_utc), - ("getUTCFullYear", full_year_utc), - ("setUTCFullYear", set_full_year_utc), - ("getUTCDay", day_utc), - ("getTimezoneOffset", timezone_offset), - ("valueOf", time), - ("toString", to_string), - ("toUTCString", to_utc_string), - ("toLocaleString", to_locale_string), - ("toTimeString", to_time_string), - ("toLocaleTimeString", to_locale_time_string), - ("toDateString", to_date_string), - ("toLocaleDateString", to_date_string), -]; - struct DateAdjustment<'builder, 'activation_a: 'builder, 'gc: 'activation_a, T: TimeZone + 'builder> { activation: &'builder mut Activation<'activation_a, 'gc>, @@ -227,88 +179,67 @@ impl<'builder, 'activation_a, 'gc, T: TimeZone> DateAdjustment<'builder, 'activa } } +fn get_arguments_array<'gc>(args: &[Value<'gc>]) -> Vec> { + let object = args[0].as_object().unwrap(); + let array_storage = object.as_array_storage().unwrap(); + array_storage + .iter() + .map(|v| v.unwrap()) // Arguments should be array with no holes + .collect() +} + /// Implements `Date`'s instance constructor. -pub fn instance_init<'gc>( +pub fn init<'gc>( activation: &mut Activation<'_, 'gc>, this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - activation.super_init(this, &[])?; - if let Some(date) = this.as_date_object() { - let timestamp = args.get(0).unwrap_or(&Value::Undefined); - if timestamp != &Value::Undefined { - if args.len() > 1 { - let timezone = get_timezone(); + let this = this.as_date_object().unwrap(); + let arguments = get_arguments_array(args); - // We need a starting value to adjust from. - date.set_date_time(Some( - timezone - .with_ymd_and_hms(0, 1, 1, 0, 0, 0) - .single() - .expect("Found ambiguous epoch time when constructing Date") - .into(), - )); + let timestamp = arguments.get(0).unwrap_or(&Value::Undefined); + if timestamp != &Value::Undefined { + if arguments.len() > 1 { + let timezone = get_timezone(); - DateAdjustment::new(activation, &timezone) - .year(args.get(0))? - .month(args.get(1))? - .day(args.get(2))? - .hour(args.get(3))? - .minute(args.get(4))? - .second(args.get(5))? - .millisecond(args.get(6))? - .map_year(|year| if year < 100.0 { year + 1900.0 } else { year }) - .apply(date); + // We need a starting value to adjust from. + this.set_date_time(Some( + timezone + .with_ymd_and_hms(0, 1, 1, 0, 0, 0) + .single() + .expect("Found ambiguous epoch time when constructing Date") + .into(), + )); + + DateAdjustment::new(activation, &timezone) + .year(arguments.get(0))? + .month(arguments.get(1))? + .day(arguments.get(2))? + .hour(arguments.get(3))? + .minute(arguments.get(4))? + .second(arguments.get(5))? + .millisecond(arguments.get(6))? + .map_year(|year| if year < 100.0 { year + 1900.0 } else { year }) + .apply(this); + } else { + let timestamp = if let Value::String(date_str) = timestamp { + parse_full_date(activation, *date_str).unwrap_or(f64::NAN) } else { - let timestamp = if let Value::String(date_str) = timestamp { - parse_full_date(activation, *date_str).unwrap_or(f64::NAN) - } else { - timestamp.coerce_to_number(activation)? - }; - if timestamp.is_finite() { - if let LocalResult::Single(time) = Utc.timestamp_millis_opt(timestamp as i64) { - date.set_date_time(Some(time)) - } + timestamp.coerce_to_number(activation)? + }; + if timestamp.is_finite() { + if let LocalResult::Single(time) = Utc.timestamp_millis_opt(timestamp as i64) { + this.set_date_time(Some(time)) } } - } else { - date.set_date_time(Some(get_current_date_time())) } + } else { + this.set_date_time(Some(get_current_date_time())) } Ok(Value::Undefined) } -/// Implements `Date`'s class constructor. -pub fn class_init<'gc>( - activation: &mut Activation<'_, 'gc>, - this: Object<'gc>, - _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let scope = activation.create_scopechain(); - let gc_context = activation.context.gc_context; - let this_class = this.as_class_object().unwrap(); - let date_proto = this_class.prototype(); - - for (name, method) in PUBLIC_INSTANCE_AND_PROTO_METHODS { - date_proto.set_string_property_local( - *name, - FunctionObject::from_method( - activation, - Method::from_builtin(*method, name, gc_context), - scope, - None, - None, - None, - ) - .into(), - activation, - )?; - date_proto.set_local_property_is_enumerable(gc_context, (*name).into(), false); - } - Ok(Value::Undefined) -} - pub fn call_handler<'gc>( activation: &mut Activation<'_, 'gc>, _this: Object<'gc>, @@ -322,606 +253,567 @@ pub fn call_handler<'gc>( .into()) } -/// Implements `time` property's getter, and the `getTime` method. This will also be used for `valueOf`. -pub fn time<'gc>( +/// Implements `getTime` method. +pub fn get_time<'gc>( activation: &mut Activation<'_, 'gc>, this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - return this.value_of(activation.context.gc_context); - } - - Ok(Value::Undefined) + let this = this.as_date_object().unwrap(); + this.value_of(activation.context.gc_context) } -/// Implements `time` property's setter, and the `setTime` method. +/// Implements `setTime` method. pub fn set_time<'gc>( activation: &mut Activation<'_, 'gc>, this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - let new_time = args - .get(0) - .unwrap_or(&Value::Undefined) - .coerce_to_number(activation)?; - if new_time.is_finite() { - let time = Utc - .timestamp_millis_opt(new_time as i64) + let this = this.as_date_object().unwrap(); + + let new_time = args + .get(0) + .unwrap_or(&Value::Undefined) + .coerce_to_number(activation)?; + if new_time.is_finite() { + let time = Utc + .timestamp_millis_opt(new_time as i64) + .single() + .expect("Found ambiguous timestamp for current time zone"); + this.set_date_time(Some(time)); + Ok((time.timestamp_millis() as f64).into()) + } else { + this.set_date_time(None); + Ok(f64::NAN.into()) + } +} + +/// Implements the `getMilliseconds` method. +pub fn get_milliseconds<'gc>( + _activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let this = this.as_date_object().unwrap(); + + if let Some(date) = this + .date_time() + .map(|date| date.with_timezone(&get_timezone())) + { + Ok((date.timestamp_subsec_millis() as f64).into()) + } else { + Ok(f64::NAN.into()) + } +} + +/// Implements the `setMilliseconds` method. +pub fn _set_milliseconds<'gc>( + activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let this = this.as_date_object().unwrap(); + let args = get_arguments_array(args); + + let timestamp = DateAdjustment::new(activation, &get_timezone()) + .millisecond(args.get(0))? + .apply(this); + Ok(timestamp.into()) +} + +/// Implements the `getSeconds` method. +pub fn get_seconds<'gc>( + _activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let this = this.as_date_object().unwrap(); + + if let Some(date) = this + .date_time() + .map(|date| date.with_timezone(&get_timezone())) + { + Ok((date.second() as f64).into()) + } else { + Ok(f64::NAN.into()) + } +} + +/// Implements `setSeconds` method. +pub fn _set_seconds<'gc>( + activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let this = this.as_date_object().unwrap(); + let args = get_arguments_array(args); + + let timestamp = DateAdjustment::new(activation, &get_timezone()) + .second(args.get(0))? + .millisecond(args.get(1))? + .apply(this); + Ok(timestamp.into()) +} + +/// Implements `getMinutes` method. +pub fn get_minutes<'gc>( + _activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let this = this.as_date_object().unwrap(); + + if let Some(date) = this + .date_time() + .map(|date| date.with_timezone(&get_timezone())) + { + Ok((date.minute() as f64).into()) + } else { + Ok(f64::NAN.into()) + } +} + +/// Implements the `setMinutes` method. +pub fn _set_minutes<'gc>( + activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let this = this.as_date_object().unwrap(); + let args = get_arguments_array(args); + + let timestamp = DateAdjustment::new(activation, &get_timezone()) + .minute(args.get(0))? + .second(args.get(1))? + .millisecond(args.get(2))? + .apply(this); + Ok(timestamp.into()) +} + +/// Implements the `getHours` method. +pub fn get_hours<'gc>( + _activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let this = this.as_date_object().unwrap(); + + if let Some(date) = this + .date_time() + .map(|date| date.with_timezone(&get_timezone())) + { + Ok((date.hour() as f64).into()) + } else { + Ok(f64::NAN.into()) + } +} + +/// Implements `setHours` method. +pub fn _set_hours<'gc>( + activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let this = this.as_date_object().unwrap(); + let args = get_arguments_array(args); + + let timestamp = DateAdjustment::new(activation, &get_timezone()) + .hour(args.get(0))? + .minute(args.get(1))? + .second(args.get(2))? + .millisecond(args.get(3))? + .apply(this); + Ok(timestamp.into()) +} + +/// Implements `getDate` method. +pub fn get_date<'gc>( + _activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let this = this.as_date_object().unwrap(); + + if let Some(date) = this + .date_time() + .map(|date| date.with_timezone(&get_timezone())) + { + Ok((date.day() as f64).into()) + } else { + Ok(f64::NAN.into()) + } +} + +/// Implements `setDate` method. +pub fn _set_date<'gc>( + activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let this = this.as_date_object().unwrap(); + let args = get_arguments_array(args); + + let timestamp = DateAdjustment::new(activation, &get_timezone()) + .day(args.get(0))? + .apply(this); + Ok(timestamp.into()) +} + +/// Implements the `getMonth` method. +pub fn get_month<'gc>( + _activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let this = this.as_date_object().unwrap(); + + if let Some(date) = this + .date_time() + .map(|date| date.with_timezone(&get_timezone())) + { + Ok((date.month0() as f64).into()) + } else { + Ok(f64::NAN.into()) + } +} + +/// Implements the `setMonth` method. +pub fn _set_month<'gc>( + activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let this = this.as_date_object().unwrap(); + let args = get_arguments_array(args); + + let timestamp = DateAdjustment::new(activation, &get_timezone()) + .month(args.get(0))? + .day(args.get(1))? + .apply(this); + Ok(timestamp.into()) +} + +/// Implements the `getFullYear` method. +pub fn get_full_year<'gc>( + _activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let this = this.as_date_object().unwrap(); + + if let Some(date) = this + .date_time() + .map(|date| date.with_timezone(&get_timezone())) + { + Ok((date.year() as f64).into()) + } else { + Ok(f64::NAN.into()) + } +} + +/// Implements the `setFullYear` method. +pub fn _set_full_year<'gc>( + activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let this = this.as_date_object().unwrap(); + let args = get_arguments_array(args); + + let timezone = get_timezone(); + if this.date_time().is_none() { + this.set_date_time(Some( + timezone + .with_ymd_and_hms(0, 1, 1, 0, 0, 0) .single() - .expect("Found ambiguous timestamp for current time zone"); - this.set_date_time(Some(time)); - return Ok((time.timestamp_millis() as f64).into()); - } else { - this.set_date_time(None); - return Ok(f64::NAN.into()); - } + .expect("Found ambiguous epoch time when constructing Date") + .into(), + )); } - - Ok(Value::Undefined) + let timestamp = DateAdjustment::new(activation, &timezone) + .year(args.get(0))? + .month(args.get(1))? + .day(args.get(2))? + .apply(this); + Ok(timestamp.into()) } -/// Implements `milliseconds` property's getter, and the `getMilliseconds` method. -pub fn milliseconds<'gc>( +/// Implements the `getDay` method. +pub fn get_day<'gc>( _activation: &mut Activation<'_, 'gc>, this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - if let Some(date) = this - .date_time() - .map(|date| date.with_timezone(&get_timezone())) - { - return Ok((date.timestamp_subsec_millis() as f64).into()); - } else { - return Ok(f64::NAN.into()); - } - } + let this = this.as_date_object().unwrap(); - Ok(Value::Undefined) + if let Some(date) = this + .date_time() + .map(|date| date.with_timezone(&get_timezone())) + { + Ok((date.weekday().num_days_from_sunday() as f64).into()) + } else { + Ok(f64::NAN.into()) + } } -/// Implements `milliseconds` property's setter, and the `setMilliseconds` method. -pub fn set_milliseconds<'gc>( +/// Implements the `getUTCMilliseconds` method. +pub fn get_utc_milliseconds<'gc>( + _activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let this = this.as_date_object().unwrap(); + + if let Some(date) = this.date_time() { + Ok((date.timestamp_subsec_millis() as f64).into()) + } else { + Ok(f64::NAN.into()) + } +} + +/// Implements the `setUTCMilliseconds` method. +pub fn _set_utc_milliseconds<'gc>( activation: &mut Activation<'_, 'gc>, this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - let timestamp = DateAdjustment::new(activation, &get_timezone()) - .millisecond(args.get(0))? - .apply(this); - return Ok(timestamp.into()); - } - Ok(Value::Undefined) + let this = this.as_date_object().unwrap(); + let args = get_arguments_array(args); + + let timestamp = DateAdjustment::new(activation, &Utc) + .millisecond(args.get(0))? + .apply(this); + Ok(timestamp.into()) } -/// Implements `seconds` property's getter, and the `getSeconds` method. -pub fn seconds<'gc>( +/// Implements the `getUTCSeconds` method. +pub fn get_utc_seconds<'gc>( _activation: &mut Activation<'_, 'gc>, this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - if let Some(date) = this - .date_time() - .map(|date| date.with_timezone(&get_timezone())) - { - return Ok((date.second() as f64).into()); - } else { - return Ok(f64::NAN.into()); - } - } + let this = this.as_date_object().unwrap(); - Ok(Value::Undefined) + if let Some(date) = this.date_time() { + Ok((date.second() as f64).into()) + } else { + Ok(f64::NAN.into()) + } } -/// Implements `seconds` property's setter, and the `setSeconds` method. -pub fn set_seconds<'gc>( +/// Implements the `setUTCSeconds` method. +pub fn _set_utc_seconds<'gc>( activation: &mut Activation<'_, 'gc>, this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - let timestamp = DateAdjustment::new(activation, &get_timezone()) - .second(args.get(0))? - .millisecond(args.get(1))? - .apply(this); - return Ok(timestamp.into()); - } - Ok(Value::Undefined) + let this = this.as_date_object().unwrap(); + let args = get_arguments_array(args); + + let timestamp = DateAdjustment::new(activation, &Utc) + .second(args.get(0))? + .millisecond(args.get(1))? + .apply(this); + Ok(timestamp.into()) } -/// Implements `minutes` property's getter, and the `getMinutes` method. -pub fn minutes<'gc>( +/// Implements the `getUTCMinutes` method. +pub fn get_utc_minutes<'gc>( _activation: &mut Activation<'_, 'gc>, this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - if let Some(date) = this - .date_time() - .map(|date| date.with_timezone(&get_timezone())) - { - return Ok((date.minute() as f64).into()); - } else { - return Ok(f64::NAN.into()); - } - } + let this = this.as_date_object().unwrap(); - Ok(Value::Undefined) + if let Some(date) = this.date_time() { + Ok((date.minute() as f64).into()) + } else { + Ok(f64::NAN.into()) + } } -/// Implements `minutes` property's setter, and the `setMinutes` method. -pub fn set_minutes<'gc>( +/// Implements the `setUTCMinutes` method. +pub fn _set_utc_minutes<'gc>( activation: &mut Activation<'_, 'gc>, this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - let timestamp = DateAdjustment::new(activation, &get_timezone()) - .minute(args.get(0))? - .second(args.get(1))? - .millisecond(args.get(2))? - .apply(this); - return Ok(timestamp.into()); - } - Ok(Value::Undefined) + let this = this.as_date_object().unwrap(); + let args = get_arguments_array(args); + + let timestamp = DateAdjustment::new(activation, &Utc) + .minute(args.get(0))? + .second(args.get(1))? + .millisecond(args.get(2))? + .apply(this); + Ok(timestamp.into()) } -/// Implements `hour` property's getter, and the `getHours` method. -pub fn hours<'gc>( +/// Implements the `getUTCHours` method. +pub fn get_utc_hours<'gc>( _activation: &mut Activation<'_, 'gc>, this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - if let Some(date) = this - .date_time() - .map(|date| date.with_timezone(&get_timezone())) - { - return Ok((date.hour() as f64).into()); - } else { - return Ok(f64::NAN.into()); - } - } + let this = this.as_date_object().unwrap(); - Ok(Value::Undefined) + if let Some(date) = this.date_time() { + Ok((date.hour() as f64).into()) + } else { + Ok(f64::NAN.into()) + } } -/// Implements `hours` property's setter, and the `setHours` method. -pub fn set_hours<'gc>( +/// Implements the `setUTCHours` method. +pub fn _set_utc_hours<'gc>( activation: &mut Activation<'_, 'gc>, this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - let timestamp = DateAdjustment::new(activation, &get_timezone()) - .hour(args.get(0))? - .minute(args.get(1))? - .second(args.get(2))? - .millisecond(args.get(3))? - .apply(this); - return Ok(timestamp.into()); - } - Ok(Value::Undefined) + let this = this.as_date_object().unwrap(); + let args = get_arguments_array(args); + + let timestamp = DateAdjustment::new(activation, &Utc) + .hour(args.get(0))? + .minute(args.get(1))? + .second(args.get(2))? + .millisecond(args.get(3))? + .apply(this); + Ok(timestamp.into()) } -/// Implements `date` property's getter, and the `getDate` method. -pub fn date<'gc>( +/// Implements the `getUTCDate` method. +pub fn get_utc_date<'gc>( _activation: &mut Activation<'_, 'gc>, this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - if let Some(date) = this - .date_time() - .map(|date| date.with_timezone(&get_timezone())) - { - return Ok((date.day() as f64).into()); - } else { - return Ok(f64::NAN.into()); - } - } + let this = this.as_date_object().unwrap(); - Ok(Value::Undefined) + if let Some(date) = this.date_time() { + Ok((date.day() as f64).into()) + } else { + Ok(f64::NAN.into()) + } } -/// Implements `date` property's setter, and the `setDate` method. -pub fn set_date<'gc>( +/// Implements the `setUTCDate` method. +pub fn _set_utc_date<'gc>( activation: &mut Activation<'_, 'gc>, this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - let timestamp = DateAdjustment::new(activation, &get_timezone()) - .day(args.get(0))? - .apply(this); - return Ok(timestamp.into()); - } - Ok(Value::Undefined) + let this = this.as_date_object().unwrap(); + let args = get_arguments_array(args); + + let timestamp = DateAdjustment::new(activation, &Utc) + .day(args.get(0))? + .apply(this); + Ok(timestamp.into()) } -/// Implements `month` property's getter, and the `getMonth` method. -pub fn month<'gc>( +/// Implements the `getUTCMonth` method. +pub fn get_utc_month<'gc>( _activation: &mut Activation<'_, 'gc>, this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - if let Some(date) = this - .date_time() - .map(|date| date.with_timezone(&get_timezone())) - { - return Ok((date.month0() as f64).into()); - } else { - return Ok(f64::NAN.into()); - } - } + let this = this.as_date_object().unwrap(); - Ok(Value::Undefined) + if let Some(date) = this.date_time() { + Ok((date.month0() as f64).into()) + } else { + Ok(f64::NAN.into()) + } } -/// Implements `month` property's setter, and the `setMonth` method. -pub fn set_month<'gc>( +/// Implements the `setUTCMonth` method. +pub fn _set_utc_month<'gc>( activation: &mut Activation<'_, 'gc>, this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - let timestamp = DateAdjustment::new(activation, &get_timezone()) - .month(args.get(0))? - .day(args.get(1))? - .apply(this); - return Ok(timestamp.into()); - } - Ok(Value::Undefined) + let this = this.as_date_object().unwrap(); + let args = get_arguments_array(args); + + let timestamp = DateAdjustment::new(activation, &Utc) + .month(args.get(0))? + .day(args.get(1))? + .apply(this); + Ok(timestamp.into()) } -/// Implements `fullYear` property's getter, and the `getFullYear` method. -pub fn full_year<'gc>( +/// Implements the `getUTCFullYear` method. +pub fn get_utc_full_year<'gc>( _activation: &mut Activation<'_, 'gc>, this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - if let Some(date) = this - .date_time() - .map(|date| date.with_timezone(&get_timezone())) - { - return Ok((date.year() as f64).into()); - } else { - return Ok(f64::NAN.into()); - } - } + let this = this.as_date_object().unwrap(); - Ok(Value::Undefined) + if let Some(date) = this.date_time() { + Ok((date.year() as f64).into()) + } else { + Ok(f64::NAN.into()) + } } -/// Implements `fullYear` property's setter, and the `setFullYear` method. -pub fn set_full_year<'gc>( +/// Implements the `setUTCFullYear` method. +pub fn _set_utc_full_year<'gc>( activation: &mut Activation<'_, 'gc>, this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - let timezone = get_timezone(); - if this.date_time().is_none() { - this.set_date_time(Some( - timezone - .with_ymd_and_hms(0, 1, 1, 0, 0, 0) - .single() - .expect("Found ambiguous epoch time when constructing Date") - .into(), - )); - } - let timestamp = DateAdjustment::new(activation, &timezone) - .year(args.get(0))? - .month(args.get(1))? - .day(args.get(2))? - .apply(this); - return Ok(timestamp.into()); + let this = this.as_date_object().unwrap(); + let args = get_arguments_array(args); + + if this.date_time().is_none() { + this.set_date_time(Some( + Utc.with_ymd_and_hms(0, 1, 1, 0, 0, 0) + .single() + .expect("Found ambiguous epoch time when constructing Date"), + )); } - Ok(Value::Undefined) + let timestamp = DateAdjustment::new(activation, &Utc) + .year(args.get(0))? + .month(args.get(1))? + .day(args.get(2))? + .apply(this); + Ok(timestamp.into()) } -/// Implements `day` property's getter, and the `getDay` method. -pub fn day<'gc>( +/// Implements the `getUTCDay` method. +pub fn get_utc_day<'gc>( _activation: &mut Activation<'_, 'gc>, this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - if let Some(date) = this - .date_time() - .map(|date| date.with_timezone(&get_timezone())) - { - return Ok((date.weekday().num_days_from_sunday() as f64).into()); - } else { - return Ok(f64::NAN.into()); - } - } + let this = this.as_date_object().unwrap(); - Ok(Value::Undefined) + if let Some(date) = this.date_time() { + Ok((date.weekday().num_days_from_sunday() as f64).into()) + } else { + Ok(f64::NAN.into()) + } } -/// Implements `millisecondsUTC` property's getter, and the `getUTCMilliseconds` method. -pub fn milliseconds_utc<'gc>( +/// Implements the `getTimezoneOffset` method. +pub fn get_timezone_offset<'gc>( _activation: &mut Activation<'_, 'gc>, this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - if let Some(date) = this.date_time() { - return Ok((date.timestamp_subsec_millis() as f64).into()); - } else { - return Ok(f64::NAN.into()); - } + let this = this.as_date_object().unwrap(); + + if let Some(date) = this + .date_time() + .map(|date| date.with_timezone(&get_timezone())) + { + let offset = date.offset().utc_minus_local() as f64; + Ok((offset / 60.0).into()) + } else { + Ok(f64::NAN.into()) } - - Ok(Value::Undefined) -} - -/// Implements `millisecondsUTC` property's setter, and the `setUTCMilliseconds` method. -pub fn set_milliseconds_utc<'gc>( - activation: &mut Activation<'_, 'gc>, - this: Object<'gc>, - args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - let timestamp = DateAdjustment::new(activation, &Utc) - .millisecond(args.get(0))? - .apply(this); - return Ok(timestamp.into()); - } - Ok(Value::Undefined) -} - -/// Implements `secondsUTC` property's getter, and the `getUTCSeconds` method. -pub fn seconds_utc<'gc>( - _activation: &mut Activation<'_, 'gc>, - this: Object<'gc>, - _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - if let Some(date) = this.date_time() { - return Ok((date.second() as f64).into()); - } else { - return Ok(f64::NAN.into()); - } - } - - Ok(Value::Undefined) -} - -/// Implements `secondsUTC` property's setter, and the `setUTCSeconds` method. -pub fn set_seconds_utc<'gc>( - activation: &mut Activation<'_, 'gc>, - this: Object<'gc>, - args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - let timestamp = DateAdjustment::new(activation, &Utc) - .second(args.get(0))? - .millisecond(args.get(1))? - .apply(this); - return Ok(timestamp.into()); - } - Ok(Value::Undefined) -} - -/// Implements `minutesUTC` property's getter, and the `getUTCMinutes` method. -pub fn minutes_utc<'gc>( - _activation: &mut Activation<'_, 'gc>, - this: Object<'gc>, - _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - if let Some(date) = this.date_time() { - return Ok((date.minute() as f64).into()); - } else { - return Ok(f64::NAN.into()); - } - } - - Ok(Value::Undefined) -} - -/// Implements `minutesUTC` property's setter, and the `setUTCMinutes` method. -pub fn set_minutes_utc<'gc>( - activation: &mut Activation<'_, 'gc>, - this: Object<'gc>, - args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - let timestamp = DateAdjustment::new(activation, &Utc) - .minute(args.get(0))? - .second(args.get(1))? - .millisecond(args.get(2))? - .apply(this); - return Ok(timestamp.into()); - } - Ok(Value::Undefined) -} - -/// Implements `hourUTC` property's getter, and the `getUTCHours` method. -pub fn hours_utc<'gc>( - _activation: &mut Activation<'_, 'gc>, - this: Object<'gc>, - _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - if let Some(date) = this.date_time() { - return Ok((date.hour() as f64).into()); - } else { - return Ok(f64::NAN.into()); - } - } - - Ok(Value::Undefined) -} - -/// Implements `hoursUTC` property's setter, and the `setUTCHours` method. -pub fn set_hours_utc<'gc>( - activation: &mut Activation<'_, 'gc>, - this: Object<'gc>, - args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - let timestamp = DateAdjustment::new(activation, &Utc) - .hour(args.get(0))? - .minute(args.get(1))? - .second(args.get(2))? - .millisecond(args.get(3))? - .apply(this); - return Ok(timestamp.into()); - } - Ok(Value::Undefined) -} - -/// Implements `dateUTC` property's getter, and the `getUTCDate` method. -pub fn date_utc<'gc>( - _activation: &mut Activation<'_, 'gc>, - this: Object<'gc>, - _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - if let Some(date) = this.date_time() { - return Ok((date.day() as f64).into()); - } else { - return Ok(f64::NAN.into()); - } - } - - Ok(Value::Undefined) -} - -/// Implements `dateUTC` property's setter, and the `setUTCDate` method. -pub fn set_date_utc<'gc>( - activation: &mut Activation<'_, 'gc>, - this: Object<'gc>, - args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - let timestamp = DateAdjustment::new(activation, &Utc) - .day(args.get(0))? - .apply(this); - return Ok(timestamp.into()); - } - Ok(Value::Undefined) -} - -/// Implements `monthUTC` property's getter, and the `getUTCMonth` method. -pub fn month_utc<'gc>( - _activation: &mut Activation<'_, 'gc>, - this: Object<'gc>, - _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - if let Some(date) = this.date_time() { - return Ok((date.month0() as f64).into()); - } else { - return Ok(f64::NAN.into()); - } - } - - Ok(Value::Undefined) -} - -/// Implements `monthUTC` property's setter, and the `setUTCMonth` method. -pub fn set_month_utc<'gc>( - activation: &mut Activation<'_, 'gc>, - this: Object<'gc>, - args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - let timestamp = DateAdjustment::new(activation, &Utc) - .month(args.get(0))? - .day(args.get(1))? - .apply(this); - return Ok(timestamp.into()); - } - Ok(Value::Undefined) -} - -/// Implements `fullYearUTC` property's getter, and the `getUTCFullYear` method. -pub fn full_year_utc<'gc>( - _activation: &mut Activation<'_, 'gc>, - this: Object<'gc>, - _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - if let Some(date) = this.date_time() { - return Ok((date.year() as f64).into()); - } else { - return Ok(f64::NAN.into()); - } - } - - Ok(Value::Undefined) -} - -/// Implements `fullYearUTC` property's setter, and the `setUTCFullYear` method. -pub fn set_full_year_utc<'gc>( - activation: &mut Activation<'_, 'gc>, - this: Object<'gc>, - args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - if this.date_time().is_none() { - this.set_date_time(Some( - Utc.with_ymd_and_hms(0, 1, 1, 0, 0, 0) - .single() - .expect("Found ambiguous epoch time when constructing Date"), - )); - } - let timestamp = DateAdjustment::new(activation, &Utc) - .year(args.get(0))? - .month(args.get(1))? - .day(args.get(2))? - .apply(this); - return Ok(timestamp.into()); - } - Ok(Value::Undefined) -} - -/// Implements `dayUTC` property's getter, and the `getUTCDay` method. -pub fn day_utc<'gc>( - _activation: &mut Activation<'_, 'gc>, - this: Object<'gc>, - _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - if let Some(date) = this.date_time() { - return Ok((date.weekday().num_days_from_sunday() as f64).into()); - } else { - return Ok(f64::NAN.into()); - } - } - - Ok(Value::Undefined) -} - -/// Implements `timezoneOffset` property's getter, and the `getTimezoneOffset` method. -pub fn timezone_offset<'gc>( - _activation: &mut Activation<'_, 'gc>, - this: Object<'gc>, - _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - if let Some(date) = this - .date_time() - .map(|date| date.with_timezone(&get_timezone())) - { - let offset = date.offset().utc_minus_local() as f64; - return Ok((offset / 60.0).into()); - } else { - return Ok(f64::NAN.into()); - } - } - - Ok(Value::Undefined) } /// Implements the `UTC` class method. @@ -959,22 +851,20 @@ pub fn to_string<'gc>( this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - if let Some(date) = this - .date_time() - .map(|date| date.with_timezone(&get_timezone())) - { - return Ok(AvmString::new_utf8( - activation.context.gc_context, - date.format("%a %b %-d %T GMT%z %-Y").to_string(), - ) - .into()); - } else { - return Ok("Invalid Date".into()); - } - } + let this = this.as_date_object().unwrap(); - Ok(Value::Undefined) + if let Some(date) = this + .date_time() + .map(|date| date.with_timezone(&get_timezone())) + { + Ok(AvmString::new_utf8( + activation.context.gc_context, + date.format("%a %b %-d %T GMT%z %-Y").to_string(), + ) + .into()) + } else { + Ok("Invalid Date".into()) + } } /// Implements the `toUTCString` method. @@ -983,19 +873,17 @@ pub fn to_utc_string<'gc>( this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - if let Some(date) = this.date_time() { - return Ok(AvmString::new_utf8( - activation.context.gc_context, - date.format("%a %b %-d %T %-Y UTC").to_string(), - ) - .into()); - } else { - return Ok("Invalid Date".into()); - } - } + let this = this.as_date_object().unwrap(); - Ok(Value::Undefined) + if let Some(date) = this.date_time() { + Ok(AvmString::new_utf8( + activation.context.gc_context, + date.format("%a %b %-d %T %-Y UTC").to_string(), + ) + .into()) + } else { + Ok("Invalid Date".into()) + } } /// Implements the `toLocaleString` method. @@ -1004,22 +892,20 @@ pub fn to_locale_string<'gc>( this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - if let Some(date) = this - .date_time() - .map(|date| date.with_timezone(&get_timezone())) - { - return Ok(AvmString::new_utf8( - activation.context.gc_context, - date.format("%a %b %-d %-Y %T %p").to_string(), - ) - .into()); - } else { - return Ok("Invalid Date".into()); - } - } + let this = this.as_date_object().unwrap(); - Ok(Value::Undefined) + if let Some(date) = this + .date_time() + .map(|date| date.with_timezone(&get_timezone())) + { + Ok(AvmString::new_utf8( + activation.context.gc_context, + date.format("%a %b %-d %-Y %T %p").to_string(), + ) + .into()) + } else { + Ok("Invalid Date".into()) + } } /// Implements the `toTimeString` method. @@ -1028,22 +914,20 @@ pub fn to_time_string<'gc>( this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - if let Some(date) = this - .date_time() - .map(|date| date.with_timezone(&get_timezone())) - { - return Ok(AvmString::new_utf8( - activation.context.gc_context, - date.format("%T GMT%z").to_string(), - ) - .into()); - } else { - return Ok("Invalid Date".into()); - } - } + let this = this.as_date_object().unwrap(); - Ok(Value::Undefined) + if let Some(date) = this + .date_time() + .map(|date| date.with_timezone(&get_timezone())) + { + Ok(AvmString::new_utf8( + activation.context.gc_context, + date.format("%T GMT%z").to_string(), + ) + .into()) + } else { + Ok("Invalid Date".into()) + } } /// Implements the `toLocaleTimeString` method. @@ -1052,22 +936,20 @@ pub fn to_locale_time_string<'gc>( this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - if let Some(date) = this - .date_time() - .map(|date| date.with_timezone(&get_timezone())) - { - return Ok(AvmString::new_utf8( - activation.context.gc_context, - date.format("%T %p").to_string(), - ) - .into()); - } else { - return Ok("Invalid Date".into()); - } - } + let this = this.as_date_object().unwrap(); - Ok(Value::Undefined) + if let Some(date) = this + .date_time() + .map(|date| date.with_timezone(&get_timezone())) + { + Ok(AvmString::new_utf8( + activation.context.gc_context, + date.format("%T %p").to_string(), + ) + .into()) + } else { + Ok("Invalid Date".into()) + } } /// Implements the `toDateString` & `toLocaleDateString` method. @@ -1076,22 +958,20 @@ pub fn to_date_string<'gc>( this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - if let Some(this) = this.as_date_object() { - if let Some(date) = this - .date_time() - .map(|date| date.with_timezone(&get_timezone())) - { - return Ok(AvmString::new_utf8( - activation.context.gc_context, - date.format("%a %b %-d %-Y").to_string(), - ) - .into()); - } else { - return Ok("Invalid Date".into()); - } - } + let this = this.as_date_object().unwrap(); - Ok(Value::Undefined) + if let Some(date) = this + .date_time() + .map(|date| date.with_timezone(&get_timezone())) + { + Ok(AvmString::new_utf8( + activation.context.gc_context, + date.format("%a %b %-d %-Y").to_string(), + ) + .into()) + } else { + Ok("Invalid Date".into()) + } } /// Parse a date, in any of the three formats: YYYY/MM/DD, MM/DD/YYYY, Mon/DD/YYYY. @@ -1320,91 +1200,3 @@ pub fn parse<'gc>( .unwrap_or(f64::NAN) .into()) } - -/// Construct `Date`'s class. -pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { - let mc = activation.context.gc_context; - let class = Class::new( - QName::new(activation.avm2().public_namespace_base_version, "Date"), - Some(activation.avm2().class_defs().object), - Method::from_builtin(instance_init, "", mc), - Method::from_builtin(class_init, "", mc), - activation.avm2().class_defs().class, - mc, - ); - - class.set_instance_allocator(mc, date_allocator); - class.set_call_handler( - mc, - Method::from_builtin(call_handler, "", mc), - ); - - const PUBLIC_INSTANCE_PROPERTIES: &[( - &str, - Option, - Option, - )] = &[ - ("time", Some(time), Some(set_time)), - ("milliseconds", Some(milliseconds), Some(set_milliseconds)), - ("seconds", Some(seconds), Some(set_seconds)), - ("minutes", Some(minutes), Some(set_minutes)), - ("hours", Some(hours), Some(set_hours)), - ("date", Some(date), Some(set_date)), - ("month", Some(month), Some(set_month)), - ("fullYear", Some(full_year), Some(set_full_year)), - ( - "millisecondsUTC", - Some(milliseconds_utc), - Some(set_milliseconds_utc), - ), - ("day", Some(day), None), - ("secondsUTC", Some(seconds_utc), Some(set_seconds_utc)), - ("minutesUTC", Some(minutes_utc), Some(set_minutes_utc)), - ("hoursUTC", Some(hours_utc), Some(set_hours_utc)), - ("dateUTC", Some(date_utc), Some(set_date_utc)), - ("monthUTC", Some(month_utc), Some(set_month_utc)), - ("fullYearUTC", Some(full_year_utc), Some(set_full_year_utc)), - ("dayUTC", Some(day_utc), None), - ("timezoneOffset", Some(timezone_offset), None), - ]; - class.define_builtin_instance_properties( - mc, - activation.avm2().public_namespace_base_version, - PUBLIC_INSTANCE_PROPERTIES, - ); - class.define_builtin_instance_methods( - mc, - activation.avm2().as3_namespace, - PUBLIC_INSTANCE_AND_PROTO_METHODS, - ); - - const PUBLIC_CLASS_METHODS: &[(&str, NativeMethodImpl)] = &[("UTC", utc), ("parse", parse)]; - - class.define_builtin_class_methods( - mc, - activation.avm2().public_namespace_base_version, - PUBLIC_CLASS_METHODS, - ); - - const CLASS_CONSTANTS_INT: &[(&str, i32)] = &[("length", 7)]; - - class.define_constant_int_class_traits( - activation.avm2().public_namespace_base_version, - CLASS_CONSTANTS_INT, - activation, - ); - - class.mark_traits_loaded(activation.context.gc_context); - class - .init_vtable(activation.context) - .expect("Native class's vtable should initialize"); - - let c_class = class.c_class().expect("Class::new returns an i_class"); - - c_class.mark_traits_loaded(activation.context.gc_context); - c_class - .init_vtable(activation.context) - .expect("Native class's vtable should initialize"); - - class -} diff --git a/core/src/avm2/globals/globals.as b/core/src/avm2/globals/globals.as index 281ee0db7..bb49b58f0 100644 --- a/core/src/avm2/globals/globals.as +++ b/core/src/avm2/globals/globals.as @@ -27,6 +27,8 @@ include "UninitializedError.as" include "URIError.as" include "VerifyError.as" +include "Date.as" + include "avmplus.as" include "flash/accessibility/Accessibility.as" diff --git a/core/src/avm2/globals/stubs.as b/core/src/avm2/globals/stubs.as index e33fa893f..ba8aec699 100644 --- a/core/src/avm2/globals/stubs.as +++ b/core/src/avm2/globals/stubs.as @@ -8,7 +8,6 @@ include "Object.as" include "Array.as" include "Boolean.as" include "Class.as" -include "Date.as" include "Function.as" include "Number.as"