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}