boa_engine/builtins/temporal/plain_date/mod.rs
1//! Boa's implementation of the ECMAScript `Temporal.PlainDate` built-in object.
2//!
3//! This implementation is a ECMAScript compliant wrapper around the `temporal_rs` library.
4
5use std::str::FromStr;
6
7use crate::{
8 Context, JsArgs, JsData, JsError, JsNativeError, JsObject, JsResult, JsString, JsSymbol,
9 JsValue,
10 builtins::{
11 BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject,
12 options::{get_option, get_options_object},
13 temporal::calendar::to_temporal_calendar_identifier,
14 },
15 context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
16 js_string,
17 object::internal_methods::get_prototype_from_constructor,
18 property::Attribute,
19 realm::Realm,
20 string::StaticJsStrings,
21 value::IntoOrUndefined,
22};
23use boa_gc::{Finalize, Trace};
24use icu_calendar::AnyCalendarKind;
25use temporal_rs::{
26 Calendar, MonthCode, PlainDate as InnerDate, TinyAsciiStr,
27 fields::CalendarFields,
28 options::{DisplayCalendar, Overflow},
29 partial::PartialDate,
30};
31
32use super::{
33 PlainDateTime, ZonedDateTime, calendar::get_temporal_calendar_slot_value_with_default,
34 create_temporal_datetime, create_temporal_duration, create_temporal_zoneddatetime,
35 options::get_difference_settings, to_temporal_duration_record, to_temporal_time,
36 to_temporal_timezone_identifier,
37};
38use super::{create_temporal_month_day, create_temporal_year_month};
39
40#[cfg(feature = "temporal")]
41#[cfg(test)]
42mod tests;
43
44/// `Temporal.PlainDate` built-in implementation
45///
46/// A `Temporal.PlainDate` is a calendar date represented by ISO date fields
47/// and a calendar system.
48///
49/// More information:
50///
51/// - [ECMAScript Temporal proposal][spec]
52/// - [MDN reference][mdn]
53/// - [`temporal_rs` documentation][temporal_rs-docs]
54///
55/// [spec]: https://tc39.es/proposal-temporal/#sec-temporal-plaindate-objects
56/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate
57/// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html
58#[derive(Debug, Clone, Trace, Finalize, JsData)]
59#[boa_gc(unsafe_empty_trace)]
60pub struct PlainDate {
61 pub(crate) inner: InnerDate,
62}
63
64impl PlainDate {
65 pub(crate) fn new(inner: InnerDate) -> Self {
66 Self { inner }
67 }
68}
69
70impl BuiltInObject for PlainDate {
71 const NAME: JsString = StaticJsStrings::PLAIN_DATE_NAME;
72}
73
74impl IntrinsicObject for PlainDate {
75 fn init(realm: &Realm) {
76 let get_calendar_id = BuiltInBuilder::callable(realm, Self::get_calendar_id)
77 .name(js_string!("get calendarId"))
78 .build();
79
80 let get_era = BuiltInBuilder::callable(realm, Self::get_era)
81 .name(js_string!("get era"))
82 .build();
83
84 let get_era_year = BuiltInBuilder::callable(realm, Self::get_era_year)
85 .name(js_string!("get eraYear"))
86 .build();
87
88 let get_year = BuiltInBuilder::callable(realm, Self::get_year)
89 .name(js_string!("get year"))
90 .build();
91
92 let get_month = BuiltInBuilder::callable(realm, Self::get_month)
93 .name(js_string!("get month"))
94 .build();
95
96 let get_month_code = BuiltInBuilder::callable(realm, Self::get_month_code)
97 .name(js_string!("get monthCode"))
98 .build();
99
100 let get_day = BuiltInBuilder::callable(realm, Self::get_day)
101 .name(js_string!("get day"))
102 .build();
103
104 let get_day_of_week = BuiltInBuilder::callable(realm, Self::get_day_of_week)
105 .name(js_string!("get dayOfWeek"))
106 .build();
107
108 let get_day_of_year = BuiltInBuilder::callable(realm, Self::get_day_of_year)
109 .name(js_string!("get dayOfYear"))
110 .build();
111
112 let get_week_of_year = BuiltInBuilder::callable(realm, Self::get_week_of_year)
113 .name(js_string!("get weekOfYear"))
114 .build();
115
116 let get_year_of_week = BuiltInBuilder::callable(realm, Self::get_year_of_week)
117 .name(js_string!("get yearOfWeek"))
118 .build();
119
120 let get_days_in_week = BuiltInBuilder::callable(realm, Self::get_days_in_week)
121 .name(js_string!("get daysInWeek"))
122 .build();
123
124 let get_days_in_month = BuiltInBuilder::callable(realm, Self::get_days_in_month)
125 .name(js_string!("get daysInMonth"))
126 .build();
127
128 let get_days_in_year = BuiltInBuilder::callable(realm, Self::get_days_in_year)
129 .name(js_string!("get daysInYear"))
130 .build();
131
132 let get_months_in_year = BuiltInBuilder::callable(realm, Self::get_months_in_year)
133 .name(js_string!("get monthsInYear"))
134 .build();
135
136 let get_in_leap_year = BuiltInBuilder::callable(realm, Self::get_in_leap_year)
137 .name(js_string!("get inLeapYear"))
138 .build();
139
140 BuiltInBuilder::from_standard_constructor::<Self>(realm)
141 .property(
142 JsSymbol::to_string_tag(),
143 StaticJsStrings::PLAIN_DATE_TAG,
144 Attribute::CONFIGURABLE,
145 )
146 .accessor(
147 js_string!("calendarId"),
148 Some(get_calendar_id),
149 None,
150 Attribute::CONFIGURABLE,
151 )
152 .accessor(
153 js_string!("era"),
154 Some(get_era),
155 None,
156 Attribute::CONFIGURABLE,
157 )
158 .accessor(
159 js_string!("eraYear"),
160 Some(get_era_year),
161 None,
162 Attribute::CONFIGURABLE,
163 )
164 .accessor(
165 js_string!("year"),
166 Some(get_year),
167 None,
168 Attribute::CONFIGURABLE,
169 )
170 .accessor(
171 js_string!("month"),
172 Some(get_month),
173 None,
174 Attribute::CONFIGURABLE,
175 )
176 .accessor(
177 js_string!("monthCode"),
178 Some(get_month_code),
179 None,
180 Attribute::CONFIGURABLE,
181 )
182 .accessor(
183 js_string!("day"),
184 Some(get_day),
185 None,
186 Attribute::CONFIGURABLE,
187 )
188 .accessor(
189 js_string!("dayOfWeek"),
190 Some(get_day_of_week),
191 None,
192 Attribute::CONFIGURABLE,
193 )
194 .accessor(
195 js_string!("dayOfYear"),
196 Some(get_day_of_year),
197 None,
198 Attribute::CONFIGURABLE,
199 )
200 .accessor(
201 js_string!("weekOfYear"),
202 Some(get_week_of_year),
203 None,
204 Attribute::CONFIGURABLE,
205 )
206 .accessor(
207 js_string!("yearOfWeek"),
208 Some(get_year_of_week),
209 None,
210 Attribute::CONFIGURABLE,
211 )
212 .accessor(
213 js_string!("daysInWeek"),
214 Some(get_days_in_week),
215 None,
216 Attribute::CONFIGURABLE,
217 )
218 .accessor(
219 js_string!("daysInMonth"),
220 Some(get_days_in_month),
221 None,
222 Attribute::CONFIGURABLE,
223 )
224 .accessor(
225 js_string!("daysInYear"),
226 Some(get_days_in_year),
227 None,
228 Attribute::CONFIGURABLE,
229 )
230 .accessor(
231 js_string!("monthsInYear"),
232 Some(get_months_in_year),
233 None,
234 Attribute::CONFIGURABLE,
235 )
236 .accessor(
237 js_string!("inLeapYear"),
238 Some(get_in_leap_year),
239 None,
240 Attribute::CONFIGURABLE,
241 )
242 .static_method(Self::from, js_string!("from"), 1)
243 .static_method(Self::compare, js_string!("compare"), 2)
244 .method(Self::to_plain_year_month, js_string!("toPlainYearMonth"), 0)
245 .method(Self::to_plain_month_day, js_string!("toPlainMonthDay"), 0)
246 .method(Self::add, js_string!("add"), 1)
247 .method(Self::subtract, js_string!("subtract"), 1)
248 .method(Self::with, js_string!("with"), 1)
249 .method(Self::with_calendar, js_string!("withCalendar"), 1)
250 .method(Self::until, js_string!("until"), 1)
251 .method(Self::since, js_string!("since"), 1)
252 .method(Self::equals, js_string!("equals"), 1)
253 .method(Self::to_plain_date_time, js_string!("toPlainDateTime"), 0)
254 .method(Self::to_zoned_date_time, js_string!("toZonedDateTime"), 1)
255 .method(Self::to_string, js_string!("toString"), 0)
256 .method(Self::to_locale_string, js_string!("toLocaleString"), 0)
257 .method(Self::to_json, js_string!("toJSON"), 0)
258 .method(Self::value_of, js_string!("valueOf"), 0)
259 .build();
260 }
261
262 fn get(intrinsics: &Intrinsics) -> JsObject {
263 Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor()
264 }
265}
266
267impl BuiltInConstructor for PlainDate {
268 const CONSTRUCTOR_ARGUMENTS: usize = 3;
269 const PROTOTYPE_STORAGE_SLOTS: usize = 48;
270 const CONSTRUCTOR_STORAGE_SLOTS: usize = 2;
271
272 const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
273 StandardConstructors::plain_date;
274
275 fn constructor(
276 new_target: &JsValue,
277 args: &[JsValue],
278 context: &mut Context,
279 ) -> JsResult<JsValue> {
280 if new_target.is_undefined() {
281 return Err(JsNativeError::typ()
282 .with_message("NewTarget cannot be undefined.")
283 .into());
284 }
285
286 let year = args
287 .get_or_undefined(0)
288 .to_finitef64(context)?
289 .as_integer_with_truncation::<i32>();
290 let month = args
291 .get_or_undefined(1)
292 .to_finitef64(context)?
293 .as_integer_with_truncation::<u8>();
294 let day = args
295 .get_or_undefined(2)
296 .to_finitef64(context)?
297 .as_integer_with_truncation::<u8>();
298 let calendar_slot = args
299 .get_or_undefined(3)
300 .map(|s| {
301 s.as_string()
302 .as_ref()
303 .map(JsString::to_std_string_lossy)
304 .ok_or_else(|| JsNativeError::typ().with_message("calendar must be a string."))
305 })
306 .transpose()?
307 .map(|s| Calendar::try_from_utf8(s.as_bytes()))
308 .transpose()?
309 .unwrap_or_default();
310
311 let inner = InnerDate::try_new(year, month, day, calendar_slot)?;
312
313 Ok(create_temporal_date(inner, Some(new_target), context)?.into())
314 }
315}
316
317// ==== `PlainDate` accessors implementation ====
318
319impl PlainDate {
320 /// 3.3.3 get `Temporal.PlainDate.prototype.calendarId`
321 ///
322 /// Returns the calendar identifier for this `Temporal.PlainDate`.
323 ///
324 /// More information:
325 ///
326 /// - [ECMAScript Temporal proposal][spec]
327 /// - [MDN reference][mdn]
328 /// - [`temporal_rs` documentation][temporal_rs-docs]
329 ///
330 /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.calendarid
331 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/calendarId
332 /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html#method.calendar
333 fn get_calendar_id(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
334 let object = this.as_object();
335 let date = object
336 .as_ref()
337 .and_then(JsObject::downcast_ref::<Self>)
338 .ok_or_else(|| {
339 JsNativeError::typ().with_message("the this object must be a PlainDate object.")
340 })?;
341
342 Ok(JsString::from(date.inner.calendar().identifier()).into())
343 }
344
345 /// 3.3.4 get `Temporal.PlainDate.prototype.era`
346 ///
347 /// Returns the era identifier for this `Temporal.PlainDate`.
348 ///
349 /// More information:
350 ///
351 /// - [ECMAScript Temporal proposal][spec]
352 /// - [MDN reference][mdn]
353 /// - [`temporal_rs` documentation][temporal_rs-docs]
354 ///
355 /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.era
356 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/era
357 /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html#method.era
358 fn get_era(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
359 let obj = this
360 .as_object()
361 .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
362
363 let Some(date) = obj.downcast_ref::<Self>() else {
364 return Err(JsNativeError::typ()
365 .with_message("the this object must be a PlainDate object.")
366 .into());
367 };
368
369 Ok(date
370 .inner
371 .era()
372 .map(|s| JsString::from(s.as_str()))
373 .into_or_undefined())
374 }
375
376 /// 3.3.5 get `Temporal.PlainDate.prototype.eraYear`
377 ///
378 /// Returns the year within the era for this `Temporal.PlainDate`.
379 ///
380 /// More information:
381 ///
382 /// - [ECMAScript Temporal proposal][spec]
383 /// - [MDN reference][mdn]
384 /// - [`temporal_rs` documentation][temporal_rs-docs]
385 ///
386 /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.erayear
387 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/eraYear
388 /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html#method.era_year
389 fn get_era_year(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
390 let obj = this
391 .as_object()
392 .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
393
394 let Some(date) = obj.downcast_ref::<Self>() else {
395 return Err(JsNativeError::typ()
396 .with_message("the this object must be a PlainDate object.")
397 .into());
398 };
399
400 Ok(date.inner.era_year().into_or_undefined())
401 }
402
403 /// 3.3.6 get `Temporal.PlainDate.prototype.year`
404 ///
405 /// Returns the calendar year for this `Temporal.PlainDate`.
406 ///
407 /// More information:
408 ///
409 /// - [ECMAScript Temporal proposal][spec]
410 /// - [MDN reference][mdn]
411 /// - [`temporal_rs` documentation][temporal_rs-docs]
412 ///
413 /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.year
414 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/year
415 /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html#method.year
416 fn get_year(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
417 let obj = this
418 .as_object()
419 .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
420
421 let Some(date) = obj.downcast_ref::<Self>() else {
422 return Err(JsNativeError::typ()
423 .with_message("the this object must be a PlainDate object.")
424 .into());
425 };
426
427 Ok(date.inner.year().into())
428 }
429
430 /// 3.3.7 get `Temporal.PlainDate.prototype.month`
431 ///
432 /// Returns the calendar month for this `Temporal.PlainDate`.
433 ///
434 /// More information:
435 ///
436 /// - [ECMAScript Temporal proposal][spec]
437 /// - [MDN reference][mdn]
438 /// - [`temporal_rs` documentation][temporal_rs-docs]
439 ///
440 /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.month
441 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/month
442 /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html#method.month
443 fn get_month(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
444 let obj = this
445 .as_object()
446 .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
447
448 let Some(date) = obj.downcast_ref::<Self>() else {
449 return Err(JsNativeError::typ()
450 .with_message("the this object must be a PlainDate object.")
451 .into());
452 };
453
454 Ok(date.inner.month().into())
455 }
456
457 /// 3.3.8 get Temporal.PlainDate.prototype.monthCode
458 ///
459 /// Returns the month code of the calendar month for this `Temporal.PlainDate`.
460 ///
461 /// More information:
462 ///
463 /// - [ECMAScript Temporal proposal][spec]
464 /// - [MDN reference][mdn]
465 /// - [`temporal_rs` documentation][temporal_rs-docs]
466 ///
467 /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.monthcode
468 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/monthCode
469 /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html#method.month_code
470 fn get_month_code(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
471 let obj = this
472 .as_object()
473 .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
474
475 let Some(date) = obj.downcast_ref::<Self>() else {
476 return Err(JsNativeError::typ()
477 .with_message("the this object must be a PlainDate object.")
478 .into());
479 };
480
481 Ok(JsString::from(date.inner.month_code().as_str()).into())
482 }
483
484 /// 3.3.9 get `Temporal.PlainDate.prototype.day`
485 ///
486 /// Returns the day in the calendar month for this `Temporal.PlainDate`.
487 ///
488 /// More information:
489 ///
490 /// - [ECMAScript Temporal proposal][spec]
491 /// - [MDN reference][mdn]
492 /// - [`temporal_rs` documentation][temporal_rs-docs]
493 ///
494 /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.day
495 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/day
496 /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html#method.day
497 fn get_day(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
498 let obj = this
499 .as_object()
500 .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
501
502 let Some(date) = obj.downcast_ref::<Self>() else {
503 return Err(JsNativeError::typ()
504 .with_message("the this object must be a PlainDate object.")
505 .into());
506 };
507
508 Ok(date.inner.day().into())
509 }
510
511 /// 3.3.10 get `Temporal.PlainDate.prototype.dayOfWeek`
512 ///
513 /// More information:
514 ///
515 /// - [ECMAScript Temporal proposal][spec]
516 /// - [MDN reference][mdn]
517 /// - [`temporal_rs` documentation][temporal_rs-docs]
518 ///
519 /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.dayofweek
520 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/dayOfWeek
521 /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html#method.day_of_week
522 fn get_day_of_week(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
523 let obj = this
524 .as_object()
525 .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
526
527 let Some(date) = obj.downcast_ref::<Self>() else {
528 return Err(JsNativeError::typ()
529 .with_message("the this object must be a PlainDate object.")
530 .into());
531 };
532
533 Ok(date.inner.day_of_week().into())
534 }
535
536 /// 3.3.11 get `Temporal.PlainDate.prototype.dayOfYear`
537 ///
538 /// More information:
539 ///
540 /// - [ECMAScript Temporal proposal][spec]
541 /// - [MDN reference][mdn]
542 /// - [`temporal_rs` documentation][temporal_rs-docs]
543 ///
544 /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.dayofyear
545 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/dayOfYear
546 /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html#method.day_of_year
547 fn get_day_of_year(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
548 let obj = this
549 .as_object()
550 .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
551
552 let Some(date) = obj.downcast_ref::<Self>() else {
553 return Err(JsNativeError::typ()
554 .with_message("the this object must be a PlainDate object.")
555 .into());
556 };
557
558 Ok(date.inner.day_of_year().into())
559 }
560
561 /// 3.3.12 get `Temporal.PlainDate.prototype.weekOfYear`
562 ///
563 /// More information:
564 ///
565 /// - [ECMAScript Temporal proposal][spec]
566 /// - [MDN reference][mdn]
567 /// - [`temporal_rs` documentation][temporal_rs-docs]
568 ///
569 /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.weekofyear
570 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/weekOfYear
571 /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html#method.week_of_year
572 fn get_week_of_year(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
573 let obj = this
574 .as_object()
575 .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
576
577 let Some(date) = obj.downcast_ref::<Self>() else {
578 return Err(JsNativeError::typ()
579 .with_message("the this object must be a PlainDate object.")
580 .into());
581 };
582
583 Ok(date.inner.week_of_year().into_or_undefined())
584 }
585
586 /// 3.3.13 get `Temporal.PlainDate.prototype.yearOfWeek`
587 ///
588 /// More information:
589 ///
590 /// - [ECMAScript Temporal proposal][spec]
591 /// - [MDN reference][mdn]
592 /// - [`temporal_rs` documentation][temporal_rs-docs]
593 ///
594 /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.yearofweek
595 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/yearOfWeek
596 /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html#method.year_of_week
597 fn get_year_of_week(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
598 let obj = this
599 .as_object()
600 .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
601
602 let Some(date) = obj.downcast_ref::<Self>() else {
603 return Err(JsNativeError::typ()
604 .with_message("the this object must be a PlainDate object.")
605 .into());
606 };
607
608 Ok(date.inner.year_of_week().into_or_undefined())
609 }
610
611 /// 3.3.14 get `Temporal.PlainDate.prototype.daysInWeek`
612 ///
613 /// More information:
614 ///
615 /// - [ECMAScript Temporal proposal][spec]
616 /// - [MDN reference][mdn]
617 /// - [`temporal_rs` documentation][temporal_rs-docs]
618 ///
619 /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.daysinweek
620 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/daysInWeek
621 /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html#method.days_in_week
622 fn get_days_in_week(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
623 let obj = this
624 .as_object()
625 .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
626
627 let Some(date) = obj.downcast_ref::<Self>() else {
628 return Err(JsNativeError::typ()
629 .with_message("the this object must be a PlainDate object.")
630 .into());
631 };
632
633 Ok(date.inner.days_in_week().into())
634 }
635
636 /// 3.3.15 get `Temporal.PlainDate.prototype.daysInMonth`
637 ///
638 /// More information:
639 ///
640 /// - [ECMAScript Temporal proposal][spec]
641 /// - [MDN reference][mdn]
642 /// - [`temporal_rs` documentation][temporal_rs-docs]
643 ///
644 /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.daysinmonth
645 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/daysInMonth
646 /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html#method.days_in_month
647 fn get_days_in_month(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
648 let obj = this
649 .as_object()
650 .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
651
652 let Some(date) = obj.downcast_ref::<Self>() else {
653 return Err(JsNativeError::typ()
654 .with_message("the this object must be a PlainDate object.")
655 .into());
656 };
657
658 Ok(date.inner.days_in_month().into())
659 }
660
661 /// 3.3.16 get `Temporal.PlainDate.prototype.daysInYear`
662 ///
663 /// More information:
664 ///
665 /// - [ECMAScript Temporal proposal][spec]
666 /// - [MDN reference][mdn]
667 /// - [`temporal_rs` documentation][temporal_rs-docs]
668 ///
669 /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.daysinyear
670 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/daysInYear
671 /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html#method.days_in_year
672 fn get_days_in_year(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
673 let obj = this
674 .as_object()
675 .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
676
677 let Some(date) = obj.downcast_ref::<Self>() else {
678 return Err(JsNativeError::typ()
679 .with_message("the this object must be a PlainDate object.")
680 .into());
681 };
682
683 Ok(date.inner.days_in_year().into())
684 }
685
686 /// 3.3.17 get `Temporal.PlainDate.prototype.monthsInYear`
687 ///
688 /// More information:
689 ///
690 /// - [ECMAScript Temporal proposal][spec]
691 /// - [MDN reference][mdn]
692 /// - [`temporal_rs` documentation][temporal_rs-docs]
693 ///
694 /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.monthsinyear
695 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/monthsInYear
696 /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html#method.months_in_year
697 fn get_months_in_year(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
698 let obj = this
699 .as_object()
700 .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
701
702 let Some(date) = obj.downcast_ref::<Self>() else {
703 return Err(JsNativeError::typ()
704 .with_message("the this object must be a PlainDate object.")
705 .into());
706 };
707
708 Ok(date.inner.months_in_year().into())
709 }
710
711 /// 3.3.18 get `Temporal.PlainDate.prototype.inLeapYear`
712 ///
713 /// More information:
714 ///
715 /// - [ECMAScript Temporal proposal][spec]
716 /// - [MDN reference][mdn]
717 /// - [`temporal_rs` documentation][temporal_rs-docs]
718 ///
719 /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.inleapyear
720 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/inLeapYear
721 /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html#method.inLeapYear
722 fn get_in_leap_year(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
723 let obj = this
724 .as_object()
725 .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
726
727 let Some(date) = obj.downcast_ref::<Self>() else {
728 return Err(JsNativeError::typ()
729 .with_message("the this object must be a PlainDate object.")
730 .into());
731 };
732
733 Ok(date.inner.in_leap_year().into())
734 }
735}
736
737// ==== `PlainDate` static methods implementation ====
738
739impl PlainDate {
740 /// 3.2.2 `Temporal.PlainDate.from ( item [ , options ] )`
741 ///
742 /// More information:
743 ///
744 /// - [ECMAScript Temporal proposal][spec]
745 /// - [MDN reference][mdn]
746 ///
747 /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaindate.from
748 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/from
749 fn from(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
750 let item = args.get_or_undefined(0);
751 let options = args.get(1);
752
753 let object = item.as_object();
754 if let Some(date) = object.as_ref().and_then(JsObject::downcast_ref::<Self>) {
755 let options = get_options_object(options.unwrap_or(&JsValue::undefined()))?;
756 let _ = get_option::<Overflow>(&options, js_string!("overflow"), context)?;
757 return create_temporal_date(date.inner.clone(), None, context).map(Into::into);
758 }
759
760 let resolved_date = to_temporal_date(item, options.cloned(), context)?;
761 create_temporal_date(resolved_date, None, context).map(Into::into)
762 }
763
764 /// 3.2.3 `Temporal.PlainDate.compare ( one, two )`
765 ///
766 /// More information:
767 ///
768 /// - [ECMAScript Temporal proposal][spec]
769 /// - [MDN reference][mdn]
770 /// - [`temporal_rs` documentation][temporal_rs-docs]
771 ///
772 /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaindate.compare
773 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/compare
774 /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html#method.compare_iso
775 fn compare(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
776 let one = to_temporal_date(args.get_or_undefined(0), None, context)?;
777 let two = to_temporal_date(args.get_or_undefined(1), None, context)?;
778
779 Ok((one.compare_iso(&two) as i8).into())
780 }
781}
782
783// ==== `PlainDate` method implementation ====
784
785impl PlainDate {
786 /// 3.3.19 `Temporal.PlainDate.toPlainYearMonth ( )`
787 ///
788 /// More information:
789 ///
790 /// - [ECMAScript Temporal proposal][spec]
791 /// - [MDN reference][mdn]
792 /// - [`temporal_rs` documentation][temporal_rs-docs]
793 ///
794 /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.toplainyearmonth
795 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/toPlainYearMonth
796 /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html#method.to_plain_year_month
797 fn to_plain_year_month(
798 this: &JsValue,
799 _: &[JsValue],
800 context: &mut Context,
801 ) -> JsResult<JsValue> {
802 // 1. Let temporalDate be the this value.
803 // 2. Perform ? RequireInternalSlot(temporalDate, [[InitializedTemporalDate]]).
804 let object = this.as_object();
805 let date = object
806 .as_ref()
807 .and_then(JsObject::downcast_ref::<Self>)
808 .ok_or_else(|| {
809 JsNativeError::typ().with_message("the this object must be a PlainDate object.")
810 })?;
811
812 let year_month = date.inner.to_plain_year_month()?;
813 create_temporal_year_month(year_month, None, context)
814 }
815
816 /// 3.3.20 `Temporal.PlainDate.toPlainMonthDay ( )`
817 ///
818 /// More information:
819 ///
820 /// - [ECMAScript Temporal proposal][spec]
821 /// - [MDN reference][mdn]
822 /// - [`temporal_rs` documentation][temporal_rs-docs]
823 ///
824 /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.toplainmonthday
825 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/toPlainMonthDay
826 /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html#method.to_plain_month_day
827 fn to_plain_month_day(
828 this: &JsValue,
829 _: &[JsValue],
830 context: &mut Context,
831 ) -> JsResult<JsValue> {
832 // 1. Let temporalDate be the this value.
833 // 2. Perform ? RequireInternalSlot(temporalDate, [[InitializedTemporalDate]]).
834 let object = this.as_object();
835 let date = object
836 .as_ref()
837 .and_then(JsObject::downcast_ref::<Self>)
838 .ok_or_else(|| {
839 JsNativeError::typ().with_message("the this object must be a PlainDate object.")
840 })?;
841
842 let month_day = date.inner.to_plain_month_day()?;
843 create_temporal_month_day(month_day, None, context)
844 }
845
846 /// 3.3.21 `Temporal.PlainDate.add ( temporalDurationLike [ , options ] )`
847 ///
848 /// More information:
849 ///
850 /// - [ECMAScript Temporal proposal][spec]
851 /// - [MDN reference][mdn]
852 /// - [`temporal_rs` documentation][temporal_rs-docs]
853 ///
854 /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.add
855 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/add
856 /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html#method.add
857 fn add(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
858 // 1. Let temporalDate be the this value.
859 // 2. Perform ? RequireInternalSlot(temporalDate, [[InitializedTemporalDate]]).
860 let object = this.as_object();
861 let date = object
862 .as_ref()
863 .and_then(JsObject::downcast_ref::<Self>)
864 .ok_or_else(|| {
865 JsNativeError::typ().with_message("the this object must be a PlainDate object.")
866 })?;
867
868 // 3. Let duration be ? ToTemporalDuration(temporalDurationLike).
869 let duration = to_temporal_duration_record(args.get_or_undefined(0), context)?;
870
871 // 4. Set options to ? GetOptionsObject(options).
872 let options = get_options_object(args.get_or_undefined(1))?;
873
874 let overflow = get_option::<Overflow>(&options, js_string!("overflow"), context)?;
875
876 // 5. Let calendarRec be ? CreateCalendarMethodsRecord(temporalDate.[[Calendar]], « date-add »).
877 // 6. Return ? AddDate(calendarRec, temporalDate, duration, options).
878 let resolved_date = date.inner.add(&duration, overflow)?;
879 create_temporal_date(resolved_date, None, context).map(Into::into)
880 }
881
882 /// 3.3.22 `Temporal.PlainDate.subtract ( temporalDurationLike [ , options ] )`
883 ///
884 /// More information:
885 ///
886 /// - [ECMAScript Temporal proposal][spec]
887 /// - [MDN reference][mdn]
888 /// - [`temporal_rs` documentation][temporal_rs-docs]
889 ///
890 /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.subtract
891 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/subtract
892 /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html#method.subtract
893 fn subtract(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
894 // 1. Let temporalDate be the this value.
895 // 2. Perform ? RequireInternalSlot(temporalDate, [[InitializedTemporalDate]]).
896 let object = this.as_object();
897 let date = object
898 .as_ref()
899 .and_then(JsObject::downcast_ref::<Self>)
900 .ok_or_else(|| {
901 JsNativeError::typ().with_message("the this object must be a PlainDate object.")
902 })?;
903
904 // 3. Let duration be ? ToTemporalDuration(temporalDurationLike).
905 let duration = to_temporal_duration_record(args.get_or_undefined(0), context)?;
906
907 // 4. Set options to ? GetOptionsObject(options).
908 let options = get_options_object(args.get_or_undefined(1))?;
909 let overflow = get_option::<Overflow>(&options, js_string!("overflow"), context)?;
910
911 // 5. Let negatedDuration be CreateNegatedTemporalDuration(duration).
912 // 6. Let calendarRec be ? CreateCalendarMethodsRecord(temporalDate.[[Calendar]], « date-add »).
913 // 7. Return ? AddDate(calendarRec, temporalDate, negatedDuration, options).
914 let resolved_date = date.inner.subtract(&duration, overflow)?;
915 create_temporal_date(resolved_date, None, context).map(Into::into)
916 }
917
918 // 3.3.23 `Temporal.PlainDate.prototype.with ( temporalDateLike [ , options ] )`
919 ///
920 /// More information:
921 ///
922 /// - [ECMAScript Temporal proposal][spec]
923 /// - [MDN reference][mdn]
924 /// - [`temporal_rs` documentation][temporal_rs-docs]
925 ///
926 /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.with
927 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/with
928 /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html#method.with
929 fn with(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
930 // 1. Let temporalDate be the this value.
931 // 2. Perform ? RequireInternalSlot(temporalDate, [[InitializedTemporalDate]]).
932 let object = this.as_object();
933 let date = object
934 .as_ref()
935 .and_then(JsObject::downcast_ref::<Self>)
936 .ok_or_else(|| {
937 JsNativeError::typ().with_message("the this object must be a PlainDate object.")
938 })?;
939
940 // 3. If ? IsPartialTemporalObject(temporalDateLike) is false, throw a TypeError exception.
941 let Some(partial_object) =
942 super::is_partial_temporal_object(args.get_or_undefined(0), context)?
943 else {
944 return Err(JsNativeError::typ()
945 .with_message("with object was not a PartialTemporalObject.")
946 .into());
947 };
948
949 // SKIP: Steps 4-9 are handled by the with method of temporal_rs's Date
950 // 4. Let calendar be temporalDate.[[Calendar]].
951 // 5. Let fields be ISODateToFields(calendar, temporalDate.[[ISODate]], date).
952 // 6. Let partialDate be ? PrepareCalendarFields(calendar, temporalDateLike, « year, month, month-code, day », « », partial).
953 // 7. Set fields to CalendarMergeFields(calendar, fields, partialDate).
954 let fields = to_calendar_fields(&partial_object, date.inner.calendar(), context)?;
955
956 // 8. Let resolvedOptions be ? GetOptionsObject(options).
957 let options = get_options_object(args.get_or_undefined(1))?;
958 // 9. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
959 let overflow = get_option::<Overflow>(&options, js_string!("overflow"), context)?;
960
961 // 10. Return ? CalendarDateFromFields(calendarRec, fields, resolvedOptions).
962 let resolved_date = date.inner.with(fields, overflow)?;
963 create_temporal_date(resolved_date, None, context).map(Into::into)
964 }
965
966 // UPDATED: nekevss, 2025-07-21
967 /// 3.3.24 `Temporal.PlainDate.prototype.withCalendar ( calendarLike )`
968 ///
969 /// More information:
970 ///
971 /// - [ECMAScript Temporal proposal][spec]
972 /// - [MDN reference][mdn]
973 /// - [`temporal_rs` documentation][temporal_rs-docs]
974 ///
975 /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.withcalendar
976 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/withCalendar
977 /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html#method.with_calendar
978 fn with_calendar(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
979 // 1. Let plainDate be the this value.
980 let object = this.as_object();
981 // 2. Perform ? RequireInternalSlot(plainDate, [[InitializedTemporalDate]]).
982 let date = object
983 .as_ref()
984 .and_then(JsObject::downcast_ref::<Self>)
985 .ok_or_else(|| {
986 JsNativeError::typ().with_message("the this object must be a PlainDate object.")
987 })?;
988
989 // 3. Let calendar be ? ToTemporalCalendarIdentifier(calendarLike).
990 let calendar = to_temporal_calendar_identifier(args.get_or_undefined(0))?;
991 // 4. Return ! CreateTemporalDate(plainDate.[[ISODate]], calendar).
992 let resolved_date = date.inner.with_calendar(calendar);
993 create_temporal_date(resolved_date, None, context).map(Into::into)
994 }
995
996 /// 3.3.25 `Temporal.PlainDate.prototype.until ( other [ , options ] )`
997 ///
998 /// More information:
999 ///
1000 /// - [ECMAScript Temporal proposal][spec]
1001 /// - [MDN reference][mdn]
1002 /// - [`temporal_rs` documentation][temporal_rs-docs]
1003 ///
1004 /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.until
1005 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/until
1006 /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html#method.until
1007 fn until(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
1008 // 1. Let temporalDate be the this value.
1009 // 2. Perform ? RequireInternalSlot(temporalDate, [[InitializedTemporalDate]]).
1010 let object = this.as_object();
1011 let date = object
1012 .as_ref()
1013 .and_then(JsObject::downcast_ref::<Self>)
1014 .ok_or_else(|| {
1015 JsNativeError::typ().with_message("the this object must be a PlainDate object.")
1016 })?;
1017
1018 let other = to_temporal_date(args.get_or_undefined(0), None, context)?;
1019
1020 // 3. Return ? DifferenceTemporalPlainDate(until, temporalDate, other, options).
1021 let options = get_options_object(args.get_or_undefined(1))?;
1022 let settings = get_difference_settings(&options, context)?;
1023
1024 create_temporal_duration(date.inner.until(&other, settings)?, None, context).map(Into::into)
1025 }
1026
1027 /// 3.3.26 `Temporal.PlainDate.prototype.since ( other [ , options ] )`
1028 ///
1029 /// More information:
1030 ///
1031 /// - [ECMAScript Temporal proposal][spec]
1032 /// - [MDN reference][mdn]
1033 /// - [`temporal_rs` documentation][temporal_rs-docs]
1034 ///
1035 /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.since
1036 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/since
1037 /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html#method.since
1038 fn since(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
1039 // 1. Let temporalDate be the this value.
1040 // 2. Perform ? RequireInternalSlot(temporalDate, [[InitializedTemporalDate]]).
1041 let object = this.as_object();
1042 let date = object
1043 .as_ref()
1044 .and_then(JsObject::downcast_ref::<Self>)
1045 .ok_or_else(|| {
1046 JsNativeError::typ().with_message("the this object must be a PlainDate object.")
1047 })?;
1048
1049 // 3. Return ? DifferenceTemporalPlainDate(since, temporalDate, other, options).
1050 let other = to_temporal_date(args.get_or_undefined(0), None, context)?;
1051
1052 let options = get_options_object(args.get_or_undefined(1))?;
1053 let settings = get_difference_settings(&options, context)?;
1054
1055 create_temporal_duration(date.inner.since(&other, settings)?, None, context).map(Into::into)
1056 }
1057
1058 /// 3.3.27 `Temporal.PlainDate.prototype.equals ( other )`
1059 ///
1060 /// More information:
1061 ///
1062 /// - [ECMAScript Temporal proposal][spec]
1063 /// - [MDN reference][mdn]
1064 /// - [`temporal_rs` documentation][temporal_rs-docs]
1065 ///
1066 /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.equals
1067 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/equals
1068 /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html#impl-Eq-for-PlainDate
1069 fn equals(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
1070 let object = this.as_object();
1071 let date = object
1072 .as_ref()
1073 .and_then(JsObject::downcast_ref::<Self>)
1074 .ok_or_else(|| {
1075 JsNativeError::typ().with_message("the this object must be a PlainDate object.")
1076 })?;
1077
1078 let other = to_temporal_date(args.get_or_undefined(0), None, context)?;
1079
1080 Ok((date.inner == other).into())
1081 }
1082
1083 /// 3.3.28 `Temporal.PlainDate.prototype.toPlainDateTime ( [ temporalTime ] )`
1084 ///
1085 /// More information:
1086 ///
1087 /// - [ECMAScript Temporal proposal][spec]
1088 /// - [MDN reference][mdn]
1089 /// - [`temporal_rs` documentation][temporal_rs-docs]
1090 ///
1091 /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.toplaindatetime
1092 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/toPlainDateTime
1093 /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html#method.to_plain_date_time
1094 fn to_plain_date_time(
1095 this: &JsValue,
1096 args: &[JsValue],
1097 context: &mut Context,
1098 ) -> JsResult<JsValue> {
1099 // 1. Let temporalDate be the this value.
1100 // 2. Perform ? RequireInternalSlot(temporalDate, [[InitializedTemporalDate]]).
1101 let object = this.as_object();
1102 let date = object
1103 .as_ref()
1104 .and_then(JsObject::downcast_ref::<Self>)
1105 .ok_or_else(|| {
1106 JsNativeError::typ().with_message("the this object must be a PlainDate object.")
1107 })?;
1108
1109 // 3. Set temporalTime to ? ToTemporalTimeOrMidnight(temporalTime).
1110 let time = args
1111 .get_or_undefined(0)
1112 .map(|v| to_temporal_time(v, None, context))
1113 .transpose()?;
1114 // 4. Return ? CreateTemporalDateTime(temporalDate.[[ISOYear]], temporalDate.[[ISOMonth]], temporalDate.[[ISODay]], temporalTime.[[ISOHour]], temporalTime.[[ISOMinute]], temporalTime.[[ISOSecond]], temporalTime.[[ISOMillisecond]], temporalTime.[[ISOMicrosecond]], temporalTime.[[ISONanosecond]], temporalDate.[[Calendar]]).
1115 create_temporal_datetime(date.inner.to_plain_date_time(time)?, None, context)
1116 .map(Into::into)
1117 }
1118
1119 /// 3.3.29 `Temporal.PlainDate.prototype.toZonedDateTime ( item )`
1120 ///
1121 /// More information:
1122 ///
1123 /// - [ECMAScript Temporal proposal][spec]
1124 /// - [MDN reference][mdn]
1125 /// - [`temporal_rs` documentation][temporal_rs-docs]
1126 ///
1127 /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.tozoneddatetime
1128 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/toZonedDateTime
1129 /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html#method.to_zoned_date_time
1130 fn to_zoned_date_time(
1131 this: &JsValue,
1132 args: &[JsValue],
1133 context: &mut Context,
1134 ) -> JsResult<JsValue> {
1135 // 1. Let temporalDate be the this value.
1136 // 2. Perform ? RequireInternalSlot(temporalDate, [[InitializedTemporalDate]]).
1137 let object = this.as_object();
1138 let date = object
1139 .as_ref()
1140 .and_then(JsObject::downcast_ref::<Self>)
1141 .ok_or_else(|| {
1142 JsNativeError::typ().with_message("the this object must be a PlainDate object.")
1143 })?;
1144
1145 let item = args.get_or_undefined(0);
1146 // 3. If item is an Object, then
1147 let (timezone, time) = if let Some(obj) = item.as_object() {
1148 // a. Let timeZoneLike be ? Get(item, "timeZone").
1149 let time_zone_like = obj.get(js_string!("timeZone"), context)?;
1150 // b. If timeZoneLike is undefined, then
1151 if time_zone_like.is_undefined() {
1152 // i. Let timeZone be ? ToTemporalTimeZoneIdentifier(item).
1153 // ii. Let temporalTime be undefined.
1154 (
1155 to_temporal_timezone_identifier(&time_zone_like, context)?,
1156 None,
1157 )
1158 // c. Else,
1159 } else {
1160 // i. Let timeZone be ? ToTemporalTimeZoneIdentifier(timeZoneLike).
1161 let tz = to_temporal_timezone_identifier(&time_zone_like, context)?;
1162 // ii. Let temporalTime be ? Get(item, "plainTime").
1163 let plain_time = obj
1164 .get(js_string!("plainTime"), context)?
1165 .map(|v| to_temporal_time(v, None, context))
1166 .transpose()?;
1167
1168 (tz, plain_time)
1169 }
1170 // 4. Else,
1171 } else {
1172 // a. Let timeZone be ? ToTemporalTimeZoneIdentifier(item).
1173 // b. Let temporalTime be undefined.
1174 (to_temporal_timezone_identifier(item, context)?, None)
1175 };
1176
1177 let result = date.inner.to_zoned_date_time_with_provider(
1178 timezone,
1179 time,
1180 context.timezone_provider(),
1181 )?;
1182
1183 // 7. Return ! CreateTemporalZonedDateTime(epochNs, timeZone, temporalDate.[[Calendar]]).
1184 create_temporal_zoneddatetime(result, None, context).map(Into::into)
1185 }
1186
1187 /// 3.3.30 `Temporal.PlainDate.prototype.toString ( [ options ] )`
1188 ///
1189 /// More information:
1190 ///
1191 /// - [ECMAScript Temporal proposal][spec]
1192 /// - [MDN reference][mdn]
1193 /// - [`temporal_rs` documentation][temporal_rs-docs]
1194 ///
1195 /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.tostring
1196 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/toString
1197 /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainDate.html#method.to_ixdtf_string
1198 fn to_string(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
1199 let object = this.as_object();
1200 let date = object
1201 .as_ref()
1202 .and_then(JsObject::downcast_ref::<Self>)
1203 .ok_or_else(|| {
1204 JsNativeError::typ().with_message("the this object must be a PlainDate object.")
1205 })?;
1206
1207 let options = get_options_object(args.get_or_undefined(0))?;
1208 let display_calendar =
1209 get_option::<DisplayCalendar>(&options, js_string!("calendarName"), context)?
1210 .unwrap_or(DisplayCalendar::Auto);
1211 Ok(JsString::from(date.inner.to_ixdtf_string(display_calendar)).into())
1212 }
1213
1214 /// 3.3.31 `Temporal.PlainDate.prototype.toLocaleString ( [ locales [ , options ] ] )`
1215 ///
1216 /// More information:
1217 ///
1218 /// - [ECMAScript Temporal proposal][spec]
1219 /// - [MDN reference][mdn]
1220 ///
1221 /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.tolocalestring
1222 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/toLocaleString
1223 fn to_locale_string(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
1224 // TODO: Update for ECMA-402 compliance
1225 let object = this.as_object();
1226 let date = object
1227 .as_ref()
1228 .and_then(JsObject::downcast_ref::<Self>)
1229 .ok_or_else(|| {
1230 JsNativeError::typ().with_message("the this object must be a PlainDate object.")
1231 })?;
1232
1233 Ok(JsString::from(date.inner.to_string()).into())
1234 }
1235
1236 /// 3.3.32 `Temporal.PlainDate.prototype.toJSON ( )`
1237 ///
1238 /// More information:
1239 ///
1240 /// - [ECMAScript Temporal proposal][spec]
1241 /// - [MDN reference][mdn]
1242 ///
1243 /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.toJSON
1244 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/toJSON
1245 fn to_json(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
1246 let object = this.as_object();
1247 let date = object
1248 .as_ref()
1249 .and_then(JsObject::downcast_ref::<Self>)
1250 .ok_or_else(|| {
1251 JsNativeError::typ().with_message("the this object must be a PlainDate object.")
1252 })?;
1253
1254 Ok(JsString::from(date.inner.to_string()).into())
1255 }
1256
1257 /// 3.3.33 `Temporal.PlainDate.prototype.valueOf ( )`
1258 ///
1259 /// More information:
1260 ///
1261 /// - [ECMAScript Temporal proposal][spec]
1262 /// - [MDN reference][mdn]
1263 ///
1264 /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.valueof
1265 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/valueOf
1266 pub(crate) fn value_of(_this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
1267 Err(JsNativeError::typ()
1268 .with_message("`valueOf` not supported by Temporal built-ins. See 'compare', 'equals', or `toString`")
1269 .into())
1270 }
1271}
1272
1273// ==== `PlainDate` Abstract Operations ====
1274
1275/// 3.5.3 `CreateTemporalDate ( isoYear, isoMonth, isoDay, calendar [ , newTarget ] )`
1276pub(crate) fn create_temporal_date(
1277 inner: InnerDate,
1278 new_target: Option<&JsValue>,
1279 context: &mut Context,
1280) -> JsResult<JsObject> {
1281 // 1. If IsValidISODate(isoYear, isoMonth, isoDay) is false, throw a RangeError exception.
1282 // 2. If ISODateTimeWithinLimits(isoYear, isoMonth, isoDay, 12, 0, 0, 0, 0, 0) is false, throw a RangeError exception.
1283
1284 // 3. If newTarget is not present, set newTarget to %Temporal.PlainDate%.
1285 let new_target = if let Some(new_target) = new_target {
1286 new_target.clone()
1287 } else {
1288 context
1289 .realm()
1290 .intrinsics()
1291 .constructors()
1292 .plain_date()
1293 .constructor()
1294 .into()
1295 };
1296
1297 // 4. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.PlainDate.prototype%", « [[InitializedTemporalDate]], [[ISOYear]], [[ISOMonth]], [[ISODay]], [[Calendar]] »).
1298 let prototype =
1299 get_prototype_from_constructor(&new_target, StandardConstructors::plain_date, context)?;
1300
1301 // 5. Set object.[[ISOYear]] to isoYear.
1302 // 6. Set object.[[ISOMonth]] to isoMonth.
1303 // 7. Set object.[[ISODay]] to isoDay.
1304 // 8. Set object.[[Calendar]] to calendar.
1305 let obj = JsObject::from_proto_and_data(prototype, PlainDate::new(inner));
1306
1307 // 9. Return object.
1308 Ok(obj)
1309}
1310
1311/// 3.5.4 `ToTemporalDate ( item [ , options ] )`
1312///
1313/// Converts an ambiguous `JsValue` into a `PlainDate`
1314pub(crate) fn to_temporal_date(
1315 item: &JsValue,
1316 options: Option<JsValue>,
1317 context: &mut Context,
1318) -> JsResult<InnerDate> {
1319 // 1. If options is not present, set options to undefined.
1320 let options = options.unwrap_or_default();
1321
1322 // 2. Assert: Type(options) is Object or Undefined.
1323 // 3. If options is not undefined, set options to ? SnapshotOwnProperties(? GetOptionsObject(options), null).
1324
1325 // 4. If Type(item) is Object, then
1326 if let Some(object) = item.as_object() {
1327 // a. If item has an [[InitializedTemporalDate]] internal slot, then
1328 if let Some(date) = object.downcast_ref::<PlainDate>() {
1329 let _options_obj = get_options_object(&options)?;
1330 return Ok(date.inner.clone());
1331 // b. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then
1332 } else if let Some(zdt) = object.downcast_ref::<ZonedDateTime>() {
1333 let options_obj = get_options_object(&options)?;
1334 // i. Perform ? ToTemporalOverflow(options).
1335 let _overflow = get_option(&options_obj, js_string!("overflow"), context)?
1336 .unwrap_or(Overflow::Constrain);
1337
1338 // ii. Let instant be ! CreateTemporalInstant(item.[[Nanoseconds]]).
1339 // iii. Let plainDateTime be ? GetPlainDateTimeFor(item.[[TimeZone]], instant, item.[[Calendar]]).
1340 // iv. Return ! CreateTemporalDate(plainDateTime.[[ISOYear]], plainDateTime.[[ISOMonth]], plainDateTime.[[ISODay]], plainDateTime.[[Calendar]]).
1341 return Ok(zdt.inner.to_plain_date());
1342 // c. If item has an [[InitializedTemporalDateTime]] internal slot, then
1343 } else if let Some(dt) = object.downcast_ref::<PlainDateTime>() {
1344 let options_obj = get_options_object(&options)?;
1345 // i. Perform ? ToTemporalOverflow(options).
1346 let _overflow = get_option(&options_obj, js_string!("overflow"), context)?
1347 .unwrap_or(Overflow::Constrain);
1348
1349 let date = InnerDate::from(dt.inner.clone());
1350
1351 // ii. Return ! CreateTemporalDate(item.[[ISOYear]], item.[[ISOMonth]], item.[[ISODay]], item.[[Calendar]]).
1352 return Ok(date);
1353 }
1354 // NOTE: d. is called in to_partial_date_record
1355 // d. Let calendar be ? GetTemporalCalendarIdentifierWithISODefault(item).
1356 // e. Let fields be ? PrepareCalendarFields(calendar, item, « year, month, month-code, day », «», «»).
1357 let partial = to_partial_date_record(&object, context)?;
1358 // f. Let resolvedOptions be ? GetOptionsObject(options).
1359 let resolved_options = get_options_object(&options)?;
1360 // g. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
1361 let overflow = get_option::<Overflow>(&resolved_options, js_string!("overflow"), context)?;
1362 // h. Let isoDate be ? CalendarDateFromFields(calendar, fields, overflow).
1363 // i. Return ! CreateTemporalDate(isoDate, calendar).
1364 return Ok(InnerDate::from_partial(partial, overflow)?);
1365 }
1366
1367 // 5. If item is not a String, throw a TypeError exception.
1368 let Some(date_like_string) = item.as_string() else {
1369 return Err(JsNativeError::typ()
1370 .with_message("ToTemporalDate item must be an object or string.")
1371 .into());
1372 };
1373
1374 // 4. Let result be ? ParseISODateTime(item, « TemporalDateTimeString[~Zoned] »).
1375 let result = date_like_string
1376 .to_std_string_escaped()
1377 .parse::<InnerDate>()
1378 .map_err(|err| JsNativeError::range().with_message(err.to_string()))?;
1379
1380 // 5. Let calendar be result.[[Calendar]].
1381 // 6. If calendar is empty, set calendar to "iso8601".
1382 // 7. Set calendar to ? CanonicalizeCalendar(calendar).
1383 // 8. Let resolvedOptions be ? GetOptionsObject(options).
1384 let resolved_options = get_options_object(&options)?;
1385 // 9. Perform ? GetTemporalOverflowOption(resolvedOptions).
1386 let _overflow = get_option::<Overflow>(&resolved_options, js_string!("overflow"), context)?
1387 .unwrap_or(Overflow::Constrain);
1388
1389 // 10. Let isoDate be CreateISODateRecord(result.[[Year]], result.[[Month]], result.[[Day]]).
1390 // 11. Return ? CreateTemporalDate(isoDate, calendar).
1391 Ok(result)
1392}
1393
1394// TODO: For order of operations, `to_partial_date_record` may need to take a `Option<Calendar>` arg.
1395pub(crate) fn to_partial_date_record(
1396 partial_object: &JsObject,
1397 context: &mut Context,
1398) -> JsResult<PartialDate> {
1399 let calendar = get_temporal_calendar_slot_value_with_default(partial_object, context)?;
1400 // TODO: Most likely need to use an iterator to handle.
1401 let calendar_fields = to_calendar_fields(partial_object, &calendar, context)?;
1402 Ok(PartialDate {
1403 calendar_fields,
1404 calendar,
1405 })
1406}
1407
1408pub(crate) fn to_calendar_fields(
1409 obj: &JsObject,
1410 calendar: &Calendar,
1411 context: &mut Context,
1412) -> JsResult<CalendarFields> {
1413 let day = obj
1414 .get(js_string!("day"), context)?
1415 .map(|v| {
1416 let finite = v.to_finitef64(context)?;
1417 finite
1418 .as_positive_integer_with_truncation()
1419 .map_err(JsError::from)
1420 })
1421 .transpose()?;
1422 // TODO: `temporal_rs` needs a `has_era` method
1423 let has_no_era = calendar.kind() == AnyCalendarKind::Iso
1424 || calendar.kind() == AnyCalendarKind::Chinese
1425 || calendar.kind() == AnyCalendarKind::Dangi;
1426 let (era, era_year) = if has_no_era {
1427 (None, None)
1428 } else {
1429 let era = obj
1430 .get(js_string!("era"), context)?
1431 .map(|v| {
1432 let v = v.to_primitive(context, crate::value::PreferredType::String)?;
1433 let Some(era) = v.as_string() else {
1434 return Err(JsError::from(
1435 JsNativeError::typ()
1436 .with_message("The monthCode field value must be a string."),
1437 ));
1438 };
1439 // TODO: double check if an invalid monthCode is a range or type error.
1440 TinyAsciiStr::<19>::try_from_str(&era.to_std_string_escaped())
1441 .map_err(|e| JsError::from(JsNativeError::range().with_message(e.to_string())))
1442 })
1443 .transpose()?;
1444 let era_year = obj
1445 .get(js_string!("eraYear"), context)?
1446 .map(|v| {
1447 let finite = v.to_finitef64(context)?;
1448 Ok::<i32, JsError>(finite.as_integer_with_truncation::<i32>())
1449 })
1450 .transpose()?;
1451 (era, era_year)
1452 };
1453 let month = obj
1454 .get(js_string!("month"), context)?
1455 .map(|v| {
1456 let finite = v.to_finitef64(context)?;
1457 finite
1458 .as_positive_integer_with_truncation()
1459 .map_err(JsError::from)
1460 })
1461 .transpose()?;
1462 let month_code = obj
1463 .get(js_string!("monthCode"), context)?
1464 .map(|v| {
1465 let v = v.to_primitive(context, crate::value::PreferredType::String)?;
1466 let Some(month_code) = v.as_string() else {
1467 return Err(JsNativeError::typ()
1468 .with_message("The monthCode field value must be a string.")
1469 .into());
1470 };
1471 MonthCode::from_str(&month_code.to_std_string_escaped()).map_err(JsError::from)
1472 })
1473 .transpose()?;
1474 let year = obj
1475 .get(js_string!("year"), context)?
1476 .map(|v| {
1477 let finite = v.to_finitef64(context)?;
1478 Ok::<i32, JsError>(finite.as_integer_with_truncation::<i32>())
1479 })
1480 .transpose()?;
1481 Ok(CalendarFields {
1482 year,
1483 month,
1484 month_code,
1485 day,
1486 era,
1487 era_year,
1488 })
1489}