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}