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}