#[cfg(test)]
mod tests;
use super::JsArgs;
use crate::{
builtins::BuiltIn,
context::intrinsics::StandardConstructors,
object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
},
symbol::WellKnownSymbols,
value::{JsValue, PreferredType},
Context, JsResult, JsString,
};
use boa_profiler::Profiler;
use chrono::{prelude::*, Duration, LocalResult};
use std::fmt::Display;
use tap::{Conv, Pipe};
/// The number of nanoseconds in a millisecond.
const NANOS_PER_MS: i64 = 1_000_000;
/// The number of milliseconds in an hour.
const MILLIS_PER_HOUR: i64 = 3_600_000;
/// The number of milliseconds in a minute.
const MILLIS_PER_MINUTE: i64 = 60_000;
/// The number of milliseconds in a second.
const MILLIS_PER_SECOND: i64 = 1000;
#[inline]
fn is_zero_or_normal_opt(value: Option<f64>) -> bool {
value.map_or(true, |value| value == 0f64 || value.is_normal())
}
macro_rules! check_normal_opt {
($($v:expr),+) => {
$(is_zero_or_normal_opt($v.into()) &&)+ true
};
}
#[inline]
fn ignore_ambiguity<T>(result: LocalResult<T>) -> Option<T> {
match result {
LocalResult::Ambiguous(v, _) | LocalResult::Single(v) => Some(v),
LocalResult::None => None,
}
}
macro_rules! getter_method {
($name:ident) => {{
fn get_value(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
Ok(JsValue::new(this_time_value(this, context)?.$name()))
}
get_value
}};
(Self::$name:ident) => {{
fn get_value(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Ok(JsValue::new(Date::$name()))
}
get_value
}};
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Date(Option<NaiveDateTime>);
impl Display for Date {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.to_local() {
Some(v) => write!(f, "{}", v.format("%a %b %d %Y %H:%M:%S GMT%:z")),
_ => write!(f, "Invalid Date"),
}
}
}
impl Default for Date {
fn default() -> Self {
Self(Some(Utc::now().naive_utc()))
}
}
impl BuiltIn for Date {
const NAME: &'static str = "Date";
fn init(context: &mut Context) -> Option<JsValue> {
let _timer = Profiler::global().start_event(Self::NAME, "init");
ConstructorBuilder::with_standard_constructor(
context,
Self::constructor,
context.intrinsics().constructors().date().clone(),
)
.name(Self::NAME)
.length(Self::LENGTH)
.method(getter_method!(get_date), "getDate", 0)
.method(getter_method!(get_day), "getDay", 0)
.method(getter_method!(get_full_year), "getFullYear", 0)
.method(getter_method!(get_hours), "getHours", 0)
.method(getter_method!(get_milliseconds), "getMilliseconds", 0)
.method(getter_method!(get_minutes), "getMinutes", 0)
.method(getter_method!(get_month), "getMonth", 0)
.method(getter_method!(get_seconds), "getSeconds", 0)
.method(getter_method!(get_time), "getTime", 0)
.method(getter_method!(get_year), "getYear", 0)
.method(Self::get_timezone_offset, "getTimezoneOffset", 0)
.method(getter_method!(get_utc_date), "getUTCDate", 0)
.method(getter_method!(get_utc_day), "getUTCDay", 0)
.method(getter_method!(get_utc_full_year), "getUTCFullYear", 0)
.method(getter_method!(get_utc_hours), "getUTCHours", 0)
.method(
getter_method!(get_utc_milliseconds),
"getUTCMilliseconds",
0,
)
.method(getter_method!(get_utc_minutes), "getUTCMinutes", 0)
.method(getter_method!(get_utc_month), "getUTCMonth", 0)
.method(getter_method!(get_utc_seconds), "getUTCSeconds", 0)
.method(Self::set_date, "setDate", 1)
.method(Self::set_full_year, "setFullYear", 3)
.method(Self::set_hours, "setHours", 4)
.method(Self::set_milliseconds, "setMilliseconds", 1)
.method(Self::set_minutes, "setMinutes", 3)
.method(Self::set_month, "setMonth", 2)
.method(Self::set_seconds, "setSeconds", 2)
.method(Self::set_year, "setYear", 1)
.method(Self::set_time, "setTime", 1)
.method(Self::set_utc_date, "setUTCDate", 1)
.method(Self::set_utc_full_year, "setUTCFullYear", 3)
.method(Self::set_utc_hours, "setUTCHours", 4)
.method(Self::set_utc_milliseconds, "setUTCMilliseconds", 1)
.method(Self::set_utc_minutes, "setUTCMinutes", 3)
.method(Self::set_utc_month, "setUTCMonth", 2)
.method(Self::set_utc_seconds, "setUTCSeconds", 2)
.method(Self::to_date_string, "toDateString", 0)
.method(getter_method!(to_gmt_string), "toGMTString", 0)
.method(Self::to_iso_string, "toISOString", 0)
.method(Self::to_json, "toJSON", 1)
// Locale strings
.method(Self::to_string, "toString", 0)
.method(Self::to_time_string, "toTimeString", 0)
.method(getter_method!(to_utc_string), "toUTCString", 0)
.method(getter_method!(value_of), "valueOf", 0)
.method(
Self::to_primitive,
(WellKnownSymbols::to_primitive(), "[Symbol.toPrimitive]"),
1,
)
.static_method(Self::now, "now", 0)
.static_method(Self::parse, "parse", 1)
.static_method(Self::utc, "UTC", 7)
.build()
.conv::<JsValue>()
.pipe(Some)
}
}
impl Date {
/// The amount of arguments this function object takes.
pub(crate) const LENGTH: usize = 7;
/// Check if the time (number of milliseconds) is in the expected range.
/// Returns None if the time is not in the range, otherwise returns the time itself in option.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-timeclip
#[inline]
pub fn time_clip(time: f64) -> Option<f64> {
if time.abs() > 8.64e15 {
None
} else {
Some(time)
}
}
/// Converts the `Date` to a local `DateTime`.
///
/// If the `Date` is invalid (i.e. NAN), this function will return `None`.
#[inline]
pub fn to_local(self) -> Option<DateTime<Local>> {
self.0
.map(|utc| Local::now().timezone().from_utc_datetime(&utc))
}
/// Converts the `Date` to a UTC `DateTime`.
///
/// If the `Date` is invalid (i.e. NAN), this function will return `None`.
pub fn to_utc(self) -> Option<DateTime<Utc>> {
self.0
.map(|utc| Utc::now().timezone().from_utc_datetime(&utc))
}
/// Optionally sets the individual components of the `Date`.
///
/// Each component does not have to be within the range of valid values. For example, if `month` is too large
/// then `year` will be incremented by the required amount.
#[allow(clippy::too_many_arguments)]
pub fn set_components(
&mut self,
utc: bool,
year: Option<f64>,
month: Option<f64>,
day: Option<f64>,
hour: Option<f64>,
minute: Option<f64>,
second: Option<f64>,
millisecond: Option<f64>,
) {
#[inline]
fn num_days_in(year: i32, month: u32) -> Option<u32> {
let month = month + 1; // zero-based for calculations
Some(
NaiveDate::from_ymd_opt(
match month {
12 => year.checked_add(1)?,
_ => year,
},
match month {
12 => 1,
_ => month + 1,
},
1,
)?
.signed_duration_since(NaiveDate::from_ymd_opt(year, month, 1)?)
.num_days() as u32,
)
}
#[inline]
fn fix_month(year: i32, month: i32) -> Option<(i32, u32)> {
let year = year.checked_add(month / 12)?;
if month < 0 {
let year = year.checked_sub(1)?;
let month = (11 + (month + 1) % 12) as u32;
Some((year, month))
} else {
let month = (month % 12) as u32;
Some((year, month))
}
}
#[inline]
fn fix_day(mut year: i32, mut month: i32, mut day: i32) -> Option<(i32, u32, u32)> {
loop {
if day < 0 {
let (fixed_year, fixed_month) = fix_month(year, month.checked_sub(1)?)?;
year = fixed_year;
month = fixed_month as i32;
day += num_days_in(fixed_year, fixed_month)? as i32;
} else {
let (fixed_year, fixed_month) = fix_month(year, month)?;
let num_days = num_days_in(fixed_year, fixed_month)? as i32;
if day >= num_days {
day -= num_days;
month = month.checked_add(1)?;
} else {
break;
}
}
}
let (fixed_year, fixed_month) = fix_month(year, month)?;
Some((fixed_year, fixed_month, day as u32))
}
// 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 year = year.unwrap_or_else(|| f64::from(naive.year())) as i32;
let month = month.unwrap_or_else(|| f64::from(naive.month0())) as i32;
let day = (day.unwrap_or_else(|| f64::from(naive.day())) as i32).checked_sub(1)?;
let hour = hour.unwrap_or_else(|| f64::from(naive.hour())) as i64;
let minute = minute.unwrap_or_else(|| f64::from(naive.minute())) as i64;
let second = second.unwrap_or_else(|| f64::from(naive.second())) as i64;
let millisecond = millisecond
.unwrap_or_else(|| f64::from(naive.nanosecond()) / NANOS_PER_MS as f64)
as i64;
let (year, month, day) = fix_day(year, month, day)?;
let duration_hour = Duration::milliseconds(hour.checked_mul(MILLIS_PER_HOUR)?);
let duration_minute = Duration::milliseconds(minute.checked_mul(MILLIS_PER_MINUTE)?);
let duration_second = Duration::milliseconds(second.checked_mul(MILLIS_PER_SECOND)?);
let duration_millisecond = Duration::milliseconds(millisecond);
let duration = duration_hour
.checked_add(&duration_minute)?
.checked_add(&duration_second)?
.checked_add(&duration_millisecond)?;
NaiveDate::from_ymd_opt(year, month + 1, day + 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())
}
})
.filter(|dt| Self::time_clip(dt.timestamp_millis() as f64).is_some())
});
}
/// `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 constructor(
new_target: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
if new_target.is_undefined() {
Ok(Self::make_date_string())
} else {
let prototype =
get_prototype_from_constructor(new_target, StandardConstructors::date, context)?;
Ok(if args.is_empty() {
Self::make_date_now(prototype)
} else if args.len() == 1 {
Self::make_date_single(prototype, args, context)?
} else {
Self::make_date_multiple(prototype, args, context)?
}
.into())
}
}
/// `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() -> JsValue {
JsValue::new(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(prototype: JsObject) -> JsObject {
JsObject::from_proto_and_data(prototype, ObjectData::date(Self::default()))
}
/// `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(
prototype: JsObject,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsObject> {
let value = &args[0];
let tv = match this_time_value(value, context) {
Ok(dt) => dt.0,
_ => match value.to_primitive(context, PreferredType::Default)? {
JsValue::String(ref str) => match chrono::DateTime::parse_from_rfc3339(str) {
Ok(dt) => Some(dt.naive_utc()),
_ => None,
},
tv => {
let tv = tv.to_number(context)?;
if tv.is_nan() {
None
} else {
let secs = (tv / 1_000f64) as i64;
let nano_secs = ((tv % 1_000f64) * 1_000_000f64) as u32;
NaiveDateTime::from_timestamp_opt(secs, nano_secs)
}
}
},
};
let tv = tv.filter(|time| Self::time_clip(time.timestamp_millis() as f64).is_some());
Ok(JsObject::from_proto_and_data(
prototype,
ObjectData::date(Self(tv)),
))
}
/// `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(
prototype: JsObject,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsObject> {
let mut year = args[0].to_number(context)?;
let month = args[1].to_number(context)?;
let day = args
.get(2)
.map_or(Ok(1f64), |value| value.to_number(context))?;
let hour = args
.get(3)
.map_or(Ok(0f64), |value| value.to_number(context))?;
let min = args
.get(4)
.map_or(Ok(0f64), |value| value.to_number(context))?;
let sec = args
.get(5)
.map_or(Ok(0f64), |value| value.to_number(context))?;
let milli = args
.get(6)
.map_or(Ok(0f64), |value| value.to_number(context))?;
// If any of the args are infinity or NaN, return an invalid date.
if !check_normal_opt!(year, month, day, hour, min, sec, milli) {
return Ok(JsObject::from_proto_and_data(
prototype,
ObjectData::date(Self(None)),
));
}
if (0.0..=99.0).contains(&year) {
year += 1900.0;
}
let mut date = Self(
NaiveDateTime::from_timestamp_opt(0, 0)
.and_then(|local| ignore_ambiguity(Local.from_local_datetime(&local)))
.map(|local| local.naive_utc())
.filter(|time| Self::time_clip(time.timestamp_millis() as f64).is_some()),
);
date.set_components(
false,
Some(year),
Some(month),
Some(day),
Some(hour),
Some(min),
Some(sec),
Some(milli),
);
Ok(JsObject::from_proto_and_data(
prototype,
ObjectData::date(date),
))
}
/// `Date.prototype[@@toPrimitive]`
///
/// The [@@toPrimitive]() method converts a Date object to a primitive value.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-date.prototype-@@toprimitive
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/@@toPrimitive
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_primitive(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. If Type(O) is not Object, throw a TypeError exception.
let o = if let Some(o) = this.as_object() {
o
} else {
return context.throw_type_error("Date.prototype[@@toPrimitive] called on non object");
};
let hint = args.get_or_undefined(0);
let try_first = match hint.as_string().map(JsString::as_str) {
// 3. If hint is "string" or "default", then
// a. Let tryFirst be string.
Some("string" | "default") => PreferredType::String,
// 4. Else if hint is "number", then
// a. Let tryFirst be number.
Some("number") => PreferredType::Number,
// 5. Else, throw a TypeError exception.
_ => {
return context
.throw_type_error("Date.prototype[@@toPrimitive] called with invalid hint")
}
};
// 6. Return ? OrdinaryToPrimitive(O, tryFirst).
o.ordinary_to_primitive(context, try_first)
}
/// `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| f64::from(dt.day()))
}
/// `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
f64::from(weekday)
})
}
/// `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| f64::from(dt.year()))
}
/// `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| f64::from(dt.hour()))
}
/// `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| {
f64::from(dt.nanosecond()) / NANOS_PER_MS 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| f64::from(dt.minute()))
}
/// `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| f64::from(dt.month0()))
}
/// `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| f64::from(dt.second()))
}
/// `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| f64::from(dt.year()) - 1900f64)
}
/// `Date.prototype.getTime()`
///
/// The `getTime()` method returns the number of milliseconds since the Unix Epoch.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-date.prototype.gettime
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTime
pub fn get_time(&self) -> f64 {
self.to_utc()
.map_or(f64::NAN, |dt| dt.timestamp_millis() as f64)
}
/// `Date.prototype.getTimeZoneOffset()`
///
/// The `getTimezoneOffset()` method returns the time zone difference, in minutes, from current locale (host system
/// settings) to UTC.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-date.prototype.gettimezoneoffset
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset
#[inline]
pub fn get_timezone_offset(
this: &JsValue,
_: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let t be ? thisTimeValue(this value).
let t = this_time_value(this, context)?;
// 2. If t is NaN, return NaN.
if t.0.is_none() {
return Ok(JsValue::nan());
}
// 3. Return (t - LocalTime(t)) / msPerMinute.
Ok(JsValue::new(
f64::from(-Local::now().offset().local_minus_utc()) / 60f64,
))
}
/// `Date.prototype.getUTCDate()`
///
/// The `getUTCDate()` method returns the day (date) of the month in the specified date according to universal time.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-date.prototype.getutcdate
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getUTCDate
pub fn get_utc_date(&self) -> f64 {
self.to_utc().map_or(f64::NAN, |dt| f64::from(dt.day()))
}
/// `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
f64::from(weekday)
})
}
/// `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| f64::from(dt.year()))
}
/// `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| f64::from(dt.hour()))
}
/// `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| {
f64::from(dt.nanosecond()) / NANOS_PER_MS 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| f64::from(dt.minute()))
}
/// `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| f64::from(dt.month0()))
}
/// `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| f64::from(dt.second()))
}
/// `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(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let t be LocalTime(? thisTimeValue(this value)).
let mut t = this_time_value(this, context)?;
// 2. Let dt be ? ToNumber(date).
let dt = args
.get(0)
.cloned()
.unwrap_or_default()
.to_number(context)?;
// 3. Let newDate be MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), dt), TimeWithinDay(t)).
t.set_components(false, None, None, Some(dt), None, None, None, None);
// 4. Let u be TimeClip(UTC(newDate)).
let u = t.get_time();
// 5. Set the [[DateValue]] internal slot of this Date object to u.
this.set_data(ObjectData::date(t));
// 6. Return u.
Ok(u.into())
}
/// `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(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let t be ? thisTimeValue(this value).
let mut t = this_time_value(this, context)?;
// 2. If t is NaN, set t to +0𝔽; otherwise, set t to LocalTime(t).
if t.0.is_none() {
t.0 = NaiveDateTime::from_timestamp_opt(0, 0)
.and_then(|local| ignore_ambiguity(Local.from_local_datetime(&local)))
.map(|local| local.naive_utc())
.filter(|time| Self::time_clip(time.timestamp_millis() as f64).is_some());
}
// 3. Let y be ? ToNumber(year).
let y = args
.get(0)
.cloned()
.unwrap_or_default()
.to_number(context)?;
// 4. If month is not present, let m be MonthFromTime(t); otherwise, let m be ? ToNumber(month).
let m = if let Some(m) = args.get(1) {
Some(m.to_number(context)?)
} else {
None
};
// 5. If date is not present, let dt be DateFromTime(t); otherwise, let dt be ? ToNumber(date).
let dt = if let Some(dt) = args.get(2) {
Some(dt.to_number(context)?)
} else {
None
};
// 6. Let newDate be MakeDate(MakeDay(y, m, dt), TimeWithinDay(t)).
t.set_components(false, Some(y), m, dt, None, None, None, None);
// 7. Let u be TimeClip(UTC(newDate)).
let u = t.get_time();
// 8. Set the [[DateValue]] internal slot of this Date object to u.
this.set_data(ObjectData::date(t));
// 9. Return u.
Ok(u.into())
}
/// `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(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let t be LocalTime(? thisTimeValue(this value)).
let mut t = this_time_value(this, context)?;
// 2. Let h be ? ToNumber(hour).
let h = args
.get(0)
.cloned()
.unwrap_or_default()
.to_number(context)?;
// 3. If min is not present, let m be MinFromTime(t); otherwise, let m be ? ToNumber(min).
let m = if let Some(m) = args.get(1) {
Some(m.to_number(context)?)
} else {
None
};
// 4. If sec is not present, let s be SecFromTime(t); otherwise, let s be ? ToNumber(sec).
let sec = if let Some(sec) = args.get(2) {
Some(sec.to_number(context)?)
} else {
None
};
// 5. If ms is not present, let milli be msFromTime(t); otherwise, let milli be ? ToNumber(ms).
let milli = if let Some(milli) = args.get(3) {
Some(milli.to_number(context)?)
} else {
None
};
// 6. Let date be MakeDate(Day(t), MakeTime(h, m, s, milli)).
t.set_components(false, None, None, None, Some(h), m, sec, milli);
// 7. Let u be TimeClip(UTC(date)).
let u = t.get_time();
// 8. Set the [[DateValue]] internal slot of this Date object to u.
this.set_data(ObjectData::date(t));
// 9. Return u.
Ok(u.into())
}
/// `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(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let t be LocalTime(? thisTimeValue(this value)).
let mut t = this_time_value(this, context)?;
// 2. Set ms to ? ToNumber(ms).
let ms = args
.get(0)
.cloned()
.unwrap_or_default()
.to_number(context)?;
// 3. Let time be MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), ms).
t.set_components(false, None, None, None, None, None, None, Some(ms));
// 4. Let u be TimeClip(UTC(MakeDate(Day(t), time))).
let u = t.get_time();
// 5. Set the [[DateValue]] internal slot of this Date object to u.
this.set_data(ObjectData::date(t));
// 6. Return u.
Ok(u.into())
}
/// `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(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let t be LocalTime(? thisTimeValue(this value)).
let mut t = this_time_value(this, context)?;
// 2. Let m be ? ToNumber(min).
let m = args
.get(0)
.cloned()
.unwrap_or_default()
.to_number(context)?;
// 3. If sec is not present, let s be SecFromTime(t); otherwise, let s be ? ToNumber(sec).
let s = if let Some(s) = args.get(1) {
Some(s.to_number(context)?)
} else {
None
};
// 4. If ms is not present, let milli be msFromTime(t); otherwise, let milli be ? ToNumber(ms).
let milli = if let Some(milli) = args.get(2) {
Some(milli.to_number(context)?)
} else {
None
};
// 5. Let date be MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli)).
t.set_components(false, None, None, None, None, Some(m), s, milli);
// 6. Let u be TimeClip(UTC(date)).
let u = t.get_time();
// 7. Set the [[DateValue]] internal slot of this Date object to u.
this.set_data(ObjectData::date(t));
// 8. Return u.
Ok(u.into())
}
/// `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(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let t be LocalTime(? thisTimeValue(this value)).
let mut t = this_time_value(this, context)?;
// 2. Let m be ? ToNumber(month).
let m = args
.get(0)
.cloned()
.unwrap_or_default()
.to_number(context)?;
// 3. If date is not present, let dt be DateFromTime(t); otherwise, let dt be ? ToNumber(date).
let dt = if let Some(date) = args.get(1) {
Some(date.to_number(context)?)
} else {
None
};
// 4. Let newDate be MakeDate(MakeDay(YearFromTime(t), m, dt), TimeWithinDay(t)).
t.set_components(false, None, Some(m), dt, None, None, None, None);
// 5. Let u be TimeClip(UTC(newDate)).
let u = t.get_time();
// 6. Set the [[DateValue]] internal slot of this Date object to u.
this.set_data(ObjectData::date(t));
// 7. Return u.
Ok(u.into())
}
/// `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(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let t be LocalTime(? thisTimeValue(this value)).
let mut t = this_time_value(this, context)?;
// 2. Let s be ? ToNumber(sec).
let s = args
.get(0)
.cloned()
.unwrap_or_default()
.to_number(context)?;
// 3. If ms is not present, let milli be msFromTime(t); otherwise, let milli be ? ToNumber(ms).
let milli = if let Some(milli) = args.get(1) {
Some(milli.to_number(context)?)
} else {
None
};
// 4. Let date be MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli)).
t.set_components(false, None, None, None, None, None, Some(s), milli);
// 5. Let u be TimeClip(UTC(date)).
let u = t.get_time();
// 6. Set the [[DateValue]] internal slot of this Date object to u.
this.set_data(ObjectData::date(t));
// 7. Return u.
Ok(u.into())
}
/// `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(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let t be ? thisTimeValue(this value).
let mut t = this_time_value(this, context)?;
// 2. If t is NaN, set t to +0𝔽; otherwise, set t to LocalTime(t).
if t.0.is_none() {
t.0 = NaiveDateTime::from_timestamp_opt(0, 0)
.and_then(|local| ignore_ambiguity(Local.from_local_datetime(&local)))
.map(|local| local.naive_utc())
.filter(|time| Self::time_clip(time.timestamp_millis() as f64).is_some());
}
// 3. Let y be ? ToNumber(year).
let mut y = args
.get(0)
.cloned()
.unwrap_or_default()
.to_number(context)?;
// 4. If y is NaN, then
if y.is_nan() {
// a. Set the [[DateValue]] internal slot of this Date object to NaN.
this.set_data(ObjectData::date(Self(None)));
// b. Return NaN.
return Ok(JsValue::nan());
}
// 5. Let yi be ! ToIntegerOrInfinity(y).
// 6. If 0 ≤ yi ≤ 99, let yyyy be 1900𝔽 + 𝔽(yi).
// 7. Else, let yyyy be y.
if (0f64..=99f64).contains(&y) {
y += 1900f64;
}
// 8. Let d be MakeDay(yyyy, MonthFromTime(t), DateFromTime(t)).
// 9. Let date be UTC(MakeDate(d, TimeWithinDay(t))).
t.set_components(false, Some(y), None, None, None, None, None, None);
// 10. Set the [[DateValue]] internal slot of this Date object to TimeClip(date).
this.set_data(ObjectData::date(t));
// 11. Return the value of the [[DateValue]] internal slot of this Date object.
Ok(t.get_time().into())
}
/// `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(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Perform ? thisTimeValue(this value).
this_time_value(this, context)?;
// 2. Let t be ? ToNumber(time).
let t = if let Some(t) = args.get(0) {
let t = t.to_number(context)?;
let seconds = (t / 1_000f64) as i64;
let nanoseconds = ((t % 1_000f64) * 1_000_000f64) as u32;
Self(
ignore_ambiguity(Local.timestamp_opt(seconds, nanoseconds))
.map(|dt| dt.naive_utc()),
)
} else {
Self(None)
};
// 3. Let v be TimeClip(t).
let v = t.get_time();
// 4. Set the [[DateValue]] internal slot of this Date object to v.
this.set_data(ObjectData::date(t));
// 5. Return v.
Ok(v.into())
}
/// `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(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let t be ? thisTimeValue(this value).
let mut t = this_time_value(this, context)?;
// 2. Let dt be ? ToNumber(date).
let dt = args
.get(0)
.cloned()
.unwrap_or_default()
.to_number(context)?;
// 3. Let newDate be MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), dt), TimeWithinDay(t)).
t.set_components(true, None, None, Some(dt), None, None, None, None);
// 4. Let v be TimeClip(newDate).
let v = t.get_time();
// 5. Set the [[DateValue]] internal slot of this Date object to v.
this.set_data(ObjectData::date(t));
// 6. Return v.
Ok(v.into())
}
/// `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(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let t be ? thisTimeValue(this value).
let mut t = this_time_value(this, context)?;
// 2. If t is NaN, set t to +0𝔽.
if t.0.is_none() {
t.0 = NaiveDateTime::from_timestamp_opt(0, 0)
.and_then(|local| ignore_ambiguity(Local.from_local_datetime(&local)))
.map(|local| local.naive_utc())
.filter(|time| Self::time_clip(time.timestamp_millis() as f64).is_some());
}
// 3. Let y be ? ToNumber(year).
let y = args
.get(0)
.cloned()
.unwrap_or_default()
.to_number(context)?;
// 4. If month is not present, let m be MonthFromTime(t); otherwise, let m be ? ToNumber(month).
let m = if let Some(m) = args.get(1) {
Some(m.to_number(context)?)
} else {
None
};
// 5. If date is not present, let dt be DateFromTime(t); otherwise, let dt be ? ToNumber(date).
let dt = if let Some(dt) = args.get(2) {
Some(dt.to_number(context)?)
} else {
None
};
// 6. Let newDate be MakeDate(MakeDay(y, m, dt), TimeWithinDay(t)).
t.set_components(true, Some(y), m, dt, None, None, None, None);
// 7. Let v be TimeClip(newDate).
let v = t.get_time();
// 8. Set the [[DateValue]] internal slot of this Date object to v.
this.set_data(ObjectData::date(t));
// 9. Return v.
Ok(v.into())
}
/// `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(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let t be ? thisTimeValue(this value).
let mut t = this_time_value(this, context)?;
// 2. Let h be ? ToNumber(hour).
let h = args
.get(0)
.cloned()
.unwrap_or_default()
.to_number(context)?;
// 3. If min is not present, let m be MinFromTime(t); otherwise, let m be ? ToNumber(min).
let m = if let Some(m) = args.get(1) {
Some(m.to_number(context)?)
} else {
None
};
// 4. If sec is not present, let s be SecFromTime(t); otherwise, let s be ? ToNumber(sec).
let sec = if let Some(s) = args.get(2) {
Some(s.to_number(context)?)
} else {
None
};
// 5. If ms is not present, let milli be msFromTime(t); otherwise, let milli be ? ToNumber(ms).
let ms = if let Some(ms) = args.get(3) {
Some(ms.to_number(context)?)
} else {
None
};
// 6. Let newDate be MakeDate(Day(t), MakeTime(h, m, s, milli)).
t.set_components(true, None, None, None, Some(h), m, sec, ms);
// 7. Let v be TimeClip(newDate).
let v = t.get_time();
// 8. Set the [[DateValue]] internal slot of this Date object to v.
this.set_data(ObjectData::date(t));
// 9. Return v.
Ok(v.into())
}
/// `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(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let t be ? thisTimeValue(this value).
let mut t = this_time_value(this, context)?;
// 2. Let milli be ? ToNumber(ms).
let ms = args
.get(0)
.cloned()
.unwrap_or_default()
.to_number(context)?;
// 3. Let time be MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), milli).
t.set_components(true, None, None, None, None, None, None, Some(ms));
// 4. Let v be TimeClip(MakeDate(Day(t), time)).
let v = t.get_time();
// 5. Set the [[DateValue]] internal slot of this Date object to v.
this.set_data(ObjectData::date(t));
// 6. Return v.
Ok(v.into())
}
/// `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(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let t be ? thisTimeValue(this value).
let mut t = this_time_value(this, context)?;
// 2. Let m be ? ToNumber(min).
let m = args
.get(0)
.cloned()
.unwrap_or_default()
.to_number(context)?;
// 3. If sec is not present, let s be SecFromTime(t).
// 4. Else,
let s = if let Some(s) = args.get(1) {
// a. Let s be ? ToNumber(sec).
Some(s.to_number(context)?)
} else {
None
};
// 5. If ms is not present, let milli be msFromTime(t).
// 6. Else,
let milli = if let Some(ms) = args.get(2) {
// a. Let milli be ? ToNumber(ms).
Some(ms.to_number(context)?)
} else {
None
};
// 7. Let date be MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli)).
t.set_components(true, None, None, None, None, Some(m), s, milli);
// 8. Let v be TimeClip(date).
let v = t.get_time();
// 9. Set the [[DateValue]] internal slot of this Date object to v.
this.set_data(ObjectData::date(t));
// 10. Return v.
Ok(v.into())
}
/// `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(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let t be ? thisTimeValue(this value).
let mut t = this_time_value(this, context)?;
// 2. Let m be ? ToNumber(month).
let m = args
.get(0)
.cloned()
.unwrap_or_default()
.to_number(context)?;
// 3. If date is not present, let dt be DateFromTime(t).
// 4. Else,
let dt = if let Some(dt) = args.get(1) {
// a. Let dt be ? ToNumber(date).
Some(dt.to_number(context)?)
} else {
None
};
// 5. Let newDate be MakeDate(MakeDay(YearFromTime(t), m, dt), TimeWithinDay(t)).
t.set_components(true, None, Some(m), dt, None, None, None, None);
// 6. Let v be TimeClip(newDate).
let v = t.get_time();
// 7. Set the [[DateValue]] internal slot of this Date object to v.
this.set_data(ObjectData::date(t));
// 8. Return v.
Ok(v.into())
}
/// `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(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let t be ? thisTimeValue(this value).
let mut t = this_time_value(this, context)?;
// 2. Let s be ? ToNumber(sec).
let s = args
.get(0)
.cloned()
.unwrap_or_default()
.to_number(context)?;
// 3. If ms is not present, let milli be msFromTime(t).
// 4. Else,
let milli = if let Some(milli) = args.get(1) {
// a. Let milli be ? ToNumber(ms).
Some(milli.to_number(context)?)
} else {
None
};
// 5. Let date be MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli)).
t.set_components(true, None, None, None, None, None, Some(s), milli);
// 6. Let v be TimeClip(date).
let v = t.get_time();
// 7. Set the [[DateValue]] internal slot of this Date object to v.
this.set_data(ObjectData::date(t));
// 8. Return v.
Ok(v.into())
}
/// `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 fn to_date_string(
this: &JsValue,
_: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be this Date object.
// 2. Let tv be ? thisTimeValue(O).
let tv = this_time_value(this, context)?;
// 3. If tv is NaN, return "Invalid Date".
// 4. Let t be LocalTime(tv).
// 5. Return DateString(t).
if let Some(t) = tv.0 {
Ok(Local::now()
.timezone()
.from_utc_datetime(&t)
.format("%a %b %d %Y")
.to_string()
.into())
} else {
Ok(JsString::from("Invalid Date").into())
}
}
/// `Date.prototype.toGMTString()`
///
/// The `toGMTString()` method converts a date to a string, using Internet Greenwich Mean Time (GMT) conventions.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-date.prototype.togmtstring
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toGMTString
pub fn to_gmt_string(self) -> String {
self.to_utc_string()
}
/// `Date.prototype.toISOString()`
///
/// The `toISOString()` method returns a string in simplified extended ISO format (ISO 8601).
///
/// More information:
/// - [ISO 8601][iso8601]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [iso8601]: http://en.wikipedia.org/wiki/ISO_8601
/// [spec]: https://tc39.es/ecma262/#sec-date.prototype.toisostring
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString
#[allow(clippy::wrong_self_convention)]
pub fn to_iso_string(
this: &JsValue,
_: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
if let Some(t) = this_time_value(this, context)?.0 {
Ok(Utc::now()
.timezone()
.from_utc_datetime(&t)
.format("%Y-%m-%dT%H:%M:%S.%3fZ")
.to_string()
.into())
} else {
context.throw_range_error("Invalid time value")
}
}
/// `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 fn to_json(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let O be ? ToObject(this value).
let o = this.to_object(context)?;
// 2. Let tv be ? ToPrimitive(O, number).
let tv = this.to_primitive(context, PreferredType::Number)?;
// 3. If Type(tv) is Number and tv is not finite, return null.
if let Some(number) = tv.as_number() {
if !number.is_finite() {
return Ok(JsValue::null());
}
}
// 4. Return ? Invoke(O, "toISOString").
let func = o.get("toISOString", context)?;
context.call(&func, &o.into(), &[])
}
/// `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 fn to_string(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let tv be ? thisTimeValue(this value).
let tv = this_time_value(this, context)?;
// 2. Return ToDateString(tv).
if let Some(t) = tv.0 {
Ok(Local::now()
.timezone()
.from_utc_datetime(&t)
.format("%a %b %d %Y %H:%M:%S GMT%z")
.to_string()
.into())
} else {
Ok(JsString::from("Invalid Date").into())
}
}
/// `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 fn to_time_string(
this: &JsValue,
_: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be this Date object.
// 2. Let tv be ? thisTimeValue(O).
let tv = this_time_value(this, context)?;
// 3. If tv is NaN, return "Invalid Date".
// 4. Let t be LocalTime(tv).
// 5. Return the string-concatenation of TimeString(t) and TimeZoneString(tv).
if let Some(t) = tv.0 {
Ok(Local::now()
.timezone()
.from_utc_datetime(&t)
.format("%H:%M:%S GMT%z")
.to_string()
.into())
} else {
Ok(JsString::from("Invalid Date").into())
}
}
/// `Date.prototype.toUTCString()`
///
/// The `toUTCString()` method returns a string representing the specified Date object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-date.prototype.toutcstring
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toUTCString
pub fn to_utc_string(self) -> String {
self.to_utc().map_or_else(
|| "Invalid Date".to_string(),
|date_time| date_time.format("%a, %d %b %Y %H:%M:%S GMT").to_string(),
)
}
/// `Date.prototype.valueOf()`
///
/// The `valueOf()` method returns the primitive value of a `Date` object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-date.prototype.valueof
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/valueOf
pub fn value_of(&self) -> f64 {
self.get_time()
}
/// `Date.now()`
///
/// The static `Date.now()` method returns the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-date.now
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now
#[allow(clippy::unnecessary_wraps)]
pub(crate) fn now(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Ok(JsValue::new(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(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// This method is implementation-defined and discouraged, so we just require the same format as the string
// constructor.
if args.is_empty() {
return Ok(JsValue::nan());
}
match DateTime::parse_from_rfc3339(&args[0].to_string(context)?) {
Ok(v) => Ok(JsValue::new(v.naive_utc().timestamp_millis() as f64)),
_ => Ok(JsValue::new(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(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let year = args
.get(0)
.map_or(Ok(f64::NAN), |value| value.to_number(context))?;
let month = args
.get(1)
.map_or(Ok(0f64), |value| value.to_number(context))?;
let day = args
.get(2)
.map_or(Ok(1f64), |value| value.to_number(context))?;
let hour = args
.get(3)
.map_or(Ok(0f64), |value| value.to_number(context))?;
let min = args
.get(4)
.map_or(Ok(0f64), |value| value.to_number(context))?;
let sec = args
.get(5)
.map_or(Ok(0f64), |value| value.to_number(context))?;
let milli = args
.get(6)
.map_or(Ok(0f64), |value| value.to_number(context))?;
if !check_normal_opt!(year, month, day, hour, min, sec, milli) {
return Ok(JsValue::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..=99).contains(&year) {
1900 + year
} else {
year
};
NaiveDate::from_ymd_opt(year, month + 1, day)
.and_then(|f| f.and_hms_milli_opt(hour, min, sec, milli))
.and_then(|f| Self::time_clip(f.timestamp_millis() as f64))
.map_or(Ok(JsValue::nan()), |time| Ok(JsValue::new(time)))
}
}
/// 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: &JsValue, context: &mut Context) -> JsResult<Date> {
value
.as_object()
.and_then(|obj| obj.borrow().as_date().copied())
.ok_or_else(|| context.construct_type_error("'this' is not a Date"))
}