Skip to main content

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}