boa_engine/builtins/temporal/plain_year_month/
mod.rs

1//! Boa's implementation of the `Temporal.PlainYearMonth` built-in object.
2
3use std::str::FromStr;
4
5use crate::{
6    Context, JsArgs, JsData, JsError, JsNativeError, JsObject, JsResult, JsString, JsSymbol,
7    JsValue,
8    builtins::{
9        BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject,
10        options::{get_option, get_options_object},
11    },
12    context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
13    js_string,
14    object::internal_methods::get_prototype_from_constructor,
15    property::Attribute,
16    realm::Realm,
17    string::StaticJsStrings,
18    value::IntoOrUndefined,
19};
20use boa_gc::{Finalize, Trace};
21
22use icu_calendar::AnyCalendarKind;
23use temporal_rs::{
24    Calendar, Duration, MonthCode, PlainYearMonth as InnerYearMonth, TinyAsciiStr,
25    fields::{CalendarFields, YearMonthCalendarFields},
26    options::{DisplayCalendar, Overflow},
27    partial::PartialYearMonth,
28};
29
30use super::{
31    DateTimeValues, calendar::get_temporal_calendar_slot_value_with_default, create_temporal_date,
32    create_temporal_duration, is_partial_temporal_object, options::get_difference_settings,
33    to_temporal_duration,
34};
35
36/// The `Temporal.PlainYearMonth` built-in implementation
37///
38/// More information:
39///
40/// - [ECMAScript Temporal proposal][spec]
41/// - [MDN reference][mdn]
42/// - [`temporal_rs` documentation][temporal_rs-docs]
43///
44/// [spec]: https://tc39.es/proposal-temporal/#sec-temporal-plainyearmonth-objects
45/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth
46/// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainYearMonth.html
47#[derive(Debug, Clone, Trace, Finalize, JsData)]
48#[boa_gc(unsafe_empty_trace)]
49pub struct PlainYearMonth {
50    pub(crate) inner: InnerYearMonth,
51}
52
53impl PlainYearMonth {
54    pub(crate) fn new(inner: InnerYearMonth) -> Self {
55        Self { inner }
56    }
57}
58
59impl BuiltInObject for PlainYearMonth {
60    const NAME: JsString = StaticJsStrings::PLAIN_YM_NAME;
61}
62
63impl IntrinsicObject for PlainYearMonth {
64    fn init(realm: &Realm) {
65        let get_calendar_id = BuiltInBuilder::callable(realm, Self::get_calendar_id)
66            .name(js_string!("get calendarId"))
67            .build();
68
69        let get_era_year = BuiltInBuilder::callable(realm, Self::get_era_year)
70            .name(js_string!("get eraYear"))
71            .build();
72
73        let get_era = BuiltInBuilder::callable(realm, Self::get_era)
74            .name(js_string!("get era"))
75            .build();
76
77        let get_year = BuiltInBuilder::callable(realm, Self::get_year)
78            .name(js_string!("get year"))
79            .build();
80
81        let get_month = BuiltInBuilder::callable(realm, Self::get_month)
82            .name(js_string!("get month"))
83            .build();
84
85        let get_month_code = BuiltInBuilder::callable(realm, Self::get_month_code)
86            .name(js_string!("get monthCode"))
87            .build();
88
89        let get_days_in_month = BuiltInBuilder::callable(realm, Self::get_days_in_month)
90            .name(js_string!("get daysInMonth"))
91            .build();
92
93        let get_days_in_year = BuiltInBuilder::callable(realm, Self::get_days_in_year)
94            .name(js_string!("get daysInYear"))
95            .build();
96
97        let get_months_in_year = BuiltInBuilder::callable(realm, Self::get_months_in_year)
98            .name(js_string!("get monthsInYear"))
99            .build();
100
101        let get_in_leap_year = BuiltInBuilder::callable(realm, Self::get_in_leap_year)
102            .name(js_string!("get inLeapYear"))
103            .build();
104
105        BuiltInBuilder::from_standard_constructor::<Self>(realm)
106            .property(
107                JsSymbol::to_string_tag(),
108                StaticJsStrings::PLAIN_YM_TAG,
109                Attribute::CONFIGURABLE,
110            )
111            .accessor(
112                js_string!("calendarId"),
113                Some(get_calendar_id),
114                None,
115                Attribute::CONFIGURABLE,
116            )
117            .accessor(
118                js_string!("era"),
119                Some(get_era),
120                None,
121                Attribute::CONFIGURABLE,
122            )
123            .accessor(
124                js_string!("eraYear"),
125                Some(get_era_year),
126                None,
127                Attribute::CONFIGURABLE,
128            )
129            .accessor(
130                js_string!("year"),
131                Some(get_year),
132                None,
133                Attribute::CONFIGURABLE,
134            )
135            .accessor(
136                js_string!("month"),
137                Some(get_month),
138                None,
139                Attribute::CONFIGURABLE,
140            )
141            .accessor(
142                js_string!("monthCode"),
143                Some(get_month_code),
144                None,
145                Attribute::CONFIGURABLE,
146            )
147            .accessor(
148                js_string!("daysInMonth"),
149                Some(get_days_in_month),
150                None,
151                Attribute::CONFIGURABLE,
152            )
153            .accessor(
154                js_string!("daysInYear"),
155                Some(get_days_in_year),
156                None,
157                Attribute::CONFIGURABLE,
158            )
159            .accessor(
160                js_string!("monthsInYear"),
161                Some(get_months_in_year),
162                None,
163                Attribute::CONFIGURABLE,
164            )
165            .accessor(
166                js_string!("inLeapYear"),
167                Some(get_in_leap_year),
168                None,
169                Attribute::CONFIGURABLE,
170            )
171            .static_method(Self::from, js_string!("from"), 1)
172            .static_method(Self::compare, js_string!("compare"), 2)
173            .method(Self::with, js_string!("with"), 1)
174            .method(Self::add, js_string!("add"), 1)
175            .method(Self::subtract, js_string!("subtract"), 1)
176            .method(Self::until, js_string!("until"), 1)
177            .method(Self::since, js_string!("since"), 1)
178            .method(Self::equals, js_string!("equals"), 1)
179            .method(Self::to_string, js_string!("toString"), 0)
180            .method(Self::to_locale_string, js_string!("toLocaleString"), 0)
181            .method(Self::to_json, js_string!("toJSON"), 0)
182            .method(Self::value_of, js_string!("valueOf"), 0)
183            .method(Self::to_plain_date, js_string!("toPlainDate"), 1)
184            .build();
185    }
186
187    fn get(intrinsics: &Intrinsics) -> JsObject {
188        Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor()
189    }
190}
191
192impl BuiltInConstructor for PlainYearMonth {
193    const CONSTRUCTOR_ARGUMENTS: usize = 2;
194    const PROTOTYPE_STORAGE_SLOTS: usize = 32;
195    const CONSTRUCTOR_STORAGE_SLOTS: usize = 2;
196
197    const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
198        StandardConstructors::plain_year_month;
199
200    fn constructor(
201        new_target: &JsValue,
202        args: &[JsValue],
203        context: &mut Context,
204    ) -> JsResult<JsValue> {
205        // 1. If NewTarget is undefined, then
206        if new_target.is_undefined() {
207            // a. Throw a TypeError exception.
208            return Err(JsNativeError::typ()
209                .with_message("NewTarget cannot be undefined when constructing a PlainYearMonth.")
210                .into());
211        }
212
213        // 2. If referenceISODay is undefined, then
214        // a. Set referenceISODay to 1𝔽.
215        // 3. Let y be ? ToIntegerWithTruncation(isoYear).
216        let y = args
217            .get_or_undefined(0)
218            .to_finitef64(context)?
219            .as_integer_with_truncation::<i32>();
220
221        // 4. Let m be ? ToIntegerWithTruncation(isoMonth).
222        let m = args
223            .get_or_undefined(1)
224            .to_finitef64(context)?
225            .as_integer_with_truncation::<u8>();
226
227        // 5. Let calendar be ? ToTemporalCalendarSlotValue(calendarLike, "iso8601").
228        let calendar = args
229            .get_or_undefined(2)
230            .map(|s| {
231                s.as_string()
232                    .as_ref()
233                    .map(JsString::to_std_string_lossy)
234                    .ok_or_else(|| JsNativeError::typ().with_message("calendar must be a string."))
235            })
236            .transpose()?
237            .map(|s| Calendar::try_from_utf8(s.as_bytes()))
238            .transpose()?
239            .unwrap_or_default();
240
241        // 6. Let ref be ? ToIntegerWithTruncation(referenceISODay).
242        let ref_day = args
243            .get_or_undefined(3)
244            .map(|v| {
245                let finite = v.to_finitef64(context)?;
246                Ok::<u8, JsError>(finite.as_integer_with_truncation::<u8>())
247            })
248            .transpose()?;
249
250        // 7. Return ? CreateTemporalYearMonth(y, m, calendar, ref, NewTarget).
251        let inner = InnerYearMonth::new_with_overflow(y, m, ref_day, calendar, Overflow::Reject)?;
252
253        create_temporal_year_month(inner, Some(new_target), context)
254    }
255}
256
257// ==== `Temporal.PlainYearMonth` static methods implementation ====
258
259impl PlainYearMonth {
260    /// 9.2.2 `Temporal.PlainYearMonth.from ( item [ , options ] )`
261    ///
262    /// More information:
263    ///
264    /// - [ECMAScript Temporal proposal][spec]
265    /// - [MDN reference][mdn]
266    ///
267    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.from
268    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/from
269    fn from(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
270        // 1. Return ? ToTemporalYearMonth(item, options).
271        let item = args.get_or_undefined(0);
272        let options = args.get_or_undefined(1);
273        let inner = to_temporal_year_month(item, Some(options.clone()), context)?;
274        create_temporal_year_month(inner, None, context)
275    }
276
277    /// 9.2.3 `Temporal.PlainYearMonth.compare ( one, two )`
278    ///
279    /// More information:
280    ///
281    /// - [ECMAScript Temporal proposal][spec]
282    /// - [MDN reference][mdn]
283    /// - [`temporal_rs` documentation][temporal_rs-docs]
284    ///
285    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.compare
286    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/compare
287    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainYearMonth.html#method.compare_iso
288    fn compare(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
289        let one = to_temporal_year_month(args.get_or_undefined(0), None, context)?;
290        let two = to_temporal_year_month(args.get_or_undefined(1), None, context)?;
291        Ok((one.compare_iso(&two) as i8).into())
292    }
293}
294
295// ==== `PlainYearMonth` accessors implementation ====
296
297impl PlainYearMonth {
298    // Helper for retrieving internal fields
299    fn get_internal_field(this: &JsValue, field: &DateTimeValues) -> JsResult<JsValue> {
300        let object = this.as_object();
301        let year_month = object
302            .as_ref()
303            .and_then(JsObject::downcast_ref::<Self>)
304            .ok_or_else(|| {
305                JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
306            })?;
307        let inner = &year_month.inner;
308        match field {
309            DateTimeValues::Year => Ok(inner.year().into()),
310            DateTimeValues::Month => Ok(inner.month().into()),
311            DateTimeValues::MonthCode => {
312                Ok(JsString::from(InnerYearMonth::month_code(inner).as_str()).into())
313            }
314            _ => unreachable!(),
315        }
316    }
317
318    /// 9.3.3 get `Temporal.PlainYearMonth.prototype.calendarId`
319    ///
320    /// More information:
321    ///
322    /// - [ECMAScript Temporal proposal][spec]
323    /// - [MDN reference][mdn]
324    /// - [`temporal_rs` documentation][temporal_rs-docs]
325    ///
326    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.calendarid
327    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/calendarId
328    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainYearMonth.html#method.calendar
329    fn get_calendar_id(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
330        let obj = this
331            .as_object()
332            .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?;
333
334        let Ok(year_month) = obj.clone().downcast::<Self>() else {
335            return Err(JsNativeError::typ()
336                .with_message("the this object must be a PlainYearMonth object.")
337                .into());
338        };
339
340        let calendar = year_month.borrow().data().inner.calendar().clone();
341        Ok(js_string!(calendar.identifier()).into())
342    }
343
344    /// 9.3.4 get `Temporal.PlainYearMonth.prototype.era`
345    ///
346    /// More information:
347    ///
348    /// - [ECMAScript Temporal proposal][spec]
349    /// - [MDN reference][mdn]
350    /// - [`temporal_rs` documentation][temporal_rs-docs]
351    ///
352    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.era
353    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/era
354    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainYearMonth.html#method.era
355    fn get_era(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
356        let object = this.as_object();
357        let year_month = object
358            .as_ref()
359            .and_then(JsObject::downcast_ref::<Self>)
360            .ok_or_else(|| {
361                JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
362            })?;
363
364        Ok(year_month
365            .inner
366            .era()
367            .map(|s| JsString::from(s.as_str()))
368            .into_or_undefined())
369    }
370
371    /// 9.3.5 get `Temporal.PlainYearMonth.prototype.eraYear`
372    ///
373    /// More information:
374    ///
375    /// - [ECMAScript Temporal proposal][spec]
376    /// - [MDN reference][mdn]
377    /// - [`temporal_rs` documentation][temporal_rs-docs]
378    ///
379    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.erayear
380    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/eraYear
381    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainYearMonth.html#method.era_year
382    fn get_era_year(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
383        let object = this.as_object();
384        let year_month = object
385            .as_ref()
386            .and_then(JsObject::downcast_ref::<Self>)
387            .ok_or_else(|| {
388                JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
389            })?;
390        Ok(year_month.inner.era_year().into_or_undefined())
391    }
392
393    /// 9.3.6 get `Temporal.PlainYearMonth.prototype.year`
394    ///
395    /// More information:
396    ///
397    /// - [ECMAScript Temporal proposal][spec]
398    /// - [MDN reference][mdn]
399    /// - [`temporal_rs` documentation][temporal_rs-docs]
400    ///
401    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.year
402    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/year
403    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainYearMonth.html#method.year
404    fn get_year(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
405        Self::get_internal_field(this, &DateTimeValues::Year)
406    }
407
408    /// 9.3.7 get `Temporal.PlainYearMonth.prototype.month`
409    ///
410    /// More information:
411    ///
412    /// - [ECMAScript Temporal proposal][spec]
413    /// - [MDN reference][mdn]
414    /// - [`temporal_rs` documentation][temporal_rs-docs]
415    ///
416    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.month
417    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/month
418    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainYearMonth.html#method.month
419    fn get_month(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
420        Self::get_internal_field(this, &DateTimeValues::Month)
421    }
422
423    /// 9.3.8 get `Temporal.PlainYearMonth.prototype.monthCode`
424    ///
425    /// More information:
426    ///
427    /// - [ECMAScript Temporal proposal][spec]
428    /// - [MDN reference][mdn]
429    /// - [`temporal_rs` documentation][temporal_rs-docs]
430    ///
431    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.monthcode
432    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/monthCode
433    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainYearMonth.html#method.month_code
434    fn get_month_code(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
435        Self::get_internal_field(this, &DateTimeValues::MonthCode)
436    }
437
438    /// 9.3.9 get `Temporal.PlainYearMonth.prototype.daysInYear`
439    ///
440    /// More information:
441    ///
442    /// - [ECMAScript Temporal proposal][spec]
443    /// - [MDN reference][mdn]
444    /// - [`temporal_rs` documentation][temporal_rs-docs]
445    ///
446    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.daysinyear
447    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/daysInYear
448    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainYearMonth.html#method.days_in_year
449    fn get_days_in_year(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
450        let object = this.as_object();
451        let year_month = object
452            .as_ref()
453            .and_then(JsObject::downcast_ref::<Self>)
454            .ok_or_else(|| {
455                JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
456            })?;
457        let inner = &year_month.inner;
458        Ok(inner.days_in_year().into())
459    }
460
461    /// 9.3.10 get `Temporal.PlainYearMonth.prototype.daysInMonth`
462    ///
463    /// More information:
464    ///
465    /// - [ECMAScript Temporal proposal][spec]
466    /// - [MDN reference][mdn]
467    /// - [`temporal_rs` documentation][temporal_rs-docs]
468    ///
469    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.daysinmonth
470    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/daysInMonth
471    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainYearMonth.html#method.days_in_month
472    fn get_days_in_month(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
473        let object = this.as_object();
474        let year_month = object
475            .as_ref()
476            .and_then(JsObject::downcast_ref::<Self>)
477            .ok_or_else(|| {
478                JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
479            })?;
480        let inner = &year_month.inner;
481        Ok(inner.days_in_month().into())
482    }
483
484    /// 9.3.11 get `Temporal.PlainYearMonth.prototype.monthsInYear`
485    ///
486    /// More information:
487    ///
488    /// - [ECMAScript Temporal proposal][spec]
489    /// - [MDN reference][mdn]
490    /// - [`temporal_rs` documentation][temporal_rs-docs]
491    ///
492    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.monthsinyear
493    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/monthsInYear
494    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainYearMonth.html#method.months_in_year
495    fn get_months_in_year(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
496        let object = this.as_object();
497        let year_month = object
498            .as_ref()
499            .and_then(JsObject::downcast_ref::<Self>)
500            .ok_or_else(|| {
501                JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
502            })?;
503        let inner = &year_month.inner;
504        Ok(inner.months_in_year().into())
505    }
506
507    /// 9.3.12 get `Temporal.PlainYearMonth.prototype.inLeapYear`
508    ///
509    /// More information:
510    ///
511    /// - [ECMAScript Temporal proposal][spec]
512    /// - [MDN reference][mdn]
513    /// - [`temporal_rs` documentation][temporal_rs-docs]
514    ///
515    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.inleapyear
516    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/inLeapYear
517    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainYearMonth.html#method.in_leap_year
518    fn get_in_leap_year(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
519        let object = this.as_object();
520        let year_month = object
521            .as_ref()
522            .and_then(JsObject::downcast_ref::<Self>)
523            .ok_or_else(|| {
524                JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
525            })?;
526
527        Ok(year_month.inner.in_leap_year().into())
528    }
529}
530
531// ==== `PlainYearMonth` method implementations ====
532
533impl PlainYearMonth {
534    /// 9.3.13 `Temporal.PlainYearMonth.prototype.with ( temporalYearMonthLike [ , options ] )`
535    ///
536    /// More information:
537    ///
538    /// - [ECMAScript Temporal proposal][spec]
539    /// - [MDN reference][mdn]
540    /// - [`temporal_rs` documentation][temporal_rs-docs]
541    ///
542    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.with
543    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/with
544    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainYearMonth.html#method.with
545    fn with(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
546        // 1. Let yearMonth be the this value.
547        // 2. Perform ? RequireInternalSlot(yearMonth, [[InitializedTemporalYearMonth]]).
548        let object = this.as_object();
549        let year_month = object
550            .as_ref()
551            .and_then(JsObject::downcast_ref::<Self>)
552            .ok_or_else(|| {
553                JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
554            })?;
555
556        // 3. If ? IsPartialTemporalObject(temporalYearMonthLike) is false, throw a TypeError exception.
557        let Some(obj) = is_partial_temporal_object(args.get_or_undefined(0), context)? else {
558            return Err(JsNativeError::typ()
559                .with_message("temporalYearMonthLike was not a partial object")
560                .into());
561        };
562        // 4. Let calendar be yearMonth.[[Calendar]].
563        // 5. Let fields be ISODateToFields(calendar, yearMonth.[[ISODate]], year-month).
564        // TODO: We may need to throw early on an empty partial for Order of operations, but ideally this is enforced by `temporal_rs`
565        // 6. Let partialYearMonth be ? PrepareCalendarFields(calendar, temporalYearMonthLike, « year, month, month-code », « », partial).
566        // 7. Set fields to CalendarMergeFields(calendar, fields, partialYearMonth).
567        let fields = to_year_month_calendar_fields(&obj, year_month.inner.calendar(), context)?;
568        // 8. Let resolvedOptions be ? GetOptionsObject(options).
569        let resolved_options = get_options_object(args.get_or_undefined(1))?;
570        // 9. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
571        let overflow = get_option::<Overflow>(&resolved_options, js_string!("overflow"), context)?
572            .unwrap_or_default();
573        // 10. Let isoDate be ? CalendarYearMonthFromFields(calendar, fields, overflow).
574        let result = year_month.inner.with(fields, Some(overflow))?;
575        // 11. Return ! CreateTemporalYearMonth(isoDate, calendar).
576        create_temporal_year_month(result, None, context)
577    }
578
579    /// 9.3.14 `Temporal.PlainYearMonth.prototype.add ( temporalDurationLike [ , options ] )`
580    ///
581    /// More information:
582    ///
583    /// - [ECMAScript Temporal proposal][spec]
584    /// - [MDN reference][mdn]
585    /// - [`temporal_rs` documentation][temporal_rs-docs]
586    ///
587    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.add
588    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/add
589    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainYearMonth.html#method.add
590    fn add(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
591        let duration_like = args.get_or_undefined(0);
592        let options = get_options_object(args.get_or_undefined(1))?;
593
594        add_or_subtract_duration(true, this, duration_like, &options, context)
595    }
596
597    /// 9.3.15 `Temporal.PlainYearMonth.prototype.subtract ( temporalDurationLike [ , options ] )`
598    ///
599    /// More information:
600    ///
601    /// - [ECMAScript Temporal proposal][spec]
602    /// - [MDN reference][mdn]
603    /// - [`temporal_rs` documentation][temporal_rs-docs]
604    ///
605    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.subtract
606    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/subtract
607    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainYearMonth.html#method.subtract
608    fn subtract(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
609        let duration_like = args.get_or_undefined(0);
610        let options = get_options_object(args.get_or_undefined(1))?;
611
612        add_or_subtract_duration(false, this, duration_like, &options, context)
613    }
614
615    /// 9.3.16 `Temporal.PlainYearMonth.prototype.until ( other [ , options ] )`
616    ///
617    /// More information:
618    ///
619    /// - [ECMAScript Temporal proposal][spec]
620    /// - [MDN reference][mdn]
621    /// - [`temporal_rs` documentation][temporal_rs-docs]
622    ///
623    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.until
624    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/until
625    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainYearMonth.html#method.until
626    fn until(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
627        let object = this.as_object();
628        let year_month = object
629            .as_ref()
630            .and_then(JsObject::downcast_ref::<Self>)
631            .ok_or_else(|| {
632                JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
633            })?;
634
635        let other = to_temporal_year_month(args.get_or_undefined(0), None, context)?;
636
637        if year_month.inner.calendar() != other.calendar() {
638            return Err(JsNativeError::range()
639                .with_message("calendars are not the same.")
640                .into());
641        }
642
643        let resolved_options = get_options_object(args.get_or_undefined(1))?;
644        // TODO: Disallowed units must be rejected in `temporal_rs`.
645        let settings = get_difference_settings(&resolved_options, context)?;
646        let result = year_month.inner.until(&other, settings)?;
647        create_temporal_duration(result, None, context).map(Into::into)
648    }
649
650    /// 9.3.17 `Temporal.PlainYearMonth.prototype.since ( other [ , options ] )`
651    ///
652    /// More information:
653    ///
654    /// - [ECMAScript Temporal proposal][spec]
655    /// - [MDN reference][mdn]
656    /// - [`temporal_rs` documentation][temporal_rs-docs]
657    ///
658    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.since
659    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/since
660    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainYearMonth.html#method.since
661    fn since(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
662        let object = this.as_object();
663        let year_month = object
664            .as_ref()
665            .and_then(JsObject::downcast_ref::<Self>)
666            .ok_or_else(|| {
667                JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
668            })?;
669
670        let other = to_temporal_year_month(args.get_or_undefined(0), None, context)?;
671
672        if year_month.inner.calendar() != other.calendar() {
673            return Err(JsNativeError::range()
674                .with_message("calendars are not the same.")
675                .into());
676        }
677
678        let resolved_options = get_options_object(args.get_or_undefined(1))?;
679        // TODO: Disallowed units must be rejected in `temporal_rs`.
680        let settings = get_difference_settings(&resolved_options, context)?;
681        let result = year_month.inner.since(&other, settings)?;
682        create_temporal_duration(result, None, context).map(Into::into)
683    }
684
685    /// 9.3.18 `Temporal.PlainYearMonth.prototype.equals ( other )`
686    ///
687    /// More information:
688    ///
689    /// - [ECMAScript Temporal proposal][spec]
690    /// - [MDN reference][mdn]
691    /// - [`temporal_rs` documentation][temporal_rs-docs]
692    ///
693    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.equals
694    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/equals
695    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainYearMonth.html#impl-PartialEq-for-PlainYearMonth
696    fn equals(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
697        let object = this.as_object();
698        let year_month = object
699            .as_ref()
700            .and_then(JsObject::downcast_ref::<Self>)
701            .ok_or_else(|| {
702                JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
703            })?;
704
705        let other = to_temporal_year_month(args.get_or_undefined(0), None, context)?;
706
707        Ok((year_month.inner == other).into())
708    }
709
710    /// 9.3.19 `Temporal.PlainYearMonth.prototype.toString ( [ options ] )`
711    ///
712    /// More information:
713    ///
714    /// - [ECMAScript Temporal proposal][spec]
715    /// - [MDN reference][mdn]
716    /// - [`temporal_rs` documentation][temporal_rs-docs]
717    ///
718    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.tostring
719    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/toString
720    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainYearMonth.html#method.to_ixdtf_string
721    fn to_string(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
722        // 1. Let YearMonth be the this value.
723        // 2. Perform ? RequireInternalSlot(yearMonth, [[InitializedTemporalYearMonth]]).
724        let object = this.as_object();
725        let year_month = object
726            .as_ref()
727            .and_then(JsObject::downcast_ref::<Self>)
728            .ok_or_else(|| {
729                JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
730            })?;
731
732        // 3. Set options to ? NormalizeOptionsObject(options).
733        let options = get_options_object(args.get_or_undefined(0))?;
734        // 4. Let showCalendar be ? ToShowCalendarOption(options).
735        // Get calendarName from the options object
736        let show_calendar =
737            get_option::<DisplayCalendar>(&options, js_string!("calendarName"), context)?
738                .unwrap_or(DisplayCalendar::Auto);
739
740        let ixdtf = year_month.inner.to_ixdtf_string(show_calendar);
741        Ok(JsString::from(ixdtf).into())
742    }
743
744    /// 9.3.20 `Temporal.PlainYearMonth.prototype.toLocaleString ( [ locales [ , options ] ] )`
745    ///
746    /// More information:
747    ///
748    /// - [ECMAScript Temporal proposal][spec]
749    /// - [MDN reference][mdn]
750    ///
751    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.tolocalestring
752    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/toLocaleString
753    pub(crate) fn to_locale_string(
754        this: &JsValue,
755        _: &[JsValue],
756        _: &mut Context,
757    ) -> JsResult<JsValue> {
758        // TODO: Update for ECMA-402 compliance
759        let object = this.as_object();
760        let year_month = object
761            .as_ref()
762            .and_then(JsObject::downcast_ref::<Self>)
763            .ok_or_else(|| {
764                JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
765            })?;
766
767        Ok(JsString::from(year_month.inner.to_string()).into())
768    }
769
770    /// 9.3.21 `Temporal.PlainYearMonth.prototype.toJSON ( )`
771    ///
772    /// More information:
773    ///
774    /// - [ECMAScript Temporal proposal][spec]
775    /// - [MDN reference][mdn]
776    ///
777    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.tojson
778    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/toJSON
779    pub(crate) fn to_json(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
780        let object = this.as_object();
781        let year_month = object
782            .as_ref()
783            .and_then(JsObject::downcast_ref::<Self>)
784            .ok_or_else(|| {
785                JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
786            })?;
787
788        Ok(JsString::from(year_month.inner.to_string()).into())
789    }
790
791    /// 9.3.22 `Temporal.PlainYearMonth.prototype.valueOf ( )`
792    ///
793    /// More information:
794    ///
795    /// - [ECMAScript Temporal proposal][spec]
796    /// - [MDN reference][mdn]
797    ///
798    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.valueof
799    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/valueOf
800    pub(crate) fn value_of(_this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
801        Err(JsNativeError::typ()
802            .with_message("`valueOf` not supported by Temporal built-ins. See 'compare', 'equals', or `toString`")
803            .into())
804    }
805
806    /// 9.3.23 `Temporal.PlainYearMonth.prototype.toPlainDate ( item )`
807    ///
808    /// More information:
809    ///
810    /// - [ECMAScript Temporal proposal][spec]
811    /// - [MDN reference][mdn]
812    /// - [`temporal_rs` documentation][temporal_rs-docs]
813    ///
814    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.toplaindate
815    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/toPlainDate
816    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainYearMonth.html#method.to_plain_date
817    fn to_plain_date(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
818        // 1. Let yearMonth be the this value.
819        // 2. Perform ? RequireInternalSlot(yearMonth, [[InitializedTemporalYearMonth]]).
820        let object = this.as_object();
821        let year_month = object
822            .as_ref()
823            .and_then(JsObject::downcast_ref::<Self>)
824            .ok_or_else(|| {
825                JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
826            })?;
827
828        // 3. If item is not an Object, then
829        let Some(obj) = args.get_or_undefined(0).as_object() else {
830            // a. Throw a TypeError exception.
831            return Err(JsNativeError::typ()
832                .with_message("toPlainDate item must be an object.")
833                .into());
834        };
835        // 4. Let calendar be yearMonth.[[Calendar]].
836        // 5. Let fields be ISODateToFields(calendar, yearMonth.[[ISODate]], year-month).
837        // 6. Let inputFields be ? PrepareCalendarFields(calendar, item, « day », « », « »).
838        let day = obj
839            .get(js_string!("day"), context)?
840            .map(|v| {
841                let finite = v.to_finitef64(context)?;
842                finite
843                    .as_positive_integer_with_truncation::<u8>()
844                    .map_err(JsError::from)
845            })
846            .transpose()?;
847
848        let fields = CalendarFields::new().with_optional_day(day);
849
850        // 7. Let mergedFields be CalendarMergeFields(calendar, fields, inputFields).
851        // 8. Let isoDate be ? CalendarDateFromFields(calendar, mergedFields, constrain).
852        let result = year_month.inner.to_plain_date(Some(fields))?;
853        // 9. Return ! CreateTemporalDate(isoDate, calendar).
854        create_temporal_date(result, None, context).map(Into::into)
855    }
856}
857
858// ==== PlainYearMonth Abstract Operations ====
859
860/// 9.5.2 `ToTemporalYearMonth ( item [ , options ] )`
861fn to_temporal_year_month(
862    value: &JsValue,
863    options: Option<JsValue>,
864    context: &mut Context,
865) -> JsResult<InnerYearMonth> {
866    // If options is not present, set options to undefined.
867    let options = options.unwrap_or_default();
868    // 2. If item is an Object, then
869    if let Some(obj) = value.as_object() {
870        // a. If item has an [[InitializedTemporalYearMonth]] internal slot, then
871        if let Some(ym) = obj.downcast_ref::<PlainYearMonth>() {
872            // i. Let resolvedOptions be ? GetOptionsObject(options).
873            let resolved_options = get_options_object(&options)?;
874            // ii. Perform ? GetTemporalOverflowOption(resolvedOptions).
875            let _overflow =
876                get_option::<Overflow>(&resolved_options, js_string!("overflow"), context)?
877                    .unwrap_or(Overflow::Constrain);
878            // iii. Return ! CreateTemporalYearMonth(item.[[ISODate]], item.[[Calendar]]).
879            return Ok(ym.inner.clone());
880        }
881        // b. Let calendar be ? GetTemporalCalendarIdentifierWithISODefault(item).
882        // c. Let fields be ? PrepareCalendarFields(calendar, item, « year, month, month-code », «», «»).
883        let partial = to_partial_year_month(&obj, context)?;
884        // d. Let resolvedOptions be ? GetOptionsObject(options).
885        let resolved_options = get_options_object(&options)?;
886        // e. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
887        let overflow = get_option::<Overflow>(&resolved_options, js_string!("overflow"), context)?;
888        // f. Let isoDate be ? CalendarYearMonthFromFields(calendar, fields, overflow).
889        // g. Return ! CreateTemporalYearMonth(isoDate, calendar).
890        return Ok(InnerYearMonth::from_partial(partial, overflow)?);
891    }
892
893    // 3. If item is not a String, throw a TypeError exception.
894    let Some(ym_string) = value.as_string() else {
895        return Err(JsNativeError::typ()
896            .with_message("toTemporalYearMonth target must be an object or string")
897            .into());
898    };
899
900    // 4. Let result be ? ParseISODateTime(item, « TemporalYearMonthString »).
901    let result = InnerYearMonth::from_str(&ym_string.to_std_string_escaped())?;
902    // 5. Let calendar be result.[[Calendar]].
903    // 6. If calendar is empty, set calendar to "iso8601".
904    // 7. Set calendar to ? CanonicalizeCalendar(calendar).
905    // 8. Let resolvedOptions be ? GetOptionsObject(options).
906    let resolved_options = get_options_object(&options)?;
907    // 9. Perform ? GetTemporalOverflowOption(resolvedOptions).
908    let _overflow = get_option::<Overflow>(&resolved_options, js_string!("overflow"), context)?
909        .unwrap_or(Overflow::Constrain);
910
911    // 10. Let isoDate be CreateISODateRecord(result.[[Year]], result.[[Month]], result.[[Day]]).
912    // 11. If ISOYearMonthWithinLimits(isoDate) is false, throw a RangeError exception.
913    // 12. Set result to ISODateToFields(calendar, isoDate, year-month).
914    // 13. NOTE: The following operation is called with constrain regardless of the value of overflow, in order for the calendar to store a canonical value in the [[Day]] field of the [[ISODate]] internal slot of the result.
915    // 14. Set isoDate to ? CalendarYearMonthFromFields(calendar, result, constrain).
916    // 15. Return ! CreateTemporalYearMonth(isoDate, calendar).
917    Ok(result)
918}
919
920// 9.5.6 `CreateTemporalYearMonth ( isoYear, isoMonth, calendar, referenceISODay [ , newTarget ] )`
921pub(crate) fn create_temporal_year_month(
922    ym: InnerYearMonth,
923    new_target: Option<&JsValue>,
924    context: &mut Context,
925) -> JsResult<JsValue> {
926    // 1. If IsValidISODate(isoYear, isoMonth, referenceISODay) is false, throw a RangeError exception.
927    // 2. If ! ISOYearMonthWithinLimits(isoYear, isoMonth) is false, throw a RangeError exception.
928
929    // 3. If newTarget is not present, set newTarget to %Temporal.PlainYearMonth%.
930    let new_target = if let Some(target) = new_target {
931        target.clone()
932    } else {
933        context
934            .realm()
935            .intrinsics()
936            .constructors()
937            .plain_year_month()
938            .constructor()
939            .into()
940    };
941
942    // 4. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.PlainYearMonth.prototype%", « [[InitializedTemporalYearMonth]], [[ISOYear]], [[ISOMonth]], [[ISODay]], [[Calendar]] »).
943    let proto = get_prototype_from_constructor(
944        &new_target,
945        StandardConstructors::plain_year_month,
946        context,
947    )?;
948
949    // 5. Set object.[[ISOYear]] to isoYear.
950    // 6. Set object.[[ISOMonth]] to isoMonth.
951    // 7. Set object.[[Calendar]] to calendar.
952    // 8. Set object.[[ISODay]] to referenceISODay.
953
954    let obj = JsObject::from_proto_and_data(proto, PlainYearMonth::new(ym));
955
956    // 9. Return object.
957    Ok(obj.into())
958}
959
960// 9.5.9 AddDurationToOrSubtractDurationFromPlainYearMonth ( operation, yearMonth, temporalDurationLike, options )
961fn add_or_subtract_duration(
962    is_addition: bool,
963    this: &JsValue,
964    duration_like: &JsValue,
965    options: &JsObject,
966    context: &mut Context,
967) -> JsResult<JsValue> {
968    let duration: Duration = if duration_like.is_object() {
969        to_temporal_duration(duration_like, context)?
970    } else if let Some(duration_string) = duration_like.as_string() {
971        Duration::from_str(duration_string.to_std_string_escaped().as_str())?
972    } else {
973        return Err(JsNativeError::typ()
974            .with_message("cannot handler string durations yet.")
975            .into());
976    };
977
978    let overflow =
979        get_option(options, js_string!("overflow"), context)?.unwrap_or(Overflow::Constrain);
980
981    let object = this.as_object();
982    let year_month = object
983        .as_ref()
984        .and_then(JsObject::downcast_ref::<PlainYearMonth>)
985        .ok_or_else(|| {
986            JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
987        })?;
988
989    let inner = &year_month.inner;
990    let year_month_result = if is_addition {
991        inner.add(&duration, overflow)?
992    } else {
993        inner.subtract(&duration, overflow)?
994    };
995
996    create_temporal_year_month(year_month_result, None, context)
997}
998
999fn to_partial_year_month(
1000    partial_object: &JsObject,
1001    context: &mut Context,
1002) -> JsResult<PartialYearMonth> {
1003    // a. Let calendar be ? ToTemporalCalendar(item).
1004    let calendar = get_temporal_calendar_slot_value_with_default(partial_object, context)?;
1005    let calendar_fields = to_year_month_calendar_fields(partial_object, &calendar, context)?;
1006    Ok(PartialYearMonth {
1007        calendar_fields,
1008        calendar,
1009    })
1010}
1011
1012pub(crate) fn to_year_month_calendar_fields(
1013    partial_object: &JsObject,
1014    calendar: &Calendar,
1015    context: &mut Context,
1016) -> JsResult<YearMonthCalendarFields> {
1017    // TODO: `temporal_rs` needs a `has_era` method
1018    let has_no_era = calendar.kind() == AnyCalendarKind::Iso
1019        || calendar.kind() == AnyCalendarKind::Chinese
1020        || calendar.kind() == AnyCalendarKind::Dangi;
1021    let (era, era_year) = if has_no_era {
1022        (None, None)
1023    } else {
1024        let era = partial_object
1025            .get(js_string!("era"), context)?
1026            .map(|v| {
1027                let v = v.to_primitive(context, crate::value::PreferredType::String)?;
1028                let Some(era) = v.as_string() else {
1029                    return Err(JsError::from(
1030                        JsNativeError::typ()
1031                            .with_message("The monthCode field value must be a string."),
1032                    ));
1033                };
1034                // TODO: double check if an invalid monthCode is a range or type error.
1035                TinyAsciiStr::<19>::try_from_str(&era.to_std_string_escaped())
1036                    .map_err(|e| JsError::from(JsNativeError::range().with_message(e.to_string())))
1037            })
1038            .transpose()?;
1039        let era_year = partial_object
1040            .get(js_string!("eraYear"), context)?
1041            .map(|v| {
1042                let finite = v.to_finitef64(context)?;
1043                Ok::<i32, JsError>(finite.as_integer_with_truncation::<i32>())
1044            })
1045            .transpose()?;
1046        (era, era_year)
1047    };
1048
1049    let month = partial_object
1050        .get(js_string!("month"), context)?
1051        .map(|v| {
1052            let finite = v.to_finitef64(context)?;
1053            finite
1054                .as_positive_integer_with_truncation::<u8>()
1055                .map_err(JsError::from)
1056        })
1057        .transpose()?;
1058    let month_code = partial_object
1059        .get(js_string!("monthCode"), context)?
1060        .map(|v| {
1061            let v = v.to_primitive(context, crate::value::PreferredType::String)?;
1062            let Some(month_code) = v.as_string() else {
1063                return Err(JsNativeError::typ()
1064                    .with_message("The monthCode field value must be a string.")
1065                    .into());
1066            };
1067            MonthCode::from_str(&month_code.to_std_string_escaped()).map_err(JsError::from)
1068        })
1069        .transpose()?;
1070
1071    let year = partial_object
1072        .get(js_string!("year"), context)?
1073        .map(|v| {
1074            let finite = v.to_finitef64(context)?;
1075            Ok::<i32, JsError>(finite.as_integer_with_truncation::<i32>())
1076        })
1077        .transpose()?;
1078
1079    Ok(YearMonthCalendarFields::new()
1080        .with_era(era)
1081        .with_era_year(era_year)
1082        .with_optional_year(year)
1083        .with_optional_month(month)
1084        .with_optional_month_code(month_code))
1085}