@@ -350,16 +350,71 @@ impl Calendar {
350350 // For example, constructing a PlainMonthDay for {year: 2025, month: 2, day: 29}
351351 // with overflow: constrain will produce 02-28 since it will constrain
352352 // the date to 2025-02-28 first, and only *then* will it construct an MD.
353- //
354- // This is specced partially in https://tc39.es/proposal-temporal/#sec-temporal-calendarmonthdaytoisoreferencedate
355- // notice that RegulateISODate is called with the passed-in year, but the reference year is used regardless
356- // of the passed in year in the final result.
357- //
358- // There may be more efficient ways to do this, but this works pretty well and doesn't require
359- // calendrical knowledge.
360353 if fields. year . is_some ( ) || ( fields. era . is_some ( ) && fields. era_year . is_some ( ) ) {
361- let date = self . date_from_fields ( fields, overflow) ?;
362- fields = CalendarFields :: from_date ( & date) ;
354+ // The ISO case is specified in <https://tc39.es/proposal-temporal/#sec-temporal-calendarmonthdaytoisoreferencedate>
355+ // It does not perform any range checks, arbitrarily large year values can be used.
356+ if self . is_iso ( ) {
357+ let resolved = ResolvedIsoFields :: try_from_fields (
358+ & fields,
359+ overflow,
360+ ResolutionType :: MonthDayWithYear ,
361+ ) ?;
362+ fields = CalendarFields {
363+ year : Some ( 1972 ) ,
364+ month : Some ( resolved. month ) ,
365+ day : Some ( resolved. day ) ,
366+ ..Default :: default ( )
367+ } ;
368+ } else {
369+ // The non-ISO case is specified in <https://tc39.es/proposal-intl-era-monthcode/#sup-temporal-nonisomonthdaytoisoreferencedate>
370+ //
371+ let date_fields = DateFields :: try_from ( & fields) ?;
372+ {
373+ // This algorithm requires an early-check to ensure the year is *somewhat* valid:
374+ // > b. If there exists no combination of inputs such that ! CalendarIntegersToISO(calendar, fields.[[Year]], ..., ...) would
375+ // return an ISO Date Record isoDate for which ISODateWithinLimits(isoDate) is true, throw a RangeError exception.
376+
377+ // We do this by using constrain with minimal and maximal month-day values to try and
378+ // see if either is in the ISO year range.
379+ //
380+ // This is complicated, it would be nice to not have to do this: <https://github.com/tc39/proposal-intl-era-monthcode/issues/127>
381+ let mut options = DateFromFieldsOptions :: default ( ) ;
382+ options. overflow = Some ( IcuOverflow :: Constrain ) ;
383+ options. missing_fields_strategy = Some ( MissingFieldsStrategy :: Reject ) ;
384+
385+ let mut fields_min = fields. clone ( ) ;
386+ let mut fields_max = fields. clone ( ) ;
387+ fields_min. month = Some ( 1 ) ;
388+ fields_max. month = Some ( 15 ) ;
389+ fields_min. month_code = None ;
390+ fields_max. month_code = None ;
391+ fields_min. day = Some ( 1 ) ;
392+ fields_max. day = Some ( 40 ) ;
393+ let fields_min = DateFields :: try_from ( & fields_min) ?;
394+ let fields_max = DateFields :: try_from ( & fields_max) ?;
395+ let date_min = self . 0 . from_fields ( fields_min, options) ?;
396+ let date_max = self . 0 . from_fields ( fields_max, options) ?;
397+ let iso_min = IsoDate :: from_icu4x ( self . 0 . to_iso ( & date_min) ) ;
398+ let iso_max = IsoDate :: from_icu4x ( self . 0 . to_iso ( & date_max) ) ;
399+
400+ // If *both* are an error, then no date in this year maps to the ISO year range.
401+ if iso_min. check_within_limits ( ) . is_err ( )
402+ && iso_max. check_within_limits ( ) . is_err ( )
403+ {
404+ return Err ( TemporalError :: range ( ) . with_enum ( ErrorMessage :: DateOutOfRange ) ) ;
405+ }
406+ }
407+ let mut options = DateFromFieldsOptions :: default ( ) ;
408+ options. overflow = Some ( overflow. into ( ) ) ;
409+ options. missing_fields_strategy = Some ( MissingFieldsStrategy :: Reject ) ;
410+ let calendar_date = self . 0 . from_fields ( date_fields, options) ?;
411+
412+ fields = CalendarFields {
413+ month_code : Some ( MonthCode ( self . 0 . month ( & calendar_date) . standard_code . 0 ) ) ,
414+ day : Some ( self . 0 . day_of_month ( & calendar_date) . 0 ) ,
415+ ..Default :: default ( )
416+ } ;
417+ }
363418 }
364419 if self . is_iso ( ) {
365420 let resolved_fields =
0 commit comments