Skip to main content

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        // 6. Let partialYearMonth be ? PrepareCalendarFields(calendar, temporalYearMonthLike, « year, month, month-code », « », partial).
565        // 7. Set fields to CalendarMergeFields(calendar, fields, partialYearMonth).
566        let fields = to_year_month_calendar_fields(&obj, year_month.inner.calendar(), context)?;
567        // NOTE: Update temporal_rs to handle this.
568        if fields.is_empty() {
569            return Err(JsNativeError::typ()
570                .with_message("temporalYearMonthLike cannot be an empty object")
571                .into());
572        }
573        // 8. Let resolvedOptions be ? GetOptionsObject(options).
574        let resolved_options = get_options_object(args.get_or_undefined(1))?;
575        // 9. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
576        let overflow = get_option::<Overflow>(&resolved_options, js_string!("overflow"), context)?
577            .unwrap_or_default();
578        // 10. Let isoDate be ? CalendarYearMonthFromFields(calendar, fields, overflow).
579        let result = year_month.inner.with(fields, Some(overflow))?;
580        // 11. Return ! CreateTemporalYearMonth(isoDate, calendar).
581        create_temporal_year_month(result, None, context)
582    }
583
584    /// 9.3.14 `Temporal.PlainYearMonth.prototype.add ( temporalDurationLike [ , options ] )`
585    ///
586    /// More information:
587    ///
588    /// - [ECMAScript Temporal proposal][spec]
589    /// - [MDN reference][mdn]
590    /// - [`temporal_rs` documentation][temporal_rs-docs]
591    ///
592    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.add
593    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/add
594    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainYearMonth.html#method.add
595    fn add(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
596        let duration_like = args.get_or_undefined(0);
597        let options = get_options_object(args.get_or_undefined(1))?;
598
599        add_or_subtract_duration(true, this, duration_like, &options, context)
600    }
601
602    /// 9.3.15 `Temporal.PlainYearMonth.prototype.subtract ( temporalDurationLike [ , options ] )`
603    ///
604    /// More information:
605    ///
606    /// - [ECMAScript Temporal proposal][spec]
607    /// - [MDN reference][mdn]
608    /// - [`temporal_rs` documentation][temporal_rs-docs]
609    ///
610    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.subtract
611    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/subtract
612    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainYearMonth.html#method.subtract
613    fn subtract(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
614        let duration_like = args.get_or_undefined(0);
615        let options = get_options_object(args.get_or_undefined(1))?;
616
617        add_or_subtract_duration(false, this, duration_like, &options, context)
618    }
619
620    /// 9.3.16 `Temporal.PlainYearMonth.prototype.until ( other [ , options ] )`
621    ///
622    /// More information:
623    ///
624    /// - [ECMAScript Temporal proposal][spec]
625    /// - [MDN reference][mdn]
626    /// - [`temporal_rs` documentation][temporal_rs-docs]
627    ///
628    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.until
629    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/until
630    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainYearMonth.html#method.until
631    fn until(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
632        let object = this.as_object();
633        let year_month = object
634            .as_ref()
635            .and_then(JsObject::downcast_ref::<Self>)
636            .ok_or_else(|| {
637                JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
638            })?;
639
640        let other = to_temporal_year_month(args.get_or_undefined(0), None, context)?;
641
642        if year_month.inner.calendar() != other.calendar() {
643            return Err(JsNativeError::range()
644                .with_message("calendars are not the same.")
645                .into());
646        }
647
648        let resolved_options = get_options_object(args.get_or_undefined(1))?;
649        // TODO: Disallowed units must be rejected in `temporal_rs`.
650        let settings = get_difference_settings(&resolved_options, context)?;
651        let result = year_month.inner.until(&other, settings)?;
652        create_temporal_duration(result, None, context).map(Into::into)
653    }
654
655    /// 9.3.17 `Temporal.PlainYearMonth.prototype.since ( other [ , options ] )`
656    ///
657    /// More information:
658    ///
659    /// - [ECMAScript Temporal proposal][spec]
660    /// - [MDN reference][mdn]
661    /// - [`temporal_rs` documentation][temporal_rs-docs]
662    ///
663    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.since
664    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/since
665    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainYearMonth.html#method.since
666    fn since(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
667        let object = this.as_object();
668        let year_month = object
669            .as_ref()
670            .and_then(JsObject::downcast_ref::<Self>)
671            .ok_or_else(|| {
672                JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
673            })?;
674
675        let other = to_temporal_year_month(args.get_or_undefined(0), None, context)?;
676
677        if year_month.inner.calendar() != other.calendar() {
678            return Err(JsNativeError::range()
679                .with_message("calendars are not the same.")
680                .into());
681        }
682
683        let resolved_options = get_options_object(args.get_or_undefined(1))?;
684        // TODO: Disallowed units must be rejected in `temporal_rs`.
685        let settings = get_difference_settings(&resolved_options, context)?;
686        let result = year_month.inner.since(&other, settings)?;
687        create_temporal_duration(result, None, context).map(Into::into)
688    }
689
690    /// 9.3.18 `Temporal.PlainYearMonth.prototype.equals ( other )`
691    ///
692    /// More information:
693    ///
694    /// - [ECMAScript Temporal proposal][spec]
695    /// - [MDN reference][mdn]
696    /// - [`temporal_rs` documentation][temporal_rs-docs]
697    ///
698    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.equals
699    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/equals
700    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainYearMonth.html#impl-PartialEq-for-PlainYearMonth
701    fn equals(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
702        let object = this.as_object();
703        let year_month = object
704            .as_ref()
705            .and_then(JsObject::downcast_ref::<Self>)
706            .ok_or_else(|| {
707                JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
708            })?;
709
710        let other = to_temporal_year_month(args.get_or_undefined(0), None, context)?;
711
712        Ok((year_month.inner == other).into())
713    }
714
715    /// 9.3.19 `Temporal.PlainYearMonth.prototype.toString ( [ options ] )`
716    ///
717    /// More information:
718    ///
719    /// - [ECMAScript Temporal proposal][spec]
720    /// - [MDN reference][mdn]
721    /// - [`temporal_rs` documentation][temporal_rs-docs]
722    ///
723    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.tostring
724    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/toString
725    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainYearMonth.html#method.to_ixdtf_string
726    fn to_string(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
727        // 1. Let YearMonth be the this value.
728        // 2. Perform ? RequireInternalSlot(yearMonth, [[InitializedTemporalYearMonth]]).
729        let object = this.as_object();
730        let year_month = object
731            .as_ref()
732            .and_then(JsObject::downcast_ref::<Self>)
733            .ok_or_else(|| {
734                JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
735            })?;
736
737        // 3. Set options to ? NormalizeOptionsObject(options).
738        let options = get_options_object(args.get_or_undefined(0))?;
739        // 4. Let showCalendar be ? ToShowCalendarOption(options).
740        // Get calendarName from the options object
741        let show_calendar =
742            get_option::<DisplayCalendar>(&options, js_string!("calendarName"), context)?
743                .unwrap_or(DisplayCalendar::Auto);
744
745        let ixdtf = year_month.inner.to_ixdtf_string(show_calendar);
746        Ok(JsString::from(ixdtf).into())
747    }
748
749    /// 9.3.20 `Temporal.PlainYearMonth.prototype.toLocaleString ( [ locales [ , options ] ] )`
750    ///
751    /// More information:
752    ///
753    /// - [ECMAScript Temporal proposal][spec]
754    /// - [MDN reference][mdn]
755    ///
756    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.tolocalestring
757    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/toLocaleString
758    pub(crate) fn to_locale_string(
759        this: &JsValue,
760        _: &[JsValue],
761        _: &mut Context,
762    ) -> JsResult<JsValue> {
763        // TODO: Update for ECMA-402 compliance
764        let object = this.as_object();
765        let year_month = object
766            .as_ref()
767            .and_then(JsObject::downcast_ref::<Self>)
768            .ok_or_else(|| {
769                JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
770            })?;
771
772        Ok(JsString::from(year_month.inner.to_string()).into())
773    }
774
775    /// 9.3.21 `Temporal.PlainYearMonth.prototype.toJSON ( )`
776    ///
777    /// More information:
778    ///
779    /// - [ECMAScript Temporal proposal][spec]
780    /// - [MDN reference][mdn]
781    ///
782    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.tojson
783    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/toJSON
784    pub(crate) fn to_json(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
785        let object = this.as_object();
786        let year_month = object
787            .as_ref()
788            .and_then(JsObject::downcast_ref::<Self>)
789            .ok_or_else(|| {
790                JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
791            })?;
792
793        Ok(JsString::from(year_month.inner.to_string()).into())
794    }
795
796    /// 9.3.22 `Temporal.PlainYearMonth.prototype.valueOf ( )`
797    ///
798    /// More information:
799    ///
800    /// - [ECMAScript Temporal proposal][spec]
801    /// - [MDN reference][mdn]
802    ///
803    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.valueof
804    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/valueOf
805    pub(crate) fn value_of(_this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
806        Err(JsNativeError::typ()
807            .with_message("`valueOf` not supported by Temporal built-ins. See 'compare', 'equals', or `toString`")
808            .into())
809    }
810
811    /// 9.3.23 `Temporal.PlainYearMonth.prototype.toPlainDate ( item )`
812    ///
813    /// More information:
814    ///
815    /// - [ECMAScript Temporal proposal][spec]
816    /// - [MDN reference][mdn]
817    /// - [`temporal_rs` documentation][temporal_rs-docs]
818    ///
819    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.toplaindate
820    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/toPlainDate
821    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainYearMonth.html#method.to_plain_date
822    fn to_plain_date(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
823        // 1. Let yearMonth be the this value.
824        // 2. Perform ? RequireInternalSlot(yearMonth, [[InitializedTemporalYearMonth]]).
825        let object = this.as_object();
826        let year_month = object
827            .as_ref()
828            .and_then(JsObject::downcast_ref::<Self>)
829            .ok_or_else(|| {
830                JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
831            })?;
832
833        // 3. If item is not an Object, then
834        let Some(obj) = args.get_or_undefined(0).as_object() else {
835            // a. Throw a TypeError exception.
836            return Err(JsNativeError::typ()
837                .with_message("toPlainDate item must be an object.")
838                .into());
839        };
840        // 4. Let calendar be yearMonth.[[Calendar]].
841        // 5. Let fields be ISODateToFields(calendar, yearMonth.[[ISODate]], year-month).
842        // 6. Let inputFields be ? PrepareCalendarFields(calendar, item, « day », « », « »).
843        let day = obj
844            .get(js_string!("day"), context)?
845            .map(|v| {
846                let finite = v.to_finitef64(context)?;
847                finite
848                    .as_positive_integer_with_truncation::<u8>()
849                    .map_err(JsError::from)
850            })
851            .transpose()?;
852
853        let fields = CalendarFields::new().with_optional_day(day);
854
855        // 7. Let mergedFields be CalendarMergeFields(calendar, fields, inputFields).
856        // 8. Let isoDate be ? CalendarDateFromFields(calendar, mergedFields, constrain).
857        let result = year_month.inner.to_plain_date(Some(fields))?;
858        // 9. Return ! CreateTemporalDate(isoDate, calendar).
859        create_temporal_date(result, None, context).map(Into::into)
860    }
861}
862
863// ==== PlainYearMonth Abstract Operations ====
864
865/// 9.5.2 `ToTemporalYearMonth ( item [ , options ] )`
866fn to_temporal_year_month(
867    value: &JsValue,
868    options: Option<JsValue>,
869    context: &mut Context,
870) -> JsResult<InnerYearMonth> {
871    // If options is not present, set options to undefined.
872    let options = options.unwrap_or_default();
873    // 2. If item is an Object, then
874    if let Some(obj) = value.as_object() {
875        // a. If item has an [[InitializedTemporalYearMonth]] internal slot, then
876        if let Some(ym) = obj.downcast_ref::<PlainYearMonth>() {
877            // i. Let resolvedOptions be ? GetOptionsObject(options).
878            let resolved_options = get_options_object(&options)?;
879            // ii. Perform ? GetTemporalOverflowOption(resolvedOptions).
880            let _overflow =
881                get_option::<Overflow>(&resolved_options, js_string!("overflow"), context)?
882                    .unwrap_or(Overflow::Constrain);
883            // iii. Return ! CreateTemporalYearMonth(item.[[ISODate]], item.[[Calendar]]).
884            return Ok(ym.inner.clone());
885        }
886        // b. Let calendar be ? GetTemporalCalendarIdentifierWithISODefault(item).
887        // c. Let fields be ? PrepareCalendarFields(calendar, item, « year, month, month-code », «», «»).
888        let partial = to_partial_year_month(&obj, context)?;
889        // d. Let resolvedOptions be ? GetOptionsObject(options).
890        let resolved_options = get_options_object(&options)?;
891        // e. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
892        let overflow = get_option::<Overflow>(&resolved_options, js_string!("overflow"), context)?;
893        // f. Let isoDate be ? CalendarYearMonthFromFields(calendar, fields, overflow).
894        // g. Return ! CreateTemporalYearMonth(isoDate, calendar).
895        return Ok(InnerYearMonth::from_partial(partial, overflow)?);
896    }
897
898    // 3. If item is not a String, throw a TypeError exception.
899    let Some(ym_string) = value.as_string() else {
900        return Err(JsNativeError::typ()
901            .with_message("toTemporalYearMonth target must be an object or string")
902            .into());
903    };
904
905    // 4. Let result be ? ParseISODateTime(item, « TemporalYearMonthString »).
906    let result = InnerYearMonth::from_str(&ym_string.to_std_string_escaped())?;
907    // 5. Let calendar be result.[[Calendar]].
908    // 6. If calendar is empty, set calendar to "iso8601".
909    // 7. Set calendar to ? CanonicalizeCalendar(calendar).
910    // 8. Let resolvedOptions be ? GetOptionsObject(options).
911    let resolved_options = get_options_object(&options)?;
912    // 9. Perform ? GetTemporalOverflowOption(resolvedOptions).
913    let _overflow = get_option::<Overflow>(&resolved_options, js_string!("overflow"), context)?
914        .unwrap_or(Overflow::Constrain);
915
916    // 10. Let isoDate be CreateISODateRecord(result.[[Year]], result.[[Month]], result.[[Day]]).
917    // 11. If ISOYearMonthWithinLimits(isoDate) is false, throw a RangeError exception.
918    // 12. Set result to ISODateToFields(calendar, isoDate, year-month).
919    // 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.
920    // 14. Set isoDate to ? CalendarYearMonthFromFields(calendar, result, constrain).
921    // 15. Return ! CreateTemporalYearMonth(isoDate, calendar).
922    Ok(result)
923}
924
925// 9.5.6 `CreateTemporalYearMonth ( isoYear, isoMonth, calendar, referenceISODay [ , newTarget ] )`
926pub(crate) fn create_temporal_year_month(
927    ym: InnerYearMonth,
928    new_target: Option<&JsValue>,
929    context: &mut Context,
930) -> JsResult<JsValue> {
931    // 1. If IsValidISODate(isoYear, isoMonth, referenceISODay) is false, throw a RangeError exception.
932    // 2. If ! ISOYearMonthWithinLimits(isoYear, isoMonth) is false, throw a RangeError exception.
933
934    // 3. If newTarget is not present, set newTarget to %Temporal.PlainYearMonth%.
935    let new_target = if let Some(target) = new_target {
936        target.clone()
937    } else {
938        context
939            .realm()
940            .intrinsics()
941            .constructors()
942            .plain_year_month()
943            .constructor()
944            .into()
945    };
946
947    // 4. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.PlainYearMonth.prototype%", « [[InitializedTemporalYearMonth]], [[ISOYear]], [[ISOMonth]], [[ISODay]], [[Calendar]] »).
948    let proto = get_prototype_from_constructor(
949        &new_target,
950        StandardConstructors::plain_year_month,
951        context,
952    )?;
953
954    // 5. Set object.[[ISOYear]] to isoYear.
955    // 6. Set object.[[ISOMonth]] to isoMonth.
956    // 7. Set object.[[Calendar]] to calendar.
957    // 8. Set object.[[ISODay]] to referenceISODay.
958
959    let obj = JsObject::from_proto_and_data(proto, PlainYearMonth::new(ym));
960
961    // 9. Return object.
962    Ok(obj.into())
963}
964
965// 9.5.9 AddDurationToOrSubtractDurationFromPlainYearMonth ( operation, yearMonth, temporalDurationLike, options )
966fn add_or_subtract_duration(
967    is_addition: bool,
968    this: &JsValue,
969    duration_like: &JsValue,
970    options: &JsObject,
971    context: &mut Context,
972) -> JsResult<JsValue> {
973    let duration: Duration = if duration_like.is_object() {
974        to_temporal_duration(duration_like, context)?
975    } else if let Some(duration_string) = duration_like.as_string() {
976        Duration::from_str(duration_string.to_std_string_escaped().as_str())?
977    } else {
978        return Err(JsNativeError::typ()
979            .with_message("cannot handler string durations yet.")
980            .into());
981    };
982
983    let overflow =
984        get_option(options, js_string!("overflow"), context)?.unwrap_or(Overflow::Constrain);
985
986    let object = this.as_object();
987    let year_month = object
988        .as_ref()
989        .and_then(JsObject::downcast_ref::<PlainYearMonth>)
990        .ok_or_else(|| {
991            JsNativeError::typ().with_message("this value must be a PlainYearMonth object.")
992        })?;
993
994    let inner = &year_month.inner;
995    let year_month_result = if is_addition {
996        inner.add(&duration, overflow)?
997    } else {
998        inner.subtract(&duration, overflow)?
999    };
1000
1001    create_temporal_year_month(year_month_result, None, context)
1002}
1003
1004fn to_partial_year_month(
1005    partial_object: &JsObject,
1006    context: &mut Context,
1007) -> JsResult<PartialYearMonth> {
1008    // a. Let calendar be ? ToTemporalCalendar(item).
1009    let calendar = get_temporal_calendar_slot_value_with_default(partial_object, context)?;
1010    let calendar_fields = to_year_month_calendar_fields(partial_object, &calendar, context)?;
1011    Ok(PartialYearMonth {
1012        calendar_fields,
1013        calendar,
1014    })
1015}
1016
1017pub(crate) fn to_year_month_calendar_fields(
1018    partial_object: &JsObject,
1019    calendar: &Calendar,
1020    context: &mut Context,
1021) -> JsResult<YearMonthCalendarFields> {
1022    // TODO: `temporal_rs` needs a `has_era` method
1023    let has_no_era = calendar.kind() == AnyCalendarKind::Iso
1024        || calendar.kind() == AnyCalendarKind::Chinese
1025        || calendar.kind() == AnyCalendarKind::Dangi;
1026    let (era, era_year) = if has_no_era {
1027        (None, None)
1028    } else {
1029        let era = partial_object
1030            .get(js_string!("era"), context)?
1031            .map(|v| {
1032                let v = v.to_primitive(context, crate::value::PreferredType::String)?;
1033                let Some(era) = v.as_string() else {
1034                    return Err(JsError::from(
1035                        JsNativeError::typ()
1036                            .with_message("The monthCode field value must be a string."),
1037                    ));
1038                };
1039                // TODO: double check if an invalid monthCode is a range or type error.
1040                TinyAsciiStr::<19>::try_from_str(&era.to_std_string_escaped())
1041                    .map_err(|e| JsError::from(JsNativeError::range().with_message(e.to_string())))
1042            })
1043            .transpose()?;
1044        let era_year = partial_object
1045            .get(js_string!("eraYear"), context)?
1046            .map(|v| {
1047                let finite = v.to_finitef64(context)?;
1048                Ok::<i32, JsError>(finite.as_integer_with_truncation::<i32>())
1049            })
1050            .transpose()?;
1051        (era, era_year)
1052    };
1053
1054    let month = partial_object
1055        .get(js_string!("month"), context)?
1056        .map(|v| {
1057            let finite = v.to_finitef64(context)?;
1058            finite
1059                .as_positive_integer_with_truncation::<u8>()
1060                .map_err(JsError::from)
1061        })
1062        .transpose()?;
1063    let month_code = partial_object
1064        .get(js_string!("monthCode"), context)?
1065        .map(|v| {
1066            let v = v.to_primitive(context, crate::value::PreferredType::String)?;
1067            let Some(month_code) = v.as_string() else {
1068                return Err(JsNativeError::typ()
1069                    .with_message("The monthCode field value must be a string.")
1070                    .into());
1071            };
1072            MonthCode::from_str(&month_code.to_std_string_escaped()).map_err(JsError::from)
1073        })
1074        .transpose()?;
1075
1076    let year = partial_object
1077        .get(js_string!("year"), context)?
1078        .map(|v| {
1079            let finite = v.to_finitef64(context)?;
1080            Ok::<i32, JsError>(finite.as_integer_with_truncation::<i32>())
1081        })
1082        .transpose()?;
1083
1084    Ok(YearMonthCalendarFields::new()
1085        .with_era(era)
1086        .with_era_year(era_year)
1087        .with_optional_year(year)
1088        .with_optional_month(month)
1089        .with_optional_month_code(month_code))
1090}