Skip to main content

boa_engine/object/builtins/
jsdate.rs

1//! A Rust API wrapper for Boa's `Date` ECMAScript Builtin Object.
2
3use crate::{
4    Context, JsNativeError, JsResult, JsValue, builtins::Date, object::JsObject, value::TryFromJs,
5};
6use boa_gc::{Finalize, Trace};
7use std::ops::Deref;
8use time::{OffsetDateTime, format_description::well_known::Rfc3339};
9
10/// `JsDate` is a wrapper for JavaScript `JsDate` builtin object
11///
12/// # Example
13///
14/// Create a `JsDate` object and set date to December 4 1995
15///
16/// ```
17/// use boa_engine::{
18///     Context, JsResult, JsValue, js_string, object::builtins::JsDate,
19/// };
20///
21/// fn main() -> JsResult<()> {
22///     // JS mutable Context
23///     let context = &mut Context::default();
24///
25///     let date = JsDate::new(context);
26///
27///     date.set_full_year(&[1995.into(), 11.into(), 4.into()], context)?;
28///
29///     assert_eq!(
30///         date.to_date_string(context)?,
31///         JsValue::from(js_string!("Mon Dec 04 1995"))
32///     );
33///
34///     Ok(())
35/// }
36/// ```
37#[derive(Debug, Clone, Trace, Finalize)]
38pub struct JsDate {
39    inner: JsObject,
40}
41
42impl JsDate {
43    /// Create a new `Date` object with universal time.
44    #[inline]
45    pub fn new(context: &mut Context) -> Self {
46        let prototype = context.intrinsics().constructors().date().prototype();
47        let now = Date::utc_now(context);
48        let inner =
49            JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, now)
50                .upcast();
51
52        Self { inner }
53    }
54
55    /// Create a new `JsDate` object from an existing object.
56    #[inline]
57    pub fn from_object(object: JsObject) -> JsResult<Self> {
58        if object.is::<Date>() {
59            Ok(Self { inner: object })
60        } else {
61            Err(JsNativeError::typ()
62                .with_message("object is not a Date")
63                .into())
64        }
65    }
66
67    /// Return a `Number` representing the milliseconds elapsed since the UNIX epoch.
68    ///
69    /// Same as JavaScript's `Date.now()`
70    #[inline]
71    pub fn now(context: &mut Context) -> JsResult<JsValue> {
72        Date::now(&JsValue::null(), &[JsValue::null()], context)
73    }
74
75    // DEBUG: Uses RFC3339 internally therefore could match es6 spec of ISO8601  <========
76    /// Parse a `String` representation of date.
77    /// String should be ISO 8601 format.
78    /// Returns the `Number` of milliseconds since UNIX epoch if `String`
79    /// is valid, else return a `NaN`.
80    ///
81    /// Same as JavaScript's `Date.parse(value)`.
82    #[inline]
83    pub fn parse(value: JsValue, context: &mut Context) -> JsResult<JsValue> {
84        Date::parse(&JsValue::null(), &[value], context)
85    }
86
87    /// Takes a [year, month, day, hour, minute, second, millisecond]
88    /// Return a `Number` representing the milliseconds elapsed since the UNIX epoch.
89    ///
90    /// Same as JavaScript's `Date.UTC()`
91    #[inline]
92    pub fn utc(values: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
93        Date::utc(&JsValue::null(), values, context)
94    }
95
96    /// Returns the day of the month(1-31) for the specified date
97    /// according to local time.
98    ///
99    /// Same as JavaScript's `Date.prototype.getDate()`.
100    #[inline]
101    pub fn get_date(&self, context: &mut Context) -> JsResult<JsValue> {
102        Date::get_date::<true>(&self.inner.clone().into(), &[JsValue::null()], context)
103    }
104
105    /// Returns the day of the week (0–6) for the specified date
106    /// according to local time.
107    ///
108    /// Same as JavaScript's `Date.prototype.getDay()`.
109    #[inline]
110    pub fn get_day(&self, context: &mut Context) -> JsResult<JsValue> {
111        Date::get_day::<true>(&self.inner.clone().into(), &[JsValue::null()], context)
112    }
113
114    /// Returns the year (4 digits for 4-digit years) of the specified date
115    /// according to local time.
116    ///
117    /// Same as JavaScript's `Date.prototype.getFullYear()`.
118    #[inline]
119    pub fn get_full_year(&self, context: &mut Context) -> JsResult<JsValue> {
120        Date::get_full_year::<true>(&self.inner.clone().into(), &[JsValue::null()], context)
121    }
122
123    /// Returns the hour (0–23) in the specified date according to local time.
124    ///
125    /// Same as JavaScript's `Date.prototype.getHours()`.
126    #[inline]
127    pub fn get_hours(&self, context: &mut Context) -> JsResult<JsValue> {
128        Date::get_hours::<true>(&self.inner.clone().into(), &[JsValue::null()], context)
129    }
130
131    /// Returns the milliseconds (0–999) in the specified date according
132    /// to local time.
133    ///
134    /// Same as JavaScript's `Date.prototype.getMilliseconds()`.
135    #[inline]
136    pub fn get_milliseconds(&self, context: &mut Context) -> JsResult<JsValue> {
137        Date::get_milliseconds::<true>(&self.inner.clone().into(), &[JsValue::null()], context)
138    }
139
140    /// Returns the minutes (0–59) in the specified date according to local time.
141    ///
142    /// Same as JavaScript's `Date.prototype.getMinutes()`.
143    #[inline]
144    pub fn get_minutes(&self, context: &mut Context) -> JsResult<JsValue> {
145        Date::get_minutes::<true>(&self.inner.clone().into(), &[JsValue::null()], context)
146    }
147
148    /// Returns the month (0–11) in the specified date according to local time.
149    ///
150    /// Same as JavaScript's `Date.prototype.getMonth()`.
151    #[inline]
152    pub fn get_month(&self, context: &mut Context) -> JsResult<JsValue> {
153        Date::get_month::<true>(&self.inner.clone().into(), &[JsValue::null()], context)
154    }
155
156    /// Returns the seconds (0–59) in the specified date according to local time.
157    ///
158    /// Same as JavaScript's `Date.prototype.getSeconds()`.
159    #[inline]
160    pub fn get_seconds(&self, context: &mut Context) -> JsResult<JsValue> {
161        Date::get_seconds::<true>(&self.inner.clone().into(), &[JsValue::null()], context)
162    }
163
164    /// Returns the numeric value of the specified date as the number
165    /// of milliseconds since UNIX epoch.
166    /// Negative values are returned for prior times.
167    ///
168    /// Same as JavaScript's `Date.prototype.getTime()`.
169    #[inline]
170    pub fn get_time(&self, context: &mut Context) -> JsResult<JsValue> {
171        Date::get_time(&self.inner.clone().into(), &[JsValue::null()], context)
172    }
173
174    /// Returns the time-zone offset in minutes for the current locale.
175    ///
176    /// Same as JavaScript's `Date.prototype.getTimezoneOffset()`.
177    #[inline]
178    pub fn get_timezone_offset(&self, context: &mut Context) -> JsResult<JsValue> {
179        Date::get_timezone_offset(&self.inner.clone().into(), &[JsValue::null()], context)
180    }
181
182    /// Returns the day (date) of the month (1–31) in the specified
183    /// date according to universal time.
184    ///
185    /// Same as JavaScript's `Date.prototype.getUTCDate()`.
186    #[inline]
187    pub fn get_utc_date(&self, context: &mut Context) -> JsResult<JsValue> {
188        Date::get_date::<false>(&self.inner.clone().into(), &[JsValue::null()], context)
189    }
190
191    /// Returns the day of the week (0–6) in the specified
192    /// date according to universal time.
193    ///
194    /// Same as JavaScript's `Date.prototype.getUTCDay()`.
195    #[inline]
196    pub fn get_utc_day(&self, context: &mut Context) -> JsResult<JsValue> {
197        Date::get_day::<false>(&self.inner.clone().into(), &[JsValue::null()], context)
198    }
199
200    /// Returns the year (4 digits for 4-digit years) in the specified
201    /// date according to universal time.
202    ///
203    /// Same as JavaScript's `Date.prototype.getUTCFullYear()`.
204    #[inline]
205    pub fn get_utc_full_year(&self, context: &mut Context) -> JsResult<JsValue> {
206        Date::get_full_year::<false>(&self.inner.clone().into(), &[JsValue::null()], context)
207    }
208
209    /// Returns the hours (0–23) in the specified date according
210    /// to universal time.
211    ///
212    /// Same as JavaScript's `Date.prototype.getUTCHours()`.
213    #[inline]
214    pub fn get_utc_hours(&self, context: &mut Context) -> JsResult<JsValue> {
215        Date::get_hours::<false>(&self.inner.clone().into(), &[JsValue::null()], context)
216    }
217
218    /// Returns the milliseconds (0–999) in the specified date
219    /// according to universal time.
220    ///
221    /// Same as JavaScript's `Date.prototype.getUTCMilliseconds()`.
222    #[inline]
223    pub fn get_utc_milliseconds(&self, context: &mut Context) -> JsResult<JsValue> {
224        Date::get_milliseconds::<false>(&self.inner.clone().into(), &[JsValue::null()], context)
225    }
226
227    /// Returns the minutes (0–59) in the specified date according
228    /// to universal time.
229    ///
230    /// Same as JavaScript's `Date.prototype.getUTCMinutes()`.
231    #[inline]
232    pub fn get_utc_minutes(&self, context: &mut Context) -> JsResult<JsValue> {
233        Date::get_minutes::<false>(&self.inner.clone().into(), &[JsValue::null()], context)
234    }
235
236    /// Returns the month (0–11) in the specified date according
237    /// to universal time.
238    ///
239    /// Same as JavaScript's `Date.prototype.getUTCMonth()`.
240    #[inline]
241    pub fn get_utc_month(&self, context: &mut Context) -> JsResult<JsValue> {
242        Date::get_month::<false>(&self.inner.clone().into(), &[JsValue::null()], context)
243    }
244
245    /// Returns the seconds (0–59) in the specified date according
246    /// to universal time.
247    ///
248    /// Same as JavaScript's `Date.prototype.getUTCSeconds()`.
249    #[inline]
250    pub fn get_utc_seconds(&self, context: &mut Context) -> JsResult<JsValue> {
251        Date::get_seconds::<false>(&self.inner.clone().into(), &[JsValue::null()], context)
252    }
253
254    /// Sets the day of the month for a specified date according
255    /// to local time.
256    /// Takes a `month_value`.
257    /// Return a `Number` representing the milliseconds elapsed between
258    /// the UNIX epoch and the given date.
259    ///
260    /// Same as JavaScript's `Date.prototype.setDate()`.
261    pub fn set_date<T>(&self, value: T, context: &mut Context) -> JsResult<JsValue>
262    where
263        T: Into<JsValue>,
264    {
265        Date::set_date::<true>(&self.inner.clone().into(), &[value.into()], context)
266    }
267
268    /// Sets the full year (e.g. 4 digits for 4-digit years) for a
269    /// specified date according to local time.
270    /// Takes [`year_value`, `month_value`, `date_value`]
271    /// Return a `Number` representing the milliseconds elapsed between
272    /// the UNIX epoch and updated date.
273    ///
274    /// Same as JavaScript's `Date.prototype.setFullYear()`.
275    #[inline]
276    pub fn set_full_year(&self, values: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
277        Date::set_full_year::<true>(&self.inner.clone().into(), values, context)
278    }
279
280    /// Sets the hours for a specified date according to local time.
281    /// Takes [`hours_value`, `minutes_value`, `seconds_value`, `ms_value`]
282    /// Return a `Number` representing the milliseconds elapsed between
283    /// the UNIX epoch and the updated date.
284    ///
285    /// Same as JavaScript's `Date.prototype.setHours()`.
286    #[inline]
287    pub fn set_hours(&self, values: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
288        Date::set_hours::<true>(&self.inner.clone().into(), values, context)
289    }
290
291    /// Sets the milliseconds for a specified date according to local time.
292    /// Takes a `milliseconds_value`
293    /// Return a `Number` representing the milliseconds elapsed between
294    /// the UNIX epoch and updated date.
295    ///
296    /// Same as JavaScript's `Date.prototype.setMilliseconds()`.
297    pub fn set_milliseconds<T>(&self, value: T, context: &mut Context) -> JsResult<JsValue>
298    where
299        T: Into<JsValue>,
300    {
301        Date::set_milliseconds::<true>(&self.inner.clone().into(), &[value.into()], context)
302    }
303
304    /// Sets the minutes for a specified date according to local time.
305    /// Takes [`minutes_value`, `seconds_value`, `ms_value`]
306    /// Return a `Number` representing the milliseconds elapsed between
307    /// the UNIX epoch and the updated date.
308    ///
309    /// Same as JavaScript's `Date.prototype.setMinutes()`.
310    #[inline]
311    pub fn set_minutes(&self, values: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
312        Date::set_minutes::<true>(&self.inner.clone().into(), values, context)
313    }
314
315    /// Sets the month for a specified date according to local time.
316    /// Takes [`month_value`, `day_value`]
317    /// Return a `Number` representing the milliseconds elapsed between
318    /// the UNIX epoch and the updated date.
319    ///
320    /// Same as JavaScript's `Date.prototype.setMonth()`.
321    #[inline]
322    pub fn set_month(&self, values: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
323        Date::set_month::<true>(&self.inner.clone().into(), values, context)
324    }
325
326    /// Sets the seconds for a specified date according to local time.
327    /// Takes [`seconds_value`, `ms_value`]
328    /// Return a `Number` representing the milliseconds elapsed between
329    /// the UNIX epoch and the updated date.
330    ///
331    /// Same as JavaScript's `Date.prototype.setSeconds()`.
332    #[inline]
333    pub fn set_seconds(&self, values: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
334        Date::set_seconds::<true>(&self.inner.clone().into(), values, context)
335    }
336
337    /// Sets the Date object to the time represented by a number
338    /// of milliseconds since UNIX epoch.
339    /// Takes number of milliseconds since UNIX epoch.
340    /// Use negative numbers for times prior.
341    /// Return a `Number` representing the milliseconds elapsed between
342    /// the UNIX epoch and the updated date.
343    ///
344    /// Same as JavaScript's `Date.prototype.setTime()`.
345    pub fn set_time<T>(&self, value: T, context: &mut Context) -> JsResult<JsValue>
346    where
347        T: Into<JsValue>,
348    {
349        Date::set_time(&self.inner.clone().into(), &[value.into()], context)
350    }
351
352    /// Sets the day of the month for a specified date according
353    /// to universal time.
354    /// Takes a `month_value`.
355    /// Return a `Number` representing the milliseconds elapsed between
356    /// the UNIX epoch and the updated date.
357    ///
358    /// Same as JavaScript's `Date.prototype.setUTCDate()`.
359    pub fn set_utc_date<T>(&self, value: T, context: &mut Context) -> JsResult<JsValue>
360    where
361        T: Into<JsValue>,
362    {
363        Date::set_date::<false>(&self.inner.clone().into(), &[value.into()], context)
364    }
365
366    /// Sets the full year (e.g. 4 digits for 4-digit years) for a
367    /// specified date according to universal time.
368    /// Takes [`year_value`, `month_value`, `date_value`]
369    /// Return a `Number` representing the milliseconds elapsed between
370    /// the UNIX epoch and the updated date.
371    ///
372    /// Same as JavaScript's `Date.prototype.setUTCFullYear()`.
373    #[inline]
374    pub fn set_utc_full_year(
375        &self,
376        values: &[JsValue],
377        context: &mut Context,
378    ) -> JsResult<JsValue> {
379        Date::set_full_year::<false>(&self.inner.clone().into(), values, context)
380    }
381
382    /// Sets the hours for a specified date according to universal time.
383    /// Takes [`hours_value`, `minutes_value`, `seconds_value`, `ms_value`]
384    /// Return a `Number` representing the milliseconds elapsed between
385    /// the UNIX epoch and the updated dated.
386    ///
387    /// Same as JavaScript's `Date.prototype.setUTCHours()`.
388    #[inline]
389    pub fn set_utc_hours(&self, values: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
390        Date::set_hours::<false>(&self.inner.clone().into(), values, context)
391    }
392
393    /// Sets the milliseconds for a specified date according to universal time.
394    /// Takes a `milliseconds_value`
395    /// Return a `Number` representing the milliseconds elapsed between
396    /// the UNIX epoch and the updated date.
397    ///
398    /// Same as JavaScript's `Date.prototype.setUTCMilliseconds()`.
399    pub fn set_utc_milliseconds<T>(&self, value: T, context: &mut Context) -> JsResult<JsValue>
400    where
401        T: Into<JsValue>,
402    {
403        Date::set_milliseconds::<false>(&self.inner.clone().into(), &[value.into()], context)
404    }
405
406    /// Sets the minutes for a specified date according to universal time.
407    /// Takes [`minutes_value`, `seconds_value`, `ms_value`]
408    /// Return a `Number` representing the milliseconds elapsed between
409    /// the UNIX epoch and the updated date.
410    ///
411    /// Same as JavaScript's `Date.prototype.setUTCMinutes()`.
412    #[inline]
413    pub fn set_utc_minutes(&self, values: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
414        Date::set_minutes::<false>(&self.inner.clone().into(), values, context)
415    }
416
417    /// Sets the month for a specified date according to universal time.
418    /// Takes [`month_value`, `day_value`]
419    /// Return a `Number` representing the milliseconds elapsed between
420    /// the UNIX epoch and the updated date.
421    ///
422    /// Same as JavaScript's `Date.prototype.setUTCMonth()`.
423    #[inline]
424    pub fn set_utc_month(&self, values: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
425        Date::set_month::<false>(&self.inner.clone().into(), values, context)
426    }
427
428    /// Sets the seconds for a specified date according to universal time.
429    /// Takes [`seconds_value`, `ms_value`]
430    /// Return a `Number` representing the milliseconds elapsed between
431    /// the UNIX epoch and the updated date.
432    ///
433    /// Same as JavaScript's `Date.prototype.setUTCSeconds()`.
434    #[inline]
435    pub fn set_utc_seconds(&self, values: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
436        Date::set_seconds::<false>(&self.inner.clone().into(), values, context)
437    }
438
439    /// Returns the "date" portion of the Date as a human-readable string.
440    ///
441    /// Same as JavaScript's `Date.prototype.toDateString()`.
442    #[inline]
443    pub fn to_date_string(&self, context: &mut Context) -> JsResult<JsValue> {
444        Date::to_date_string(&self.inner.clone().into(), &[JsValue::null()], context)
445    }
446
447    /// DEPRECATED: This feature is no longer recommended.
448    /// USE: `to_utc_string()` instead.
449    /// Returns a string representing the Date based on the GMT timezone.
450    ///
451    /// Same as JavaScript's legacy `Date.prototype.toGMTString()`
452    #[deprecated]
453    #[inline]
454    pub fn to_gmt_string(&self, context: &mut Context) -> JsResult<JsValue> {
455        Date::to_utc_string(&self.inner.clone().into(), &[JsValue::null()], context)
456    }
457
458    /// Returns the given date in the ISO 8601 format according to universal
459    /// time.
460    ///
461    /// Same as JavaScript's `Date.prototype.toISOString()`.
462    #[inline]
463    pub fn to_iso_string(&self, context: &mut Context) -> JsResult<JsValue> {
464        Date::to_iso_string(&self.inner.clone().into(), &[JsValue::null()], context)
465    }
466
467    /// Returns a string representing the Date using `to_iso_string()`.
468    ///
469    /// Same as JavaScript's `Date.prototype.toJSON()`.
470    #[inline]
471    pub fn to_json(&self, context: &mut Context) -> JsResult<JsValue> {
472        Date::to_json(&self.inner.clone().into(), &[JsValue::null()], context)
473    }
474
475    /// Returns a string representing the date portion of the given Date instance
476    /// according to language-specific conventions.
477    /// Takes [locales, options]
478    ///
479    /// Same as JavaScript's `Date.prototype.toLocaleDateString()`.
480    #[inline]
481    pub fn to_local_date_string(
482        &self,
483        values: &[JsValue],
484        context: &mut Context,
485    ) -> JsResult<JsValue> {
486        Date::to_locale_date_string(&self.inner.clone().into(), values, context)
487    }
488
489    /// Returns a string representing the given date according to language-specific conventions.
490    /// Takes [locales, options]
491    ///
492    /// Same as JavaScript's `Date.prototype.toLocaleDateString()`.
493    #[inline]
494    pub fn to_locale_string(&self, values: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
495        Date::to_locale_string(&self.inner.clone().into(), values, context)
496    }
497
498    /// Returns the "time" portion of the Date as human-readable string.
499    ///
500    /// Same as JavaScript's `Date.prototype.toTimeString()`.
501    #[inline]
502    pub fn to_locale_time_string(
503        &self,
504        values: &[JsValue],
505        context: &mut Context,
506    ) -> JsResult<JsValue> {
507        Date::to_locale_time_string(&self.inner.clone().into(), values, context)
508    }
509
510    /// Returns a string representing the specified Date object.
511    ///
512    /// Same as JavaScript's `Date.prototype.toString()`.
513    #[inline]
514    pub fn to_string(&self, context: &mut Context) -> JsResult<JsValue> {
515        Date::to_string(&self.inner.clone().into(), &[JsValue::null()], context)
516    }
517
518    /// Returns the "time" portion of the Date as human-readable string.
519    ///
520    /// Same as JavaScript's `Date.prototype.toTimeString()`.
521    #[inline]
522    pub fn to_time_string(&self, context: &mut Context) -> JsResult<JsValue> {
523        Date::to_time_string(&self.inner.clone().into(), &[JsValue::null()], context)
524    }
525
526    /// Returns a string representing the given date using the UTC time zone.
527    ///
528    /// Same as JavaScript's `Date.prototype.toUTCString()`.
529    #[inline]
530    pub fn to_utc_string(&self, context: &mut Context) -> JsResult<JsValue> {
531        Date::to_utc_string(&self.inner.clone().into(), &[JsValue::null()], context)
532    }
533
534    /// Returns the primitive value pf Date object.
535    ///
536    /// Same as JavaScript's `Date.prototype.valueOf()`.
537    #[inline]
538    pub fn value_of(&self, context: &mut Context) -> JsResult<JsValue> {
539        Date::value_of(&self.inner.clone().into(), &[JsValue::null()], context)
540    }
541
542    /// Utility create a `Date` object from RFC3339 string
543    pub fn new_from_parse(value: &JsValue, context: &mut Context) -> JsResult<Self> {
544        let prototype = context.intrinsics().constructors().date().prototype();
545        let string = value
546            .to_string(context)?
547            .to_std_string()
548            .map_err(|_| JsNativeError::typ().with_message("unpaired surrogate on date string"))?;
549        let t = OffsetDateTime::parse(&string, &Rfc3339)
550            .map_err(|err| JsNativeError::typ().with_message(err.to_string()))?;
551        let date_time = Date::new((t.unix_timestamp() * 1000 + i64::from(t.millisecond())) as f64);
552
553        Ok(Self {
554            inner: JsObject::from_proto_and_data_with_shared_shape(
555                context.root_shape(),
556                prototype,
557                date_time,
558            )
559            .upcast(),
560        })
561    }
562}
563
564impl From<JsDate> for JsObject {
565    #[inline]
566    fn from(o: JsDate) -> Self {
567        o.inner.clone()
568    }
569}
570
571impl From<JsDate> for JsValue {
572    #[inline]
573    fn from(o: JsDate) -> Self {
574        o.inner.clone().into()
575    }
576}
577
578impl Deref for JsDate {
579    type Target = JsObject;
580
581    #[inline]
582    fn deref(&self) -> &Self::Target {
583        &self.inner
584    }
585}
586
587impl TryFromJs for JsDate {
588    fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult<Self> {
589        if let Some(o) = value.as_object() {
590            Self::from_object(o.clone())
591        } else {
592            Err(JsNativeError::typ()
593                .with_message("value is not a Date object")
594                .into())
595        }
596    }
597}